summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--api/Android.bp7
-rw-r--r--core/java/android/app/AppOpsManager.java19
-rw-r--r--core/java/android/app/DownloadManager.java42
-rw-r--r--core/java/android/app/slice/SliceProvider.java20
-rw-r--r--core/java/android/content/Context.java13
-rw-r--r--core/java/android/content/pm/LauncherApps.java35
-rw-r--r--core/java/android/content/res/Configuration.java21
-rw-r--r--core/java/android/database/sqlite/SQLiteQueryBuilder.java405
-rw-r--r--core/java/android/database/sqlite/SQLiteTokenizer.java297
-rw-r--r--core/java/android/hardware/display/DisplayManager.java60
-rw-r--r--core/java/android/os/LocaleList.java22
-rw-r--r--core/java/android/os/VibrationEffect.java6
-rw-r--r--core/java/android/provider/DeviceConfig.java17
-rw-r--r--core/java/android/provider/Settings.java20
-rw-r--r--core/java/android/view/accessibility/TEST_MAPPING3
-rw-r--r--core/java/android/view/textclassifier/TEST_MAPPING3
-rw-r--r--core/java/android/webkit/WebView.java9
-rw-r--r--core/java/android/widget/ImageView.java8
-rw-r--r--core/java/com/android/internal/os/ZygoteConnection.java4
-rw-r--r--core/proto/android/content/configuration.proto3
-rw-r--r--core/proto/android/content/locale.proto1
-rw-r--r--core/res/res/values/config.xml4
-rw-r--r--core/res/res/values/symbols.xml1
-rw-r--r--core/tests/coretests/src/android/content/res/ConfigurationTest.java79
-rw-r--r--core/tests/coretests/src/android/database/sqlite/SQLiteTokenizerTest.java173
-rw-r--r--core/tests/coretests/src/android/provider/SettingsBackupTest.java2
-rw-r--r--data/keyboards/Vendor_045e_Product_02dd.kl57
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java6
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java2
-rw-r--r--packages/SystemUI/res/drawable/ic_5g_e_mobiledata.xml14
-rw-r--r--packages/SystemUI/res/values/config.xml3
-rw-r--r--packages/SystemUI/src/com/android/systemui/SystemUIFactory.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java39
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java37
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitor.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitorImpl.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java25
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java25
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java15
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardMonitor.java5
-rw-r--r--services/accessibility/TEST_MAPPING6
-rw-r--r--services/core/java/com/android/server/VibratorService.java1
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java2
-rw-r--r--services/core/java/com/android/server/am/TEST_MAPPING9
-rw-r--r--services/core/java/com/android/server/biometrics/face/FaceService.java3
-rw-r--r--services/core/java/com/android/server/display/DisplayModeDirector.java444
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerService.java5
-rw-r--r--services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java7
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperManagerService.java12
-rw-r--r--services/core/java/com/android/server/wm/HighRefreshRateBlacklist.java10
-rw-r--r--services/java/com/android/server/SystemServer.java1
-rw-r--r--services/net/java/android/net/ConnectivityModuleConnector.java36
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/HighRefreshRateBlacklistTest.java12
-rwxr-xr-xtelephony/java/android/telephony/CarrierConfigManager.java13
-rw-r--r--tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java2
60 files changed, 1657 insertions, 463 deletions
diff --git a/api/Android.bp b/api/Android.bp
new file mode 100644
index 000000000000..54ff82c97e17
--- /dev/null
+++ b/api/Android.bp
@@ -0,0 +1,7 @@
+genrule {
+ name: "current-api-xml",
+ tools: ["metalava"],
+ srcs: ["current.txt"],
+ out: ["current.api"],
+ cmd: "$(location metalava) --no-banner -convert2xmlnostrip $(in) $(out)",
+}
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index da4c0386a76f..8cae56d1acf4 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -240,7 +240,8 @@ public class AppOpsManager {
public @interface UidState {}
/**
- * Uid state: The UID is a foreground persistent app.
+ * Uid state: The UID is a foreground persistent app. The lower the UID
+ * state the more important the UID is for the user.
* @hide
*/
@TestApi
@@ -248,7 +249,8 @@ public class AppOpsManager {
public static final int UID_STATE_PERSISTENT = 100;
/**
- * Uid state: The UID is top foreground app.
+ * Uid state: The UID is top foreground app. The lower the UID
+ * state the more important the UID is for the user.
* @hide
*/
@TestApi
@@ -257,6 +259,7 @@ public class AppOpsManager {
/**
* Uid state: The UID is running a foreground service of location type.
+ * The lower the UID state the more important the UID is for the user.
* @hide
*/
@TestApi
@@ -264,7 +267,8 @@ public class AppOpsManager {
public static final int UID_STATE_FOREGROUND_SERVICE_LOCATION = 300;
/**
- * Uid state: The UID is running a foreground service.
+ * Uid state: The UID is running a foreground service. The lower the UID
+ * state the more important the UID is for the user.
* @hide
*/
@TestApi
@@ -279,7 +283,8 @@ public class AppOpsManager {
public static final int UID_STATE_MAX_LAST_NON_RESTRICTED = UID_STATE_FOREGROUND_SERVICE;
/**
- * Uid state: The UID is a foreground app.
+ * Uid state: The UID is a foreground app. The lower the UID
+ * state the more important the UID is for the user.
* @hide
*/
@TestApi
@@ -287,7 +292,8 @@ public class AppOpsManager {
public static final int UID_STATE_FOREGROUND = 500;
/**
- * Uid state: The UID is a background app.
+ * Uid state: The UID is a background app. The lower the UID
+ * state the more important the UID is for the user.
* @hide
*/
@TestApi
@@ -295,7 +301,8 @@ public class AppOpsManager {
public static final int UID_STATE_BACKGROUND = 600;
/**
- * Uid state: The UID is a cached app.
+ * Uid state: The UID is a cached app. The lower the UID
+ * state the more important the UID is for the user.
* @hide
*/
@TestApi
diff --git a/core/java/android/app/DownloadManager.java b/core/java/android/app/DownloadManager.java
index 1166cb57cca7..77a777024a21 100644
--- a/core/java/android/app/DownloadManager.java
+++ b/core/java/android/app/DownloadManager.java
@@ -132,6 +132,9 @@ public class DownloadManager {
*/
public final static String COLUMN_STATUS = Downloads.Impl.COLUMN_STATUS;
+ /** {@hide} */
+ public static final String COLUMN_FILE_NAME_HINT = Downloads.Impl.COLUMN_FILE_NAME_HINT;
+
/**
* Provides more detail on the status of the download. Its meaning depends on the value of
* {@link #COLUMN_STATUS}.
@@ -173,6 +176,9 @@ public class DownloadManager {
@TestApi
public static final String COLUMN_MEDIASTORE_URI = Downloads.Impl.COLUMN_MEDIASTORE_URI;
+ /** {@hide} */
+ public static final String COLUMN_DESTINATION = Downloads.Impl.COLUMN_DESTINATION;
+
/**
* @hide
*/
@@ -340,26 +346,22 @@ public class DownloadManager {
*/
@UnsupportedAppUsage
public static final String[] UNDERLYING_COLUMNS = new String[] {
- Downloads.Impl._ID,
- Downloads.Impl._DATA + " AS " + COLUMN_LOCAL_FILENAME,
- Downloads.Impl.COLUMN_MEDIAPROVIDER_URI,
- Downloads.Impl.COLUMN_DESTINATION,
- Downloads.Impl.COLUMN_TITLE,
- Downloads.Impl.COLUMN_DESCRIPTION,
- Downloads.Impl.COLUMN_URI,
- Downloads.Impl.COLUMN_STATUS,
- Downloads.Impl.COLUMN_FILE_NAME_HINT,
- Downloads.Impl.COLUMN_MIME_TYPE + " AS " + COLUMN_MEDIA_TYPE,
- Downloads.Impl.COLUMN_TOTAL_BYTES + " AS " + COLUMN_TOTAL_SIZE_BYTES,
- Downloads.Impl.COLUMN_LAST_MODIFICATION + " AS " + COLUMN_LAST_MODIFIED_TIMESTAMP,
- Downloads.Impl.COLUMN_CURRENT_BYTES + " AS " + COLUMN_BYTES_DOWNLOADED_SO_FAR,
- Downloads.Impl.COLUMN_ALLOW_WRITE,
- /* add the following 'computed' columns to the cursor.
- * they are not 'returned' by the database, but their inclusion
- * eliminates need to have lot of methods in CursorTranslator
- */
- "'placeholder' AS " + COLUMN_LOCAL_URI,
- "'placeholder' AS " + COLUMN_REASON
+ DownloadManager.COLUMN_ID,
+ DownloadManager.COLUMN_LOCAL_FILENAME,
+ DownloadManager.COLUMN_MEDIAPROVIDER_URI,
+ DownloadManager.COLUMN_DESTINATION,
+ DownloadManager.COLUMN_TITLE,
+ DownloadManager.COLUMN_DESCRIPTION,
+ DownloadManager.COLUMN_URI,
+ DownloadManager.COLUMN_STATUS,
+ DownloadManager.COLUMN_FILE_NAME_HINT,
+ DownloadManager.COLUMN_MEDIA_TYPE,
+ DownloadManager.COLUMN_TOTAL_SIZE_BYTES,
+ DownloadManager.COLUMN_LAST_MODIFIED_TIMESTAMP,
+ DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR,
+ DownloadManager.COLUMN_ALLOW_WRITE,
+ DownloadManager.COLUMN_LOCAL_URI,
+ DownloadManager.COLUMN_REASON
};
/**
diff --git a/core/java/android/app/slice/SliceProvider.java b/core/java/android/app/slice/SliceProvider.java
index 0ccd49f2e028..5e530eedd818 100644
--- a/core/java/android/app/slice/SliceProvider.java
+++ b/core/java/android/app/slice/SliceProvider.java
@@ -355,7 +355,8 @@ public abstract class SliceProvider extends ContentProvider {
@Override
public Bundle call(String method, String arg, Bundle extras) {
if (method.equals(METHOD_SLICE)) {
- Uri uri = getUriWithoutUserId(extras.getParcelable(EXTRA_BIND_URI));
+ Uri uri = getUriWithoutUserId(validateIncomingUriOrNull(
+ extras.getParcelable(EXTRA_BIND_URI)));
List<SliceSpec> supportedSpecs = extras.getParcelableArrayList(EXTRA_SUPPORTED_SPECS);
String callingPackage = getCallingPackage();
@@ -369,7 +370,7 @@ public abstract class SliceProvider extends ContentProvider {
} else if (method.equals(METHOD_MAP_INTENT)) {
Intent intent = extras.getParcelable(EXTRA_INTENT);
if (intent == null) return null;
- Uri uri = onMapIntentToUri(intent);
+ Uri uri = validateIncomingUriOrNull(onMapIntentToUri(intent));
List<SliceSpec> supportedSpecs = extras.getParcelableArrayList(EXTRA_SUPPORTED_SPECS);
Bundle b = new Bundle();
if (uri != null) {
@@ -383,24 +384,27 @@ public abstract class SliceProvider extends ContentProvider {
} else if (method.equals(METHOD_MAP_ONLY_INTENT)) {
Intent intent = extras.getParcelable(EXTRA_INTENT);
if (intent == null) return null;
- Uri uri = onMapIntentToUri(intent);
+ Uri uri = validateIncomingUriOrNull(onMapIntentToUri(intent));
Bundle b = new Bundle();
b.putParcelable(EXTRA_SLICE, uri);
return b;
} else if (method.equals(METHOD_PIN)) {
- Uri uri = getUriWithoutUserId(extras.getParcelable(EXTRA_BIND_URI));
+ Uri uri = getUriWithoutUserId(validateIncomingUriOrNull(
+ extras.getParcelable(EXTRA_BIND_URI)));
if (Binder.getCallingUid() != Process.SYSTEM_UID) {
throw new SecurityException("Only the system can pin/unpin slices");
}
handlePinSlice(uri);
} else if (method.equals(METHOD_UNPIN)) {
- Uri uri = getUriWithoutUserId(extras.getParcelable(EXTRA_BIND_URI));
+ Uri uri = getUriWithoutUserId(validateIncomingUriOrNull(
+ extras.getParcelable(EXTRA_BIND_URI)));
if (Binder.getCallingUid() != Process.SYSTEM_UID) {
throw new SecurityException("Only the system can pin/unpin slices");
}
handleUnpinSlice(uri);
} else if (method.equals(METHOD_GET_DESCENDANTS)) {
- Uri uri = getUriWithoutUserId(extras.getParcelable(EXTRA_BIND_URI));
+ Uri uri = getUriWithoutUserId(
+ validateIncomingUriOrNull(extras.getParcelable(EXTRA_BIND_URI)));
Bundle b = new Bundle();
b.putParcelableArrayList(EXTRA_SLICE_DESCENDANTS,
new ArrayList<>(handleGetDescendants(uri)));
@@ -416,6 +420,10 @@ public abstract class SliceProvider extends ContentProvider {
return super.call(method, arg, extras);
}
+ private Uri validateIncomingUriOrNull(Uri uri) {
+ return uri == null ? null : validateIncomingUri(uri);
+ }
+
private Collection<Uri> handleGetDescendants(Uri uri) {
mCallback = "onGetSliceDescendants";
return onGetSliceDescendants(uri);
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index a6b95a95dcd8..4a5ea03b4530 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -854,11 +854,16 @@ public abstract class Context {
* to any callers for the same name, meaning they will see each other's
* edits as soon as they are made.
*
- * This method is thead-safe.
+ * <p>This method is thread-safe.
*
- * @param name Desired preferences file. If a preferences file by this name
- * does not exist, it will be created when you retrieve an
- * editor (SharedPreferences.edit()) and then commit changes (Editor.commit()).
+ * <p>If the preferences directory does not already exist, it will be created when this method
+ * is called.
+ *
+ * <p>If a preferences file by this name does not exist, it will be created when you retrieve an
+ * editor ({@link SharedPreferences#edit()}) and then commit changes ({@link
+ * SharedPreferences.Editor#commit()} or {@link SharedPreferences.Editor#apply()}).
+ *
+ * @param name Desired preferences file.
* @param mode Operating mode.
*
* @return The single {@link SharedPreferences} instance that can be used
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index 037a149bfe37..c74daa8eadfc 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -503,9 +503,38 @@ public class LauncherApps {
}
/**
- * Retrieves a list of launchable activities that match {@link Intent#ACTION_MAIN} and
- * {@link Intent#CATEGORY_LAUNCHER}, for a specified user. Result may include
- * synthesized activities like app details Activity injected by system.
+ * Retrieves a list of activities that specify {@link Intent#ACTION_MAIN} and
+ * {@link Intent#CATEGORY_LAUNCHER}, across all apps, for a specified user. If an app doesn't
+ * have any activities that specify <code>ACTION_MAIN</code> or <code>CATEGORY_LAUNCHER</code>,
+ * the system adds a synthesized activity to the list. This synthesized activity represents the
+ * app's details page within system settings.
+ *
+ * <p class="note"><b>Note: </b>It's possible for system apps, such as app stores, to prevent
+ * the system from adding synthesized activities to the returned list.</p>
+ *
+ * <p>As of <a href="/reference/android/os/Build.VERSION_CODES.html#Q">Android Q</a>, at least
+ * one of the app's activities or synthesized activities appears in the returned list unless the
+ * app satisfies at least one of the following conditions:</p>
+ * <ul>
+ * <li>The app is a system app.</li>
+ * <li>The app doesn't request any <a href="/guide/topics/permissions/overview">permissions</a>.
+ * </li>
+ * <li>The <code>&lt;application&gt;</code> tag in the app's manifest doesn't contain any child
+ * elements that represent
+ * <a href="/guide/components/fundamentals#DeclaringComponents">app components</a>.</li>
+ * </ul>
+ *
+ * <p>Additionally, the system hides synthesized activities for some or all apps in the
+ * following enterprise-related cases:</p>
+ * <ul>
+ * <li>If the device is a
+ * <a href="https://developers.google.com/android/work/overview#company-owned-devices-for-knowledge-workers">fully
+ * managed device</a>, no synthesized activities for any app appear in the returned list.</li>
+ * <li>If the current user has a
+ * <a href="https://developers.google.com/android/work/overview#employee-owned-devices-byod">work
+ * profile</a>, no synthesized activities for the user's work apps appear in the returned
+ * list.</li>
+ * </ul>
*
* @param packageName The specific package to query. If null, it checks all installed packages
* in the profile.
diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java
index 861ae7ba122e..ac1cbd4619df 100644
--- a/core/java/android/content/res/Configuration.java
+++ b/core/java/android/content/res/Configuration.java
@@ -23,6 +23,7 @@ import static android.content.ConfigurationProto.HARD_KEYBOARD_HIDDEN;
import static android.content.ConfigurationProto.KEYBOARD;
import static android.content.ConfigurationProto.KEYBOARD_HIDDEN;
import static android.content.ConfigurationProto.LOCALES;
+import static android.content.ConfigurationProto.LOCALE_LIST;
import static android.content.ConfigurationProto.MCC;
import static android.content.ConfigurationProto.MNC;
import static android.content.ConfigurationProto.NAVIGATION;
@@ -1111,7 +1112,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration
protoOutputStream.write(MCC, mcc);
protoOutputStream.write(MNC, mnc);
if (mLocaleList != null) {
- mLocaleList.writeToProto(protoOutputStream, LOCALES);
+ protoOutputStream.write(LOCALE_LIST, mLocaleList.toLanguageTags());
}
protoOutputStream.write(SCREEN_LAYOUT, screenLayout);
protoOutputStream.write(COLOR_MODE, colorMode);
@@ -1222,7 +1223,15 @@ public final class Configuration implements Parcelable, Comparable<Configuration
.setVariant(variant)
.setScript(script)
.build();
- list.add(locale);
+ // Log a WTF here if a repeated locale is found to avoid throwing an
+ // exception in system server when LocaleList is created below
+ final int inListIndex = list.indexOf(locale);
+ if (inListIndex != -1) {
+ Slog.wtf(TAG, "Repeated locale (" + list.get(inListIndex) + ")"
+ + " found when trying to add: " + locale.toString());
+ } else {
+ list.add(locale);
+ }
} catch (IllformedLocaleException e) {
Slog.e(TAG, "readFromProto error building locale with: "
+ "language-" + language + ";country-" + country
@@ -1275,6 +1284,14 @@ public final class Configuration implements Parcelable, Comparable<Configuration
case (int) WINDOW_CONFIGURATION:
windowConfiguration.readFromProto(protoInputStream, WINDOW_CONFIGURATION);
break;
+ case (int) LOCALE_LIST:
+ try {
+ setLocales(LocaleList.forLanguageTags(protoInputStream.readString(
+ LOCALE_LIST)));
+ } catch (Exception e) {
+ Slog.e(TAG, "error parsing locale list in configuration.", e);
+ }
+ break;
}
}
} finally {
diff --git a/core/java/android/database/sqlite/SQLiteQueryBuilder.java b/core/java/android/database/sqlite/SQLiteQueryBuilder.java
index 3523e956656a..58901798b5f7 100644
--- a/core/java/android/database/sqlite/SQLiteQueryBuilder.java
+++ b/core/java/android/database/sqlite/SQLiteQueryBuilder.java
@@ -30,11 +30,14 @@ import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
+import com.android.internal.util.ArrayUtils;
+
import libcore.util.EmptyArray;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
+import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
@@ -49,14 +52,11 @@ import java.util.regex.Pattern;
public class SQLiteQueryBuilder {
private static final String TAG = "SQLiteQueryBuilder";
- private static final Pattern sLimitPattern =
- Pattern.compile("\\s*\\d+\\s*(,\\s*\\d+\\s*)?");
private static final Pattern sAggregationPattern = Pattern.compile(
"(?i)(AVG|COUNT|MAX|MIN|SUM|TOTAL|GROUP_CONCAT)\\((.+)\\)");
private Map<String, String> mProjectionMap = null;
private List<Pattern> mProjectionGreylist = null;
- private boolean mProjectionAggregationAllowed = false;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
private String mTables = "";
@@ -65,7 +65,12 @@ public class SQLiteQueryBuilder {
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
private boolean mDistinct;
private SQLiteDatabase.CursorFactory mFactory;
- private boolean mStrict;
+
+ private static final int STRICT_PARENTHESES = 1 << 0;
+ private static final int STRICT_COLUMNS = 1 << 1;
+ private static final int STRICT_GRAMMAR = 1 << 2;
+
+ private int mStrictFlags;
public SQLiteQueryBuilder() {
mDistinct = false;
@@ -208,14 +213,23 @@ public class SQLiteQueryBuilder {
return mProjectionGreylist;
}
- /** {@hide} */
+ /**
+ * @deprecated Projection aggregation is now always allowed
+ *
+ * @hide
+ */
+ @Deprecated
public void setProjectionAggregationAllowed(boolean projectionAggregationAllowed) {
- mProjectionAggregationAllowed = projectionAggregationAllowed;
}
- /** {@hide} */
+ /**
+ * @deprecated Projection aggregation is now always allowed
+ *
+ * @hide
+ */
+ @Deprecated
public boolean isProjectionAggregationAllowed() {
- return mProjectionAggregationAllowed;
+ return true;
}
/**
@@ -258,8 +272,12 @@ public class SQLiteQueryBuilder {
* </ul>
* By default, this value is false.
*/
- public void setStrict(boolean flag) {
- mStrict = flag;
+ public void setStrict(boolean strict) {
+ if (strict) {
+ mStrictFlags |= STRICT_PARENTHESES;
+ } else {
+ mStrictFlags &= ~STRICT_PARENTHESES;
+ }
}
/**
@@ -267,7 +285,75 @@ public class SQLiteQueryBuilder {
* {@link #setStrict(boolean)}.
*/
public boolean isStrict() {
- return mStrict;
+ return (mStrictFlags & STRICT_PARENTHESES) != 0;
+ }
+
+ /**
+ * When enabled, verify that all projections and {@link ContentValues} only
+ * contain valid columns as defined by {@link #setProjectionMap(Map)}.
+ * <p>
+ * This enforcement applies to {@link #insert}, {@link #query}, and
+ * {@link #update} operations. Any enforcement failures will throw an
+ * {@link IllegalArgumentException}.
+ *
+ * {@hide}
+ */
+ public void setStrictColumns(boolean strictColumns) {
+ if (strictColumns) {
+ mStrictFlags |= STRICT_COLUMNS;
+ } else {
+ mStrictFlags &= ~STRICT_COLUMNS;
+ }
+ }
+
+ /**
+ * Get if the query is marked as strict, as last configured by
+ * {@link #setStrictColumns(boolean)}.
+ *
+ * {@hide}
+ */
+ public boolean isStrictColumns() {
+ return (mStrictFlags & STRICT_COLUMNS) != 0;
+ }
+
+ /**
+ * When enabled, verify that all untrusted SQL conforms to a restricted SQL
+ * grammar. Here are the restrictions applied:
+ * <ul>
+ * <li>In {@code WHERE} and {@code HAVING} clauses: subqueries, raising, and
+ * windowing terms are rejected.
+ * <li>In {@code GROUP BY} clauses: only valid columns are allowed.
+ * <li>In {@code ORDER BY} clauses: only valid columns, collation, and
+ * ordering terms are allowed.
+ * <li>In {@code LIMIT} clauses: only numerical values and offset terms are
+ * allowed.
+ * </ul>
+ * All column references must be valid as defined by
+ * {@link #setProjectionMap(Map)}.
+ * <p>
+ * This enforcement applies to {@link #query}, {@link #update} and
+ * {@link #delete} operations. This enforcement does not apply to trusted
+ * inputs, such as those provided by {@link #appendWhere}. Any enforcement
+ * failures will throw an {@link IllegalArgumentException}.
+ *
+ * {@hide}
+ */
+ public void setStrictGrammar(boolean strictGrammar) {
+ if (strictGrammar) {
+ mStrictFlags |= STRICT_GRAMMAR;
+ } else {
+ mStrictFlags &= ~STRICT_GRAMMAR;
+ }
+ }
+
+ /**
+ * Get if the query is marked as strict, as last configured by
+ * {@link #setStrictGrammar(boolean)}.
+ *
+ * {@hide}
+ */
+ public boolean isStrictGrammar() {
+ return (mStrictFlags & STRICT_GRAMMAR) != 0;
}
/**
@@ -303,9 +389,6 @@ public class SQLiteQueryBuilder {
throw new IllegalArgumentException(
"HAVING clauses are only permitted when using a groupBy clause");
}
- if (!TextUtils.isEmpty(limit) && !sLimitPattern.matcher(limit).matches()) {
- throw new IllegalArgumentException("invalid LIMIT clauses:" + limit);
- }
StringBuilder query = new StringBuilder(120);
@@ -479,7 +562,13 @@ public class SQLiteQueryBuilder {
projectionIn, selection, groupBy, having,
sortOrder, limit);
- if (mStrict && selection != null && selection.length() > 0) {
+ if (isStrictColumns()) {
+ enforceStrictColumns(projectionIn);
+ }
+ if (isStrictGrammar()) {
+ enforceStrictGrammar(selection, groupBy, having, sortOrder, limit);
+ }
+ if (isStrict()) {
// Validate the user-supplied selection to detect syntactic anomalies
// in the selection string that could indicate a SQL injection attempt.
// The idea is to ensure that the selection clause is a valid SQL expression
@@ -497,7 +586,7 @@ public class SQLiteQueryBuilder {
// Execute wrapped query for extra protection
final String wrappedSql = buildQuery(projectionIn, wrap(selection), groupBy,
- having, sortOrder, limit);
+ wrap(having), sortOrder, limit);
sql = wrappedSql;
} else {
// Execute unwrapped query
@@ -519,6 +608,42 @@ public class SQLiteQueryBuilder {
}
/**
+ * Perform an insert by combining all current settings and the
+ * information passed into this method.
+ *
+ * @param db the database to insert on
+ * @return the row ID of the newly inserted row, or -1 if an error occurred
+ *
+ * {@hide}
+ */
+ public long insert(@NonNull SQLiteDatabase db, @NonNull ContentValues values) {
+ Objects.requireNonNull(mTables, "No tables defined");
+ Objects.requireNonNull(db, "No database defined");
+ Objects.requireNonNull(values, "No values defined");
+
+ if (isStrictColumns()) {
+ enforceStrictColumns(values);
+ }
+
+ final String sql = buildInsert(values);
+
+ final ArrayMap<String, Object> rawValues = values.getValues();
+ final int valuesLength = rawValues.size();
+ final Object[] sqlArgs = new Object[valuesLength];
+ for (int i = 0; i < sqlArgs.length; i++) {
+ sqlArgs[i] = rawValues.valueAt(i);
+ }
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ if (Build.IS_DEBUGGABLE) {
+ Log.d(TAG, sql + " with args " + Arrays.toString(sqlArgs));
+ } else {
+ Log.d(TAG, sql);
+ }
+ }
+ return db.executeSql(sql, sqlArgs);
+ }
+
+ /**
* Perform an update by combining all current settings and the
* information passed into this method.
*
@@ -541,7 +666,13 @@ public class SQLiteQueryBuilder {
final String sql;
final String unwrappedSql = buildUpdate(values, selection);
- if (mStrict) {
+ if (isStrictColumns()) {
+ enforceStrictColumns(values);
+ }
+ if (isStrictGrammar()) {
+ enforceStrictGrammar(selection, null, null, null, null);
+ }
+ if (isStrict()) {
// Validate the user-supplied selection to detect syntactic anomalies
// in the selection string that could indicate a SQL injection attempt.
// The idea is to ensure that the selection clause is a valid SQL expression
@@ -610,7 +741,10 @@ public class SQLiteQueryBuilder {
final String sql;
final String unwrappedSql = buildDelete(selection);
- if (mStrict) {
+ if (isStrictGrammar()) {
+ enforceStrictGrammar(selection, null, null, null, null);
+ }
+ if (isStrict()) {
// Validate the user-supplied selection to detect syntactic anomalies
// in the selection string that could indicate a SQL injection attempt.
// The idea is to ensure that the selection clause is a valid SQL expression
@@ -645,6 +779,81 @@ public class SQLiteQueryBuilder {
return db.executeSql(sql, sqlArgs);
}
+ private void enforceStrictColumns(@Nullable String[] projection) {
+ Objects.requireNonNull(mProjectionMap, "No projection map defined");
+
+ computeProjection(projection);
+ }
+
+ private void enforceStrictColumns(@NonNull ContentValues values) {
+ Objects.requireNonNull(mProjectionMap, "No projection map defined");
+
+ final ArrayMap<String, Object> rawValues = values.getValues();
+ for (int i = 0; i < rawValues.size(); i++) {
+ final String column = rawValues.keyAt(i);
+ if (!mProjectionMap.containsKey(column)) {
+ throw new IllegalArgumentException("Invalid column " + column);
+ }
+ }
+ }
+
+ private void enforceStrictGrammar(@Nullable String selection, @Nullable String groupBy,
+ @Nullable String having, @Nullable String sortOrder, @Nullable String limit) {
+ SQLiteTokenizer.tokenize(selection, SQLiteTokenizer.OPTION_NONE,
+ this::enforceStrictGrammarWhereHaving);
+ SQLiteTokenizer.tokenize(groupBy, SQLiteTokenizer.OPTION_NONE,
+ this::enforceStrictGrammarGroupBy);
+ SQLiteTokenizer.tokenize(having, SQLiteTokenizer.OPTION_NONE,
+ this::enforceStrictGrammarWhereHaving);
+ SQLiteTokenizer.tokenize(sortOrder, SQLiteTokenizer.OPTION_NONE,
+ this::enforceStrictGrammarOrderBy);
+ SQLiteTokenizer.tokenize(limit, SQLiteTokenizer.OPTION_NONE,
+ this::enforceStrictGrammarLimit);
+ }
+
+ private void enforceStrictGrammarWhereHaving(@NonNull String token) {
+ if (isTableOrColumn(token)) return;
+ if (SQLiteTokenizer.isFunction(token)) return;
+ if (SQLiteTokenizer.isType(token)) return;
+
+ // NOTE: we explicitly don't allow SELECT subqueries, since they could
+ // leak data that should have been filtered by the trusted where clause
+ switch (token.toUpperCase(Locale.US)) {
+ case "AND": case "AS": case "BETWEEN": case "BINARY":
+ case "CASE": case "CAST": case "COLLATE": case "DISTINCT":
+ case "ELSE": case "END": case "ESCAPE": case "EXISTS":
+ case "GLOB": case "IN": case "IS": case "ISNULL":
+ case "LIKE": case "MATCH": case "NOCASE": case "NOT":
+ case "NOTNULL": case "NULL": case "OR": case "REGEXP":
+ case "RTRIM": case "THEN": case "WHEN":
+ return;
+ }
+ throw new IllegalArgumentException("Invalid token " + token);
+ }
+
+ private void enforceStrictGrammarGroupBy(@NonNull String token) {
+ if (isTableOrColumn(token)) return;
+ throw new IllegalArgumentException("Invalid token " + token);
+ }
+
+ private void enforceStrictGrammarOrderBy(@NonNull String token) {
+ if (isTableOrColumn(token)) return;
+ switch (token.toUpperCase(Locale.US)) {
+ case "COLLATE": case "ASC": case "DESC":
+ case "BINARY": case "RTRIM": case "NOCASE":
+ return;
+ }
+ throw new IllegalArgumentException("Invalid token " + token);
+ }
+
+ private void enforceStrictGrammarLimit(@NonNull String token) {
+ switch (token.toUpperCase(Locale.US)) {
+ case "OFFSET":
+ return;
+ }
+ throw new IllegalArgumentException("Invalid token " + token);
+ }
+
/**
* Construct a {@code SELECT} statement suitable for use in a group of
* {@code SELECT} statements that will be joined through {@code UNION} operators
@@ -698,6 +907,35 @@ public class SQLiteQueryBuilder {
}
/** {@hide} */
+ public String buildInsert(ContentValues values) {
+ if (values == null || values.isEmpty()) {
+ throw new IllegalArgumentException("Empty values");
+ }
+
+ StringBuilder sql = new StringBuilder(120);
+ sql.append("INSERT INTO ");
+ sql.append(SQLiteDatabase.findEditTable(mTables));
+ sql.append(" (");
+
+ final ArrayMap<String, Object> rawValues = values.getValues();
+ for (int i = 0; i < rawValues.size(); i++) {
+ if (i > 0) {
+ sql.append(',');
+ }
+ sql.append(rawValues.keyAt(i));
+ }
+ sql.append(") VALUES (");
+ for (int i = 0; i < rawValues.size(); i++) {
+ if (i > 0) {
+ sql.append(',');
+ }
+ sql.append('?');
+ }
+ sql.append(")");
+ return sql.toString();
+ }
+
+ /** {@hide} */
public String buildUpdate(ContentValues values, String selection) {
if (values == null || values.isEmpty()) {
throw new IllegalArgumentException("Empty values");
@@ -705,7 +943,7 @@ public class SQLiteQueryBuilder {
StringBuilder sql = new StringBuilder(120);
sql.append("UPDATE ");
- sql.append(mTables);
+ sql.append(SQLiteDatabase.findEditTable(mTables));
sql.append(" SET ");
final ArrayMap<String, Object> rawValues = values.getValues();
@@ -726,7 +964,7 @@ public class SQLiteQueryBuilder {
public String buildDelete(String selection) {
StringBuilder sql = new StringBuilder(120);
sql.append("DELETE FROM ");
- sql.append(mTables);
+ sql.append(SQLiteDatabase.findEditTable(mTables));
final String where = computeWhere(selection);
appendClause(sql, " WHERE ", where);
@@ -868,65 +1106,13 @@ public class SQLiteQueryBuilder {
/** {@hide} */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
- public String[] computeProjection(String[] projectionIn) {
- if (projectionIn != null && projectionIn.length > 0) {
- if (mProjectionMap != null) {
- String[] projection = new String[projectionIn.length];
- int length = projectionIn.length;
-
- for (int i = 0; i < length; i++) {
- String operator = null;
- String userColumn = projectionIn[i];
- String column = mProjectionMap.get(userColumn);
-
- // If aggregation is allowed, extract the underlying column
- // that may be aggregated
- if (mProjectionAggregationAllowed) {
- final Matcher matcher = sAggregationPattern.matcher(userColumn);
- if (matcher.matches()) {
- operator = matcher.group(1);
- userColumn = matcher.group(2);
- column = mProjectionMap.get(userColumn);
- }
- }
-
- if (column != null) {
- projection[i] = maybeWithOperator(operator, column);
- continue;
- }
-
- if (!mStrict &&
- ( userColumn.contains(" AS ") || userColumn.contains(" as "))) {
- /* A column alias already exist */
- projection[i] = maybeWithOperator(operator, userColumn);
- continue;
- }
-
- // If greylist is configured, we might be willing to let
- // this custom column bypass our strict checks.
- if (mProjectionGreylist != null) {
- boolean match = false;
- for (Pattern p : mProjectionGreylist) {
- if (p.matcher(userColumn).matches()) {
- match = true;
- break;
- }
- }
-
- if (match) {
- Log.w(TAG, "Allowing abusive custom column: " + userColumn);
- projection[i] = maybeWithOperator(operator, userColumn);
- continue;
- }
- }
-
- throw new IllegalArgumentException("Invalid column "
- + projectionIn[i]);
- }
- return projection;
- } else {
- return projectionIn;
+ public @Nullable String[] computeProjection(@Nullable String[] projectionIn) {
+ if (!ArrayUtils.isEmpty(projectionIn)) {
+ String[] projectionOut = new String[projectionIn.length];
+ for (int i = 0; i < projectionIn.length; i++) {
+ projectionOut[i] = computeSingleProjectionOrThrow(projectionIn[i]);
}
+ return projectionOut;
} else if (mProjectionMap != null) {
// Return all columns in projection map.
Set<Entry<String, String>> entrySet = mProjectionMap.entrySet();
@@ -948,6 +1134,69 @@ public class SQLiteQueryBuilder {
return null;
}
+ private @NonNull String computeSingleProjectionOrThrow(@NonNull String userColumn) {
+ final String column = computeSingleProjection(userColumn);
+ if (column != null) {
+ return column;
+ } else {
+ throw new IllegalArgumentException("Invalid column " + userColumn);
+ }
+ }
+
+ private @Nullable String computeSingleProjection(@NonNull String userColumn) {
+ // When no mapping provided, anything goes
+ if (mProjectionMap == null) {
+ return userColumn;
+ }
+
+ String operator = null;
+ String column = mProjectionMap.get(userColumn);
+
+ // When no direct match found, look for aggregation
+ if (column == null) {
+ final Matcher matcher = sAggregationPattern.matcher(userColumn);
+ if (matcher.matches()) {
+ operator = matcher.group(1);
+ userColumn = matcher.group(2);
+ column = mProjectionMap.get(userColumn);
+ }
+ }
+
+ if (column != null) {
+ return maybeWithOperator(operator, column);
+ }
+
+ if (mStrictFlags == 0
+ && (userColumn.contains(" AS ") || userColumn.contains(" as "))) {
+ /* A column alias already exist */
+ return maybeWithOperator(operator, userColumn);
+ }
+
+ // If greylist is configured, we might be willing to let
+ // this custom column bypass our strict checks.
+ if (mProjectionGreylist != null) {
+ boolean match = false;
+ for (Pattern p : mProjectionGreylist) {
+ if (p.matcher(userColumn).matches()) {
+ match = true;
+ break;
+ }
+ }
+
+ if (match) {
+ Log.w(TAG, "Allowing abusive custom column: " + userColumn);
+ return maybeWithOperator(operator, userColumn);
+ }
+ }
+
+ return null;
+ }
+
+ private boolean isTableOrColumn(String token) {
+ if (mTables.equals(token)) return true;
+ return computeSingleProjection(token) != null;
+ }
+
/** {@hide} */
public @Nullable String computeWhere(@Nullable String selection) {
final boolean hasInternal = !TextUtils.isEmpty(mWhereClause);
diff --git a/core/java/android/database/sqlite/SQLiteTokenizer.java b/core/java/android/database/sqlite/SQLiteTokenizer.java
new file mode 100644
index 000000000000..7e7c3fb976c7
--- /dev/null
+++ b/core/java/android/database/sqlite/SQLiteTokenizer.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright (C) 2019 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.database.sqlite;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.function.Consumer;
+
+/**
+ * SQL Tokenizer specialized to extract tokens from SQL (snippets).
+ * <p>
+ * Based on sqlite3GetToken() in tokenzie.c in SQLite.
+ * <p>
+ * Source for v3.8.6 (which android uses): http://www.sqlite.org/src/artifact/ae45399d6252b4d7
+ * (Latest source as of now: http://www.sqlite.org/src/artifact/78c8085bc7af1922)
+ * <p>
+ * Also draft spec: http://www.sqlite.org/draft/tokenreq.html
+ *
+ * @hide
+ */
+public class SQLiteTokenizer {
+ private static boolean isAlpha(char ch) {
+ return ('a' <= ch && ch <= 'z') || ('A' <= ch && ch <= 'Z') || (ch == '_');
+ }
+
+ private static boolean isNum(char ch) {
+ return ('0' <= ch && ch <= '9');
+ }
+
+ private static boolean isAlNum(char ch) {
+ return isAlpha(ch) || isNum(ch);
+ }
+
+ private static boolean isAnyOf(char ch, String set) {
+ return set.indexOf(ch) >= 0;
+ }
+
+ private static IllegalArgumentException genException(String message, String sql) {
+ throw new IllegalArgumentException(message + " in '" + sql + "'");
+ }
+
+ private static char peek(String s, int index) {
+ return index < s.length() ? s.charAt(index) : '\0';
+ }
+
+ public static final int OPTION_NONE = 0;
+
+ /**
+ * Require that SQL contains only tokens; any comments or values will result
+ * in an exception.
+ */
+ public static final int OPTION_TOKEN_ONLY = 1 << 0;
+
+ /**
+ * Tokenize the given SQL, returning the list of each encountered token.
+ *
+ * @throws IllegalArgumentException if invalid SQL is encountered.
+ */
+ public static List<String> tokenize(@Nullable String sql, int options) {
+ final ArrayList<String> res = new ArrayList<>();
+ tokenize(sql, options, res::add);
+ return res;
+ }
+
+ /**
+ * Tokenize the given SQL, sending each encountered token to the given
+ * {@link Consumer}.
+ *
+ * @throws IllegalArgumentException if invalid SQL is encountered.
+ */
+ public static void tokenize(@Nullable String sql, int options, Consumer<String> checker) {
+ if (sql == null) {
+ return;
+ }
+ int pos = 0;
+ final int len = sql.length();
+ while (pos < len) {
+ final char ch = peek(sql, pos);
+
+ // Regular token.
+ if (isAlpha(ch)) {
+ final int start = pos;
+ pos++;
+ while (isAlNum(peek(sql, pos))) {
+ pos++;
+ }
+ final int end = pos;
+
+ final String token = sql.substring(start, end);
+ checker.accept(token);
+
+ continue;
+ }
+
+ // Handle quoted tokens
+ if (isAnyOf(ch, "'\"`")) {
+ final int quoteStart = pos;
+ pos++;
+
+ for (;;) {
+ pos = sql.indexOf(ch, pos);
+ if (pos < 0) {
+ throw genException("Unterminated quote", sql);
+ }
+ if (peek(sql, pos + 1) != ch) {
+ break;
+ }
+ // Quoted quote char -- e.g. "abc""def" is a single string.
+ pos += 2;
+ }
+ final int quoteEnd = pos;
+ pos++;
+
+ if (ch != '\'') {
+ // Extract the token
+ final String tokenUnquoted = sql.substring(quoteStart + 1, quoteEnd);
+
+ final String token;
+
+ // Unquote if needed. i.e. "aa""bb" -> aa"bb
+ if (tokenUnquoted.indexOf(ch) >= 0) {
+ token = tokenUnquoted.replaceAll(
+ String.valueOf(ch) + ch, String.valueOf(ch));
+ } else {
+ token = tokenUnquoted;
+ }
+ checker.accept(token);
+ } else {
+ if ((options &= OPTION_TOKEN_ONLY) != 0) {
+ throw genException("Non-token detected", sql);
+ }
+ }
+ continue;
+ }
+ // Handle tokens enclosed in [...]
+ if (ch == '[') {
+ final int quoteStart = pos;
+ pos++;
+
+ pos = sql.indexOf(']', pos);
+ if (pos < 0) {
+ throw genException("Unterminated quote", sql);
+ }
+ final int quoteEnd = pos;
+ pos++;
+
+ final String token = sql.substring(quoteStart + 1, quoteEnd);
+
+ checker.accept(token);
+ continue;
+ }
+ if ((options &= OPTION_TOKEN_ONLY) != 0) {
+ throw genException("Non-token detected", sql);
+ }
+
+ // Detect comments.
+ if (ch == '-' && peek(sql, pos + 1) == '-') {
+ pos += 2;
+ pos = sql.indexOf('\n', pos);
+ if (pos < 0) {
+ // We disallow strings ending in an inline comment.
+ throw genException("Unterminated comment", sql);
+ }
+ pos++;
+
+ continue;
+ }
+ if (ch == '/' && peek(sql, pos + 1) == '*') {
+ pos += 2;
+ pos = sql.indexOf("*/", pos);
+ if (pos < 0) {
+ throw genException("Unterminated comment", sql);
+ }
+ pos += 2;
+
+ continue;
+ }
+
+ // Semicolon is never allowed.
+ if (ch == ';') {
+ throw genException("Semicolon is not allowed", sql);
+ }
+
+ // For this purpose, we can simply ignore other characters.
+ // (Note it doesn't handle the X'' literal properly and reports this X as a token,
+ // but that should be fine...)
+ pos++;
+ }
+ }
+
+ /**
+ * Test if given token is a
+ * <a href="https://www.sqlite.org/lang_keywords.html">SQLite reserved
+ * keyword</a>.
+ */
+ public static boolean isKeyword(@NonNull String token) {
+ switch (token.toUpperCase(Locale.US)) {
+ case "ABORT": case "ACTION": case "ADD": case "AFTER":
+ case "ALL": case "ALTER": case "ANALYZE": case "AND":
+ case "AS": case "ASC": case "ATTACH": case "AUTOINCREMENT":
+ case "BEFORE": case "BEGIN": case "BETWEEN": case "BINARY":
+ case "BY": case "CASCADE": case "CASE": case "CAST":
+ case "CHECK": case "COLLATE": case "COLUMN": case "COMMIT":
+ case "CONFLICT": case "CONSTRAINT": case "CREATE": case "CROSS":
+ case "CURRENT": case "CURRENT_DATE": case "CURRENT_TIME": case "CURRENT_TIMESTAMP":
+ case "DATABASE": case "DEFAULT": case "DEFERRABLE": case "DEFERRED":
+ case "DELETE": case "DESC": case "DETACH": case "DISTINCT":
+ case "DO": case "DROP": case "EACH": case "ELSE":
+ case "END": case "ESCAPE": case "EXCEPT": case "EXCLUDE":
+ case "EXCLUSIVE": case "EXISTS": case "EXPLAIN": case "FAIL":
+ case "FILTER": case "FOLLOWING": case "FOR": case "FOREIGN":
+ case "FROM": case "FULL": case "GLOB": case "GROUP":
+ case "GROUPS": case "HAVING": case "IF": case "IGNORE":
+ case "IMMEDIATE": case "IN": case "INDEX": case "INDEXED":
+ case "INITIALLY": case "INNER": case "INSERT": case "INSTEAD":
+ case "INTERSECT": case "INTO": case "IS": case "ISNULL":
+ case "JOIN": case "KEY": case "LEFT": case "LIKE":
+ case "LIMIT": case "MATCH": case "NATURAL": case "NO":
+ case "NOCASE": case "NOT": case "NOTHING": case "NOTNULL":
+ case "NULL": case "OF": case "OFFSET": case "ON":
+ case "OR": case "ORDER": case "OTHERS": case "OUTER":
+ case "OVER": case "PARTITION": case "PLAN": case "PRAGMA":
+ case "PRECEDING": case "PRIMARY": case "QUERY": case "RAISE":
+ case "RANGE": case "RECURSIVE": case "REFERENCES": case "REGEXP":
+ case "REINDEX": case "RELEASE": case "RENAME": case "REPLACE":
+ case "RESTRICT": case "RIGHT": case "ROLLBACK": case "ROW":
+ case "ROWS": case "RTRIM": case "SAVEPOINT": case "SELECT":
+ case "SET": case "TABLE": case "TEMP": case "TEMPORARY":
+ case "THEN": case "TIES": case "TO": case "TRANSACTION":
+ case "TRIGGER": case "UNBOUNDED": case "UNION": case "UNIQUE":
+ case "UPDATE": case "USING": case "VACUUM": case "VALUES":
+ case "VIEW": case "VIRTUAL": case "WHEN": case "WHERE":
+ case "WINDOW": case "WITH": case "WITHOUT":
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Test if given token is a
+ * <a href="https://www.sqlite.org/lang_corefunc.html">SQLite reserved
+ * function</a>.
+ */
+ public static boolean isFunction(@NonNull String token) {
+ switch (token.toLowerCase(Locale.US)) {
+ case "abs": case "avg": case "char": case "coalesce":
+ case "count": case "glob": case "group_concat": case "hex":
+ case "ifnull": case "instr": case "length": case "like":
+ case "likelihood": case "likely": case "lower": case "ltrim":
+ case "max": case "min": case "nullif": case "random":
+ case "randomblob": case "replace": case "round": case "rtrim":
+ case "substr": case "sum": case "total": case "trim":
+ case "typeof": case "unicode": case "unlikely": case "upper":
+ case "zeroblob":
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Test if given token is a
+ * <a href="https://www.sqlite.org/datatype3.html">SQLite reserved type</a>.
+ */
+ public static boolean isType(@NonNull String token) {
+ switch (token.toUpperCase(Locale.US)) {
+ case "INT": case "INTEGER": case "TINYINT": case "SMALLINT":
+ case "MEDIUMINT": case "BIGINT": case "INT2": case "INT8":
+ case "CHARACTER": case "VARCHAR": case "NCHAR": case "NVARCHAR":
+ case "TEXT": case "CLOB": case "BLOB": case "REAL":
+ case "DOUBLE": case "FLOAT": case "NUMERIC": case "DECIMAL":
+ case "BOOLEAN": case "DATE": case "DATETIME":
+ return true;
+ default:
+ return false;
+ }
+ }
+}
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 8a0a9c7c9d9d..0b25dbd78611 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -820,4 +820,64 @@ public final class DisplayManager {
*/
void onDisplayChanged(int displayId);
}
+
+ /**
+ * Interface for accessing keys belonging to {@link
+ * android.provider.DeviceConfig#NAMESPACE_DISPLAY_MANAGER}.
+ * @hide
+ */
+ public interface DeviceConfig {
+
+ /**
+ * Key for refresh rate in the zone defined by thresholds.
+ *
+ * @see android.provider.DeviceConfig#NAMESPACE_DISPLAY_MANAGER
+ * @see android.R.integer#config_defaultZoneBehavior
+ */
+ String KEY_REFRESH_RATE_IN_ZONE = "refresh_rate_in_zone";
+
+ /**
+ * Key for accessing the display brightness thresholds for the configured refresh rate zone.
+ * The value will be a pair of comma separated integers representing the minimum and maximum
+ * thresholds of the zone, respectively, in display backlight units (i.e. [0, 255]).
+ *
+ * @see android.provider.DeviceConfig#NAMESPACE_DISPLAY_MANAGER
+ * @see android.R.array#config_brightnessThresholdsOfPeakRefreshRate
+ * @hide
+ */
+ String KEY_PEAK_REFRESH_RATE_DISPLAY_BRIGHTNESS_THRESHOLDS =
+ "peak_refresh_rate_brightness_thresholds";
+
+ /**
+ * Key for accessing the ambient brightness thresholds for the configured refresh rate zone.
+ * The value will be a pair of comma separated integers representing the minimum and maximum
+ * thresholds of the zone, respectively, in lux.
+ *
+ * @see android.provider.DeviceConfig#NAMESPACE_DISPLAY_MANAGER
+ * @see android.R.array#config_ambientThresholdsOfPeakRefreshRate
+ * @hide
+ */
+ String KEY_PEAK_REFRESH_RATE_AMBIENT_BRIGHTNESS_THRESHOLDS =
+ "peak_refresh_rate_ambient_thresholds";
+
+ /**
+ * Key for default peak refresh rate
+ *
+ * @see android.provider.DeviceConfig#NAMESPACE_DISPLAY_MANAGER
+ * @see android.R.integer#config_defaultPeakRefreshRate
+ * @hide
+ */
+ String KEY_PEAK_REFRESH_RATE_DEFAULT = "peak_refresh_rate_default";
+
+ /**
+ * Key for controlling which packages are explicitly blocked from running at refresh rates
+ * higher than 60hz. An app may be added to this list if they exhibit performance issues at
+ * higher refresh rates.
+ *
+ * @see android.provider.DeviceConfig#NAMESPACE_DISPLAY_MANAGER
+ * @see android.R.array#config_highRefreshRateBlacklist
+ * @hide
+ */
+ String KEY_HIGH_REFRESH_RATE_BLACKLIST = "high_refresh_rate_blacklist";
+ }
}
diff --git a/core/java/android/os/LocaleList.java b/core/java/android/os/LocaleList.java
index 7782753e4abc..0de09efad8ea 100644
--- a/core/java/android/os/LocaleList.java
+++ b/core/java/android/os/LocaleList.java
@@ -21,9 +21,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Size;
import android.annotation.UnsupportedAppUsage;
-import android.content.LocaleProto;
import android.icu.util.ULocale;
-import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.GuardedBy;
@@ -143,26 +141,6 @@ public final class LocaleList implements Parcelable {
}
/**
- * Helper to write LocaleList to a protocol buffer output stream. Assumes the parent
- * protobuf has declared the locale as repeated.
- *
- * @param protoOutputStream Stream to write the locale to.
- * @param fieldId Field Id of the Locale as defined in the parent message.
- * @hide
- */
- public void writeToProto(ProtoOutputStream protoOutputStream, long fieldId) {
- for (int i = 0; i < mList.length; i++) {
- final Locale locale = mList[i];
- final long token = protoOutputStream.start(fieldId);
- protoOutputStream.write(LocaleProto.LANGUAGE, locale.getLanguage());
- protoOutputStream.write(LocaleProto.COUNTRY, locale.getCountry());
- protoOutputStream.write(LocaleProto.VARIANT, locale.getVariant());
- protoOutputStream.write(LocaleProto.SCRIPT, locale.getScript());
- protoOutputStream.end(token);
- }
- }
-
- /**
* Retrieves a String representation of the language tags in this list.
*/
@NonNull
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
index 702b41beb071..26da0a0aee07 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -53,7 +53,7 @@ public abstract class VibrationEffect implements Parcelable {
public static final int MAX_AMPLITUDE = 255;
/**
- * A click effect.
+ * A click effect. Use this effect as a baseline, as it's the most common type of click effect.
*
* @see #get(int)
*/
@@ -67,7 +67,7 @@ public abstract class VibrationEffect implements Parcelable {
public static final int EFFECT_DOUBLE_CLICK = Effect.DOUBLE_CLICK;
/**
- * A tick effect.
+ * A tick effect. This effect is less strong compared to {@link #EFFECT_CLICK}.
* @see #get(int)
*/
public static final int EFFECT_TICK = Effect.TICK;
@@ -89,7 +89,7 @@ public abstract class VibrationEffect implements Parcelable {
public static final int EFFECT_POP = Effect.POP;
/**
- * A heavy click effect.
+ * A heavy click effect. This effect is stronger than {@link #EFFECT_CLICK}.
* @see #get(int)
*/
public static final int EFFECT_HEAVY_CLICK = Effect.HEAVY_CLICK;
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index ea50ae810535..c837b9346173 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -136,6 +136,14 @@ public final class DeviceConfig {
public static final String NAMESPACE_DEX_BOOT = "dex_boot";
/**
+ * Namespace for display manager related features. The names to access the properties in this
+ * namespace should be defined in {@link android.hardware.display.DisplayManager}.
+ *
+ * @hide
+ */
+ public static final String NAMESPACE_DISPLAY_MANAGER = "display_manager";
+
+ /**
* Namespace for all Game Driver features.
*
* @hide
@@ -350,15 +358,6 @@ public final class DeviceConfig {
*/
String KEY_SYSTEM_GESTURE_EXCLUSION_LOG_DEBOUNCE_MILLIS =
"system_gesture_exclusion_log_debounce_millis";
-
- /**
- * Key for controlling which packages are explicitly blocked from running at refresh rates
- * higher than 60hz.
- *
- * @see android.provider.DeviceConfig#NAMESPACE_WINDOW_MANAGER
- * @hide
- */
- String KEY_HIGH_REFRESH_RATE_BLACKLIST = "high_refresh_rate_blacklist";
}
private static final Object sLock = new Object();
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index f92be195229e..209a3648cb82 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -3477,6 +3477,14 @@ public final class Settings {
};
/**
+ * The user selected min refresh rate in frames per second.
+ *
+ * If this isn't set, 0 will be used.
+ * @hide
+ */
+ public static final String MIN_REFRESH_RATE = "min_refresh_rate";
+
+ /**
* The user selected peak refresh rate in frames per second.
*
* If this isn't set, the system falls back to a device specific default.
@@ -5949,6 +5957,18 @@ public final class Settings {
public static final String DEVICE_PROVISIONED = Global.DEVICE_PROVISIONED;
/**
+ * Indicates whether a DPC has been downloaded during provisioning.
+ *
+ * <p>Type: int (0 for false, 1 for true)
+ *
+ * <p>If this is true, then any attempts to begin setup again should result in factory reset
+ *
+ * @hide
+ */
+ public static final String MANAGED_PROVISIONING_DPC_DOWNLOADED =
+ "managed_provisioning_dpc_downloaded";
+
+ /**
* Indicates whether the current user has completed setup via the setup wizard.
* <p>
* Type: int (0 for false, 1 for true)
diff --git a/core/java/android/view/accessibility/TEST_MAPPING b/core/java/android/view/accessibility/TEST_MAPPING
index 535a31a908a0..d2bd6ea2b702 100644
--- a/core/java/android/view/accessibility/TEST_MAPPING
+++ b/core/java/android/view/accessibility/TEST_MAPPING
@@ -30,9 +30,6 @@
},
{
"exclude-annotation": "androidx.test.filters.FlakyTest"
- },
- {
- "exclude-filter": "android.view.textclassifier.TextClassifierTest#testSuggetsConversationActions_deduplicate"
}
]
}
diff --git a/core/java/android/view/textclassifier/TEST_MAPPING b/core/java/android/view/textclassifier/TEST_MAPPING
index 31e240bf55bb..01a6edecf21e 100644
--- a/core/java/android/view/textclassifier/TEST_MAPPING
+++ b/core/java/android/view/textclassifier/TEST_MAPPING
@@ -8,9 +8,6 @@
},
{
"exclude-annotation": "androidx.test.filters.FlakyTest"
- },
- {
- "exclude-filter": "android.view.textclassifier.TextClassifierTest#testSuggetsConversationActions_deduplicate"
}
]
}
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 95fca00f2346..721ac2d9a6dc 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -1843,8 +1843,8 @@ public class WebView extends AbsoluteLayout
/**
* Injects the supplied Java object into this WebView. The object is
- * injected into the JavaScript context of the main frame, using the
- * supplied name. This allows the Java object's methods to be
+ * injected into all frames of the web page, including all the iframes,
+ * using the supplied name. This allows the Java object's methods to be
* accessed from JavaScript. For applications targeted to API
* level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
* and above, only public methods that are annotated with
@@ -1883,6 +1883,11 @@ public class WebView extends AbsoluteLayout
* thread of this WebView. Care is therefore required to maintain thread
* safety.
* </li>
+ * <li> Because the object is exposed to all the frames, any frame could
+ * obtain the object name and call methods on it. There is no way to tell the
+ * calling frame's origin from the app side, so the app must not assume that
+ * the caller is trustworthy unless the app can guarantee that no third party
+ * content is ever loaded into the WebView even inside an iframe.</li>
* <li> The Java object's fields are not accessible.</li>
* <li> For applications targeted to API level {@link android.os.Build.VERSION_CODES#LOLLIPOP}
* and above, methods of injected Java objects are enumerable from
diff --git a/core/java/android/widget/ImageView.java b/core/java/android/widget/ImageView.java
index be5d2211c670..d62b979a2ed3 100644
--- a/core/java/android/widget/ImageView.java
+++ b/core/java/android/widget/ImageView.java
@@ -62,8 +62,9 @@ import java.io.IOException;
/**
* Displays image resources, for example {@link android.graphics.Bitmap}
* or {@link android.graphics.drawable.Drawable} resources.
- * ImageView is also commonly used to {@link #setImageTintMode(PorterDuff.Mode)
- * apply tints to an image} and handle {@link #setScaleType(ScaleType) image scaling}.
+ * ImageView is also commonly used to
+ * <a href="#setImageTintMode(android.graphics.PorterDuff.Mode)">apply tints to an image</a> and
+ * handle <a href="#setScaleType(android.widget.ImageView.ScaleType)">image scaling</a>.
*
* <p>
* The following XML snippet is a common example of using an ImageView to display an image resource:
@@ -76,7 +77,8 @@ import java.io.IOException;
* &lt;ImageView
* android:layout_width="wrap_content"
* android:layout_height="wrap_content"
- * android:src="@mipmap/ic_launcher"
+ * android:src="@drawable/my_image"
+ * android:contentDescription="@string/my_image_description"
* /&gt;
* &lt;/LinearLayout&gt;
* </pre>
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index c24ffb0c329b..ad53eb9feeae 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -479,10 +479,6 @@ class ZygoteConnection {
closeSocket();
- if (parsedArgs.mNiceName != null) {
- Process.setArgV0(parsedArgs.mNiceName);
- }
-
// End of the postFork event.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
if (parsedArgs.mInvokeWith != null) {
diff --git a/core/proto/android/content/configuration.proto b/core/proto/android/content/configuration.proto
index 57ced09240f2..7fa0ff64f8bf 100644
--- a/core/proto/android/content/configuration.proto
+++ b/core/proto/android/content/configuration.proto
@@ -32,7 +32,7 @@ message ConfigurationProto {
optional float font_scale = 1;
optional uint32 mcc = 2;
optional uint32 mnc = 3 [ (.android.privacy).dest = DEST_EXPLICIT ];
- repeated LocaleProto locales = 4;
+ repeated LocaleProto locales = 4 [deprecated = true];
optional uint32 screen_layout = 5;
optional uint32 color_mode = 6;
optional uint32 touchscreen = 7;
@@ -48,6 +48,7 @@ message ConfigurationProto {
optional uint32 smallest_screen_width_dp = 17;
optional uint32 density_dpi = 18;
optional .android.app.WindowConfigurationProto window_configuration = 19;
+ optional string locale_list = 20;
}
/**
diff --git a/core/proto/android/content/locale.proto b/core/proto/android/content/locale.proto
index bae6ec141981..a8f2a1334038 100644
--- a/core/proto/android/content/locale.proto
+++ b/core/proto/android/content/locale.proto
@@ -22,6 +22,7 @@ import "frameworks/base/core/proto/android/privacy.proto";
package android.content;
message LocaleProto {
+ option deprecated = true;
option (.android.msg_privacy).dest = DEST_AUTOMATIC;
optional string language = 1;
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index e4a5074ddbbf..efc943e3d44a 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -4196,6 +4196,10 @@
-->
</integer-array>
+ <!-- Default refresh rate in the zone defined by brightness and ambient thresholds.
+ If non-positive, then the refresh rate is unchanged even if thresholds are configured. -->
+ <integer name="config_defaultRefreshRateInZone">0</integer>
+
<!-- The type of the light sensor to be used by the display framework for things like
auto-brightness. If unset, then it just gets the default sensor of type TYPE_LIGHT. -->
<string name="config_displayLightSensorType" translatable="false" />
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 261e76e35a4a..e5805fd91fbb 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3798,6 +3798,7 @@
<!-- For high refresh rate displays -->
<java-symbol type="integer" name="config_defaultPeakRefreshRate" />
+ <java-symbol type="integer" name="config_defaultRefreshRateInZone" />
<java-symbol type="array" name="config_brightnessThresholdsOfPeakRefreshRate" />
<java-symbol type="array" name="config_ambientThresholdsOfPeakRefreshRate" />
diff --git a/core/tests/coretests/src/android/content/res/ConfigurationTest.java b/core/tests/coretests/src/android/content/res/ConfigurationTest.java
index 2fc3e36e7948..ad97ff101cb0 100644
--- a/core/tests/coretests/src/android/content/res/ConfigurationTest.java
+++ b/core/tests/coretests/src/android/content/res/ConfigurationTest.java
@@ -16,16 +16,29 @@
package android.content.res;
+import android.content.Context;
+import android.os.LocaleList;
import android.platform.test.annotations.Presubmit;
+import android.util.AtomicFile;
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoOutputStream;
+import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
+import com.android.server.usage.IntervalStatsProto;
+
import junit.framework.TestCase;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.util.Locale;
+
/**
* Build/install/run: bit FrameworksCoreTests:android.content.res.ConfigurationTest
*/
@@ -54,4 +67,70 @@ public class ConfigurationTest extends TestCase {
config2.updateFrom(config);
assertEquals(config2.screenLayout, Configuration.SCREENLAYOUT_COMPAT_NEEDED);
}
+
+ @Test
+ public void testReadWriteProto() throws Exception {
+ final Context context = InstrumentationRegistry.getTargetContext();
+ final File testDir = new File(context.getFilesDir(), "ConfigurationTest");
+ testDir.mkdirs();
+ final File proto = new File(testDir, "configs");
+ if (proto.exists()) {
+ proto.delete();
+ }
+
+ final Locale arabic = new Locale.Builder().setLocale(new Locale("ar", "AE")).build();
+ final Locale urdu = new Locale.Builder().setLocale(new Locale("ur", "IN")).build();
+ final Locale urduExtension = new Locale.Builder().setLocale(new Locale("ur", "IN"))
+ .setExtension('u', "nu-latn").build();
+ Configuration write = new Configuration();
+ write.setLocales(new LocaleList(arabic, urdu, urduExtension));
+ writeToProto(proto, write);
+ assertTrue("Failed to write configs to proto.", proto.exists());
+
+ final Configuration read = new Configuration();
+ try {
+ readFromProto(proto, read);
+ } finally {
+ proto.delete();
+ }
+
+ assertEquals("Missing locales in proto file written to disk.",
+ read.getLocales().size(), write.getLocales().size());
+ assertTrue("Arabic locale not found in Configuration locale list.",
+ read.getLocales().indexOf(arabic) != -1);
+ assertTrue("Urdu locale not found in Configuration locale list.",
+ read.getLocales().indexOf(urdu) != -1);
+ assertTrue("Urdu locale with extensions not found in Configuration locale list.",
+ read.getLocales().indexOf(urduExtension) != -1);
+ }
+
+ private void writeToProto(File f, Configuration config) throws Exception {
+ final AtomicFile af = new AtomicFile(f);
+ FileOutputStream fos = af.startWrite();
+ try {
+ final ProtoOutputStream protoOut = new ProtoOutputStream(fos);
+ final long token = protoOut.start(IntervalStatsProto.CONFIGURATIONS);
+ config.writeToProto(protoOut, IntervalStatsProto.Configuration.CONFIG, false, false);
+ protoOut.end(token);
+ protoOut.flush();
+ af.finishWrite(fos);
+ fos = null;
+ } finally {
+ af.failWrite(fos);
+ }
+ }
+
+ private void readFromProto(File f, Configuration config) throws Exception {
+ final AtomicFile afRead = new AtomicFile(f);
+ try (FileInputStream in = afRead.openRead()) {
+ final ProtoInputStream protoIn = new ProtoInputStream(in);
+ if (protoIn.isNextField(IntervalStatsProto.CONFIGURATIONS)) {
+ final long token = protoIn.start(IntervalStatsProto.CONFIGURATIONS);
+ if (protoIn.isNextField(IntervalStatsProto.Configuration.CONFIG)) {
+ config.readFromProto(protoIn, IntervalStatsProto.Configuration.CONFIG);
+ protoIn.end(token);
+ }
+ }
+ }
+ }
}
diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteTokenizerTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteTokenizerTest.java
new file mode 100644
index 000000000000..a9d148289262
--- /dev/null
+++ b/core/tests/coretests/src/android/database/sqlite/SQLiteTokenizerTest.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2019 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.database.sqlite;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class SQLiteTokenizerTest {
+ private List<String> getTokens(String sql) {
+ return SQLiteTokenizer.tokenize(sql, SQLiteTokenizer.OPTION_NONE);
+ }
+
+ private void checkTokens(String sql, String spaceSeparatedExpectedTokens) {
+ final List<String> expected = spaceSeparatedExpectedTokens == null
+ ? new ArrayList<>()
+ : Arrays.asList(spaceSeparatedExpectedTokens.split(" +"));
+
+ assertEquals(expected, getTokens(sql));
+ }
+
+ private void assertInvalidSql(String sql, String message) {
+ try {
+ getTokens(sql);
+ fail("Didn't throw InvalidSqlException");
+ } catch (IllegalArgumentException e) {
+ assertTrue("Expected " + e.getMessage() + " to contain " + message,
+ e.getMessage().contains(message));
+ }
+ }
+
+ @Test
+ public void testWhitespaces() {
+ checkTokens(" select \t\r\n a\n\n ", "select a");
+ checkTokens("a b", "a b");
+ }
+
+ @Test
+ public void testComment() {
+ checkTokens("--\n", null);
+ checkTokens("a--\n", "a");
+ checkTokens("a--abcdef\n", "a");
+ checkTokens("a--abcdef\nx", "a x");
+ checkTokens("a--\nx", "a x");
+ assertInvalidSql("a--abcdef", "Unterminated comment");
+ assertInvalidSql("a--abcdef\ndef--", "Unterminated comment");
+
+ checkTokens("/**/", null);
+ assertInvalidSql("/*", "Unterminated comment");
+ assertInvalidSql("/*/", "Unterminated comment");
+ assertInvalidSql("/*\n* /*a", "Unterminated comment");
+ checkTokens("a/**/", "a");
+ checkTokens("/**/b", "b");
+ checkTokens("a/**/b", "a b");
+ checkTokens("a/* -- \n* /* **/b", "a b");
+ }
+
+ @Test
+ public void testStrings() {
+ assertInvalidSql("'", "Unterminated quote");
+ assertInvalidSql("a'", "Unterminated quote");
+ assertInvalidSql("a'''", "Unterminated quote");
+ assertInvalidSql("a''' ", "Unterminated quote");
+ checkTokens("''", null);
+ checkTokens("''''", null);
+ checkTokens("a''''b", "a b");
+ checkTokens("a' '' 'b", "a b");
+ checkTokens("'abc'", null);
+ checkTokens("'abc\ndef'", null);
+ checkTokens("a'abc\ndef'", "a");
+ checkTokens("'abc\ndef'b", "b");
+ checkTokens("a'abc\ndef'b", "a b");
+ checkTokens("a'''abc\nd''ef'''b", "a b");
+ }
+
+ @Test
+ public void testDoubleQuotes() {
+ assertInvalidSql("\"", "Unterminated quote");
+ assertInvalidSql("a\"", "Unterminated quote");
+ assertInvalidSql("a\"\"\"", "Unterminated quote");
+ assertInvalidSql("a\"\"\" ", "Unterminated quote");
+ checkTokens("\"\"", "");
+ checkTokens("\"\"\"\"", "\"");
+ checkTokens("a\"\"\"\"b", "a \" b");
+ checkTokens("a\"\t\"\"\t\"b", "a \t\"\t b");
+ checkTokens("\"abc\"", "abc");
+ checkTokens("\"abc\ndef\"", "abc\ndef");
+ checkTokens("a\"abc\ndef\"", "a abc\ndef");
+ checkTokens("\"abc\ndef\"b", "abc\ndef b");
+ checkTokens("a\"abc\ndef\"b", "a abc\ndef b");
+ checkTokens("a\"\"\"abc\nd\"\"ef\"\"\"b", "a \"abc\nd\"ef\" b");
+ }
+
+ @Test
+ public void testBackQuotes() {
+ assertInvalidSql("`", "Unterminated quote");
+ assertInvalidSql("a`", "Unterminated quote");
+ assertInvalidSql("a```", "Unterminated quote");
+ assertInvalidSql("a``` ", "Unterminated quote");
+ checkTokens("``", "");
+ checkTokens("````", "`");
+ checkTokens("a````b", "a ` b");
+ checkTokens("a`\t``\t`b", "a \t`\t b");
+ checkTokens("`abc`", "abc");
+ checkTokens("`abc\ndef`", "abc\ndef");
+ checkTokens("a`abc\ndef`", "a abc\ndef");
+ checkTokens("`abc\ndef`b", "abc\ndef b");
+ checkTokens("a`abc\ndef`b", "a abc\ndef b");
+ checkTokens("a```abc\nd``ef```b", "a `abc\nd`ef` b");
+ }
+
+ @Test
+ public void testBrackets() {
+ assertInvalidSql("[", "Unterminated quote");
+ assertInvalidSql("a[", "Unterminated quote");
+ assertInvalidSql("a[ ", "Unterminated quote");
+ assertInvalidSql("a[[ ", "Unterminated quote");
+ checkTokens("[]", "");
+ checkTokens("[[]", "[");
+ checkTokens("a[[]b", "a [ b");
+ checkTokens("a[\t[\t]b", "a \t[\t b");
+ checkTokens("[abc]", "abc");
+ checkTokens("[abc\ndef]", "abc\ndef");
+ checkTokens("a[abc\ndef]", "a abc\ndef");
+ checkTokens("[abc\ndef]b", "abc\ndef b");
+ checkTokens("a[abc\ndef]b", "a abc\ndef b");
+ checkTokens("a[[abc\nd[ef[]b", "a [abc\nd[ef[ b");
+ }
+
+ @Test
+ public void testSemicolons() {
+ assertInvalidSql(";", "Semicolon is not allowed");
+ assertInvalidSql(" ;", "Semicolon is not allowed");
+ assertInvalidSql("; ", "Semicolon is not allowed");
+ assertInvalidSql("-;-", "Semicolon is not allowed");
+ checkTokens("--;\n", null);
+ checkTokens("/*;*/", null);
+ checkTokens("';'", null);
+ checkTokens("[;]", ";");
+ checkTokens("`;`", ";");
+ }
+
+ @Test
+ public void testTokens() {
+ checkTokens("a,abc,a00b,_1,_123,abcdef", "a abc a00b _1 _123 abcdef");
+ checkTokens("a--\nabc/**/a00b''_1'''ABC'''`_123`abc[d]\"e\"f",
+ "a abc a00b _1 _123 abc d e f");
+ }
+}
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index aee178a9a01e..79c93ce773a4 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -93,6 +93,7 @@ public class SettingsBackupTest {
Settings.System.VOLUME_VOICE, // deprecated since API 2?
Settings.System.WHEN_TO_MAKE_WIFI_CALLS, // bug?
Settings.System.WINDOW_ORIENTATION_LISTENER_LOG, // used for debugging only
+ Settings.System.MIN_REFRESH_RATE, // depends on hardware capabilities
Settings.System.PEAK_REFRESH_RATE // depends on hardware capabilities
);
@@ -715,6 +716,7 @@ public class SettingsBackupTest {
Settings.Secure.PACKAGES_TO_CLEAR_DATA_BEFORE_FULL_RESTORE,
Settings.Secure.FLASHLIGHT_AVAILABLE,
Settings.Secure.FLASHLIGHT_ENABLED,
+ Settings.Secure.MANAGED_PROVISIONING_DPC_DOWNLOADED,
Settings.Secure.CROSS_PROFILE_CALENDAR_ENABLED,
Settings.Secure.LOCATION_ACCESS_CHECK_INTERVAL_MILLIS,
Settings.Secure.LOCATION_ACCESS_CHECK_DELAY_MILLIS,
diff --git a/data/keyboards/Vendor_045e_Product_02dd.kl b/data/keyboards/Vendor_045e_Product_02dd.kl
new file mode 100644
index 000000000000..3975cec24fcb
--- /dev/null
+++ b/data/keyboards/Vendor_045e_Product_02dd.kl
@@ -0,0 +1,57 @@
+# Copyright (C) 2019 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.
+
+#
+# XBox One Controller - Model 1697 - USB
+#
+
+# Mapping according to https://developer.android.com/training/game-controllers/controller-input.html
+
+key 304 BUTTON_A
+key 305 BUTTON_B
+key 307 BUTTON_X
+key 308 BUTTON_Y
+
+key 310 BUTTON_L1
+key 311 BUTTON_R1
+
+# Triggers.
+axis 0x02 LTRIGGER
+axis 0x05 RTRIGGER
+
+# Left and right stick.
+# The reported value for flat is 128 out of a range from -32767 to 32768, which is absurd.
+# This confuses applications that rely on the flat value because the joystick actually
+# settles in a flat range of +/- 4096 or so.
+axis 0x00 X flat 4096
+axis 0x01 Y flat 4096
+axis 0x03 Z flat 4096
+axis 0x04 RZ flat 4096
+
+key 317 BUTTON_THUMBL
+key 318 BUTTON_THUMBR
+
+# Hat.
+axis 0x10 HAT_X
+axis 0x11 HAT_Y
+
+
+# Mapping according to https://www.kernel.org/doc/Documentation/input/gamepad.txt
+# Two overlapping rectangles
+key 314 BUTTON_SELECT
+# Hamburger - 3 parallel lines
+key 315 BUTTON_START
+
+# Xbox key
+key 316 BUTTON_MODE
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index 9a95288a69ae..8f164f1592d3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -27,6 +27,7 @@ import android.content.SharedPreferences;
import android.os.ParcelUuid;
import android.os.SystemClock;
import android.text.TextUtils;
+import android.util.EventLog;
import android.util.Log;
import androidx.annotation.VisibleForTesting;
@@ -804,10 +805,9 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
== BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE ||
mDevice.getBluetoothClass().getDeviceClass()
== BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET) {
- mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_ALLOWED);
- } else {
- mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_REJECTED);
+ EventLog.writeEvent(0x534e4554, "138529441", -1, "");
}
+ mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_REJECTED);
}
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java
index 7f906f6c5b06..6d874ab2be9b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java
@@ -117,7 +117,7 @@ public class HidProfile implements LocalBluetoothProfile {
public boolean isPreferred(BluetoothDevice device) {
if (mService == null) return false;
- return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF;
+ return mService.getPriority(device) != BluetoothProfile.PRIORITY_OFF;
}
public int getPreferred(BluetoothDevice device) {
diff --git a/packages/SystemUI/res/drawable/ic_5g_e_mobiledata.xml b/packages/SystemUI/res/drawable/ic_5g_e_mobiledata.xml
index fe1bb265880c..7c7c8c1e4729 100644
--- a/packages/SystemUI/res/drawable/ic_5g_e_mobiledata.xml
+++ b/packages/SystemUI/res/drawable/ic_5g_e_mobiledata.xml
@@ -16,16 +16,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:viewportWidth="22"
android:viewportHeight="17"
- android:width="22dp"
- android:height="17dp">
+ android:width="19.41dp"
+ android:height="15dp">
<path
android:fillColor="#FFFFFFFF"
- android:pathData="M1.22,8.49l0.43-4.96h4.33v1.17H2.67L2.44,7.41c0.41-0.29,0.85-0.43,1.33-0.43c0.77,0,1.38,0.3,1.83,0.9 s0.66,1.41,0.66,2.43c0,1.03-0.24,1.84-0.72,2.43s-1.14,0.88-1.98,0.88c-0.75,0-1.36-0.24-1.83-0.73s-0.74-1.16-0.81-2.02h1.13 c0.07,0.57,0.23,1,0.49,1.29c0.26,0.29,0.59,0.43,1.01,0.43c0.47,0,0.84-0.2,1.1-0.61c0.26-0.41,0.4-0.96,0.4-1.65 c0-0.65-0.14-1.18-0.43-1.59S3.96,8.11,3.47,8.11c-0.4,0-0.72,0.1-0.96,0.31L2.19,8.75L1.22,8.49z" />
- <path
- android:fillColor="#FFFFFFFF"
- android:pathData="M14.14,12.24l-0.22,0.27c-0.63,0.73-1.55,1.1-2.76,1.1c-1.08,0-1.92-0.36-2.53-1.07c-0.61-0.71-0.93-1.72-0.94-3.02V7.56 c0-1.39,0.28-2.44,0.84-3.13c0.56-0.7,1.39-1.04,2.51-1.04c0.95,0,1.69,0.26,2.23,0.79c0.54,0.53,0.83,1.28,0.89,2.26h-1.25 c-0.05-0.62-0.22-1.1-0.52-1.45c-0.29-0.35-0.74-0.52-1.34-0.52c-0.72,0-1.24,0.23-1.57,0.7C9.14,5.63,8.96,6.37,8.95,7.4v2.03 c0,1,0.19,1.77,0.57,2.31c0.38,0.54,0.93,0.8,1.65,0.8c0.67,0,1.19-0.16,1.54-0.49l0.18-0.17V9.59h-1.82V8.52h3.07V12.24z" />
- <path
- android:fillColor="#FFFFFFFF"
- android:pathData="M20.96,8.88h-3.52v3.53h4.1v1.07h-5.35V3.52h5.28V4.6h-4.03V7.8h3.52V8.88z" />
-
+ android:pathData="M 6.72 3.52 L 6.54 4.69 L 3.34 4.69 L 2.63 7.41 C 3.011 7.143 3.465 7 3.93 7 C 4.5 6.986 5.041 7.251 5.38 7.71 C 5.756 8.249 5.952 8.893 5.94 9.55 C 5.98 10.276 5.864 11.002 5.6 11.68 C 5.385 12.267 5.007 12.78 4.51 13.16 C 4.043 13.499 3.476 13.671 2.9 13.65 C 2.271 13.653 1.675 13.369 1.28 12.88 C 0.854 12.302 0.636 11.597 0.66 10.88 L 1.76 10.88 C 1.81 12 2.21 12.57 3 12.58 C 3.592 12.589 4.132 12.243 4.37 11.7 C 4.708 11.044 4.85 10.305 4.78 9.57 C 4.767 9.209 4.645 8.86 4.43 8.57 C 4.239 8.309 3.934 8.156 3.61 8.16 C 3.404 8.138 3.196 8.162 3 8.23 C 2.748 8.358 2.518 8.527 2.32 8.73 L 1.31 8.46 L 2.51 3.52 L 6.72 3.52 Z M 11.7 3.39 C 12.459 3.353 13.195 3.662 13.7 4.23 C 14.185 4.864 14.42 5.654 14.36 6.45 L 13.1 6.45 C 13.131 5.938 12.998 5.43 12.72 5 C 12.455 4.679 12.056 4.498 11.64 4.51 C 11.025 4.456 10.42 4.688 10 5.14 C 9.491 5.811 9.179 6.611 9.1 7.45 L 8.75 9.54 L 8.75 10.57 C 8.82 11.86 9.36 12.52 10.36 12.57 C 10.701 12.593 11.043 12.538 11.36 12.41 C 11.661 12.281 11.943 12.113 12.2 11.91 L 12.62 9.62 L 10.77 9.62 L 11 8.52 L 14 8.52 L 13.36 12.23 C 13.176 12.483 12.953 12.706 12.7 12.89 C 11.995 13.398 11.138 13.652 10.27 13.61 C 9.507 13.634 8.773 13.315 8.27 12.74 C 7.748 12.085 7.475 11.267 7.5 10.43 C 7.47 10.097 7.47 9.763 7.5 9.43 L 7.8 7.43 C 7.927 6.332 8.36 5.293 9.05 4.43 C 9.725 3.692 10.703 3.308 11.7 3.39 Z M 20.81 7.21 L 20.62 8.29 L 18.32 8.29 L 18.06 9.8 L 20.06 9.8 L 19.83 10.84 L 17.88 10.84 L 17.59 12.54 L 19.9 12.54 L 19.71 13.61 L 16.14 13.61 L 17.25 7.21 L 20.81 7.21 Z" />
+ <path android:pathData="M 0 0 H 14 V 17 H 0 V 0 Z"/>
</vector>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 340cb3ad7358..6e8e8236a99a 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -196,9 +196,6 @@
<!-- Doze: duration to avoid false pickup gestures triggered by notification vibrations -->
<integer name="doze_pickup_vibration_threshold">2000</integer>
- <!-- Doze: can we assume the pickup sensor includes a proximity check? -->
- <bool name="doze_pickup_performs_proximity_check">false</bool>
-
<!-- Type of a sensor that provides a low-power estimate of the desired display
brightness, suitable to listen to while the device is asleep (e.g. during
always-on display) -->
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
index 264a1020ddc9..0d85a3f77270 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
@@ -134,11 +134,13 @@ public class SystemUIFactory {
public KeyguardBouncer createKeyguardBouncer(Context context, ViewMediatorCallback callback,
LockPatternUtils lockPatternUtils, ViewGroup container,
DismissCallbackRegistry dismissCallbackRegistry,
- KeyguardBouncer.BouncerExpansionCallback expansionCallback) {
+ KeyguardBouncer.BouncerExpansionCallback expansionCallback,
+ KeyguardBypassController bypassController) {
return new KeyguardBouncer(context, callback, lockPatternUtils, container,
dismissCallbackRegistry, FalsingManagerFactory.getInstance(context),
expansionCallback, UnlockMethodCache.getInstance(context),
- KeyguardUpdateMonitor.getInstance(context), new Handler(Looper.getMainLooper()));
+ KeyguardUpdateMonitor.getInstance(context), bypassController,
+ new Handler(Looper.getMainLooper()));
}
public ScrimController createScrimController(ScrimView scrimBehind, ScrimView scrimInFront,
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
index 092eb46cad5e..026a62528c8d 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java
@@ -107,8 +107,7 @@ public class DozeSensors {
config.dozePickupSensorAvailable(),
DozeLog.REASON_SENSOR_PICKUP, false /* touchCoords */,
false /* touchscreen */,
- false /* ignoresSetting */,
- mDozeParameters.getPickupPerformsProxCheck()),
+ false /* ignoresSetting */),
new TriggerSensor(
findSensorWithType(config.doubleTapSensorType()),
Settings.Secure.DOZE_DOUBLE_TAP_GESTURE,
@@ -205,11 +204,8 @@ public class DozeSensors {
public void updateListening() {
boolean anyListening = false;
for (TriggerSensor s : mSensors) {
- // We don't want to be listening while we're PAUSED (prox sensor is covered)
- // except when the sensor is already gated by prox.
- boolean listen = mListening && (!mPaused || s.performsProxCheck());
- s.setListening(listen);
- if (listen) {
+ s.setListening(mListening);
+ if (mListening) {
anyListening = true;
}
}
@@ -391,7 +387,6 @@ public class DozeSensors {
private final boolean mReportsTouchCoordinates;
private final boolean mSettingDefault;
private final boolean mRequiresTouchscreen;
- private final boolean mSensorPerformsProxCheck;
protected boolean mRequested;
protected boolean mRegistered;
@@ -408,14 +403,12 @@ public class DozeSensors {
boolean configured, int pulseReason, boolean reportsTouchCoordinates,
boolean requiresTouchscreen) {
this(sensor, setting, settingDef, configured, pulseReason, reportsTouchCoordinates,
- requiresTouchscreen, false /* ignoresSetting */,
- false /* sensorPerformsProxCheck */);
+ requiresTouchscreen, false /* ignoresSetting */);
}
private TriggerSensor(Sensor sensor, String setting, boolean settingDef,
boolean configured, int pulseReason, boolean reportsTouchCoordinates,
- boolean requiresTouchscreen, boolean ignoresSetting,
- boolean sensorPerformsProxCheck) {
+ boolean requiresTouchscreen, boolean ignoresSetting) {
mSensor = sensor;
mSetting = setting;
mSettingDefault = settingDef;
@@ -424,7 +417,6 @@ public class DozeSensors {
mReportsTouchCoordinates = reportsTouchCoordinates;
mRequiresTouchscreen = requiresTouchscreen;
mIgnoresSetting = ignoresSetting;
- mSensorPerformsProxCheck = sensorPerformsProxCheck;
}
public void setListening(boolean listen) {
@@ -498,23 +490,13 @@ public class DozeSensors {
screenX = event.values[0];
screenY = event.values[1];
}
- mCallback.onSensorPulse(mPulseReason, mSensorPerformsProxCheck, screenX, screenY,
- event.values);
+ mCallback.onSensorPulse(mPulseReason, screenX, screenY, event.values);
if (!mRegistered) {
updateListening(); // reregister, this sensor only fires once
}
}));
}
- /**
- * If the sensor itself performs proximity checks, to avoid pocket dialing.
- * Gated sensors don't need to be stopped when the {@link DozeMachine} is
- * {@link DozeMachine.State#DOZE_AOD_PAUSED}.
- */
- public boolean performsProxCheck() {
- return mSensorPerformsProxCheck;
- }
-
public void registerSettingsObserver(ContentObserver settingsObserver) {
if (mConfigured && !TextUtils.isEmpty(mSetting)) {
mResolver.registerContentObserver(
@@ -610,8 +592,7 @@ public class DozeSensors {
return;
}
if (DEBUG) Log.d(TAG, "onSensorEvent: " + triggerEventToString(event));
- mCallback.onSensorPulse(mPulseReason, true /* sensorPerformsProxCheck */, -1, -1,
- event.getValues());
+ mCallback.onSensorPulse(mPulseReason, -1, -1, event.getValues());
}));
}
}
@@ -621,13 +602,11 @@ public class DozeSensors {
/**
* Called when a sensor requests a pulse
* @param pulseReason Requesting sensor, e.g. {@link DozeLog#REASON_SENSOR_PICKUP}
- * @param sensorPerformedProxCheck true if the sensor already checked for FAR proximity.
* @param screenX the location on the screen where the sensor fired or -1
- * if the sensor doesn't support reporting screen locations.
+ * if the sensor doesn't support reporting screen locations.
* @param screenY the location on the screen where the sensor fired or -1
* @param rawValues raw values array from the event.
*/
- void onSensorPulse(int pulseReason, boolean sensorPerformedProxCheck,
- float screenX, float screenY, float[] rawValues);
+ void onSensorPulse(int pulseReason, float screenX, float screenY, float[] rawValues);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index 310f04abc36c..bab64db4519c 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -41,6 +41,7 @@ import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.util.Preconditions;
import com.android.systemui.Dependency;
+import com.android.systemui.R;
import com.android.systemui.dock.DockManager;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.util.Assert;
@@ -156,8 +157,7 @@ public class DozeTriggers implements DozeMachine.Part {
}
@VisibleForTesting
- void onSensor(int pulseReason, boolean sensorPerformedProxCheck,
- float screenX, float screenY, float[] rawValues) {
+ void onSensor(int pulseReason, float screenX, float screenY, float[] rawValues) {
boolean isDoubleTap = pulseReason == DozeLog.REASON_SENSOR_DOUBLE_TAP;
boolean isTap = pulseReason == DozeLog.REASON_SENSOR_TAP;
boolean isPickup = pulseReason == DozeLog.REASON_SENSOR_PICKUP;
@@ -169,10 +169,11 @@ public class DozeTriggers implements DozeMachine.Part {
if (isWakeDisplay) {
onWakeScreen(wakeEvent, mMachine.isExecutingTransition() ? null : mMachine.getState());
} else if (isLongPress) {
- requestPulse(pulseReason, sensorPerformedProxCheck, null /* onPulseSupressedListener */);
+ requestPulse(pulseReason, true /* alreadyPerformedProxCheck */,
+ null /* onPulseSupressedListener */);
} else if (isWakeLockScreen) {
if (wakeEvent) {
- requestPulse(pulseReason, sensorPerformedProxCheck,
+ requestPulse(pulseReason, true /* alreadyPerformedProxCheck */,
null /* onPulseSupressedListener */);
}
} else {
@@ -191,8 +192,7 @@ public class DozeTriggers implements DozeMachine.Part {
} else {
mDozeHost.extendPulse(pulseReason);
}
- }, sensorPerformedProxCheck
- || (mDockManager != null && mDockManager.isDocked()), pulseReason);
+ }, true /* alreadyPerformedProxCheck */, pulseReason);
}
if (isPickup) {
@@ -278,7 +278,7 @@ public class DozeTriggers implements DozeMachine.Part {
.setType(MetricsEvent.TYPE_OPEN)
.setSubtype(DozeLog.REASON_SENSOR_WAKE_UP));
}
- }, false /* alreadyPerformedProxCheck */, DozeLog.REASON_SENSOR_WAKE_UP);
+ }, true /* alreadyPerformedProxCheck */, DozeLog.REASON_SENSOR_WAKE_UP);
} else {
boolean paused = (state == DozeMachine.State.DOZE_AOD_PAUSED);
boolean pausing = (state == DozeMachine.State.DOZE_AOD_PAUSING);
@@ -417,6 +417,9 @@ public class DozeTriggers implements DozeMachine.Part {
mDozeSensors.dump(pw);
}
+ /**
+ * @see DozeSensors.ProxSensor
+ */
private abstract class ProximityCheck implements SensorEventListener, Runnable {
private static final int TIMEOUT_DELAY_MS = 500;
@@ -428,12 +431,18 @@ public class DozeTriggers implements DozeMachine.Part {
private boolean mRegistered;
private boolean mFinished;
private float mMaxRange;
+ private boolean mUsingBrightnessSensor;
protected abstract void onProximityResult(int result);
public void check() {
Preconditions.checkState(!mFinished && !mRegistered);
- final Sensor sensor = mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
+ Sensor sensor = DozeSensors.findSensorWithType(mSensorManager,
+ mContext.getString(R.string.doze_brightness_sensor_type));
+ mUsingBrightnessSensor = sensor != null;
+ if (sensor == null) {
+ sensor = mSensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
+ }
if (sensor == null) {
if (DozeMachine.DEBUG) Log.d(TAG, "ProxCheck: No sensor found");
finishWithResult(RESULT_UNKNOWN);
@@ -449,6 +458,9 @@ public class DozeTriggers implements DozeMachine.Part {
mRegistered = true;
}
+ /**
+ * @see DozeSensors.ProxSensor#onSensorChanged(SensorEvent)
+ */
@Override
public void onSensorChanged(SensorEvent event) {
if (event.values.length == 0) {
@@ -458,7 +470,14 @@ public class DozeTriggers implements DozeMachine.Part {
if (DozeMachine.DEBUG) {
Log.d(TAG, "ProxCheck: Event: value=" + event.values[0] + " max=" + mMaxRange);
}
- final boolean isNear = event.values[0] < mMaxRange;
+ final boolean isNear;
+ if (mUsingBrightnessSensor) {
+ // The custom brightness sensor is gated by the proximity sensor and will
+ // return 0 whenever prox is covered.
+ isNear = event.values[0] == 0;
+ } else {
+ isNear = event.values[0] < mMaxRange;
+ }
finishWithResult(isNear ? RESULT_NEAR : RESULT_FAR);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
index 10b48e71005d..bb6a38e1dcf5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -207,10 +207,6 @@ public class DozeParameters implements TunerService.Tunable,
return SystemProperties.get(propName, mContext.getString(resId));
}
- public boolean getPickupPerformsProxCheck() {
- return mContext.getResources().getBoolean(R.bool.doze_pickup_performs_proximity_check);
- }
-
public int getPulseVisibleDurationExtended() {
return 2 * getPulseVisibleDuration();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index c4d346ccaefb..dc9b373de688 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -77,6 +77,7 @@ public class KeyguardBouncer {
}
};
private final Runnable mRemoveViewRunnable = this::removeView;
+ private final KeyguardBypassController mKeyguardBypassController;
protected KeyguardHostView mKeyguardView;
private final Runnable mResetRunnable = ()-> {
if (mKeyguardView != null) {
@@ -97,7 +98,8 @@ public class KeyguardBouncer {
LockPatternUtils lockPatternUtils, ViewGroup container,
DismissCallbackRegistry dismissCallbackRegistry, FalsingManager falsingManager,
BouncerExpansionCallback expansionCallback, UnlockMethodCache unlockMethodCache,
- KeyguardUpdateMonitor keyguardUpdateMonitor, Handler handler) {
+ KeyguardUpdateMonitor keyguardUpdateMonitor,
+ KeyguardBypassController keyguardBypassController, Handler handler) {
mContext = context;
mCallback = callback;
mLockPatternUtils = lockPatternUtils;
@@ -109,6 +111,7 @@ public class KeyguardBouncer {
mHandler = handler;
mUnlockMethodCache = unlockMethodCache;
mKeyguardUpdateMonitor.registerCallback(mUpdateMonitorCallback);
+ mKeyguardBypassController = keyguardBypassController;
}
public void show(boolean resetSecuritySelection) {
@@ -171,7 +174,8 @@ public class KeyguardBouncer {
// Split up the work over multiple frames.
DejankUtils.removeCallbacks(mResetRunnable);
if (mUnlockMethodCache.isFaceAuthEnabled() && !needsFullscreenBouncer()
- && !mKeyguardUpdateMonitor.userNeedsStrongAuth()) {
+ && !mKeyguardUpdateMonitor.userNeedsStrongAuth()
+ && !mKeyguardBypassController.getBypassEnabled()) {
mHandler.postDelayed(mShowRunnable, BOUNCER_FACE_DELAY);
} else {
DejankUtils.postAfterTraversal(mShowRunnable);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 4d85a422d9b9..7e86651ffef6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -220,7 +220,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
mBiometricUnlockController = biometricUnlockController;
mBouncer = SystemUIFactory.getInstance().createKeyguardBouncer(mContext,
mViewMediatorCallback, mLockPatternUtils, container, dismissCallbackRegistry,
- mExpansionCallback);
+ mExpansionCallback, bypassController);
mNotificationPanelView = notificationPanelView;
notificationPanelView.addExpansionListener(this);
mBypassController = bypassController;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitor.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitor.java
index e1ef809a883c..6dc90b9028fa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitor.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitor.java
@@ -19,7 +19,6 @@ import com.android.systemui.statusbar.policy.KeyguardMonitor.Callback;
public interface KeyguardMonitor extends CallbackController<Callback> {
boolean isSecure();
- boolean canSkipBouncer();
boolean isShowing();
boolean isOccluded();
boolean isKeyguardFadingAway();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitorImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitorImpl.java
index 87ed14a9eeec..26d615c86c29 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitorImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardMonitorImpl.java
@@ -17,13 +17,11 @@
package com.android.systemui.statusbar.policy;
import android.annotation.NonNull;
-import android.app.ActivityManager;
import android.content.Context;
import com.android.internal.util.Preconditions;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
-import com.android.systemui.settings.CurrentUserTracker;
import java.util.ArrayList;
@@ -39,14 +37,11 @@ public class KeyguardMonitorImpl extends KeyguardUpdateMonitorCallback
private final ArrayList<Callback> mCallbacks = new ArrayList<>();
private final Context mContext;
- private final CurrentUserTracker mUserTracker;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- private int mCurrentUser;
private boolean mShowing;
private boolean mSecure;
private boolean mOccluded;
- private boolean mCanSkipBouncer;
private boolean mListening;
private boolean mKeyguardFadingAway;
@@ -62,13 +57,6 @@ public class KeyguardMonitorImpl extends KeyguardUpdateMonitorCallback
public KeyguardMonitorImpl(Context context) {
mContext = context;
mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
- mUserTracker = new CurrentUserTracker(mContext) {
- @Override
- public void onUserSwitched(int newUserId) {
- mCurrentUser = newUserId;
- updateCanSkipBouncerState();
- }
- };
}
@Override
@@ -77,10 +65,7 @@ public class KeyguardMonitorImpl extends KeyguardUpdateMonitorCallback
mCallbacks.add(callback);
if (mCallbacks.size() != 0 && !mListening) {
mListening = true;
- mCurrentUser = ActivityManager.getCurrentUser();
- updateCanSkipBouncerState();
mKeyguardUpdateMonitor.registerCallback(this);
- mUserTracker.startTracking();
}
}
@@ -90,7 +75,6 @@ public class KeyguardMonitorImpl extends KeyguardUpdateMonitorCallback
if (mCallbacks.remove(callback) && mCallbacks.size() == 0 && mListening) {
mListening = false;
mKeyguardUpdateMonitor.removeCallback(this);
- mUserTracker.stopTracking();
}
}
@@ -109,11 +93,6 @@ public class KeyguardMonitorImpl extends KeyguardUpdateMonitorCallback
return mOccluded;
}
- @Override
- public boolean canSkipBouncer() {
- return mCanSkipBouncer;
- }
-
public void notifyKeyguardState(boolean showing, boolean secure, boolean occluded) {
if (mShowing == showing && mSecure == secure && mOccluded == occluded) return;
mShowing = showing;
@@ -124,7 +103,6 @@ public class KeyguardMonitorImpl extends KeyguardUpdateMonitorCallback
@Override
public void onTrustChanged(int userId) {
- updateCanSkipBouncerState();
notifyKeyguardChanged();
}
@@ -132,10 +110,6 @@ public class KeyguardMonitorImpl extends KeyguardUpdateMonitorCallback
return mKeyguardUpdateMonitor.isDeviceInteractive();
}
- private void updateCanSkipBouncerState() {
- mCanSkipBouncer = mKeyguardUpdateMonitor.getUserCanSkipBouncer(mCurrentUser);
- }
-
private void notifyKeyguardChanged() {
// Copy the list to allow removal during callback.
new ArrayList<>(mCallbacks).forEach(Callback::onKeyguardShowingChanged);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index 395add76dda4..35e3923f285b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -61,6 +61,7 @@ import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.qs.DetailAdapter;
import com.android.systemui.qs.tiles.UserDetailView;
import com.android.systemui.statusbar.phone.SystemUIDialog;
+import com.android.systemui.statusbar.phone.UnlockMethodCache;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -595,17 +596,19 @@ public class UserSwitcherController implements Dumpable {
final UserSwitcherController mController;
private final KeyguardMonitor mKeyguardMonitor;
+ private final UnlockMethodCache mUnlockMethodCache;
protected BaseUserAdapter(UserSwitcherController controller) {
mController = controller;
mKeyguardMonitor = controller.mKeyguardMonitor;
+ mUnlockMethodCache = UnlockMethodCache.getInstance(controller.mContext);
controller.addAdapter(new WeakReference<>(this));
}
public int getUserCount() {
boolean secureKeyguardShowing = mKeyguardMonitor.isShowing()
&& mKeyguardMonitor.isSecure()
- && !mKeyguardMonitor.canSkipBouncer();
+ && !mUnlockMethodCache.canSkipBouncer();
if (!secureKeyguardShowing) {
return mController.getUsers().size();
}
@@ -627,7 +630,7 @@ public class UserSwitcherController implements Dumpable {
public int getCount() {
boolean secureKeyguardShowing = mKeyguardMonitor.isShowing()
&& mKeyguardMonitor.isSecure()
- && !mKeyguardMonitor.canSkipBouncer();
+ && !mUnlockMethodCache.canSkipBouncer();
if (!secureKeyguardShowing) {
return mController.getUsers().size();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java
index 2ed0970ce44b..0c124fff53a3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeConfigurationUtil.java
@@ -37,7 +37,6 @@ public class DozeConfigurationUtil {
when(params.getPulseOnSigMotion()).thenReturn(false);
when(params.getPickupVibrationThreshold()).thenReturn(0);
when(params.getProxCheckBeforePulse()).thenReturn(true);
- when(params.getPickupPerformsProxCheck()).thenReturn(true);
when(params.getPolicy()).thenReturn(mock(AlwaysOnDisplayPolicy.class));
when(params.doubleTapReportsTouchCoordinates()).thenReturn(false);
when(params.getDisplayNeedsBlanking()).thenReturn(false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
index 7df45a3d8949..cd6d1e069566 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSensorsTest.java
@@ -19,7 +19,6 @@ package com.android.systemui.doze;
import static com.android.systemui.plugins.SensorManagerPlugin.Sensor.TYPE_WAKE_LOCK_SCREEN;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
@@ -79,8 +78,6 @@ public class DozeSensorsTest extends SysuiTestCase {
private AlwaysOnDisplayPolicy mAlwaysOnDisplayPolicy;
@Mock
private TriggerSensor mTriggerSensor;
- @Mock
- private TriggerSensor mProxGatedTriggerSensor;
private SensorManagerPlugin.SensorEventListener mWakeLockScreenListener;
private TestableLooper mTestableLooper;
private DozeSensors mDozeSensors;
@@ -88,7 +85,6 @@ public class DozeSensorsTest extends SysuiTestCase {
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- when(mProxGatedTriggerSensor.performsProxCheck()).thenReturn(true);
mTestableLooper = TestableLooper.get(this);
when(mAmbientDisplayConfiguration.getWakeLockScreenDebounce()).thenReturn(5000L);
when(mAmbientDisplayConfiguration.alwaysOnEnabled(anyInt())).thenReturn(true);
@@ -106,14 +102,14 @@ public class DozeSensorsTest extends SysuiTestCase {
mWakeLockScreenListener.onSensorChanged(mock(SensorManagerPlugin.SensorEvent.class));
mTestableLooper.processAllMessages();
verify(mCallback).onSensorPulse(eq(DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN),
- anyBoolean(), anyFloat(), anyFloat(), eq(null));
+ anyFloat(), anyFloat(), eq(null));
mDozeSensors.requestTemporaryDisable();
reset(mCallback);
mWakeLockScreenListener.onSensorChanged(mock(SensorManagerPlugin.SensorEvent.class));
mTestableLooper.processAllMessages();
verify(mCallback, never()).onSensorPulse(eq(DozeLog.PULSE_REASON_SENSOR_WAKE_LOCK_SCREEN),
- anyBoolean(), anyFloat(), anyFloat(), eq(null));
+ anyFloat(), anyFloat(), eq(null));
}
@Test
@@ -132,20 +128,17 @@ public class DozeSensorsTest extends SysuiTestCase {
}
@Test
- public void testSetPaused_onlyPausesNonGatedSensors() {
+ public void testSetPaused_doesntPause_sensors() {
mDozeSensors.setListening(true);
verify(mTriggerSensor).setListening(eq(true));
- verify(mProxGatedTriggerSensor).setListening(eq(true));
- clearInvocations(mTriggerSensor, mProxGatedTriggerSensor);
+ clearInvocations(mTriggerSensor);
mDozeSensors.setPaused(true);
- verify(mTriggerSensor).setListening(eq(false));
- verify(mProxGatedTriggerSensor).setListening(eq(true));
-
- clearInvocations(mTriggerSensor, mProxGatedTriggerSensor);
- mDozeSensors.setPaused(false);
verify(mTriggerSensor).setListening(eq(true));
- verify(mProxGatedTriggerSensor).setListening(eq(true));
+
+ clearInvocations(mTriggerSensor);
+ mDozeSensors.setListening(false);
+ verify(mTriggerSensor).setListening(eq(false));
}
private class TestableDozeSensors extends DozeSensors {
@@ -161,7 +154,7 @@ public class DozeSensorsTest extends SysuiTestCase {
mWakeLockScreenListener = (PluginSensor) sensor;
}
}
- mSensors = new TriggerSensor[] {mTriggerSensor, mProxGatedTriggerSensor};
+ mSensors = new TriggerSensor[] {mTriggerSensor};
}
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
index d4642238d8fd..e190f9923da8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
@@ -20,7 +20,6 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
@@ -134,28 +133,4 @@ public class DozeTriggersTest extends SysuiTestCase {
mTriggers.transitionTo(DozeMachine.State.DOZE, DozeMachine.State.FINISH);
verify(mDockManagerFake).removeListener(any());
}
-
- @Test
- public void testOnSensor_whenUndockedWithNearAndDoubleTapScreen_shouldNotWakeUp() {
- mSensors.getMockProximitySensor().sendProximityResult(false /* far */);
-
- mTriggers.onSensor(DozeLog.REASON_SENSOR_DOUBLE_TAP,
- false /* sensorPerformedProxCheck */, 50 /* screenX */, 50 /* screenY */,
- null /* rawValues */);
- verify(mMachine, never()).wakeUp();
- }
-
- @Test
- public void testOnSensor_whenDockedWithNearAndDoubleTapScreen_shouldWakeUp() {
- doReturn(true).when(mDockManagerFake).isDocked();
- doReturn(true).when(mParameters).getDisplayNeedsBlanking();
- mSensors.getMockProximitySensor().sendProximityResult(false /* far */);
-
- mTriggers.onSensor(DozeLog.REASON_SENSOR_DOUBLE_TAP,
- false /* sensorPerformedProxCheck */, 50 /* screenX */, 50 /* screenY */,
- null /* rawValues */);
-
- verify(mHost).setAodDimmingScrim(eq(1f));
- verify(mMachine).wakeUp();
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java
index 907e695f2513..cd60e47eef50 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java
@@ -21,6 +21,7 @@ import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.never;
@@ -84,6 +85,8 @@ public class KeyguardBouncerTest extends SysuiTestCase {
@Mock
private UnlockMethodCache mUnlockMethodCache;
@Mock
+ private KeyguardBypassController mKeyguardBypassController;
+ @Mock
private Handler mHandler;
private KeyguardBouncer mBouncer;
@@ -98,7 +101,8 @@ public class KeyguardBouncerTest extends SysuiTestCase {
when(mKeyguardHostView.getHeight()).thenReturn(500);
mBouncer = new KeyguardBouncer(getContext(), mViewMediatorCallback,
mLockPatternUtils, container, mDismissCallbackRegistry, mFalsingManager,
- mExpansionCallback, mUnlockMethodCache, mKeyguardUpdateMonitor, mHandler) {
+ mExpansionCallback, mUnlockMethodCache, mKeyguardUpdateMonitor,
+ mKeyguardBypassController, mHandler) {
@Override
protected void inflateView() {
super.inflateView();
@@ -391,6 +395,15 @@ public class KeyguardBouncerTest extends SysuiTestCase {
}
@Test
+ public void testShow_delaysIfFaceAuthIsRunning_unlessBypass() {
+ when(mUnlockMethodCache.isFaceAuthEnabled()).thenReturn(true);
+ when(mKeyguardBypassController.getBypassEnabled()).thenReturn(true);
+ mBouncer.show(true /* reset */);
+
+ verify(mHandler, never()).postDelayed(any(), anyLong());
+ }
+
+ @Test
public void testRegisterUpdateMonitorCallback() {
verify(mKeyguardUpdateMonitor).registerCallback(any());
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardMonitor.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardMonitor.java
index 95c7a4d09f92..2fb0e0e7caf8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardMonitor.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardMonitor.java
@@ -80,9 +80,4 @@ public class FakeKeyguardMonitor implements KeyguardMonitor {
public long calculateGoingToFullShadeDelay() {
return 0;
}
-
- @Override
- public boolean canSkipBouncer() {
- return false;
- }
}
diff --git a/services/accessibility/TEST_MAPPING b/services/accessibility/TEST_MAPPING
index 45c52452afb4..d90c3bd9b4c2 100644
--- a/services/accessibility/TEST_MAPPING
+++ b/services/accessibility/TEST_MAPPING
@@ -52,9 +52,6 @@
},
{
"exclude-annotation": "androidx.test.filters.FlakyTest"
- },
- {
- "exclude-filter": "android.view.textclassifier.TextClassifierTest#testSuggetsConversationActions_deduplicate"
}
]
},
@@ -66,9 +63,6 @@
},
{
"exclude-annotation": "androidx.test.filters.FlakyTest"
- },
- {
- "exclude-filter": "android.view.textclassifier.TextClassifierTest#testSuggetsConversationActions_deduplicate"
}
]
}
diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java
index 07482796b027..9936d73fb800 100644
--- a/services/core/java/com/android/server/VibratorService.java
+++ b/services/core/java/com/android/server/VibratorService.java
@@ -1188,6 +1188,7 @@ public class VibratorService extends IVibratorService.Stub
private static boolean isNotification(int usageHint) {
switch (usageHint) {
case AudioAttributes.USAGE_NOTIFICATION:
+ case AudioAttributes.USAGE_NOTIFICATION_EVENT:
case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST:
case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT:
case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_DELAYED:
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 0a774f613670..cc5932e9b38c 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -5267,7 +5267,7 @@ public class ActivityManagerService extends IActivityManager.Stub
storageManager.commitChanges();
} catch (Exception e) {
PowerManager pm = (PowerManager)
- mInjector.getContext().getSystemService(Context.POWER_SERVICE);
+ mContext.getSystemService(Context.POWER_SERVICE);
pm.reboot("Checkpoint commit failed");
}
diff --git a/services/core/java/com/android/server/am/TEST_MAPPING b/services/core/java/com/android/server/am/TEST_MAPPING
index 884e7a564e56..21d4925722d0 100644
--- a/services/core/java/com/android/server/am/TEST_MAPPING
+++ b/services/core/java/com/android/server/am/TEST_MAPPING
@@ -14,15 +14,6 @@
},
{
"exclude-annotation": "androidx.test.filters.FlakyTest"
- },
- {
- "exclude-filter": "android.app.cts.AlarmManagerTest#testSetRepeating"
- },
- {
- "exclude-filter": "android.app.cts.SystemFeaturesTest#testLocationFeatures"
- },
- {
- "exclude-filter": "android.app.cts.SystemFeaturesTest#testSensorFeatures"
}
]
},
diff --git a/services/core/java/com/android/server/biometrics/face/FaceService.java b/services/core/java/com/android/server/biometrics/face/FaceService.java
index a7065216f6a3..ee49f5885e4a 100644
--- a/services/core/java/com/android/server/biometrics/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/face/FaceService.java
@@ -223,7 +223,8 @@ public class FaceService extends BiometricServiceBase {
@Override
public boolean wasUserDetected() {
- return mLastAcquire != FaceManager.FACE_ACQUIRED_NOT_DETECTED;
+ return mLastAcquire != FaceManager.FACE_ACQUIRED_NOT_DETECTED
+ && mLastAcquire != FaceManager.FACE_ACQUIRED_SENSOR_DIRTY;
}
@Override
diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java
index c45a314e39cc..500a24282191 100644
--- a/services/core/java/com/android/server/display/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/DisplayModeDirector.java
@@ -38,13 +38,16 @@ import android.os.Message;
import android.os.UserHandle;
import android.os.PowerManager;
import android.os.SystemClock;
+import android.provider.DeviceConfig;
import android.provider.Settings;
import android.text.TextUtils;
+import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.view.Display;
import android.view.DisplayInfo;
+import com.android.internal.os.BackgroundThread;
import com.android.internal.R;
import com.android.server.display.whitebalance.DisplayWhiteBalanceFactory;
import com.android.server.display.whitebalance.AmbientFilter;
@@ -64,6 +67,9 @@ public class DisplayModeDirector {
private static final boolean DEBUG = false;
private static final int MSG_ALLOWED_MODES_CHANGED = 1;
+ private static final int MSG_BRIGHTNESS_THRESHOLDS_CHANGED = 2;
+ private static final int MSG_DEFAULT_PEAK_REFRESH_RATE_CHANGED = 3;
+ private static final int MSG_REFRESH_RATE_IN_ZONE_CHANGED = 4;
// Special ID used to indicate that given vote is to be applied globally, rather than to a
// specific display.
@@ -91,6 +97,7 @@ public class DisplayModeDirector {
private final DisplayObserver mDisplayObserver;
private final BrightnessObserver mBrightnessObserver;
+ private final DeviceConfigDisplaySettings mDeviceConfigDisplaySettings;
private Listener mListener;
public DisplayModeDirector(@NonNull Context context, @NonNull Handler handler) {
@@ -103,7 +110,7 @@ public class DisplayModeDirector {
mSettingsObserver = new SettingsObserver(context, handler);
mDisplayObserver = new DisplayObserver(context, handler);
mBrightnessObserver = new BrightnessObserver(context, handler);
-
+ mDeviceConfigDisplaySettings = new DeviceConfigDisplaySettings();
}
/**
@@ -405,7 +412,7 @@ public class DisplayModeDirector {
void onAllowedDisplayModesChanged();
}
- private static final class DisplayModeDirectorHandler extends Handler {
+ private final class DisplayModeDirectorHandler extends Handler {
DisplayModeDirectorHandler(Looper looper) {
super(looper, null, true /*async*/);
}
@@ -417,23 +424,65 @@ public class DisplayModeDirector {
Listener listener = (Listener) msg.obj;
listener.onAllowedDisplayModesChanged();
break;
+
+ case MSG_BRIGHTNESS_THRESHOLDS_CHANGED:
+ Pair<int[], int[]> thresholds = (Pair<int[], int[]>) msg.obj;
+
+ if (thresholds != null) {
+ mBrightnessObserver.onDeviceConfigThresholdsChanged(
+ thresholds.first, thresholds.second);
+ } else {
+ mBrightnessObserver.onDeviceConfigThresholdsChanged(null, null);
+ }
+ break;
+
+ case MSG_DEFAULT_PEAK_REFRESH_RATE_CHANGED:
+ Float defaultPeakRefreshRate = (Float) msg.obj;
+ mSettingsObserver.onDeviceConfigDefaultPeakRefreshRateChanged(
+ defaultPeakRefreshRate);
+ break;
+
+ case MSG_REFRESH_RATE_IN_ZONE_CHANGED:
+ int refreshRateInZone = msg.arg1;
+ mBrightnessObserver.onDeviceConfigRefreshRateInZoneChanged(
+ refreshRateInZone);
+ break;
}
}
}
private static final class Vote {
- // We split the app request into two priorities in case we can satisfy one desire without
- // the other.
- public static final int PRIORITY_APP_REQUEST_REFRESH_RATE = 0;
- public static final int PRIORITY_APP_REQUEST_SIZE = 1;
- public static final int PRIORITY_USER_SETTING_REFRESH_RATE = 2;
- public static final int PRIORITY_LOW_BRIGHTNESS = 3;
- public static final int PRIORITY_LOW_POWER_MODE = 4;
+ // LOW_BRIGHTNESS votes for a single refresh rate like [60,60], [90,90] or null.
+ // If the higher voters result is a range, it will fix the rate to a single choice.
+ // It's used to avoid rate switch in certain conditions.
+ public static final int PRIORITY_LOW_BRIGHTNESS = 0;
+
+ // SETTING_MIN_REFRESH_RATE is used to propose a lower bound of display refresh rate.
+ // It votes [MIN_REFRESH_RATE, Float.POSITIVE_INFINITY]
+ public static final int PRIORITY_USER_SETTING_MIN_REFRESH_RATE = 1;
+
+ // We split the app request into different priorities in case we can satisfy one desire
+ // without the other.
+
+ // Application can specify preferred refresh rate with below attrs.
+ // @see android.view.WindowManager.LayoutParams#preferredRefreshRate
+ // @see android.view.WindowManager.LayoutParams#preferredDisplayModeId
+ // System also forces some apps like blacklisted app to run at a lower refresh rate.
+ // @see android.R.array#config_highRefreshRateBlacklist
+ public static final int PRIORITY_APP_REQUEST_REFRESH_RATE = 2;
+ public static final int PRIORITY_APP_REQUEST_SIZE = 3;
+
+ // SETTING_PEAK_REFRESH_RATE has a high priority and will restrict the bounds of the rest
+ // of low priority voters. It votes [0, max(PEAK, MIN)]
+ public static final int PRIORITY_USER_SETTING_PEAK_REFRESH_RATE = 4;
+
+ // LOW_POWER_MODE force display to [0, 60HZ] if Settings.Global.LOW_POWER_MODE is on.
+ public static final int PRIORITY_LOW_POWER_MODE = 5;
// Whenever a new priority is added, remember to update MIN_PRIORITY and/or MAX_PRIORITY as
// appropriate, as well as priorityToString.
- public static final int MIN_PRIORITY = PRIORITY_APP_REQUEST_REFRESH_RATE;
+ public static final int MIN_PRIORITY = PRIORITY_LOW_BRIGHTNESS;
public static final int MAX_PRIORITY = PRIORITY_LOW_POWER_MODE;
/**
@@ -477,12 +526,16 @@ public class DisplayModeDirector {
public static String priorityToString(int priority) {
switch (priority) {
+ case PRIORITY_LOW_BRIGHTNESS:
+ return "PRIORITY_LOW_BRIGHTNESS";
+ case PRIORITY_USER_SETTING_MIN_REFRESH_RATE:
+ return "PRIORITY_USER_SETTING_MIN_REFRESH_RATE";
case PRIORITY_APP_REQUEST_REFRESH_RATE:
return "PRIORITY_APP_REQUEST_REFRESH_RATE";
case PRIORITY_APP_REQUEST_SIZE:
return "PRIORITY_APP_REQUEST_SIZE";
- case PRIORITY_USER_SETTING_REFRESH_RATE:
- return "PRIORITY_USER_SETTING_REFRESH_RATE";
+ case PRIORITY_USER_SETTING_PEAK_REFRESH_RATE:
+ return "PRIORITY_USER_SETTING_PEAK_REFRESH_RATE";
case PRIORITY_LOW_POWER_MODE:
return "PRIORITY_LOW_POWER_MODE";
default:
@@ -502,13 +555,15 @@ public class DisplayModeDirector {
}
private final class SettingsObserver extends ContentObserver {
- private final Uri mRefreshRateSetting =
+ private final Uri mPeakRefreshRateSetting =
Settings.System.getUriFor(Settings.System.PEAK_REFRESH_RATE);
+ private final Uri mMinRefreshRateSetting =
+ Settings.System.getUriFor(Settings.System.MIN_REFRESH_RATE);
private final Uri mLowPowerModeSetting =
Settings.Global.getUriFor(Settings.Global.LOW_POWER_MODE);
private final Context mContext;
- private final float mDefaultPeakRefreshRate;
+ private float mDefaultPeakRefreshRate;
SettingsObserver(@NonNull Context context, @NonNull Handler handler) {
super(handler);
@@ -519,20 +574,44 @@ public class DisplayModeDirector {
public void observe() {
final ContentResolver cr = mContext.getContentResolver();
- cr.registerContentObserver(mRefreshRateSetting, false /*notifyDescendants*/, this,
+ cr.registerContentObserver(mPeakRefreshRateSetting, false /*notifyDescendants*/, this,
+ UserHandle.USER_SYSTEM);
+ cr.registerContentObserver(mMinRefreshRateSetting, false /*notifyDescendants*/, this,
UserHandle.USER_SYSTEM);
cr.registerContentObserver(mLowPowerModeSetting, false /*notifyDescendants*/, this,
UserHandle.USER_SYSTEM);
+
+ Float deviceConfigDefaultPeakRefresh =
+ mDeviceConfigDisplaySettings.getDefaultPeakRefreshRate();
+ if (deviceConfigDefaultPeakRefresh != null) {
+ mDefaultPeakRefreshRate = deviceConfigDefaultPeakRefresh;
+ }
+
synchronized (mLock) {
updateRefreshRateSettingLocked();
updateLowPowerModeSettingLocked();
}
}
+ public void onDeviceConfigDefaultPeakRefreshRateChanged(Float defaultPeakRefreshRate) {
+ if (defaultPeakRefreshRate == null) {
+ defaultPeakRefreshRate = (float) mContext.getResources().getInteger(
+ R.integer.config_defaultPeakRefreshRate);
+ }
+
+ if (mDefaultPeakRefreshRate != defaultPeakRefreshRate) {
+ synchronized (mLock) {
+ mDefaultPeakRefreshRate = defaultPeakRefreshRate;
+ updateRefreshRateSettingLocked();
+ }
+ }
+ }
+
@Override
public void onChange(boolean selfChange, Uri uri, int userId) {
synchronized (mLock) {
- if (mRefreshRateSetting.equals(uri)) {
+ if (mPeakRefreshRateSetting.equals(uri)
+ || mMinRefreshRateSetting.equals(uri)) {
updateRefreshRateSettingLocked();
} else if (mLowPowerModeSetting.equals(uri)) {
updateLowPowerModeSettingLocked();
@@ -550,15 +629,21 @@ public class DisplayModeDirector {
vote = null;
}
updateVoteLocked(Vote.PRIORITY_LOW_POWER_MODE, vote);
- mBrightnessObserver.onLowPowerModeEnabled(inLowPowerMode);
+ mBrightnessObserver.onLowPowerModeEnabledLocked(inLowPowerMode);
}
private void updateRefreshRateSettingLocked() {
+ float minRefreshRate = Settings.System.getFloat(mContext.getContentResolver(),
+ Settings.System.MIN_REFRESH_RATE, 0f);
float peakRefreshRate = Settings.System.getFloat(mContext.getContentResolver(),
Settings.System.PEAK_REFRESH_RATE, mDefaultPeakRefreshRate);
- Vote vote = Vote.forRefreshRates(0f, peakRefreshRate);
- updateVoteLocked(Vote.PRIORITY_USER_SETTING_REFRESH_RATE, vote);
- mBrightnessObserver.onPeakRefreshRateEnabled(peakRefreshRate > 60f);
+
+ updateVoteLocked(Vote.PRIORITY_USER_SETTING_PEAK_REFRESH_RATE,
+ Vote.forRefreshRates(0f, Math.max(minRefreshRate, peakRefreshRate)));
+ updateVoteLocked(Vote.PRIORITY_USER_SETTING_MIN_REFRESH_RATE,
+ Vote.forRefreshRates(minRefreshRate, Float.POSITIVE_INFINITY));
+
+ mBrightnessObserver.onRefreshRateSettingChangedLocked(minRefreshRate, peakRefreshRate);
}
public void dumpLocked(PrintWriter pw) {
@@ -599,6 +684,7 @@ public class DisplayModeDirector {
refreshRateVote = null;
sizeVote = null;
}
+
updateVoteLocked(displayId, Vote.PRIORITY_APP_REQUEST_REFRESH_RATE, refreshRateVote);
updateVoteLocked(displayId, Vote.PRIORITY_APP_REQUEST_SIZE, sizeVote);
return;
@@ -720,8 +806,8 @@ public class DisplayModeDirector {
Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS);
private final static int LIGHT_SENSOR_RATE_MS = 250;
- private final int[] mDisplayBrightnessThresholds;
- private final int[] mAmbientBrightnessThresholds;
+ private int[] mDisplayBrightnessThresholds;
+ private int[] mAmbientBrightnessThresholds;
// valid threshold if any item from the array >= 0
private boolean mShouldObserveDisplayChange;
private boolean mShouldObserveAmbientChange;
@@ -734,35 +820,127 @@ public class DisplayModeDirector {
private AmbientFilter mAmbientFilter;
private final Context mContext;
- private ScreenStateReceiver mScreenStateReceiver;
+ private final ScreenStateReceiver mScreenStateReceiver;
- // Enable light sensor only when screen is on, peak refresh rate enabled and low power mode
- // off. After initialization, these states will be updated from the same handler thread.
+ // Enable light sensor only when mShouldObserveAmbientChange is true, screen is on, peak
+ // refresh rate changeable and low power mode off. After initialization, these states will
+ // be updated from the same handler thread.
private boolean mScreenOn = false;
- private boolean mPeakRefreshRateEnabled = false;
+ private boolean mRefreshRateChangeable = false;
private boolean mLowPowerModeEnabled = false;
+ private int mRefreshRateInZone;
+
BrightnessObserver(Context context, Handler handler) {
super(handler);
mContext = context;
+ mScreenStateReceiver = new ScreenStateReceiver(mContext);
mDisplayBrightnessThresholds = context.getResources().getIntArray(
R.array.config_brightnessThresholdsOfPeakRefreshRate);
mAmbientBrightnessThresholds = context.getResources().getIntArray(
R.array.config_ambientThresholdsOfPeakRefreshRate);
+
if (mDisplayBrightnessThresholds.length != mAmbientBrightnessThresholds.length) {
throw new RuntimeException("display brightness threshold array and ambient "
+ "brightness threshold array have different length");
}
+ }
+
+ public void observe(SensorManager sensorManager) {
+ mSensorManager = sensorManager;
+
+ // DeviceConfig is accessible after system ready.
+ int[] brightnessThresholds = mDeviceConfigDisplaySettings.getBrightnessThresholds();
+ int[] ambientThresholds = mDeviceConfigDisplaySettings.getAmbientThresholds();
+
+ if (brightnessThresholds != null && ambientThresholds != null
+ && brightnessThresholds.length == ambientThresholds.length) {
+ mDisplayBrightnessThresholds = brightnessThresholds;
+ mAmbientBrightnessThresholds = ambientThresholds;
+ }
+
+ mRefreshRateInZone = mDeviceConfigDisplaySettings.getRefreshRateInZone();
+ restartObserver();
+ mDeviceConfigDisplaySettings.startListening();
+ }
+
+ public void onRefreshRateSettingChangedLocked(float min, float max) {
+ boolean changeable = (max - min > 1f && max > 60f);
+ if (mRefreshRateChangeable != changeable) {
+ mRefreshRateChangeable = changeable;
+ updateSensorStatus();
+ if (!changeable) {
+ // Revoke previous vote from BrightnessObserver
+ updateVoteLocked(Vote.PRIORITY_LOW_BRIGHTNESS, null);
+ }
+ }
+ }
+
+ public void onLowPowerModeEnabledLocked(boolean b) {
+ if (mLowPowerModeEnabled != b) {
+ mLowPowerModeEnabled = b;
+ updateSensorStatus();
+ }
+ }
+
+ public void onDeviceConfigThresholdsChanged(int[] brightnessThresholds,
+ int[] ambientThresholds) {
+ if (brightnessThresholds != null && ambientThresholds != null
+ && brightnessThresholds.length == ambientThresholds.length) {
+ mDisplayBrightnessThresholds = brightnessThresholds;
+ mAmbientBrightnessThresholds = ambientThresholds;
+ } else {
+ // Invalid or empty. Use device default.
+ mDisplayBrightnessThresholds = mContext.getResources().getIntArray(
+ R.array.config_brightnessThresholdsOfPeakRefreshRate);
+ mAmbientBrightnessThresholds = mContext.getResources().getIntArray(
+ R.array.config_ambientThresholdsOfPeakRefreshRate);
+ }
+ restartObserver();
+ }
+
+ public void onDeviceConfigRefreshRateInZoneChanged(int refreshRate) {
+ if (refreshRate != mRefreshRateInZone) {
+ mRefreshRateInZone = refreshRate;
+ restartObserver();
+ }
+ }
+
+ public void dumpLocked(PrintWriter pw) {
+ pw.println(" BrightnessObserver");
+ pw.println(" mRefreshRateInZone: " + mRefreshRateInZone);
+
+ for (int d: mDisplayBrightnessThresholds) {
+ pw.println(" mDisplayBrightnessThreshold: " + d);
+ }
+
+ for (int d: mAmbientBrightnessThresholds) {
+ pw.println(" mAmbientBrightnessThreshold: " + d);
+ }
+ }
+ @Override
+ public void onChange(boolean selfChange, Uri uri, int userId) {
+ synchronized (mLock) {
+ if (mRefreshRateChangeable) {
+ onBrightnessChangedLocked();
+ }
+ }
+ }
+
+ private void restartObserver() {
mShouldObserveDisplayChange = checkShouldObserve(mDisplayBrightnessThresholds);
mShouldObserveAmbientChange = checkShouldObserve(mAmbientBrightnessThresholds);
- }
- public void observe(SensorManager sensorManager) {
+ final ContentResolver cr = mContext.getContentResolver();
if (mShouldObserveDisplayChange) {
- final ContentResolver cr = mContext.getContentResolver();
+ // Content Service does not check if an listener has already been registered.
+ // To ensure only one listener is registered, force an unregistration first.
+ cr.unregisterContentObserver(this);
cr.registerContentObserver(mDisplayBrightnessSetting,
false /*notifyDescendants*/, this, UserHandle.USER_SYSTEM);
+ } else {
+ cr.unregisterContentObserver(this);
}
if (mShouldObserveAmbientChange) {
@@ -772,7 +950,7 @@ public class DisplayModeDirector {
Sensor lightSensor = null;
if (!TextUtils.isEmpty(lightSensorType)) {
- List<Sensor> sensors = sensorManager.getSensorList(Sensor.TYPE_ALL);
+ List<Sensor> sensors = mSensorManager.getSensorList(Sensor.TYPE_ALL);
for (int i = 0; i < sensors.size(); i++) {
Sensor sensor = sensors.get(i);
if (lightSensorType.equals(sensor.getStringType())) {
@@ -783,69 +961,44 @@ public class DisplayModeDirector {
}
if (lightSensor == null) {
- lightSensor = sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
+ lightSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
}
if (lightSensor != null) {
final Resources res = mContext.getResources();
mAmbientFilter = DisplayWhiteBalanceFactory.createBrightnessFilter(res);
- mSensorManager = sensorManager;
mLightSensor = lightSensor;
// Intent.ACTION_SCREEN_ON is not sticky. Check current screen status.
if (mContext.getSystemService(PowerManager.class).isInteractive()) {
onScreenOn(true);
}
- mScreenStateReceiver = new ScreenStateReceiver(mContext);
+ mScreenStateReceiver.register();
}
+ } else {
+ mAmbientFilter = null;
+ mLightSensor = null;
+ mScreenStateReceiver.unregister();
}
- if (mShouldObserveDisplayChange || mShouldObserveAmbientChange) {
+ if (mRefreshRateChangeable) {
+ updateSensorStatus();
synchronized (mLock) {
onBrightnessChangedLocked();
}
}
}
- public void onPeakRefreshRateEnabled(boolean b) {
- if (mShouldObserveAmbientChange && mPeakRefreshRateEnabled != b) {
- mPeakRefreshRateEnabled = b;
- updateSensorStatus();
- }
- }
-
- public void onLowPowerModeEnabled(boolean b) {
- if (mShouldObserveAmbientChange && mLowPowerModeEnabled != b) {
- mLowPowerModeEnabled = b;
- updateSensorStatus();
- }
- }
-
- public void dumpLocked(PrintWriter pw) {
- pw.println(" BrightnessObserver");
-
- for (int d: mDisplayBrightnessThresholds) {
- pw.println(" mDisplayBrightnessThreshold: " + d);
- }
-
- for (int d: mAmbientBrightnessThresholds) {
- pw.println(" mAmbientBrightnessThreshold: " + d);
- }
- }
-
- @Override
- public void onChange(boolean selfChange, Uri uri, int userId) {
- synchronized (mLock) {
- onBrightnessChangedLocked();
- }
- }
-
/**
* Checks to see if at least one value is positive, in which case it is necessary to listen
* to value changes.
*/
private boolean checkShouldObserve(int[] a) {
+ if (mRefreshRateInZone <= 0) {
+ return false;
+ }
+
for (int d: a) {
if (d >= 0) {
return true;
@@ -855,37 +1008,42 @@ public class DisplayModeDirector {
return false;
}
- private void onBrightnessChangedLocked() {
- int brightness = Settings.System.getInt(mContext.getContentResolver(),
- Settings.System.SCREEN_BRIGHTNESS, -1);
-
- Vote vote = null;
+ private boolean isInsideZone(int brightness, float lux) {
for (int i = 0; i < mDisplayBrightnessThresholds.length; i++) {
int disp = mDisplayBrightnessThresholds[i];
int ambi = mAmbientBrightnessThresholds[i];
if (disp >= 0 && ambi >= 0) {
if (brightness <= disp && mAmbientLux <= ambi) {
- vote = Vote.forRefreshRates(0f, 60f);
+ return true;
}
} else if (disp >= 0) {
if (brightness <= disp) {
- vote = Vote.forRefreshRates(0f, 60f);
+ return true;
}
} else if (ambi >= 0) {
if (mAmbientLux <= ambi) {
- vote = Vote.forRefreshRates(0f, 60f);
+ return true;
}
}
+ }
- if (vote != null) {
- break;
- }
+ return false;
+ }
+
+ private void onBrightnessChangedLocked() {
+ int brightness = Settings.System.getInt(mContext.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS, -1);
+
+ Vote vote = null;
+ boolean insideZone = isInsideZone(brightness, mAmbientLux);
+ if (insideZone) {
+ vote = Vote.forRefreshRates(mRefreshRateInZone, mRefreshRateInZone);
}
if (DEBUG) {
Slog.d(TAG, "Display brightness " + brightness + ", ambient lux " + mAmbientLux +
- (vote != null ? " 60hz only" : " no refresh rate limit"));
+ ", Vote " + vote);
}
updateVoteLocked(Vote.PRIORITY_LOW_BRIGHTNESS, vote);
}
@@ -904,7 +1062,8 @@ public class DisplayModeDirector {
return;
}
- if (mScreenOn && !mLowPowerModeEnabled && mPeakRefreshRateEnabled) {
+ if (mShouldObserveAmbientChange && mScreenOn && !mLowPowerModeEnabled
+ && mRefreshRateChangeable) {
mSensorManager.registerListener(mLightSensorListener,
mLightSensor, LIGHT_SENSOR_RATE_MS * 1000, mHandler);
} else {
@@ -993,18 +1152,133 @@ public class DisplayModeDirector {
};
private final class ScreenStateReceiver extends BroadcastReceiver {
+ final Context mContext;
+ boolean mRegistered;
+
public ScreenStateReceiver(Context context) {
- IntentFilter filter = new IntentFilter();
- filter.addAction(Intent.ACTION_SCREEN_OFF);
- filter.addAction(Intent.ACTION_SCREEN_ON);
- filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
- context.registerReceiver(this, filter, null, mHandler);
+ mContext = context;
}
@Override
public void onReceive(Context context, Intent intent) {
onScreenOn(Intent.ACTION_SCREEN_ON.equals(intent.getAction()));
}
+
+ public void register() {
+ if (!mRegistered) {
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_SCREEN_OFF);
+ filter.addAction(Intent.ACTION_SCREEN_ON);
+ filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
+ mContext.registerReceiver(this, filter, null, mHandler);
+ mRegistered = true;
+ }
+ }
+
+ public void unregister() {
+ if (mRegistered) {
+ mContext.unregisterReceiver(this);
+ mRegistered = false;
+ }
+ }
}
}
+
+ private class DeviceConfigDisplaySettings implements DeviceConfig.OnPropertiesChangedListener {
+ public DeviceConfigDisplaySettings() {
+ }
+
+ public void startListening() {
+ DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+ BackgroundThread.getExecutor(), this);
+ }
+
+ /*
+ * Return null if no such property or wrong format (not comma separated integers).
+ */
+ public int[] getBrightnessThresholds() {
+ return getIntArrayProperty(
+ DisplayManager.DeviceConfig.
+ KEY_PEAK_REFRESH_RATE_DISPLAY_BRIGHTNESS_THRESHOLDS);
+ }
+
+ /*
+ * Return null if no such property or wrong format (not comma separated integers).
+ */
+ public int[] getAmbientThresholds() {
+ return getIntArrayProperty(
+ DisplayManager.DeviceConfig.
+ KEY_PEAK_REFRESH_RATE_AMBIENT_BRIGHTNESS_THRESHOLDS);
+ }
+
+ /*
+ * Return null if no such property
+ */
+ public Float getDefaultPeakRefreshRate() {
+ float defaultPeakRefreshRate = DeviceConfig.getFloat(
+ DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+ DisplayManager.DeviceConfig.KEY_PEAK_REFRESH_RATE_DEFAULT, -1);
+
+ if (defaultPeakRefreshRate == -1) {
+ return null;
+ }
+ return defaultPeakRefreshRate;
+ }
+
+ public int getRefreshRateInZone() {
+ int defaultRefreshRateInZone = mContext.getResources().getInteger(
+ R.integer.config_defaultRefreshRateInZone);
+
+ int refreshRate = DeviceConfig.getInt(
+ DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+ DisplayManager.DeviceConfig.KEY_REFRESH_RATE_IN_ZONE,
+ defaultRefreshRateInZone);
+
+ return refreshRate;
+ }
+
+ @Override
+ public void onPropertiesChanged(@NonNull DeviceConfig.Properties properties) {
+ int[] brightnessThresholds = getBrightnessThresholds();
+ int[] ambientThresholds = getAmbientThresholds();
+ Float defaultPeakRefreshRate = getDefaultPeakRefreshRate();
+ int refreshRateInZone = getRefreshRateInZone();
+
+ mHandler.obtainMessage(MSG_BRIGHTNESS_THRESHOLDS_CHANGED,
+ new Pair<int[], int[]>(brightnessThresholds, ambientThresholds))
+ .sendToTarget();
+ mHandler.obtainMessage(MSG_DEFAULT_PEAK_REFRESH_RATE_CHANGED,
+ defaultPeakRefreshRate).sendToTarget();
+ mHandler.obtainMessage(MSG_REFRESH_RATE_IN_ZONE_CHANGED, refreshRateInZone,
+ 0).sendToTarget();
+ }
+
+ private int[] getIntArrayProperty(String prop) {
+ String strArray = DeviceConfig.getString(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, prop,
+ null);
+
+ if (strArray != null) {
+ return parseIntArray(strArray);
+ }
+
+ return null;
+ }
+
+ private int[] parseIntArray(@NonNull String strArray) {
+ String[] items = strArray.split(",");
+ int[] array = new int[items.length];
+
+ try {
+ for (int i = 0; i < array.length; i++) {
+ array[i] = Integer.parseInt(items[i]);
+ }
+ } catch (NumberFormatException e) {
+ Slog.e(TAG, "Incorrect format for array: '" + strArray + "'", e);
+ array = null;
+ }
+
+ return array;
+ }
+ }
+
}
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 0032e9a8ea51..e75f545eafaa 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -505,6 +505,11 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
params.installFlags &= ~PackageManager.INSTALL_REQUEST_DOWNGRADE;
}
+ if (callingUid != Process.SYSTEM_UID) {
+ // Only system_server can use INSTALL_DISABLE_VERIFICATION.
+ params.installFlags &= ~PackageManager.INSTALL_DISABLE_VERIFICATION;
+ }
+
boolean isApex = (params.installFlags & PackageManager.INSTALL_APEX) != 0;
if (params.isStaged || isApex) {
mContext.enforceCallingOrSelfPermission(Manifest.permission.INSTALL_PACKAGES, TAG);
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index e107c9aedf38..08c1bb536211 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -86,9 +86,9 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
private static final String TAG = "RollbackManager";
- // Rollbacks expire after 48 hours.
+ // Rollbacks expire after 14 days.
private static final long DEFAULT_ROLLBACK_LIFETIME_DURATION_MILLIS =
- TimeUnit.HOURS.toMillis(48);
+ TimeUnit.DAYS.toMillis(14);
// Lock used to synchronize accesses to in-memory rollback data
// structures. By convention, methods with the suffix "Locked" require
@@ -1289,7 +1289,8 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub {
private boolean packageVersionsEqual(VersionedPackage a, VersionedPackage b) {
- return a.getPackageName().equals(b.getPackageName())
+ return a != null && b != null
+ && a.getPackageName().equals(b.getPackageName())
&& a.getLongVersionCode() == b.getLongVersionCode();
}
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 9908b3657121..b0f1e5d69be4 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -1172,8 +1172,16 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
return false;
}
final int displayId = display.getDisplayId();
- return displayId == DEFAULT_DISPLAY
- || mWindowManagerInternal.shouldShowSystemDecorOnDisplay(displayId);
+ if (displayId == DEFAULT_DISPLAY) {
+ return true;
+ }
+
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ return mWindowManagerInternal.shouldShowSystemDecorOnDisplay(displayId);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
}
void forEachDisplayConnector(Consumer<DisplayConnector> action) {
diff --git a/services/core/java/com/android/server/wm/HighRefreshRateBlacklist.java b/services/core/java/com/android/server/wm/HighRefreshRateBlacklist.java
index 5726cb2d87d4..271db2895eac 100644
--- a/services/core/java/com/android/server/wm/HighRefreshRateBlacklist.java
+++ b/services/core/java/com/android/server/wm/HighRefreshRateBlacklist.java
@@ -16,7 +16,7 @@
package com.android.server.wm;
-import static android.provider.DeviceConfig.WindowManager.KEY_HIGH_REFRESH_RATE_BLACKLIST;
+import static android.hardware.display.DisplayManager.DeviceConfig.KEY_HIGH_REFRESH_RATE_BLACKLIST;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -58,9 +58,9 @@ class HighRefreshRateBlacklist {
@VisibleForTesting
HighRefreshRateBlacklist(Resources r, DeviceConfigInterface deviceConfig) {
mDefaultBlacklist = r.getStringArray(R.array.config_highRefreshRateBlacklist);
- deviceConfig.addOnPropertyChangedListener(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
+ deviceConfig.addOnPropertyChangedListener(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
BackgroundThread.getExecutor(), new OnPropertyChangedListener());
- final String property = deviceConfig.getProperty(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
+ final String property = deviceConfig.getProperty(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
KEY_HIGH_REFRESH_RATE_BLACKLIST);
updateBlacklist(property);
}
@@ -110,7 +110,9 @@ class HighRefreshRateBlacklist {
private class OnPropertyChangedListener implements DeviceConfig.OnPropertyChangedListener {
public void onPropertyChanged(@NonNull String namespace, @NonNull String name,
@Nullable String value) {
- updateBlacklist(value);
+ if (KEY_HIGH_REFRESH_RATE_BLACKLIST.equals(name)) {
+ updateBlacklist(value);
+ }
}
}
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 5c1dfef925cd..e6ed2edba807 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -38,6 +38,7 @@ import android.database.sqlite.SQLiteCompatibilityWalFlags;
import android.database.sqlite.SQLiteGlobal;
import android.hardware.display.DisplayManagerInternal;
import android.net.ConnectivityModuleConnector;
+import android.net.Network;
import android.net.NetworkStackClient;
import android.os.BaseBundle;
import android.os.Binder;
diff --git a/services/net/java/android/net/ConnectivityModuleConnector.java b/services/net/java/android/net/ConnectivityModuleConnector.java
index 19ade3394de7..62f2c35f339b 100644
--- a/services/net/java/android/net/ConnectivityModuleConnector.java
+++ b/services/net/java/android/net/ConnectivityModuleConnector.java
@@ -32,7 +32,7 @@ import android.os.IBinder;
import android.os.Process;
import android.os.SystemClock;
import android.os.UserHandle;
-import android.provider.Settings;
+import android.provider.DeviceConfig;
import android.text.format.DateUtils;
import android.util.ArraySet;
import android.util.Slog;
@@ -296,8 +296,6 @@ public class ConnectivityModuleConnector {
private synchronized void maybeCrashWithTerribleFailure(@NonNull String message,
@Nullable String packageName) {
logWtf(message, null);
- // Called DeviceConfig to minimize merge conflicts
- final DeviceConfigStub DeviceConfig = new DeviceConfigStub(mContext);
// uptime is monotonic even after a framework restart
final long uptime = SystemClock.elapsedRealtime();
final long now = System.currentTimeMillis();
@@ -417,36 +415,4 @@ public class ConnectivityModuleConnector {
// dump is thread-safe on SharedLog
mLog.dump(null, pw, null);
}
-
- /**
- * Stub class to replicate DeviceConfig behavior with minimal merge conflicts.
- */
- private class DeviceConfigStub {
- private final Context mContext;
-
- // Namespace is actually unused, but is here to replicate the final API.
- private static final String NAMESPACE_CONNECTIVITY = "connectivity";
-
- private DeviceConfigStub(Context context) {
- mContext = context;
- }
-
- private long getLong(
- @NonNull String namespace, @NonNull String key, long defaultVal) {
- // Temporary solution until DeviceConfig is available
- try {
- return Settings.Global.getLong(
- mContext.getContentResolver(), TAG + "_" + key, defaultVal);
- } catch (Throwable e) {
- logWtf("Could not obtain setting " + key, e);
- return defaultVal;
- }
- }
-
- private boolean getBoolean(
- @NonNull String namespace, @NonNull String key, boolean defaultVal) {
- // Temporary solution until DeviceConfig is available
- return getLong(namespace, key, defaultVal ? 1 : 0) != 0;
- }
- }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/HighRefreshRateBlacklistTest.java b/services/tests/wmtests/src/com/android/server/wm/HighRefreshRateBlacklistTest.java
index e02b69c4b058..cd90462fffe4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/HighRefreshRateBlacklistTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/HighRefreshRateBlacklistTest.java
@@ -16,7 +16,7 @@
package com.android.server.wm;
-import static android.provider.DeviceConfig.WindowManager.KEY_HIGH_REFRESH_RATE_BLACKLIST;
+import static android.hardware.display.DisplayManager.DeviceConfig.KEY_HIGH_REFRESH_RATE_BLACKLIST;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -126,9 +126,9 @@ public class HighRefreshRateBlacklistTest {
@Override
public String getProperty(String namespace, String name) {
- if (!DeviceConfig.NAMESPACE_WINDOW_MANAGER.equals(namespace)
+ if (!DeviceConfig.NAMESPACE_DISPLAY_MANAGER.equals(namespace)
|| !KEY_HIGH_REFRESH_RATE_BLACKLIST.equals(name)) {
- throw new IllegalArgumentException("Only things in NAMESPACE_WINDOW_MANAGER "
+ throw new IllegalArgumentException("Only things in NAMESPACE_DISPLAY_MANAGER "
+ "supported.");
}
return mBlacklist;
@@ -138,8 +138,8 @@ public class HighRefreshRateBlacklistTest {
public void addOnPropertyChangedListener(String namespace, Executor executor,
DeviceConfig.OnPropertyChangedListener listener) {
- if (!DeviceConfig.NAMESPACE_WINDOW_MANAGER.equals(namespace)) {
- throw new IllegalArgumentException("Only things in NAMESPACE_WINDOW_MANAGER "
+ if (!DeviceConfig.NAMESPACE_DISPLAY_MANAGER.equals(namespace)) {
+ throw new IllegalArgumentException("Only things in NAMESPACE_DISPLAY_MANAGER "
+ "supported.");
}
mListeners.add(new Pair<>(listener, executor));
@@ -153,7 +153,7 @@ public class HighRefreshRateBlacklistTest {
final Executor executor = listenerInfo.second;
final DeviceConfig.OnPropertyChangedListener listener = listenerInfo.first;
executor.execute(() -> {
- listener.onPropertyChanged(DeviceConfig.NAMESPACE_WINDOW_MANAGER,
+ listener.onPropertyChanged(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
KEY_HIGH_REFRESH_RATE_BLACKLIST, blacklist);
latch.countDown();
});
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index fccb012311c8..d675a21ede01 100755
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -1039,6 +1039,18 @@ public class CarrierConfigManager {
"call_forwarding_map_non_number_to_voicemail_bool";
/**
+ * When {@code true}, the phone will always tell the IMS stack to keep RTT enabled and
+ * determine on a per-call basis (based on extras from the dialer app) whether a call should be
+ * an RTT call or not.
+ *
+ * When {@code false}, the old behavior is used, where the toggle in accessibility settings is
+ * used to set the IMS stack's RTT enabled state.
+ * @hide
+ */
+ public static final String KEY_IGNORE_RTT_MODE_SETTING_BOOL =
+ "ignore_rtt_mode_setting_bool";
+
+ /**
* Determines whether conference calls are supported by a carrier. When {@code true},
* conference calling is supported, {@code false otherwise}.
*/
@@ -3281,6 +3293,7 @@ public class CarrierConfigManager {
sDefaults.putInt(KEY_IMS_DTMF_TONE_DELAY_INT, 0);
sDefaults.putInt(KEY_CDMA_DTMF_TONE_DELAY_INT, 100);
sDefaults.putBoolean(KEY_CALL_FORWARDING_MAP_NON_NUMBER_TO_VOICEMAIL_BOOL, false);
+ sDefaults.putBoolean(KEY_IGNORE_RTT_MODE_SETTING_BOOL, false);
sDefaults.putInt(KEY_CDMA_3WAYCALL_FLASH_DELAY_INT , 0);
sDefaults.putBoolean(KEY_SUPPORT_CONFERENCE_CALL_BOOL, true);
sDefaults.putBoolean(KEY_SUPPORT_IMS_CONFERENCE_CALL_BOOL, true);
diff --git a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
index bad294794337..4315514cb7a5 100644
--- a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
+++ b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
@@ -25,6 +25,7 @@ import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -63,6 +64,7 @@ public class StagedRollbackTest extends BaseHostJUnit4Test {
* Tests watchdog triggered staged rollbacks involving only apks.
*/
@Test
+ @Ignore("b/139175593 flaky test")
public void testBadApkOnly() throws Exception {
runPhase("testBadApkOnlyEnableRollback");
getDevice().reboot();