• R/O
  • HTTP
  • SSH
  • HTTPS

Commit

Tags
Aucun tag

Frequently used words (click to add to your profile)

javac++androidlinuxc#windowsobjective-ccocoa誰得qtpythonphprubygameguibathyscaphec計画中(planning stage)翻訳omegatframeworktwitterdomtestvb.netdirectxゲームエンジンbtronarduinopreviewer

frameworks/base


Commit MetaInfo

Révisiona3c81b0748b2f682f0670e488568e155bb3827f9 (tree)
l'heure2020-03-16 19:41:39
AuteurChih-Wei Huang <cwhuang@linu...>
CommiterChih-Wei Huang

Message de Log

Android 8.1.0 release 74
-----BEGIN PGP SIGNATURE-----

iF0EABECAB0WIQRDQNE1cO+UXoOBCWTorT+BmrEOeAUCXl1q/wAKCRDorT+BmrEO
eB3XAJ9GV9tT451mn+k3rtrk7ltiQUQYtwCeKlzfAmAOoNuxlXq/Uy/2M+Rn2bs=
=gJGS
-----END PGP SIGNATURE-----

Merge tag 'android-8.1.0_r74' into oreo-x86

Android 8.1.0 release 74

Change Summary

Modification

--- a/core/java/android/app/DownloadManager.java
+++ b/core/java/android/app/DownloadManager.java
@@ -127,6 +127,9 @@ public class DownloadManager {
127127 */
128128 public final static String COLUMN_STATUS = Downloads.Impl.COLUMN_STATUS;
129129
130+ /** {@hide} */
131+ public static final String COLUMN_FILE_NAME_HINT = Downloads.Impl.COLUMN_FILE_NAME_HINT;
132+
130133 /**
131134 * Provides more detail on the status of the download. Its meaning depends on the value of
132135 * {@link #COLUMN_STATUS}.
@@ -164,6 +167,9 @@ public class DownloadManager {
164167 */
165168 public static final String COLUMN_MEDIAPROVIDER_URI = Downloads.Impl.COLUMN_MEDIAPROVIDER_URI;
166169
170+ /** {@hide} */
171+ public static final String COLUMN_DESTINATION = Downloads.Impl.COLUMN_DESTINATION;
172+
167173 /**
168174 * @hide
169175 */
@@ -330,26 +336,22 @@ public class DownloadManager {
330336 * @hide
331337 */
332338 public static final String[] UNDERLYING_COLUMNS = new String[] {
333- Downloads.Impl._ID,
334- Downloads.Impl._DATA + " AS " + COLUMN_LOCAL_FILENAME,
335- Downloads.Impl.COLUMN_MEDIAPROVIDER_URI,
336- Downloads.Impl.COLUMN_DESTINATION,
337- Downloads.Impl.COLUMN_TITLE,
338- Downloads.Impl.COLUMN_DESCRIPTION,
339- Downloads.Impl.COLUMN_URI,
340- Downloads.Impl.COLUMN_STATUS,
341- Downloads.Impl.COLUMN_FILE_NAME_HINT,
342- Downloads.Impl.COLUMN_MIME_TYPE + " AS " + COLUMN_MEDIA_TYPE,
343- Downloads.Impl.COLUMN_TOTAL_BYTES + " AS " + COLUMN_TOTAL_SIZE_BYTES,
344- Downloads.Impl.COLUMN_LAST_MODIFICATION + " AS " + COLUMN_LAST_MODIFIED_TIMESTAMP,
345- Downloads.Impl.COLUMN_CURRENT_BYTES + " AS " + COLUMN_BYTES_DOWNLOADED_SO_FAR,
346- Downloads.Impl.COLUMN_ALLOW_WRITE,
347- /* add the following 'computed' columns to the cursor.
348- * they are not 'returned' by the database, but their inclusion
349- * eliminates need to have lot of methods in CursorTranslator
350- */
351- "'placeholder' AS " + COLUMN_LOCAL_URI,
352- "'placeholder' AS " + COLUMN_REASON
339+ DownloadManager.COLUMN_ID,
340+ DownloadManager.COLUMN_LOCAL_FILENAME,
341+ DownloadManager.COLUMN_MEDIAPROVIDER_URI,
342+ DownloadManager.COLUMN_DESTINATION,
343+ DownloadManager.COLUMN_TITLE,
344+ DownloadManager.COLUMN_DESCRIPTION,
345+ DownloadManager.COLUMN_URI,
346+ DownloadManager.COLUMN_STATUS,
347+ DownloadManager.COLUMN_FILE_NAME_HINT,
348+ DownloadManager.COLUMN_MEDIA_TYPE,
349+ DownloadManager.COLUMN_TOTAL_SIZE_BYTES,
350+ DownloadManager.COLUMN_LAST_MODIFIED_TIMESTAMP,
351+ DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR,
352+ DownloadManager.COLUMN_ALLOW_WRITE,
353+ DownloadManager.COLUMN_LOCAL_URI,
354+ DownloadManager.COLUMN_REASON
353355 };
354356
355357 /**
--- a/core/java/android/database/sqlite/SQLiteQueryBuilder.java
+++ b/core/java/android/database/sqlite/SQLiteQueryBuilder.java
@@ -28,14 +28,19 @@ import android.provider.BaseColumns;
2828 import android.text.TextUtils;
2929 import android.util.Log;
3030
31+import com.android.internal.util.ArrayUtils;
32+
3133 import libcore.util.EmptyArray;
3234
3335 import java.util.Arrays;
3436 import java.util.Iterator;
37+import java.util.List;
38+import java.util.Locale;
3539 import java.util.Map;
3640 import java.util.Map.Entry;
3741 import java.util.Objects;
3842 import java.util.Set;
43+import java.util.regex.Matcher;
3944 import java.util.regex.Pattern;
4045
4146 /**
@@ -45,15 +50,24 @@ import java.util.regex.Pattern;
4550 public class SQLiteQueryBuilder
4651 {
4752 private static final String TAG = "SQLiteQueryBuilder";
48- private static final Pattern sLimitPattern =
49- Pattern.compile("\\s*\\d+\\s*(,\\s*\\d+\\s*)?");
5053
5154 private Map<String, String> mProjectionMap = null;
55+
56+ private static final Pattern sAggregationPattern = Pattern.compile(
57+ "(?i)(AVG|COUNT|MAX|MIN|SUM|TOTAL|GROUP_CONCAT)\\((.+)\\)");
58+
59+ private List<Pattern> mProjectionGreylist = null;
60+
5261 private String mTables = "";
5362 private StringBuilder mWhereClause = null; // lazily created
5463 private boolean mDistinct;
5564 private SQLiteDatabase.CursorFactory mFactory;
56- private boolean mStrict;
65+
66+ private static final int STRICT_PARENTHESES = 1 << 0;
67+ private static final int STRICT_COLUMNS = 1 << 1;
68+ private static final int STRICT_GRAMMAR = 1 << 2;
69+
70+ private int mStrictFlags;
5771
5872 public SQLiteQueryBuilder() {
5973 mDistinct = false;
@@ -139,6 +153,37 @@ public class SQLiteQueryBuilder
139153 }
140154
141155 /**
156+ * Gets the projection map for the query, as last configured by
157+ * {@link #setProjectionMap(Map)}.
158+ *
159+ * @hide
160+ */
161+ public @Nullable Map<String, String> getProjectionMap() {
162+ return mProjectionMap;
163+ }
164+
165+ /**
166+ * Sets a projection greylist of columns that will be allowed through, even
167+ * when {@link #setStrict(boolean)} is enabled. This provides a way for
168+ * abusive custom columns like {@code COUNT(*)} to continue working.
169+ *
170+ * @hide
171+ */
172+ public void setProjectionGreylist(@Nullable List<Pattern> projectionGreylist) {
173+ mProjectionGreylist = projectionGreylist;
174+ }
175+
176+ /**
177+ * Gets the projection greylist for the query, as last configured by
178+ * {@link #setProjectionGreylist(List)}.
179+ *
180+ * @hide
181+ */
182+ public @Nullable List<Pattern> getProjectionGreylist() {
183+ return mProjectionGreylist;
184+ }
185+
186+ /**
142187 * Sets the cursor factory to be used for the query. You can use
143188 * one factory for all queries on a database but it is normally
144189 * easier to specify the factory when doing this query.
@@ -170,8 +215,90 @@ public class SQLiteQueryBuilder
170215 * </ul>
171216 * By default, this value is false.
172217 */
173- public void setStrict(boolean flag) {
174- mStrict = flag;
218+ public void setStrict(boolean strict) {
219+ if (strict) {
220+ mStrictFlags |= STRICT_PARENTHESES;
221+ } else {
222+ mStrictFlags &= ~STRICT_PARENTHESES;
223+ }
224+ }
225+
226+ /**
227+ * Get if the query is marked as strict, as last configured by
228+ * {@link #setStrict(boolean)}.
229+ *
230+ * @hide
231+ */
232+ public boolean isStrict() {
233+ return (mStrictFlags & STRICT_PARENTHESES) != 0;
234+ }
235+
236+ /**
237+ * When enabled, verify that all projections and {@link ContentValues} only
238+ * contain valid columns as defined by {@link #setProjectionMap(Map)}.
239+ * <p>
240+ * This enforcement applies to {@link #insert}, {@link #query}, and
241+ * {@link #update} operations. Any enforcement failures will throw an
242+ * {@link IllegalArgumentException}.
243+ *
244+ * @hide
245+ */
246+ public void setStrictColumns(boolean strictColumns) {
247+ if (strictColumns) {
248+ mStrictFlags |= STRICT_COLUMNS;
249+ } else {
250+ mStrictFlags &= ~STRICT_COLUMNS;
251+ }
252+ }
253+
254+ /**
255+ * Get if the query is marked as strict, as last configured by
256+ * {@link #setStrictColumns(boolean)}.
257+ *
258+ * @hide
259+ */
260+ public boolean isStrictColumns() {
261+ return (mStrictFlags & STRICT_COLUMNS) != 0;
262+ }
263+
264+ /**
265+ * When enabled, verify that all untrusted SQL conforms to a restricted SQL
266+ * grammar. Here are the restrictions applied:
267+ * <ul>
268+ * <li>In {@code WHERE} and {@code HAVING} clauses: subqueries, raising, and
269+ * windowing terms are rejected.
270+ * <li>In {@code GROUP BY} clauses: only valid columns are allowed.
271+ * <li>In {@code ORDER BY} clauses: only valid columns, collation, and
272+ * ordering terms are allowed.
273+ * <li>In {@code LIMIT} clauses: only numerical values and offset terms are
274+ * allowed.
275+ * </ul>
276+ * All column references must be valid as defined by
277+ * {@link #setProjectionMap(Map)}.
278+ * <p>
279+ * This enforcement applies to {@link #query}, {@link #update} and
280+ * {@link #delete} operations. This enforcement does not apply to trusted
281+ * inputs, such as those provided by {@link #appendWhere}. Any enforcement
282+ * failures will throw an {@link IllegalArgumentException}.
283+ *
284+ * @hide
285+ */
286+ public void setStrictGrammar(boolean strictGrammar) {
287+ if (strictGrammar) {
288+ mStrictFlags |= STRICT_GRAMMAR;
289+ } else {
290+ mStrictFlags &= ~STRICT_GRAMMAR;
291+ }
292+ }
293+
294+ /**
295+ * Get if the query is marked as strict, as last configured by
296+ * {@link #setStrictGrammar(boolean)}.
297+ *
298+ * @hide
299+ */
300+ public boolean isStrictGrammar() {
301+ return (mStrictFlags & STRICT_GRAMMAR) != 0;
175302 }
176303
177304 /**
@@ -207,9 +334,6 @@ public class SQLiteQueryBuilder
207334 throw new IllegalArgumentException(
208335 "HAVING clauses are only permitted when using a groupBy clause");
209336 }
210- if (!TextUtils.isEmpty(limit) && !sLimitPattern.matcher(limit).matches()) {
211- throw new IllegalArgumentException("invalid LIMIT clauses:" + limit);
212- }
213337
214338 StringBuilder query = new StringBuilder(120);
215339
@@ -383,7 +507,13 @@ public class SQLiteQueryBuilder
383507 projectionIn, selection, groupBy, having,
384508 sortOrder, limit);
385509
386- if (mStrict && selection != null && selection.length() > 0) {
510+ if (isStrictColumns()) {
511+ enforceStrictColumns(projectionIn);
512+ }
513+ if (isStrictGrammar()) {
514+ enforceStrictGrammar(selection, groupBy, having, sortOrder, limit);
515+ }
516+ if (isStrict()) {
387517 // Validate the user-supplied selection to detect syntactic anomalies
388518 // in the selection string that could indicate a SQL injection attempt.
389519 // The idea is to ensure that the selection clause is a valid SQL expression
@@ -401,7 +531,7 @@ public class SQLiteQueryBuilder
401531
402532 // Execute wrapped query for extra protection
403533 final String wrappedSql = buildQuery(projectionIn, wrap(selection), groupBy,
404- having, sortOrder, limit);
534+ wrap(having), sortOrder, limit);
405535 sql = wrappedSql;
406536 } else {
407537 // Execute unwrapped query
@@ -446,7 +576,13 @@ public class SQLiteQueryBuilder
446576 final String sql;
447577 final String unwrappedSql = buildUpdate(values, selection);
448578
449- if (mStrict) {
579+ if (isStrictColumns()) {
580+ enforceStrictColumns(values);
581+ }
582+ if (isStrictGrammar()) {
583+ enforceStrictGrammar(selection, null, null, null, null);
584+ }
585+ if (isStrict()) {
450586 // Validate the user-supplied selection to detect syntactic anomalies
451587 // in the selection string that could indicate a SQL injection attempt.
452588 // The idea is to ensure that the selection clause is a valid SQL expression
@@ -516,7 +652,10 @@ public class SQLiteQueryBuilder
516652 final String sql;
517653 final String unwrappedSql = buildDelete(selection);
518654
519- if (mStrict) {
655+ if (isStrictGrammar()) {
656+ enforceStrictGrammar(selection, null, null, null, null);
657+ }
658+ if (isStrict()) {
520659 // Validate the user-supplied selection to detect syntactic anomalies
521660 // in the selection string that could indicate a SQL injection attempt.
522661 // The idea is to ensure that the selection clause is a valid SQL expression
@@ -551,6 +690,82 @@ public class SQLiteQueryBuilder
551690 return db.executeSql(sql, sqlArgs);
552691 }
553692
693+ private void enforceStrictColumns(@Nullable String[] projection) {
694+ Objects.requireNonNull(mProjectionMap, "No projection map defined");
695+
696+ computeProjection(projection);
697+ }
698+
699+ private void enforceStrictColumns(@NonNull ContentValues values) {
700+ Objects.requireNonNull(mProjectionMap, "No projection map defined");
701+
702+ final Set<String> rawValues = values.keySet();
703+ final Iterator<String> rawValuesIt = rawValues.iterator();
704+ while (rawValuesIt.hasNext()) {
705+ final String column = rawValuesIt.next();
706+ if (!mProjectionMap.containsKey(column)) {
707+ throw new IllegalArgumentException("Invalid column " + column);
708+ }
709+ }
710+ }
711+
712+ private void enforceStrictGrammar(@Nullable String selection, @Nullable String groupBy,
713+ @Nullable String having, @Nullable String sortOrder, @Nullable String limit) {
714+ SQLiteTokenizer.tokenize(selection, SQLiteTokenizer.OPTION_NONE,
715+ this::enforceStrictGrammarWhereHaving);
716+ SQLiteTokenizer.tokenize(groupBy, SQLiteTokenizer.OPTION_NONE,
717+ this::enforceStrictGrammarGroupBy);
718+ SQLiteTokenizer.tokenize(having, SQLiteTokenizer.OPTION_NONE,
719+ this::enforceStrictGrammarWhereHaving);
720+ SQLiteTokenizer.tokenize(sortOrder, SQLiteTokenizer.OPTION_NONE,
721+ this::enforceStrictGrammarOrderBy);
722+ SQLiteTokenizer.tokenize(limit, SQLiteTokenizer.OPTION_NONE,
723+ this::enforceStrictGrammarLimit);
724+ }
725+
726+ private void enforceStrictGrammarWhereHaving(@NonNull String token) {
727+ if (isTableOrColumn(token)) return;
728+ if (SQLiteTokenizer.isFunction(token)) return;
729+ if (SQLiteTokenizer.isType(token)) return;
730+
731+ // NOTE: we explicitly don't allow SELECT subqueries, since they could
732+ // leak data that should have been filtered by the trusted where clause
733+ switch (token.toUpperCase(Locale.US)) {
734+ case "AND": case "AS": case "BETWEEN": case "BINARY":
735+ case "CASE": case "CAST": case "COLLATE": case "DISTINCT":
736+ case "ELSE": case "END": case "ESCAPE": case "EXISTS":
737+ case "GLOB": case "IN": case "IS": case "ISNULL":
738+ case "LIKE": case "MATCH": case "NOCASE": case "NOT":
739+ case "NOTNULL": case "NULL": case "OR": case "REGEXP":
740+ case "RTRIM": case "THEN": case "WHEN":
741+ return;
742+ }
743+ throw new IllegalArgumentException("Invalid token " + token);
744+ }
745+
746+ private void enforceStrictGrammarGroupBy(@NonNull String token) {
747+ if (isTableOrColumn(token)) return;
748+ throw new IllegalArgumentException("Invalid token " + token);
749+ }
750+
751+ private void enforceStrictGrammarOrderBy(@NonNull String token) {
752+ if (isTableOrColumn(token)) return;
753+ switch (token.toUpperCase(Locale.US)) {
754+ case "COLLATE": case "ASC": case "DESC":
755+ case "BINARY": case "RTRIM": case "NOCASE":
756+ return;
757+ }
758+ throw new IllegalArgumentException("Invalid token " + token);
759+ }
760+
761+ private void enforceStrictGrammarLimit(@NonNull String token) {
762+ switch (token.toUpperCase(Locale.US)) {
763+ case "OFFSET":
764+ return;
765+ }
766+ throw new IllegalArgumentException("Invalid token " + token);
767+ }
768+
554769 /**
555770 * Construct a SELECT statement suitable for use in a group of
556771 * SELECT statements that will be joined through UNION operators
@@ -611,7 +826,7 @@ public class SQLiteQueryBuilder
611826
612827 StringBuilder sql = new StringBuilder(120);
613828 sql.append("UPDATE ");
614- sql.append(mTables);
829+ sql.append(SQLiteDatabase.findEditTable(mTables));
615830 sql.append(" SET ");
616831
617832 final String[] rawKeys = values.keySet().toArray(EmptyArray.STRING);
@@ -632,7 +847,7 @@ public class SQLiteQueryBuilder
632847 public String buildDelete(String selection) {
633848 StringBuilder sql = new StringBuilder(120);
634849 sql.append("DELETE FROM ");
635- sql.append(mTables);
850+ sql.append(SQLiteDatabase.findEditTable(mTables));
636851
637852 final String where = computeWhere(selection);
638853 appendClause(sql, " WHERE ", where);
@@ -763,35 +978,23 @@ public class SQLiteQueryBuilder
763978 return query.toString();
764979 }
765980
766- private String[] computeProjection(String[] projectionIn) {
767- if (projectionIn != null && projectionIn.length > 0) {
768- if (mProjectionMap != null) {
769- String[] projection = new String[projectionIn.length];
770- int length = projectionIn.length;
771-
772- for (int i = 0; i < length; i++) {
773- String userColumn = projectionIn[i];
774- String column = mProjectionMap.get(userColumn);
775-
776- if (column != null) {
777- projection[i] = column;
778- continue;
779- }
780-
781- if (!mStrict &&
782- ( userColumn.contains(" AS ") || userColumn.contains(" as "))) {
783- /* A column alias already exist */
784- projection[i] = userColumn;
785- continue;
786- }
981+ private static @NonNull String maybeWithOperator(@Nullable String operator,
982+ @NonNull String column) {
983+ if (operator != null) {
984+ return operator + "(" + column + ")";
985+ } else {
986+ return column;
987+ }
988+ }
787989
788- throw new IllegalArgumentException("Invalid column "
789- + projectionIn[i]);
790- }
791- return projection;
792- } else {
793- return projectionIn;
990+ /** {@hide} */
991+ public @Nullable String[] computeProjection(@Nullable String[] projectionIn) {
992+ if (!ArrayUtils.isEmpty(projectionIn)) {
993+ String[] projectionOut = new String[projectionIn.length];
994+ for (int i = 0; i < projectionIn.length; i++) {
995+ projectionOut[i] = computeSingleProjectionOrThrow(projectionIn[i]);
794996 }
997+ return projectionOut;
795998 } else if (mProjectionMap != null) {
796999 // Return all columns in projection map.
7971000 Set<Entry<String, String>> entrySet = mProjectionMap.entrySet();
@@ -813,7 +1016,71 @@ public class SQLiteQueryBuilder
8131016 return null;
8141017 }
8151018
816- private @Nullable String computeWhere(@Nullable String selection) {
1019+ private @NonNull String computeSingleProjectionOrThrow(@NonNull String userColumn) {
1020+ final String column = computeSingleProjection(userColumn);
1021+ if (column != null) {
1022+ return column;
1023+ } else {
1024+ throw new IllegalArgumentException("Invalid column " + userColumn);
1025+ }
1026+ }
1027+
1028+ private @Nullable String computeSingleProjection(@NonNull String userColumn) {
1029+ // When no mapping provided, anything goes
1030+ if (mProjectionMap == null) {
1031+ return userColumn;
1032+ }
1033+
1034+ String operator = null;
1035+ String column = mProjectionMap.get(userColumn);
1036+
1037+ // When no direct match found, look for aggregation
1038+ if (column == null) {
1039+ final Matcher matcher = sAggregationPattern.matcher(userColumn);
1040+ if (matcher.matches()) {
1041+ operator = matcher.group(1);
1042+ userColumn = matcher.group(2);
1043+ column = mProjectionMap.get(userColumn);
1044+ }
1045+ }
1046+
1047+ if (column != null) {
1048+ return maybeWithOperator(operator, column);
1049+ }
1050+
1051+ if (mStrictFlags == 0
1052+ && (userColumn.contains(" AS ") || userColumn.contains(" as "))) {
1053+ /* A column alias already exist */
1054+ return maybeWithOperator(operator, userColumn);
1055+ }
1056+
1057+ // If greylist is configured, we might be willing to let
1058+ // this custom column bypass our strict checks.
1059+ if (mProjectionGreylist != null) {
1060+ boolean match = false;
1061+ for (Pattern p : mProjectionGreylist) {
1062+ if (p.matcher(userColumn).matches()) {
1063+ match = true;
1064+ break;
1065+ }
1066+ }
1067+
1068+ if (match) {
1069+ Log.w(TAG, "Allowing abusive custom column: " + userColumn);
1070+ return maybeWithOperator(operator, userColumn);
1071+ }
1072+ }
1073+
1074+ return null;
1075+ }
1076+
1077+ private boolean isTableOrColumn(String token) {
1078+ if (mTables.equals(token)) return true;
1079+ return computeSingleProjection(token) != null;
1080+ }
1081+
1082+ /** {@hide} */
1083+ public @Nullable String computeWhere(@Nullable String selection) {
8171084 final boolean hasInternal = !TextUtils.isEmpty(mWhereClause);
8181085 final boolean hasExternal = !TextUtils.isEmpty(selection);
8191086
--- /dev/null
+++ b/core/java/android/database/sqlite/SQLiteTokenizer.java
@@ -0,0 +1,297 @@
1+/*
2+ * Copyright (C) 2019 The Android Open Source Project
3+ *
4+ * Licensed under the Apache License, Version 2.0 (the "License");
5+ * you may not use this file except in compliance with the License.
6+ * You may obtain a copy of the License at
7+ *
8+ * http://www.apache.org/licenses/LICENSE-2.0
9+ *
10+ * Unless required by applicable law or agreed to in writing, software
11+ * distributed under the License is distributed on an "AS IS" BASIS,
12+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+ * See the License for the specific language governing permissions and
14+ * limitations under the License.
15+ */
16+
17+package android.database.sqlite;
18+
19+import android.annotation.NonNull;
20+import android.annotation.Nullable;
21+
22+import java.util.ArrayList;
23+import java.util.List;
24+import java.util.Locale;
25+import java.util.function.Consumer;
26+
27+/**
28+ * SQL Tokenizer specialized to extract tokens from SQL (snippets).
29+ * <p>
30+ * Based on sqlite3GetToken() in tokenzie.c in SQLite.
31+ * <p>
32+ * Source for v3.8.6 (which android uses): http://www.sqlite.org/src/artifact/ae45399d6252b4d7
33+ * (Latest source as of now: http://www.sqlite.org/src/artifact/78c8085bc7af1922)
34+ * <p>
35+ * Also draft spec: http://www.sqlite.org/draft/tokenreq.html
36+ *
37+ * @hide
38+ */
39+public class SQLiteTokenizer {
40+ private static boolean isAlpha(char ch) {
41+ return ('a' <= ch && ch <= 'z') || ('A' <= ch && ch <= 'Z') || (ch == '_');
42+ }
43+
44+ private static boolean isNum(char ch) {
45+ return ('0' <= ch && ch <= '9');
46+ }
47+
48+ private static boolean isAlNum(char ch) {
49+ return isAlpha(ch) || isNum(ch);
50+ }
51+
52+ private static boolean isAnyOf(char ch, String set) {
53+ return set.indexOf(ch) >= 0;
54+ }
55+
56+ private static IllegalArgumentException genException(String message, String sql) {
57+ throw new IllegalArgumentException(message + " in '" + sql + "'");
58+ }
59+
60+ private static char peek(String s, int index) {
61+ return index < s.length() ? s.charAt(index) : '\0';
62+ }
63+
64+ public static final int OPTION_NONE = 0;
65+
66+ /**
67+ * Require that SQL contains only tokens; any comments or values will result
68+ * in an exception.
69+ */
70+ public static final int OPTION_TOKEN_ONLY = 1 << 0;
71+
72+ /**
73+ * Tokenize the given SQL, returning the list of each encountered token.
74+ *
75+ * @throws IllegalArgumentException if invalid SQL is encountered.
76+ */
77+ public static List<String> tokenize(@Nullable String sql, int options) {
78+ final ArrayList<String> res = new ArrayList<>();
79+ tokenize(sql, options, res::add);
80+ return res;
81+ }
82+
83+ /**
84+ * Tokenize the given SQL, sending each encountered token to the given
85+ * {@link Consumer}.
86+ *
87+ * @throws IllegalArgumentException if invalid SQL is encountered.
88+ */
89+ public static void tokenize(@Nullable String sql, int options, Consumer<String> checker) {
90+ if (sql == null) {
91+ return;
92+ }
93+ int pos = 0;
94+ final int len = sql.length();
95+ while (pos < len) {
96+ final char ch = peek(sql, pos);
97+
98+ // Regular token.
99+ if (isAlpha(ch)) {
100+ final int start = pos;
101+ pos++;
102+ while (isAlNum(peek(sql, pos))) {
103+ pos++;
104+ }
105+ final int end = pos;
106+
107+ final String token = sql.substring(start, end);
108+ checker.accept(token);
109+
110+ continue;
111+ }
112+
113+ // Handle quoted tokens
114+ if (isAnyOf(ch, "'\"`")) {
115+ final int quoteStart = pos;
116+ pos++;
117+
118+ for (;;) {
119+ pos = sql.indexOf(ch, pos);
120+ if (pos < 0) {
121+ throw genException("Unterminated quote", sql);
122+ }
123+ if (peek(sql, pos + 1) != ch) {
124+ break;
125+ }
126+ // Quoted quote char -- e.g. "abc""def" is a single string.
127+ pos += 2;
128+ }
129+ final int quoteEnd = pos;
130+ pos++;
131+
132+ if (ch != '\'') {
133+ // Extract the token
134+ final String tokenUnquoted = sql.substring(quoteStart + 1, quoteEnd);
135+
136+ final String token;
137+
138+ // Unquote if needed. i.e. "aa""bb" -> aa"bb
139+ if (tokenUnquoted.indexOf(ch) >= 0) {
140+ token = tokenUnquoted.replaceAll(
141+ String.valueOf(ch) + ch, String.valueOf(ch));
142+ } else {
143+ token = tokenUnquoted;
144+ }
145+ checker.accept(token);
146+ } else {
147+ if ((options &= OPTION_TOKEN_ONLY) != 0) {
148+ throw genException("Non-token detected", sql);
149+ }
150+ }
151+ continue;
152+ }
153+ // Handle tokens enclosed in [...]
154+ if (ch == '[') {
155+ final int quoteStart = pos;
156+ pos++;
157+
158+ pos = sql.indexOf(']', pos);
159+ if (pos < 0) {
160+ throw genException("Unterminated quote", sql);
161+ }
162+ final int quoteEnd = pos;
163+ pos++;
164+
165+ final String token = sql.substring(quoteStart + 1, quoteEnd);
166+
167+ checker.accept(token);
168+ continue;
169+ }
170+ if ((options &= OPTION_TOKEN_ONLY) != 0) {
171+ throw genException("Non-token detected", sql);
172+ }
173+
174+ // Detect comments.
175+ if (ch == '-' && peek(sql, pos + 1) == '-') {
176+ pos += 2;
177+ pos = sql.indexOf('\n', pos);
178+ if (pos < 0) {
179+ // We disallow strings ending in an inline comment.
180+ throw genException("Unterminated comment", sql);
181+ }
182+ pos++;
183+
184+ continue;
185+ }
186+ if (ch == '/' && peek(sql, pos + 1) == '*') {
187+ pos += 2;
188+ pos = sql.indexOf("*/", pos);
189+ if (pos < 0) {
190+ throw genException("Unterminated comment", sql);
191+ }
192+ pos += 2;
193+
194+ continue;
195+ }
196+
197+ // Semicolon is never allowed.
198+ if (ch == ';') {
199+ throw genException("Semicolon is not allowed", sql);
200+ }
201+
202+ // For this purpose, we can simply ignore other characters.
203+ // (Note it doesn't handle the X'' literal properly and reports this X as a token,
204+ // but that should be fine...)
205+ pos++;
206+ }
207+ }
208+
209+ /**
210+ * Test if given token is a
211+ * <a href="https://www.sqlite.org/lang_keywords.html">SQLite reserved
212+ * keyword</a>.
213+ */
214+ public static boolean isKeyword(@NonNull String token) {
215+ switch (token.toUpperCase(Locale.US)) {
216+ case "ABORT": case "ACTION": case "ADD": case "AFTER":
217+ case "ALL": case "ALTER": case "ANALYZE": case "AND":
218+ case "AS": case "ASC": case "ATTACH": case "AUTOINCREMENT":
219+ case "BEFORE": case "BEGIN": case "BETWEEN": case "BINARY":
220+ case "BY": case "CASCADE": case "CASE": case "CAST":
221+ case "CHECK": case "COLLATE": case "COLUMN": case "COMMIT":
222+ case "CONFLICT": case "CONSTRAINT": case "CREATE": case "CROSS":
223+ case "CURRENT": case "CURRENT_DATE": case "CURRENT_TIME": case "CURRENT_TIMESTAMP":
224+ case "DATABASE": case "DEFAULT": case "DEFERRABLE": case "DEFERRED":
225+ case "DELETE": case "DESC": case "DETACH": case "DISTINCT":
226+ case "DO": case "DROP": case "EACH": case "ELSE":
227+ case "END": case "ESCAPE": case "EXCEPT": case "EXCLUDE":
228+ case "EXCLUSIVE": case "EXISTS": case "EXPLAIN": case "FAIL":
229+ case "FILTER": case "FOLLOWING": case "FOR": case "FOREIGN":
230+ case "FROM": case "FULL": case "GLOB": case "GROUP":
231+ case "GROUPS": case "HAVING": case "IF": case "IGNORE":
232+ case "IMMEDIATE": case "IN": case "INDEX": case "INDEXED":
233+ case "INITIALLY": case "INNER": case "INSERT": case "INSTEAD":
234+ case "INTERSECT": case "INTO": case "IS": case "ISNULL":
235+ case "JOIN": case "KEY": case "LEFT": case "LIKE":
236+ case "LIMIT": case "MATCH": case "NATURAL": case "NO":
237+ case "NOCASE": case "NOT": case "NOTHING": case "NOTNULL":
238+ case "NULL": case "OF": case "OFFSET": case "ON":
239+ case "OR": case "ORDER": case "OTHERS": case "OUTER":
240+ case "OVER": case "PARTITION": case "PLAN": case "PRAGMA":
241+ case "PRECEDING": case "PRIMARY": case "QUERY": case "RAISE":
242+ case "RANGE": case "RECURSIVE": case "REFERENCES": case "REGEXP":
243+ case "REINDEX": case "RELEASE": case "RENAME": case "REPLACE":
244+ case "RESTRICT": case "RIGHT": case "ROLLBACK": case "ROW":
245+ case "ROWS": case "RTRIM": case "SAVEPOINT": case "SELECT":
246+ case "SET": case "TABLE": case "TEMP": case "TEMPORARY":
247+ case "THEN": case "TIES": case "TO": case "TRANSACTION":
248+ case "TRIGGER": case "UNBOUNDED": case "UNION": case "UNIQUE":
249+ case "UPDATE": case "USING": case "VACUUM": case "VALUES":
250+ case "VIEW": case "VIRTUAL": case "WHEN": case "WHERE":
251+ case "WINDOW": case "WITH": case "WITHOUT":
252+ return true;
253+ default:
254+ return false;
255+ }
256+ }
257+
258+ /**
259+ * Test if given token is a
260+ * <a href="https://www.sqlite.org/lang_corefunc.html">SQLite reserved
261+ * function</a>.
262+ */
263+ public static boolean isFunction(@NonNull String token) {
264+ switch (token.toLowerCase(Locale.US)) {
265+ case "abs": case "avg": case "char": case "coalesce":
266+ case "count": case "glob": case "group_concat": case "hex":
267+ case "ifnull": case "instr": case "length": case "like":
268+ case "likelihood": case "likely": case "lower": case "ltrim":
269+ case "max": case "min": case "nullif": case "random":
270+ case "randomblob": case "replace": case "round": case "rtrim":
271+ case "substr": case "sum": case "total": case "trim":
272+ case "typeof": case "unicode": case "unlikely": case "upper":
273+ case "zeroblob":
274+ return true;
275+ default:
276+ return false;
277+ }
278+ }
279+
280+ /**
281+ * Test if given token is a
282+ * <a href="https://www.sqlite.org/datatype3.html">SQLite reserved type</a>.
283+ */
284+ public static boolean isType(@NonNull String token) {
285+ switch (token.toUpperCase(Locale.US)) {
286+ case "INT": case "INTEGER": case "TINYINT": case "SMALLINT":
287+ case "MEDIUMINT": case "BIGINT": case "INT2": case "INT8":
288+ case "CHARACTER": case "VARCHAR": case "NCHAR": case "NVARCHAR":
289+ case "TEXT": case "CLOB": case "BLOB": case "REAL":
290+ case "DOUBLE": case "FLOAT": case "NUMERIC": case "DECIMAL":
291+ case "BOOLEAN": case "DATE": case "DATETIME":
292+ return true;
293+ default:
294+ return false;
295+ }
296+ }
297+}
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -5263,6 +5263,18 @@ public final class Settings {
52635263 public static final String DEVICE_PROVISIONED = Global.DEVICE_PROVISIONED;
52645264
52655265 /**
5266+ * Indicates whether a DPC has been downloaded during provisioning.
5267+ *
5268+ * <p>Type: int (0 for false, 1 for true)
5269+ *
5270+ * <p>If this is true, then any attempts to begin setup again should result in factory reset
5271+ *
5272+ * @hide
5273+ */
5274+ public static final String MANAGED_PROVISIONING_DPC_DOWNLOADED =
5275+ "managed_provisioning_dpc_downloaded";
5276+
5277+ /**
52665278 * Whether the current user has been set up via setup wizard (0 = false, 1 = true)
52675279 * @hide
52685280 */
--- a/core/java/android/text/TextLine.java
+++ b/core/java/android/text/TextLine.java
@@ -242,6 +242,7 @@ class TextLine {
242242 if (runLimit > mLen) {
243243 runLimit = mLen;
244244 }
245+ if (runStart > mLen) break;
245246 boolean runIsRtl = (runs[i+1] & Layout.RUN_RTL_FLAG) != 0;
246247
247248 int segstart = runStart;
@@ -319,6 +320,7 @@ class TextLine {
319320 if (runLimit > mLen) {
320321 runLimit = mLen;
321322 }
323+ if (runStart > mLen) break;
322324 boolean runIsRtl = (runs[i+1] & Layout.RUN_RTL_FLAG) != 0;
323325
324326 int segstart = runStart;
@@ -408,6 +410,7 @@ class TextLine {
408410 if (runLimit > mLen) {
409411 runLimit = mLen;
410412 }
413+ if (runStart > mLen) break;
411414 boolean runIsRtl = (runs[i + 1] & Layout.RUN_RTL_FLAG) != 0;
412415
413416 int segstart = runStart;
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -16,6 +16,7 @@
1616
1717 package android.widget;
1818
19+import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
1920 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_LENGTH;
2021 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX;
2122 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY;
@@ -28,11 +29,13 @@ import android.annotation.FloatRange;
2829 import android.annotation.IntDef;
2930 import android.annotation.NonNull;
3031 import android.annotation.Nullable;
32+import android.annotation.RequiresPermission;
3133 import android.annotation.Size;
3234 import android.annotation.StringRes;
3335 import android.annotation.StyleRes;
3436 import android.annotation.XmlRes;
3537 import android.app.Activity;
38+import android.app.ActivityManager;
3639 import android.app.assist.AssistStructure;
3740 import android.content.ClipData;
3841 import android.content.ClipDescription;
@@ -66,6 +69,7 @@ import android.os.Parcel;
6669 import android.os.Parcelable;
6770 import android.os.ParcelableParcel;
6871 import android.os.SystemClock;
72+import android.os.UserHandle;
6973 import android.provider.Settings;
7074 import android.text.BoringLayout;
7175 import android.text.DynamicLayout;
@@ -686,6 +690,19 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
686690
687691 private InputFilter[] mFilters = NO_FILTERS;
688692
693+ /**
694+ * To keep the information to indicate if there is necessary to restrict the power of
695+ * INTERACT_ACROSS_USERS_FULL.
696+ * <p>
697+ * SystemUI always run as user 0 to process all of direct reply. SystemUI has the poer of
698+ * INTERACT_ACROSS_USERS_FULL. However, all of the notifications not only belong to user 0 but
699+ * also to the other users in multiple user environment.
700+ * </p>
701+ *
702+ * @see #setRestrictedAcrossUser(boolean)
703+ */
704+ private boolean mIsRestrictedAcrossUser;
705+
689706 private volatile Locale mCurrentSpellCheckerLocaleCache;
690707
691708 // It is possible to have a selection even when mEditor is null (programmatically set, like when
@@ -10041,6 +10058,24 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
1004110058 }
1004210059
1004310060 /**
10061+ * To notify the TextView to restricted the power of the app granted INTERACT_ACROSS_USERS_FULL
10062+ * permission.
10063+ * <p>
10064+ * Most of applications should not granted the INTERACT_ACROSS_USERS_FULL permssion.
10065+ * SystemUI is the special one that run in user 0 process to handle multiple user notification.
10066+ * Unforunately, the power of INTERACT_ACROSS_USERS_FULL should be limited or restricted for
10067+ * preventing from information leak.</p>
10068+ * <p>This function call is called for SystemUI Keyguard and Notification.</p>
10069+ *
10070+ * @param isRestricted is true if the power of INTERACT_ACROSS_USERS_FULL should be limited.
10071+ * @hide
10072+ */
10073+ @RequiresPermission(INTERACT_ACROSS_USERS_FULL)
10074+ public final void setRestrictedAcrossUser(boolean isRestricted) {
10075+ mIsRestrictedAcrossUser = isRestricted;
10076+ }
10077+
10078+ /**
1004410079 * This is a temporary method. Future versions may support multi-locale text.
1004510080 * Caveat: This method may not return the latest text services locale, but this should be
1004610081 * acceptable and it's more important to make this method asynchronous.
@@ -11073,6 +11108,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
1107311108 }
1107411109
1107511110 boolean canCut() {
11111+ if (mIsRestrictedAcrossUser
11112+ && UserHandle.myUserId() != ActivityManager.getCurrentUser()) {
11113+ // When it's restricted, and the curren user is not the process user. It can't cut
11114+ // because it may cut the text of the user 10 into the clipboard of user 0.
11115+ return false;
11116+ }
1107611117 if (hasPasswordTransformationMethod()) {
1107711118 return false;
1107811119 }
@@ -11086,6 +11127,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
1108611127 }
1108711128
1108811129 boolean canCopy() {
11130+ if (mIsRestrictedAcrossUser
11131+ && UserHandle.myUserId() != ActivityManager.getCurrentUser()) {
11132+ // When it's restricted, and the curren user is not the process user. It can't copy
11133+ // because it may copy the text of the user 10 to the clipboard of user 0.
11134+ return false;
11135+ }
1108911136 if (hasPasswordTransformationMethod()) {
1109011137 return false;
1109111138 }
@@ -11115,6 +11162,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
1111511162 }
1111611163
1111711164 boolean canPaste() {
11165+ if (mIsRestrictedAcrossUser
11166+ && UserHandle.myUserId() != ActivityManager.getCurrentUser()) {
11167+ // When it's restricted, and the curren user is not the process user. It can't paste
11168+ // because it may copy the text from the user 0 clipboard in current user is 10.
11169+ return false;
11170+ }
1111811171 return (mText instanceof Editable
1111911172 && mEditor != null && mEditor.mKeyListener != null
1112011173 && getSelectionStart() >= 0
--- /dev/null
+++ b/core/tests/coretests/src/android/database/sqlite/SQLiteTokenizerTest.java
@@ -0,0 +1,169 @@
1+/*
2+ * Copyright (C) 2019 The Android Open Source Project
3+ *
4+ * Licensed under the Apache License, Version 2.0 (the "License");
5+ * you may not use this file except in compliance with the License.
6+ * You may obtain a copy of the License at
7+ *
8+ * http://www.apache.org/licenses/LICENSE-2.0
9+ *
10+ * Unless required by applicable law or agreed to in writing, software
11+ * distributed under the License is distributed on an "AS IS" BASIS,
12+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+ * See the License for the specific language governing permissions and
14+ * limitations under the License.
15+ */
16+
17+package android.database.sqlite;
18+
19+import static org.junit.Assert.assertEquals;
20+import static org.junit.Assert.assertTrue;
21+import static org.junit.Assert.fail;
22+
23+import org.junit.Test;
24+
25+import java.util.ArrayList;
26+import java.util.Arrays;
27+import java.util.List;
28+
29+public class SQLiteTokenizerTest {
30+ private List<String> getTokens(String sql) {
31+ return SQLiteTokenizer.tokenize(sql, SQLiteTokenizer.OPTION_NONE);
32+ }
33+
34+ private void checkTokens(String sql, String spaceSeparatedExpectedTokens) {
35+ final List<String> expected = spaceSeparatedExpectedTokens == null
36+ ? new ArrayList<>()
37+ : Arrays.asList(spaceSeparatedExpectedTokens.split(" +"));
38+
39+ assertEquals(expected, getTokens(sql));
40+ }
41+
42+ private void assertInvalidSql(String sql, String message) {
43+ try {
44+ getTokens(sql);
45+ fail("Didn't throw InvalidSqlException");
46+ } catch (IllegalArgumentException e) {
47+ assertTrue("Expected " + e.getMessage() + " to contain " + message,
48+ e.getMessage().contains(message));
49+ }
50+ }
51+
52+ @Test
53+ public void testWhitespaces() {
54+ checkTokens(" select \t\r\n a\n\n ", "select a");
55+ checkTokens("a b", "a b");
56+ }
57+
58+ @Test
59+ public void testComment() {
60+ checkTokens("--\n", null);
61+ checkTokens("a--\n", "a");
62+ checkTokens("a--abcdef\n", "a");
63+ checkTokens("a--abcdef\nx", "a x");
64+ checkTokens("a--\nx", "a x");
65+ assertInvalidSql("a--abcdef", "Unterminated comment");
66+ assertInvalidSql("a--abcdef\ndef--", "Unterminated comment");
67+
68+ checkTokens("/**/", null);
69+ assertInvalidSql("/*", "Unterminated comment");
70+ assertInvalidSql("/*/", "Unterminated comment");
71+ assertInvalidSql("/*\n* /*a", "Unterminated comment");
72+ checkTokens("a/**/", "a");
73+ checkTokens("/**/b", "b");
74+ checkTokens("a/**/b", "a b");
75+ checkTokens("a/* -- \n* /* **/b", "a b");
76+ }
77+
78+ @Test
79+ public void testStrings() {
80+ assertInvalidSql("'", "Unterminated quote");
81+ assertInvalidSql("a'", "Unterminated quote");
82+ assertInvalidSql("a'''", "Unterminated quote");
83+ assertInvalidSql("a''' ", "Unterminated quote");
84+ checkTokens("''", null);
85+ checkTokens("''''", null);
86+ checkTokens("a''''b", "a b");
87+ checkTokens("a' '' 'b", "a b");
88+ checkTokens("'abc'", null);
89+ checkTokens("'abc\ndef'", null);
90+ checkTokens("a'abc\ndef'", "a");
91+ checkTokens("'abc\ndef'b", "b");
92+ checkTokens("a'abc\ndef'b", "a b");
93+ checkTokens("a'''abc\nd''ef'''b", "a b");
94+ }
95+
96+ @Test
97+ public void testDoubleQuotes() {
98+ assertInvalidSql("\"", "Unterminated quote");
99+ assertInvalidSql("a\"", "Unterminated quote");
100+ assertInvalidSql("a\"\"\"", "Unterminated quote");
101+ assertInvalidSql("a\"\"\" ", "Unterminated quote");
102+ checkTokens("\"\"", "");
103+ checkTokens("\"\"\"\"", "\"");
104+ checkTokens("a\"\"\"\"b", "a \" b");
105+ checkTokens("a\"\t\"\"\t\"b", "a \t\"\t b");
106+ checkTokens("\"abc\"", "abc");
107+ checkTokens("\"abc\ndef\"", "abc\ndef");
108+ checkTokens("a\"abc\ndef\"", "a abc\ndef");
109+ checkTokens("\"abc\ndef\"b", "abc\ndef b");
110+ checkTokens("a\"abc\ndef\"b", "a abc\ndef b");
111+ checkTokens("a\"\"\"abc\nd\"\"ef\"\"\"b", "a \"abc\nd\"ef\" b");
112+ }
113+
114+ @Test
115+ public void testBackQuotes() {
116+ assertInvalidSql("`", "Unterminated quote");
117+ assertInvalidSql("a`", "Unterminated quote");
118+ assertInvalidSql("a```", "Unterminated quote");
119+ assertInvalidSql("a``` ", "Unterminated quote");
120+ checkTokens("``", "");
121+ checkTokens("````", "`");
122+ checkTokens("a````b", "a ` b");
123+ checkTokens("a`\t``\t`b", "a \t`\t b");
124+ checkTokens("`abc`", "abc");
125+ checkTokens("`abc\ndef`", "abc\ndef");
126+ checkTokens("a`abc\ndef`", "a abc\ndef");
127+ checkTokens("`abc\ndef`b", "abc\ndef b");
128+ checkTokens("a`abc\ndef`b", "a abc\ndef b");
129+ checkTokens("a```abc\nd``ef```b", "a `abc\nd`ef` b");
130+ }
131+
132+ @Test
133+ public void testBrackets() {
134+ assertInvalidSql("[", "Unterminated quote");
135+ assertInvalidSql("a[", "Unterminated quote");
136+ assertInvalidSql("a[ ", "Unterminated quote");
137+ assertInvalidSql("a[[ ", "Unterminated quote");
138+ checkTokens("[]", "");
139+ checkTokens("[[]", "[");
140+ checkTokens("a[[]b", "a [ b");
141+ checkTokens("a[\t[\t]b", "a \t[\t b");
142+ checkTokens("[abc]", "abc");
143+ checkTokens("[abc\ndef]", "abc\ndef");
144+ checkTokens("a[abc\ndef]", "a abc\ndef");
145+ checkTokens("[abc\ndef]b", "abc\ndef b");
146+ checkTokens("a[abc\ndef]b", "a abc\ndef b");
147+ checkTokens("a[[abc\nd[ef[]b", "a [abc\nd[ef[ b");
148+ }
149+
150+ @Test
151+ public void testSemicolons() {
152+ assertInvalidSql(";", "Semicolon is not allowed");
153+ assertInvalidSql(" ;", "Semicolon is not allowed");
154+ assertInvalidSql("; ", "Semicolon is not allowed");
155+ assertInvalidSql("-;-", "Semicolon is not allowed");
156+ checkTokens("--;\n", null);
157+ checkTokens("/*;*/", null);
158+ checkTokens("';'", null);
159+ checkTokens("[;]", ";");
160+ checkTokens("`;`", ";");
161+ }
162+
163+ @Test
164+ public void testTokens() {
165+ checkTokens("a,abc,a00b,_1,_123,abcdef", "a abc a00b _1 _123 abcdef");
166+ checkTokens("a--\nabc/**/a00b''_1'''ABC'''`_123`abc[d]\"e\"f",
167+ "a abc a00b _1 _123 abc d e f");
168+ }
169+}
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -25,6 +25,7 @@ import android.content.SharedPreferences;
2525 import android.os.ParcelUuid;
2626 import android.os.SystemClock;
2727 import android.text.TextUtils;
28+import android.util.EventLog;
2829 import android.util.Log;
2930 import android.bluetooth.BluetoothAdapter;
3031
@@ -831,10 +832,9 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
831832 == BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE ||
832833 mDevice.getBluetoothClass().getDeviceClass()
833834 == BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET) {
834- setPhonebookPermissionChoice(CachedBluetoothDevice.ACCESS_ALLOWED);
835- } else {
836- setPhonebookPermissionChoice(CachedBluetoothDevice.ACCESS_REJECTED);
835+ EventLog.writeEvent(0x534e4554, "138529441", -1, "");
837836 }
837+ setPhonebookPermissionChoice(CachedBluetoothDevice.ACCESS_REJECTED);
838838 }
839839 }
840840 }
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
@@ -79,6 +79,7 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView
7979
8080 @Override
8181 protected void resetState() {
82+ mPasswordEntry.setRestrictedAcrossUser(true);
8283 mSecurityMessageDisplay.setMessage("");
8384 final boolean wasDisabled = mPasswordEntry.isEnabled();
8485 setPasswordEntryEnabled(true);
@@ -175,6 +176,7 @@ public class KeyguardPasswordView extends KeyguardAbsKeyInputView
175176 Context.INPUT_METHOD_SERVICE);
176177
177178 mPasswordEntry = findViewById(getPasswordTextViewId());
179+ mPasswordEntry.setRestrictedAcrossUser(true);
178180 mPasswordEntryDisabler = new TextViewInputDisabler(mPasswordEntry);
179181 mPasswordEntry.setKeyListener(TextKeyListener.getInstance());
180182 mPasswordEntry.setInputType(InputType.TYPE_CLASS_TEXT
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/ForegroundServiceLifetimeExtender.java
@@ -0,0 +1,87 @@
1+/*
2+ * Copyright (C) 2019 The Android Open Source Project
3+ *
4+ * Licensed under the Apache License, Version 2.0 (the "License");
5+ * you may not use this file except in compliance with the License.
6+ * You may obtain a copy of the License at
7+ *
8+ * http://www.apache.org/licenses/LICENSE-2.0
9+ *
10+ * Unless required by applicable law or agreed to in writing, software
11+ * distributed under the License is distributed on an "AS IS" BASIS,
12+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+ * See the License for the specific language governing permissions and
14+ * limitations under the License.
15+ */
16+
17+package com.android.systemui.statusbar;
18+
19+import android.annotation.NonNull;
20+import android.app.Notification;
21+import android.os.Handler;
22+import android.os.Looper;
23+import android.util.ArraySet;
24+
25+import com.android.internal.annotations.VisibleForTesting;
26+import com.android.systemui.statusbar.NotificationData;
27+
28+/**
29+ * Extends the lifetime of foreground notification services such that they show for at least
30+ * five seconds
31+ */
32+public class ForegroundServiceLifetimeExtender implements NotificationLifetimeExtender {
33+ private static final String TAG = "FGSLifetimeExtender";
34+
35+ @VisibleForTesting
36+ public static final int MIN_FGS_TIME_MS = 5000;
37+
38+ private NotificationSafeToRemoveCallback mNotificationSafeToRemoveCallback;
39+ private ArraySet<NotificationData.Entry> mManagedEntries = new ArraySet<>();
40+ private Handler mHandler = new Handler(Looper.getMainLooper());
41+
42+ public ForegroundServiceLifetimeExtender() {
43+ }
44+
45+ @Override
46+ public void setCallback(@NonNull NotificationSafeToRemoveCallback callback) {
47+ mNotificationSafeToRemoveCallback = callback;
48+ }
49+
50+ @Override
51+ public boolean shouldExtendLifetime(@NonNull NotificationData.Entry entry) {
52+ if ((entry.notification.getNotification().flags
53+ & Notification.FLAG_FOREGROUND_SERVICE) == 0) {
54+ return false;
55+ }
56+ long currentTime = System.currentTimeMillis();
57+ return currentTime - entry.notification.getPostTime() < MIN_FGS_TIME_MS;
58+ }
59+
60+ @Override
61+ public boolean shouldExtendLifetimeForPendingNotification(
62+ @NonNull NotificationData.Entry entry) {
63+ return shouldExtendLifetime(entry);
64+ }
65+
66+ @Override
67+ public void setShouldManageLifetime(
68+ @NonNull NotificationData.Entry entry, boolean shouldManage) {
69+ android.util.Log.d("FGSExtender", "setShouldManageLifetime " + shouldManage);
70+ if (!shouldManage) {
71+ mManagedEntries.remove(entry);
72+ return;
73+ }
74+ mManagedEntries.add(entry);
75+ Runnable r = () -> {
76+ if (mManagedEntries.contains(entry)) {
77+ mManagedEntries.remove(entry);
78+ if (mNotificationSafeToRemoveCallback != null) {
79+ mNotificationSafeToRemoveCallback.onSafeToRemove(entry.key);
80+ }
81+ }
82+ };
83+ long delayAmt = MIN_FGS_TIME_MS
84+ - (System.currentTimeMillis() - entry.notification.getPostTime());
85+ mHandler.postDelayed(r, delayAmt);
86+ }
87+}
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLifetimeExtender.java
@@ -0,0 +1,81 @@
1+/*
2+ * Copyright (C) 2019 The Android Open Source Project
3+ *
4+ * Licensed under the Apache License, Version 2.0 (the "License");
5+ * you may not use this file except in compliance with the License.
6+ * You may obtain a copy of the License at
7+ *
8+ * http://www.apache.org/licenses/LICENSE-2.0
9+ *
10+ * Unless required by applicable law or agreed to in writing, software
11+ * distributed under the License is distributed on an "AS IS" BASIS,
12+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+ * See the License for the specific language governing permissions and
14+ * limitations under the License.
15+ */
16+
17+package com.android.systemui.statusbar;
18+
19+import android.annotation.NonNull;
20+
21+import com.android.systemui.statusbar.NotificationData;
22+
23+/**
24+ * Interface for anything that may need to keep notifications managed even after
25+ * {@link NotificationListener} removes it. The lifetime extender is in charge of performing the
26+ * callback when the notification is then safe to remove.
27+ */
28+public interface NotificationLifetimeExtender {
29+
30+ /**
31+ * Set the handler to callback to when the notification is safe to remove.
32+ *
33+ * @param callback the handler to callback
34+ */
35+ void setCallback(@NonNull NotificationSafeToRemoveCallback callback);
36+
37+ /**
38+ * Determines whether or not the extender needs the notification kept after removal.
39+ *
40+ * @param entry the entry containing the notification to check
41+ * @return true if the notification lifetime should be extended
42+ */
43+ boolean shouldExtendLifetime(@NonNull NotificationData.Entry entry);
44+
45+ /**
46+ * It's possible that a notification was canceled before it ever became visible. This callback
47+ * gives lifetime extenders a chance to make sure it shows up. For example if a foreground
48+ * service is canceled too quickly but we still want to make sure a FGS notification shows.
49+ * @param pendingEntry the canceled (but pending) entry
50+ * @return true if the notification lifetime should be extended
51+ */
52+ default boolean shouldExtendLifetimeForPendingNotification(
53+ @NonNull NotificationData.Entry pendingEntry) {
54+ return false;
55+ }
56+
57+ /**
58+ * Sets whether or not the lifetime should be managed by the extender. In practice, if
59+ * shouldManage is true, this is where the extender starts managing the entry internally and is
60+ * now responsible for calling {@link NotificationSafeToRemoveCallback#onSafeToRemove(String)}
61+ * when the entry is safe to remove. If shouldManage is false, the extender no longer needs to
62+ * worry about it (either because we will be removing it anyway or the entry is no longer
63+ * removed due to an update).
64+ *
65+ * @param entry the entry that needs an extended lifetime
66+ * @param shouldManage true if the extender should manage the entry now, false otherwise
67+ */
68+ void setShouldManageLifetime(@NonNull NotificationData.Entry entry, boolean shouldManage);
69+
70+ /**
71+ * The callback for when the notification is now safe to remove (i.e. its lifetime has ended).
72+ */
73+ interface NotificationSafeToRemoveCallback {
74+ /**
75+ * Called when the lifetime extender determines it's safe to remove.
76+ *
77+ * @param key key of the entry that is now safe to remove
78+ */
79+ void onSafeToRemove(String key);
80+ }
81+}
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -110,6 +110,7 @@ import android.service.vr.IVrManager;
110110 import android.service.vr.IVrStateCallbacks;
111111 import android.text.TextUtils;
112112 import android.util.ArraySet;
113+import android.util.ArrayMap;
113114 import android.util.DisplayMetrics;
114115 import android.util.EventLog;
115116 import android.util.Log;
@@ -204,6 +205,7 @@ import com.android.systemui.statusbar.DismissView;
204205 import com.android.systemui.statusbar.DragDownHelper;
205206 import com.android.systemui.statusbar.EmptyShadeView;
206207 import com.android.systemui.statusbar.ExpandableNotificationRow;
208+import com.android.systemui.statusbar.ForegroundServiceLifetimeExtender;
207209 import com.android.systemui.statusbar.GestureRecorder;
208210 import com.android.systemui.statusbar.KeyboardShortcuts;
209211 import com.android.systemui.statusbar.KeyguardIndicationController;
@@ -211,6 +213,7 @@ import com.android.systemui.statusbar.NotificationData;
211213 import com.android.systemui.statusbar.NotificationData.Entry;
212214 import com.android.systemui.statusbar.NotificationGuts;
213215 import com.android.systemui.statusbar.NotificationInfo;
216+import com.android.systemui.statusbar.NotificationLifetimeExtender;
214217 import com.android.systemui.statusbar.NotificationShelf;
215218 import com.android.systemui.statusbar.NotificationSnooze;
216219 import com.android.systemui.statusbar.RemoteInputController;
@@ -751,9 +754,9 @@ public class StatusBar extends SystemUI implements DemoMode,
751754 @Nullable private View mAmbientIndicationContainer;
752755 private String mKeyToRemoveOnGutsClosed;
753756 private SysuiColorExtractor mColorExtractor;
754- private ForegroundServiceController mForegroundServiceController;
755757 private ScreenLifecycle mScreenLifecycle;
756758 @VisibleForTesting WakefulnessLifecycle mWakefulnessLifecycle;
759+ protected ForegroundServiceController mForegroundServiceController;
757760
758761 private void recycleAllVisibilityObjects(ArraySet<NotificationVisibility> array) {
759762 final int N = array.size();
@@ -807,6 +810,9 @@ public class StatusBar extends SystemUI implements DemoMode,
807810 mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
808811
809812 mForegroundServiceController = Dependency.get(ForegroundServiceController.class);
813+ mFGSExtender = new ForegroundServiceLifetimeExtender();
814+ mFGSExtender.setCallback(key -> removeNotification(key, mLatestRankingMap));
815+
810816
811817 mDisplay = mWindowManager.getDefaultDisplay();
812818 updateDisplaySize();
@@ -1808,6 +1814,11 @@ public class StatusBar extends SystemUI implements DemoMode,
18081814 }
18091815 Entry entry = mNotificationData.get(key);
18101816
1817+ if (entry != null && mFGSExtender.shouldExtendLifetime(entry)) {
1818+ extendLifetime(entry, mFGSExtender);
1819+ return;
1820+ }
1821+
18111822 if (entry != null && mRemoteInputController.isRemoteInputActive(entry)
18121823 && (entry.row != null && !entry.row.isDismissed())) {
18131824 mLatestRankingMap = ranking;
@@ -1846,9 +1857,35 @@ public class StatusBar extends SystemUI implements DemoMode,
18461857 }
18471858 }
18481859 }
1860+ // Make sure no lifetime extension is happening anymore
1861+ cancelLifetimeExtension(entry);
18491862 setAreThereNotifications();
18501863 }
18511864
1865+ /** Lifetime extension keeps entries around after they would've otherwise been canceled */
1866+ private void extendLifetime(Entry entry, NotificationLifetimeExtender extender) {
1867+ // Cancel any other extender which might be holding on to this notification entry
1868+ NotificationLifetimeExtender activeExtender = mRetainedNotifications.get(entry);
1869+ if (activeExtender != null && activeExtender != extender) {
1870+ activeExtender.setShouldManageLifetime(entry, false);
1871+ }
1872+ mRetainedNotifications.put(entry, extender);
1873+ extender.setShouldManageLifetime(entry, true);
1874+ }
1875+
1876+ /** Tells the current extender (if any) to stop extending the entry's lifetime */
1877+ private void cancelLifetimeExtension(NotificationData.Entry entry) {
1878+ NotificationLifetimeExtender activeExtender = mRetainedNotifications.remove(entry);
1879+ if (activeExtender != null) {
1880+ activeExtender.setShouldManageLifetime(entry, false);
1881+ }
1882+ }
1883+
1884+ @VisibleForTesting
1885+ public Map<Entry, NotificationLifetimeExtender> getRetainedNotificationMap() {
1886+ return mRetainedNotifications;
1887+ }
1888+
18521889 /**
18531890 * Ensures that the group children are cancelled immediately when the group summary is cancelled
18541891 * instead of waiting for the notification manager to send all cancels. Otherwise this could
@@ -3533,6 +3570,17 @@ public class StatusBar extends SystemUI implements DemoMode,
35333570 }
35343571 }
35353572
3573+ pw.println(" Lifetime-extended notifications:");
3574+ if (mRetainedNotifications.isEmpty()) {
3575+ pw.println(" None");
3576+ } else {
3577+ for (Map.Entry<NotificationData.Entry, NotificationLifetimeExtender> entry
3578+ : mRetainedNotifications.entrySet()) {
3579+ pw.println(" " + entry.getKey().notification + " retained by "
3580+ + entry.getValue().getClass().getName());
3581+ }
3582+ }
3583+
35363584 pw.print(" mInteractingWindows="); pw.println(mInteractingWindows);
35373585 pw.print(" mStatusBarWindowState=");
35383586 pw.println(windowStateToString(mStatusBarWindowState));
@@ -5703,6 +5751,11 @@ public class StatusBar extends SystemUI implements DemoMode,
57035751
57045752 protected RemoteInputController mRemoteInputController;
57055753
5754+ // A lifetime extender that watches for foreground service notifications
5755+ @VisibleForTesting protected NotificationLifetimeExtender mFGSExtender;
5756+ private final Map<Entry, NotificationLifetimeExtender> mRetainedNotifications =
5757+ new ArrayMap<>();
5758+
57065759 // for heads up notifications
57075760 protected HeadsUpManager mHeadsUpManager;
57085761
@@ -7384,6 +7437,9 @@ public class StatusBar extends SystemUI implements DemoMode,
73847437 Log.w(TAG, "Notification that was kept for guts was updated. " + key);
73857438 }
73867439
7440+ // No need to keep the lifetime extension around if an update comes in for it
7441+ cancelLifetimeExtension(entry);
7442+
73877443 Notification n = notification.getNotification();
73887444 mNotificationData.updateRanking(ranking);
73897445
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -18,6 +18,7 @@ package com.android.systemui.statusbar.policy;
1818
1919 import android.animation.Animator;
2020 import android.animation.AnimatorListenerAdapter;
21+import android.app.ActivityManager;
2122 import android.app.Notification;
2223 import android.app.PendingIntent;
2324 import android.app.RemoteInput;
@@ -27,7 +28,9 @@ import android.content.pm.ShortcutManager;
2728 import android.graphics.Rect;
2829 import android.graphics.drawable.Drawable;
2930 import android.os.Bundle;
31+import android.os.UserHandle;
3032 import android.text.Editable;
33+import android.text.InputType;
3134 import android.text.TextWatcher;
3235 import android.util.AttributeSet;
3336 import android.util.Log;
@@ -176,6 +179,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
176179 LayoutInflater.from(context).inflate(R.layout.remote_input, root, false);
177180 v.mController = controller;
178181 v.mEntry = entry;
182+ v.mEditText.setRestrictedAcrossUser(true);
179183 v.setTag(VIEW_TAG);
180184
181185 return v;
@@ -278,12 +282,22 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
278282 if (mWrapper != null) {
279283 mWrapper.setRemoteInputVisible(true);
280284 }
281- mController.addRemoteInput(mEntry, mToken);
285+
286+ // Disable suggestions on non-owner (secondary) user.
287+ // SpellCheckerService of primary user runs on secondary as well which shows
288+ // "Add to dictionary" dialog on the primary user. (See b/123232892)
289+ // Note: this doesn't affect work-profile users on P or older versions.
290+ if (UserHandle.myUserId() != ActivityManager.getCurrentUser()) {
291+ mEditText.setInputType(
292+ mEditText.getInputType() | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
293+ }
294+
282295 mEditText.setInnerFocusable(true);
283296 mEditText.mShowImeOnInputConnection = true;
284297 mEditText.setText(mEntry.remoteInputText);
285298 mEditText.setSelection(mEditText.getText().length());
286299 mEditText.requestFocus();
300+ mController.addRemoteInput(mEntry, mToken);
287301 updateSendButton();
288302 }
289303
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/ForegroundServiceLifetimeExtenderTest.java
@@ -0,0 +1,85 @@
1+/*
2+ * Copyright (C) 2019 The Android Open Source Project
3+ *
4+ * Licensed under the Apache License, Version 2.0 (the "License");
5+ * you may not use this file except in compliance with the License.
6+ * You may obtain a copy of the License at
7+ *
8+ * http://www.apache.org/licenses/LICENSE-2.0
9+ *
10+ * Unless required by applicable law or agreed to in writing, software
11+ * distributed under the License is distributed on an "AS IS" BASIS,
12+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+ * See the License for the specific language governing permissions and
14+ * limitations under the License.
15+ */
16+
17+package com.android.systemui.statusbar;
18+
19+import static com.android.systemui.statusbar.ForegroundServiceLifetimeExtender.MIN_FGS_TIME_MS;
20+
21+import static org.junit.Assert.assertFalse;
22+import static org.junit.Assert.assertTrue;
23+import static org.mockito.Mockito.mock;
24+import static org.mockito.Mockito.when;
25+
26+import android.app.Notification;
27+import android.service.notification.NotificationListenerService.Ranking;
28+import android.service.notification.StatusBarNotification;
29+import android.support.test.filters.SmallTest;
30+import android.support.test.runner.AndroidJUnit4;
31+
32+import com.android.systemui.R;
33+import com.android.systemui.SysuiTestCase;
34+import com.android.systemui.statusbar.NotificationData.Entry;
35+
36+import org.junit.Before;
37+import org.junit.Test;
38+import org.junit.runner.RunWith;
39+
40+@RunWith(AndroidJUnit4.class)
41+@SmallTest
42+public class ForegroundServiceLifetimeExtenderTest extends SysuiTestCase {
43+ private ForegroundServiceLifetimeExtender mExtender = new ForegroundServiceLifetimeExtender();
44+ private StatusBarNotification mSbn;
45+ private NotificationData.Entry mEntry;
46+ private Notification mNotif;
47+
48+ @Before
49+ public void setup() {
50+ mNotif = new Notification.Builder(mContext, "")
51+ .setSmallIcon(R.drawable.ic_person)
52+ .setContentTitle("Title")
53+ .setContentText("Text")
54+ .build();
55+ mSbn = mock(StatusBarNotification.class);
56+ when(mSbn.getNotification()).thenReturn(mNotif);
57+ mEntry = new NotificationData.Entry(mSbn);
58+ }
59+
60+ /**
61+ * ForegroundServiceLifetimeExtenderTest
62+ */
63+ @Test
64+ public void testShouldExtendLifetime_should_foreground() {
65+ // Extend the lifetime of a FGS notification iff it has not been visible
66+ // for the minimum time
67+ mNotif.flags |= Notification.FLAG_FOREGROUND_SERVICE;
68+ when(mSbn.getPostTime()).thenReturn(System.currentTimeMillis());
69+ assertTrue(mExtender.shouldExtendLifetime(mEntry));
70+ }
71+
72+ @Test
73+ public void testShouldExtendLifetime_shouldNot_foreground() {
74+ mNotif.flags |= Notification.FLAG_FOREGROUND_SERVICE;
75+ when(mSbn.getPostTime()).thenReturn(System.currentTimeMillis() - MIN_FGS_TIME_MS - 1);
76+ assertFalse(mExtender.shouldExtendLifetime(mEntry));
77+ }
78+
79+ @Test
80+ public void testShouldExtendLifetime_shouldNot_notForeground() {
81+ mNotif.flags = 0;
82+ when(mSbn.getPostTime()).thenReturn(System.currentTimeMillis() - MIN_FGS_TIME_MS - 1);
83+ assertFalse(mExtender.shouldExtendLifetime(mEntry));
84+ }
85+}
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -18,6 +18,8 @@ package com.android.systemui.statusbar.phone;
1818
1919 import static android.app.NotificationManager.IMPORTANCE_HIGH;
2020
21+import static com.android.systemui.statusbar.ForegroundServiceLifetimeExtender.MIN_FGS_TIME_MS;
22+
2123 import static junit.framework.Assert.assertFalse;
2224 import static junit.framework.Assert.assertTrue;
2325 import static junit.framework.TestCase.fail;
@@ -33,6 +35,7 @@ import static org.mockito.Mockito.times;
3335 import static org.mockito.Mockito.verify;
3436 import static org.mockito.Mockito.when;
3537
38+import android.app.ActivityManager;
3639 import android.app.Notification;
3740 import android.app.trust.TrustManager;
3841 import android.content.Context;
@@ -47,6 +50,7 @@ import android.os.PowerManager;
4750 import android.os.RemoteException;
4851 import android.os.UserHandle;
4952 import android.service.notification.StatusBarNotification;
53+import android.service.notification.NotificationListenerService.RankingMap;
5054 import android.support.test.filters.SmallTest;
5155 import android.support.test.metricshelper.MetricsAsserts;
5256 import android.testing.AndroidTestingRunner;
@@ -65,7 +69,11 @@ import com.android.internal.logging.testing.FakeMetricsLogger;
6569 import com.android.internal.statusbar.IStatusBarService;
6670 import com.android.keyguard.KeyguardHostView.OnDismissAction;
6771 import com.android.keyguard.KeyguardStatusView;
72+import com.android.systemui.ForegroundServiceController;
6873 import com.android.systemui.R;
74+import com.android.systemui.statusbar.ForegroundServiceLifetimeExtender;
75+import com.android.systemui.statusbar.NotificationLifetimeExtender;
76+import com.android.systemui.statusbar.RemoteInputController;
6977 import com.android.systemui.SysuiTestCase;
7078 import com.android.systemui.assist.AssistManager;
7179 import com.android.systemui.keyguard.WakefulnessLifecycle;
@@ -89,13 +97,20 @@ import org.junit.runner.RunWith;
8997
9098 import java.io.ByteArrayOutputStream;
9199 import java.io.PrintWriter;
100+
92101 import java.util.ArrayList;
102+import java.util.Map;
103+
104+import junit.framework.Assert;
93105
94106 @SmallTest
95107 @RunWith(AndroidTestingRunner.class)
96108 @RunWithLooper
97109 public class StatusBarTest extends SysuiTestCase {
98110
111+ private static final String TEST_PACKAGE_NAME = "test";
112+ private static final int TEST_UID = 123;
113+
99114 StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
100115 UnlockMethodCache mUnlockMethodCache;
101116 KeyguardIndicationController mKeyguardIndicationController;
@@ -108,8 +123,12 @@ public class StatusBarTest extends SysuiTestCase {
108123 SystemServicesProxy mSystemServicesProxy;
109124 NotificationPanelView mNotificationPanelView;
110125 IStatusBarService mBarService;
126+ RemoteInputController mRemoteInputController;
127+ ForegroundServiceController mForegroundServiceController;
111128 ArrayList<Entry> mNotificationList;
112129 private DisplayMetrics mDisplayMetrics = new DisplayMetrics();
130+ private ForegroundServiceLifetimeExtender mFGSExtender =
131+ new ForegroundServiceLifetimeExtender();
113132
114133 @Before
115134 public void setup() throws Exception {
@@ -135,6 +154,8 @@ public class StatusBarTest extends SysuiTestCase {
135154 when(mNotificationPanelView.getLayoutParams()).thenReturn(new LayoutParams(0, 0));
136155 mNotificationList = mock(ArrayList.class);
137156 IPowerManager powerManagerService = mock(IPowerManager.class);
157+ mRemoteInputController = mock(RemoteInputController.class);
158+ mForegroundServiceController = mock(ForegroundServiceController.class);
138159 HandlerThread handlerThread = new HandlerThread("TestThread");
139160 handlerThread.start();
140161 mPowerManager = new PowerManager(mContext, powerManagerService,
@@ -143,10 +164,11 @@ public class StatusBarTest extends SysuiTestCase {
143164 mBarService = mock(IStatusBarService.class);
144165
145166 mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger);
167+ mFGSExtender.setCallback(key -> mStatusBar.removeNotification(key, mock(RankingMap.class)));
146168 mStatusBar = new TestableStatusBar(mStatusBarKeyguardViewManager, mUnlockMethodCache,
147169 mKeyguardIndicationController, mStackScroller, mHeadsUpManager,
148170 mNotificationData, mPowerManager, mSystemServicesProxy, mNotificationPanelView,
149- mBarService);
171+ mBarService, mFGSExtender, mRemoteInputController, mForegroundServiceController);
150172 mStatusBar.mContext = mContext;
151173 mStatusBar.mComponents = mContext.getComponents();
152174 doAnswer(invocation -> {
@@ -428,6 +450,54 @@ public class StatusBarTest extends SysuiTestCase {
428450
429451
430452 @Test
453+ public void testForegroundServiceNotificationKeptForFiveSeconds() throws Exception {
454+ RankingMap rm = mock(RankingMap.class);
455+
456+ // sbn posted "just now"
457+ Notification n = new Notification.Builder(mContext, "")
458+ .setSmallIcon(R.drawable.ic_person)
459+ .setContentTitle("Title")
460+ .setContentText("Text")
461+ .build();
462+ n.flags |= Notification.FLAG_FOREGROUND_SERVICE;
463+ StatusBarNotification sbn =
464+ new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID,
465+ 0, n, new UserHandle(ActivityManager.getCurrentUser()), null,
466+ System.currentTimeMillis());
467+ NotificationData.Entry entry = new NotificationData.Entry(sbn);
468+ when(mNotificationData.get(any())).thenReturn(entry);
469+ mStatusBar.removeNotification(sbn.getKey(), rm);
470+ Map<NotificationData.Entry, NotificationLifetimeExtender> map =
471+ mStatusBar.getRetainedNotificationMap();
472+ Assert.assertTrue(map.containsKey(entry));
473+ }
474+
475+ @Test
476+ public void testForegroundServiceNotification_notRetainedIfShownForFiveSeconds()
477+ throws Exception {
478+
479+ RankingMap rm = mock(RankingMap.class);
480+
481+ // sbn posted "more than 5 seconds ago"
482+ Notification n = new Notification.Builder(mContext, "")
483+ .setSmallIcon(R.drawable.ic_person)
484+ .setContentTitle("Title")
485+ .setContentText("Text")
486+ .build();
487+ n.flags |= Notification.FLAG_FOREGROUND_SERVICE;
488+ StatusBarNotification sbn =
489+ new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID,
490+ 0, n, new UserHandle(ActivityManager.getCurrentUser()), null,
491+ System.currentTimeMillis() - MIN_FGS_TIME_MS - 1);
492+ NotificationData.Entry entry = new NotificationData.Entry(sbn);
493+ when(mNotificationData.get(any())).thenReturn(entry);
494+ mStatusBar.removeNotification(sbn.getKey(), rm);
495+ Map<NotificationData.Entry, NotificationLifetimeExtender> map =
496+ mStatusBar.getRetainedNotificationMap();
497+ Assert.assertFalse(map.containsKey(entry));
498+ }
499+
500+ @Test
431501 public void testLogHidden() {
432502 try {
433503 mStatusBar.handleVisibleToUserChanged(false);
@@ -515,7 +585,8 @@ public class StatusBarTest extends SysuiTestCase {
515585 UnlockMethodCache unlock, KeyguardIndicationController key,
516586 NotificationStackScrollLayout stack, HeadsUpManager hum, NotificationData nd,
517587 PowerManager pm, SystemServicesProxy ssp, NotificationPanelView panelView,
518- IStatusBarService barService) {
588+ IStatusBarService barService, ForegroundServiceLifetimeExtender fgsExtender,
589+ RemoteInputController ric, ForegroundServiceController fsc) {
519590 mStatusBarKeyguardViewManager = man;
520591 mUnlockMethodCache = unlock;
521592 mKeyguardIndicationController = key;
@@ -527,6 +598,10 @@ public class StatusBarTest extends SysuiTestCase {
527598 mSystemServicesProxy = ssp;
528599 mNotificationPanel = panelView;
529600 mBarService = barService;
601+ mFGSExtender = fgsExtender;
602+ mRemoteInputController = ric;
603+ mForegroundServiceController = fsc;
604+
530605 mWakefulnessLifecycle = createAwakeWakefulnessLifecycle();
531606 mScrimController = mock(ScrimController.class);
532607 }
@@ -547,4 +622,4 @@ public class StatusBarTest extends SysuiTestCase {
547622 mState = state;
548623 }
549624 }
550-}
\ No newline at end of file
625+}
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -3574,7 +3574,8 @@ public class ActivityManagerService extends IActivityManager.Stub
35743574 final int procCount = procs.size();
35753575 for (int i = 0; i < procCount; i++) {
35763576 final int procUid = procs.keyAt(i);
3577- if (UserHandle.isApp(procUid) || !UserHandle.isSameUser(procUid, uid)) {
3577+ if (UserHandle.isApp(procUid) || !UserHandle.isSameUser(procUid, uid)
3578+ || UserHandle.isIsolated(procUid)) {
35783579 // Don't use an app process or different user process for system component.
35793580 continue;
35803581 }
--- a/services/core/java/com/android/server/connectivity/PermissionMonitor.java
+++ b/services/core/java/com/android/server/connectivity/PermissionMonitor.java
@@ -21,6 +21,7 @@ import static android.Manifest.permission.CONNECTIVITY_INTERNAL;
2121 import static android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS;
2222 import static android.content.pm.ApplicationInfo.FLAG_SYSTEM;
2323 import static android.content.pm.ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
24+import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED;
2425 import static android.content.pm.PackageManager.GET_PERMISSIONS;
2526
2627 import android.content.BroadcastReceiver;
@@ -39,6 +40,8 @@ import android.os.UserManager;
3940 import android.text.TextUtils;
4041 import android.util.Log;
4142
43+import com.android.internal.util.ArrayUtils;
44+
4245 import java.util.ArrayList;
4346 import java.util.HashMap;
4447 import java.util.HashSet;
@@ -150,15 +153,13 @@ public class PermissionMonitor {
150153 update(mUsers, mApps, true);
151154 }
152155
153- private boolean hasPermission(PackageInfo app, String permission) {
154- if (app.requestedPermissions != null) {
155- for (String p : app.requestedPermissions) {
156- if (permission.equals(p)) {
157- return true;
158- }
159- }
156+ private boolean hasPermission(final PackageInfo app, final String permission) {
157+ if (app.requestedPermissions == null || app.requestedPermissionsFlags == null) {
158+ return false;
160159 }
161- return false;
160+ final int index = ArrayUtils.indexOf(app.requestedPermissions, permission);
161+ if (index < 0 || index >= app.requestedPermissionsFlags.length) return false;
162+ return (app.requestedPermissionsFlags[index] & REQUESTED_PERMISSION_GRANTED) != 0;
162163 }
163164
164165 private boolean hasNetworkPermission(PackageInfo app) {
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -4746,8 +4746,15 @@ public class NotificationManagerService extends SystemService {
47464746 userId, mustHaveFlags, mustNotHaveFlags, reason, listenerName);
47474747
47484748 synchronized (mNotificationLock) {
4749- // Look for the notification, searching both the posted and enqueued lists.
4750- NotificationRecord r = findNotificationLocked(pkg, tag, id, userId);
4749+ // If the notification is currently enqueued, repost this runnable so it has a
4750+ // chance to notify listeners
4751+ if ((findNotificationByListLocked(
4752+ mEnqueuedNotifications, pkg, tag, id, userId)) != null) {
4753+ mHandler.post(this);
4754+ }
4755+ // Look for the notification in the posted list, since we already checked enq
4756+ NotificationRecord r = findNotificationByListLocked(
4757+ mNotificationList, pkg, tag, id, userId);
47514758 if (r != null) {
47524759 // The notification was found, check if it should be removed.
47534760
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -440,6 +440,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub {
440440
441441 params.installFlags &= ~PackageManager.INSTALL_FROM_ADB;
442442 params.installFlags &= ~PackageManager.INSTALL_ALL_USERS;
443+ params.installFlags &= ~PackageManager.INSTALL_ALLOW_TEST;
443444 params.installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
444445 if ((params.installFlags & PackageManager.INSTALL_VIRTUAL_PRELOAD) != 0
445446 && !mPm.isCallerVerifier(callingUid)) {
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -19557,7 +19557,7 @@ public class PackageManagerService extends IPackageManager.Stub
1955719557 continue;
1955819558 }
1955919559 List<VersionedPackage> libClientPackages = getPackagesUsingSharedLibraryLPr(
19560- libEntry.info, 0, currUserId);
19560+ libEntry.info, MATCH_KNOWN_PACKAGES, currUserId);
1956119561 if (!ArrayUtils.isEmpty(libClientPackages)) {
1956219562 Slog.w(TAG, "Not removing package " + pkg.manifestPackageName
1956319563 + " hosting lib " + libEntry.info.getName() + " version "
@@ -19890,7 +19890,8 @@ public class PackageManagerService extends IPackageManager.Stub
1989019890 * Tries to delete system package.
1989119891 */
1989219892 private boolean deleteSystemPackageLIF(PackageParser.Package deletedPkg,
19893- PackageSetting deletedPs, int[] allUserHandles, int flags, PackageRemovedInfo outInfo,
19893+ PackageSetting deletedPs, int[] allUserHandles, int flags,
19894+ @Nullable PackageRemovedInfo outInfo,
1989419895 boolean writeSettings) {
1989519896 if (deletedPs.parentPackageName != null) {
1989619897 Slog.w(TAG, "Attempt to delete child system package " + deletedPkg.packageName);
@@ -19898,7 +19899,7 @@ public class PackageManagerService extends IPackageManager.Stub
1989819899 }
1989919900
1990019901 final boolean applyUserRestrictions
19901- = (allUserHandles != null) && (outInfo.origUsers != null);
19902+ = (allUserHandles != null) && outInfo != null && (outInfo.origUsers != null);
1990219903 final PackageSetting disabledPs;
1990319904 // Confirm if the system package has been updated
1990419905 // An updated system app can be deleted. This will also have to restore
@@ -19928,19 +19929,21 @@ public class PackageManagerService extends IPackageManager.Stub
1992819929 }
1992919930 }
1993019931
19931- // Delete the updated package
19932- outInfo.isRemovedPackageSystemUpdate = true;
19933- if (outInfo.removedChildPackages != null) {
19934- final int childCount = (deletedPs.childPackageNames != null)
19935- ? deletedPs.childPackageNames.size() : 0;
19936- for (int i = 0; i < childCount; i++) {
19937- String childPackageName = deletedPs.childPackageNames.get(i);
19938- if (disabledPs.childPackageNames != null && disabledPs.childPackageNames
19939- .contains(childPackageName)) {
19940- PackageRemovedInfo childInfo = outInfo.removedChildPackages.get(
19941- childPackageName);
19942- if (childInfo != null) {
19943- childInfo.isRemovedPackageSystemUpdate = true;
19932+ if (outInfo != null) {
19933+ // Delete the updated package
19934+ outInfo.isRemovedPackageSystemUpdate = true;
19935+ if (outInfo.removedChildPackages != null) {
19936+ final int childCount = (deletedPs.childPackageNames != null)
19937+ ? deletedPs.childPackageNames.size() : 0;
19938+ for (int i = 0; i < childCount; i++) {
19939+ String childPackageName = deletedPs.childPackageNames.get(i);
19940+ if (disabledPs.childPackageNames != null && disabledPs.childPackageNames
19941+ .contains(childPackageName)) {
19942+ PackageRemovedInfo childInfo = outInfo.removedChildPackages.get(
19943+ childPackageName);
19944+ if (childInfo != null) {
19945+ childInfo.isRemovedPackageSystemUpdate = true;
19946+ }
1994419947 }
1994519948 }
1994619949 }
@@ -24203,9 +24206,9 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName());
2420324206 mSettings.writeKernelMappingLPr(ps);
2420424207 }
2420524208
24206- final UserManager um = mContext.getSystemService(UserManager.class);
24209+ final UserManagerService um = sUserManager;
2420724210 UserManagerInternal umInternal = getUserManagerInternal();
24208- for (UserInfo user : um.getUsers()) {
24211+ for (UserInfo user : um.getUsers(false /* excludeDying */)) {
2420924212 final int flags;
2421024213 if (umInternal.isUserUnlockingOrUnlocked(user.id)) {
2421124214 flags = StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE;
@@ -24845,8 +24848,9 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName());
2484524848 continue;
2484624849 }
2484724850 final String packageName = ps.pkg.packageName;
24848- // Skip over if system app
24849- if ((ps.pkgFlags & ApplicationInfo.FLAG_SYSTEM) != 0) {
24851+ // Skip over if system app or static shared library
24852+ if ((ps.pkgFlags & ApplicationInfo.FLAG_SYSTEM) != 0
24853+ || !TextUtils.isEmpty(ps.pkg.staticSharedLibName)) {
2485024854 continue;
2485124855 }
2485224856 if (DEBUG_CLEAN_APKS) {
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -2618,6 +2618,8 @@ public class PhoneWindowManager implements WindowManagerPolicy {
26182618 attrs.hideTimeoutMilliseconds = TOAST_WINDOW_TIMEOUT;
26192619 }
26202620 attrs.windowAnimations = com.android.internal.R.style.Animation_Toast;
2621+ // Toasts can't be clickable
2622+ attrs.flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
26212623 break;
26222624 }
26232625
--- /dev/null
+++ b/services/core/java/com/android/server/wallpaper/GLHelper.java
@@ -0,0 +1,148 @@
1+/*
2+ * Copyright (C) 2019 The Android Open Source Project
3+ *
4+ * Licensed under the Apache License, Version 2.0 (the "License");
5+ * you may not use this file except in compliance with the License.
6+ * You may obtain a copy of the License at
7+ *
8+ * http://www.apache.org/licenses/LICENSE-2.0
9+ *
10+ * Unless required by applicable law or agreed to in writing, software
11+ * distributed under the License is distributed on an "AS IS" BASIS,
12+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+ * See the License for the specific language governing permissions and
14+ * limitations under the License.
15+ */
16+
17+package com.android.server.wallpaper;
18+
19+import static android.opengl.EGL14.EGL_ALPHA_SIZE;
20+import static android.opengl.EGL14.EGL_BLUE_SIZE;
21+import static android.opengl.EGL14.EGL_CONFIG_CAVEAT;
22+import static android.opengl.EGL14.EGL_CONTEXT_CLIENT_VERSION;
23+import static android.opengl.EGL14.EGL_DEFAULT_DISPLAY;
24+import static android.opengl.EGL14.EGL_DEPTH_SIZE;
25+import static android.opengl.EGL14.EGL_GREEN_SIZE;
26+import static android.opengl.EGL14.EGL_HEIGHT;
27+import static android.opengl.EGL14.EGL_NONE;
28+import static android.opengl.EGL14.EGL_NO_CONTEXT;
29+import static android.opengl.EGL14.EGL_NO_DISPLAY;
30+import static android.opengl.EGL14.EGL_NO_SURFACE;
31+import static android.opengl.EGL14.EGL_OPENGL_ES2_BIT;
32+import static android.opengl.EGL14.EGL_RED_SIZE;
33+import static android.opengl.EGL14.EGL_RENDERABLE_TYPE;
34+import static android.opengl.EGL14.EGL_STENCIL_SIZE;
35+import static android.opengl.EGL14.EGL_WIDTH;
36+import static android.opengl.EGL14.eglChooseConfig;
37+import static android.opengl.EGL14.eglCreateContext;
38+import static android.opengl.EGL14.eglCreatePbufferSurface;
39+import static android.opengl.EGL14.eglDestroyContext;
40+import static android.opengl.EGL14.eglDestroySurface;
41+import static android.opengl.EGL14.eglGetDisplay;
42+import static android.opengl.EGL14.eglGetError;
43+import static android.opengl.EGL14.eglInitialize;
44+import static android.opengl.EGL14.eglMakeCurrent;
45+import static android.opengl.EGL14.eglTerminate;
46+import static android.opengl.GLES20.GL_MAX_TEXTURE_SIZE;
47+import static android.opengl.GLES20.glGetIntegerv;
48+
49+import android.opengl.EGLConfig;
50+import android.opengl.EGLContext;
51+import android.opengl.EGLDisplay;
52+import android.opengl.EGLSurface;
53+import android.opengl.GLUtils;
54+import android.os.SystemProperties;
55+import android.util.Log;
56+
57+class GLHelper {
58+ private static final String TAG = GLHelper.class.getSimpleName();
59+ private static final int sMaxTextureSize;
60+
61+ static {
62+ int maxTextureSize = SystemProperties.getInt("sys.max_texture_size", 0);
63+ sMaxTextureSize = maxTextureSize > 0 ? maxTextureSize : retrieveTextureSizeFromGL();
64+ }
65+
66+ private static int retrieveTextureSizeFromGL() {
67+ try {
68+ String err;
69+
70+ // Before we can retrieve info from GL,
71+ // we have to create EGLContext, EGLConfig and EGLDisplay first.
72+ // We will fail at querying info from GL once one of above failed.
73+ // When this happens, we will use defValue instead.
74+ EGLDisplay eglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
75+ if (eglDisplay == null || eglDisplay == EGL_NO_DISPLAY) {
76+ err = "eglGetDisplay failed: " + GLUtils.getEGLErrorString(eglGetError());
77+ throw new RuntimeException(err);
78+ }
79+
80+ if (!eglInitialize(eglDisplay, null, 0 /* majorOffset */, null, 1 /* minorOffset */)) {
81+ err = "eglInitialize failed: " + GLUtils.getEGLErrorString(eglGetError());
82+ throw new RuntimeException(err);
83+ }
84+
85+ EGLConfig eglConfig = null;
86+ int[] configsCount = new int[1];
87+ EGLConfig[] configs = new EGLConfig[1];
88+ int[] configSpec = new int[] {
89+ EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
90+ EGL_RED_SIZE, 8,
91+ EGL_GREEN_SIZE, 8,
92+ EGL_BLUE_SIZE, 8,
93+ EGL_ALPHA_SIZE, 0,
94+ EGL_DEPTH_SIZE, 0,
95+ EGL_STENCIL_SIZE, 0,
96+ EGL_CONFIG_CAVEAT, EGL_NONE,
97+ EGL_NONE
98+ };
99+
100+ if (!eglChooseConfig(eglDisplay, configSpec, 0 /* attrib_listOffset */,
101+ configs, 0 /* configOffset */, 1 /* config_size */,
102+ configsCount, 0 /* num_configOffset */)) {
103+ err = "eglChooseConfig failed: " + GLUtils.getEGLErrorString(eglGetError());
104+ throw new RuntimeException(err);
105+ } else if (configsCount[0] > 0) {
106+ eglConfig = configs[0];
107+ }
108+
109+ if (eglConfig == null) {
110+ throw new RuntimeException("eglConfig not initialized!");
111+ }
112+
113+ int[] attr_list = new int[] {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE};
114+ EGLContext eglContext = eglCreateContext(
115+ eglDisplay, eglConfig, EGL_NO_CONTEXT, attr_list, 0 /* offset */);
116+
117+ if (eglContext == null || eglContext == EGL_NO_CONTEXT) {
118+ err = "eglCreateContext failed: " + GLUtils.getEGLErrorString(eglGetError());
119+ throw new RuntimeException(err);
120+ }
121+
122+ // We create a push buffer temporarily for querying info from GL.
123+ int[] attrs = {EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE};
124+ EGLSurface eglSurface =
125+ eglCreatePbufferSurface(eglDisplay, eglConfig, attrs, 0 /* offset */);
126+ eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext);
127+
128+ // Now, we are ready to query the info from GL.
129+ int[] maxSize = new int[1];
130+ glGetIntegerv(GL_MAX_TEXTURE_SIZE, maxSize, 0 /* offset */);
131+
132+ // We have got the info we want, release all egl resources.
133+ eglMakeCurrent(eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
134+ eglDestroySurface(eglDisplay, eglSurface);
135+ eglDestroyContext(eglDisplay, eglContext);
136+ eglTerminate(eglDisplay);
137+ return maxSize[0];
138+ } catch (RuntimeException e) {
139+ Log.w(TAG, "Retrieve from GL failed", e);
140+ return Integer.MAX_VALUE;
141+ }
142+ }
143+
144+ static int getMaxTextureSize() {
145+ return sMaxTextureSize;
146+ }
147+}
148+
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -125,6 +125,9 @@ public class WallpaperManagerService extends IWallpaperManager.Stub {
125125 static final boolean DEBUG = false;
126126 static final boolean DEBUG_LIVE = DEBUG || true;
127127
128+ // This 100MB limitation is defined in DisplayListCanvas.
129+ private static final int MAX_BITMAP_SIZE = 100 * 1024 * 1024;
130+
128131 public static class Lifecycle extends SystemService {
129132 private WallpaperManagerService mService;
130133
@@ -522,7 +525,10 @@ public class WallpaperManagerService extends IWallpaperManager.Stub {
522525 }
523526
524527 // scale if the crop height winds up not matching the recommended metrics
525- needScale = (wallpaper.height != cropHint.height());
528+ // also take care of invalid dimensions.
529+ needScale = wallpaper.height != cropHint.height()
530+ || cropHint.height() > GLHelper.getMaxTextureSize()
531+ || cropHint.width() > GLHelper.getMaxTextureSize();
526532
527533 if (DEBUG) {
528534 Slog.v(TAG, "crop: w=" + cropHint.width() + " h=" + cropHint.height());
@@ -534,14 +540,29 @@ public class WallpaperManagerService extends IWallpaperManager.Stub {
534540 if (!needCrop && !needScale) {
535541 // Simple case: the nominal crop fits what we want, so we take
536542 // the whole thing and just copy the image file directly.
537- if (DEBUG) {
538- Slog.v(TAG, "Null crop of new wallpaper; copying");
543+
544+ // TODO: It is not accurate to estimate bitmap size without decoding it,
545+ // may be we can try to remove this optimized way in the future,
546+ // that means, we will always go into the 'else' block.
547+
548+ // This is just a quick estimation, may be smaller than it is.
549+ long estimateSize = options.outWidth * options.outHeight * 4;
550+
551+ // A bitmap over than MAX_BITMAP_SIZE will make drawBitmap() fail.
552+ // Please see: DisplayListCanvas#throwIfCannotDraw.
553+ if (estimateSize < MAX_BITMAP_SIZE) {
554+ success = FileUtils.copyFile(wallpaper.wallpaperFile, wallpaper.cropFile);
539555 }
540- success = FileUtils.copyFile(wallpaper.wallpaperFile, wallpaper.cropFile);
556+
541557 if (!success) {
542558 wallpaper.cropFile.delete();
543559 // TODO: fall back to default wallpaper in this case
544560 }
561+
562+ if (DEBUG) {
563+ Slog.v(TAG, "Null crop of new wallpaper, estimate size=" + estimateSize
564+ + ", success=" + success);
565+ }
545566 } else {
546567 // Fancy case: crop and scale. First, we decode and scale down if appropriate.
547568 FileOutputStream f = null;
@@ -555,48 +576,78 @@ public class WallpaperManagerService extends IWallpaperManager.Stub {
555576 // We calculate the largest power-of-two under the actual ratio rather than
556577 // just let the decode take care of it because we also want to remap where the
557578 // cropHint rectangle lies in the decoded [super]rect.
558- final BitmapFactory.Options scaler;
559579 final int actualScale = cropHint.height() / wallpaper.height;
560580 int scale = 1;
561- while (2*scale < actualScale) {
581+ while (2*scale <= actualScale) {
562582 scale *= 2;
563583 }
564- if (scale > 1) {
565- scaler = new BitmapFactory.Options();
566- scaler.inSampleSize = scale;
584+ options.inSampleSize = scale;
585+ options.inJustDecodeBounds = false;
586+
587+ final Rect estimateCrop = new Rect(cropHint);
588+ estimateCrop.scale(1f / options.inSampleSize);
589+ final float hRatio = (float) wallpaper.height / estimateCrop.height();
590+ final int destHeight = (int) (estimateCrop.height() * hRatio);
591+ final int destWidth = (int) (estimateCrop.width() * hRatio);
592+
593+ // We estimated an invalid crop, try to adjust the cropHint to get a valid one.
594+ if (destWidth > GLHelper.getMaxTextureSize()) {
595+ int newHeight = (int) (wallpaper.height / hRatio);
596+ int newWidth = (int) (wallpaper.width / hRatio);
597+
567598 if (DEBUG) {
568- Slog.v(TAG, "Downsampling cropped rect with scale " + scale);
599+ Slog.v(TAG, "Invalid crop dimensions, trying to adjust.");
569600 }
570- } else {
571- scaler = null;
601+
602+ estimateCrop.set(cropHint);
603+ estimateCrop.left += (cropHint.width() - newWidth) / 2;
604+ estimateCrop.top += (cropHint.height() - newHeight) / 2;
605+ estimateCrop.right = estimateCrop.left + newWidth;
606+ estimateCrop.bottom = estimateCrop.top + newHeight;
607+ cropHint.set(estimateCrop);
608+ estimateCrop.scale(1f / options.inSampleSize);
609+ }
610+
611+ // We've got the safe cropHint; now we want to scale it properly to
612+ // the desired rectangle.
613+ // That's a height-biased operation: make it fit the hinted height.
614+ final int safeHeight = (int) (estimateCrop.height() * hRatio);
615+ final int safeWidth = (int) (estimateCrop.width() * hRatio);
616+
617+ if (DEBUG) {
618+ Slog.v(TAG, "Decode parameters:");
619+ Slog.v(TAG, " cropHint=" + cropHint + ", estimateCrop=" + estimateCrop);
620+ Slog.v(TAG, " down sampling=" + options.inSampleSize
621+ + ", hRatio=" + hRatio);
622+ Slog.v(TAG, " dest=" + destWidth + "x" + destHeight);
623+ Slog.v(TAG, " safe=" + safeWidth + "x" + safeHeight);
624+ Slog.v(TAG, " maxTextureSize=" + GLHelper.getMaxTextureSize());
572625 }
573- Bitmap cropped = decoder.decodeRegion(cropHint, scaler);
626+
627+ Bitmap cropped = decoder.decodeRegion(cropHint, options);
574628 decoder.recycle();
575629
576630 if (cropped == null) {
577631 Slog.e(TAG, "Could not decode new wallpaper");
578632 } else {
579- // We've got the extracted crop; now we want to scale it properly to
580- // the desired rectangle. That's a height-biased operation: make it
581- // fit the hinted height, and accept whatever width we end up with.
582- cropHint.offsetTo(0, 0);
583- cropHint.right /= scale; // adjust by downsampling factor
584- cropHint.bottom /= scale;
585- final float heightR = ((float)wallpaper.height) / ((float)cropHint.height());
586- if (DEBUG) {
587- Slog.v(TAG, "scale " + heightR + ", extracting " + cropHint);
588- }
589- final int destWidth = (int)(cropHint.width() * heightR);
633+ // We are safe to create final crop with safe dimensions now.
590634 final Bitmap finalCrop = Bitmap.createScaledBitmap(cropped,
591- destWidth, wallpaper.height, true);
635+ safeWidth, safeHeight, true);
592636 if (DEBUG) {
593637 Slog.v(TAG, "Final extract:");
594638 Slog.v(TAG, " dims: w=" + wallpaper.width
595639 + " h=" + wallpaper.height);
596- Slog.v(TAG, " out: w=" + finalCrop.getWidth()
640+ Slog.v(TAG, " out: w=" + finalCrop.getWidth()
597641 + " h=" + finalCrop.getHeight());
598642 }
599643
644+ // A bitmap over than MAX_BITMAP_SIZE will make drawBitmap() fail.
645+ // Please see: DisplayListCanvas#throwIfCannotDraw.
646+ if (finalCrop.getByteCount() > MAX_BITMAP_SIZE) {
647+ throw new RuntimeException(
648+ "Too large bitmap, limit=" + MAX_BITMAP_SIZE);
649+ }
650+
600651 f = new FileOutputStream(wallpaper.cropFile);
601652 bos = new BufferedOutputStream(f, 32*1024);
602653 finalCrop.compress(Bitmap.CompressFormat.JPEG, 100, bos);
@@ -1509,6 +1560,11 @@ public class WallpaperManagerService extends IWallpaperManager.Stub {
15091560 if (!isWallpaperSupported(callingPackage)) {
15101561 return;
15111562 }
1563+
1564+ // Make sure both width and height are not larger than max texture size.
1565+ width = Math.min(width, GLHelper.getMaxTextureSize());
1566+ height = Math.min(height, GLHelper.getMaxTextureSize());
1567+
15121568 synchronized (mLock) {
15131569 int userId = UserHandle.getCallingUserId();
15141570 WallpaperData wallpaper = getWallpaperSafeLocked(userId, FLAG_SYSTEM);
--- a/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -546,6 +546,21 @@ public class NotificationManagerServiceTest extends NotificationTestCase {
546546 assertEquals(0, mNotificationManagerService.getNotificationRecordCount());
547547 }
548548
549+
550+ @Test
551+ public void testCancelImmediatelyAfterEnqueueNotifiesListeners_ForegroundServiceFlag()
552+ throws Exception {
553+ final StatusBarNotification sbn = generateNotificationRecord(null).sbn;
554+ sbn.getNotification().flags =
555+ Notification.FLAG_ONGOING_EVENT | Notification.FLAG_FOREGROUND_SERVICE;
556+ mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag",
557+ sbn.getId(), sbn.getNotification(), sbn.getUserId());
558+ mBinderService.cancelNotificationWithTag(PKG, "tag", sbn.getId(), sbn.getUserId());
559+ waitForIdle();
560+ verify(mListeners, times(1)).notifyPostedLocked(any(), any());
561+ verify(mListeners, times(1)).notifyRemovedLocked(any(), anyInt());
562+ }
563+
549564 @Test
550565 public void testCancelNotificationWhilePostedAndEnqueued() throws Exception {
551566 mBinderService.enqueueNotificationWithTag(PKG, "opPkg", "tag", 0,