Merge "Make public pointer icon API with custom icons."
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index f3539ff..3962abd 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -4399,10 +4399,14 @@
// onConfigurationChanged
int diff = activity.mCurrentConfig.diff(config);
if (diff != 0) {
- // If this activity doesn't handle any of the config changes
- // then don't bother calling onConfigurationChanged as we're
- // going to destroy it.
- if ((~activity.mActivityInfo.getRealConfigChanged() & diff) == 0) {
+ // If this activity doesn't handle any of the config changes then don't bother
+ // calling onConfigurationChanged as we're going to destroy it.
+ // Except in the case where the configuration changed on the activity manager side,
+ // but wasn't big enough to cause a resource change so the activity wasn't destroyed.
+ // In this case we still want to change the configuration of the activity but not
+ // report it to the app.
+ if ((~activity.mActivityInfo.getRealConfigChanged() & diff) == 0
+ || !reportToActivity) {
shouldChangeConfig = true;
}
}
diff --git a/core/java/android/text/Hyphenator.java b/core/java/android/text/Hyphenator.java
index 5d9d929..568b267 100644
--- a/core/java/android/text/Hyphenator.java
+++ b/core/java/android/text/Hyphenator.java
@@ -72,10 +72,20 @@
return result;
}
- // TODO: Convert this a proper locale-fallback system
+ // If there's a variant, fall back to language+variant only, if available
+ final String variant = locale.getVariant();
+ if (!variant.isEmpty()) {
+ final Locale languageAndVariantOnlyLocale =
+ new Locale(locale.getLanguage(), "", variant);
+ result = sMap.get(languageAndVariantOnlyLocale);
+ if (result != null) {
+ sMap.put(locale, result);
+ return result;
+ }
+ }
// Fall back to language-only, if available
- Locale languageOnlyLocale = new Locale(locale.getLanguage());
+ final Locale languageOnlyLocale = new Locale(locale.getLanguage());
result = sMap.get(languageOnlyLocale);
if (result != null) {
sMap.put(locale, result);
@@ -83,9 +93,9 @@
}
// Fall back to script-only, if available
- String script = locale.getScript();
+ final String script = locale.getScript();
if (!script.equals("")) {
- Locale scriptOnlyLocale = new Locale.Builder()
+ final Locale scriptOnlyLocale = new Locale.Builder()
.setLanguage("und")
.setScript(script)
.build();
@@ -142,6 +152,11 @@
{"en-UM", "en-US"}, // English (United States Minor Outlying Islands)
{"en-VI", "en-US"}, // English (Virgin Islands)
+ // For German, we're assuming the 1996 (and later) orthography by default.
+ {"de", "de-1996"},
+ // Liechtenstein uses the Swiss hyphenation rules for the 1901 orthography.
+ {"de-LI-1901", "de-CH-1901"},
+
// Norwegian is very probably Norwegian Bokmål.
{"no", "nb"},
@@ -166,7 +181,18 @@
sMap.put(null, null);
// TODO: replace this with a discovery-based method that looks into /system/usr/hyphen-data
- String[] availableLanguages = {"en-US", "es", "eu", "hu", "hy", "nb", "nn", "und-Ethi"};
+ String[] availableLanguages = {
+ "de-1901", "de-1996", "de-CH-1901",
+ "en-US",
+ "es",
+ "eu",
+ "hu",
+ "hy",
+ "nb",
+ "nn",
+ "pt",
+ "und-Ethi"
+ };
for (int i = 0; i < availableLanguages.length; i++) {
String languageTag = availableLanguages[i];
Hyphenator h = loadHyphenator(languageTag);
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index d6bc27c..2e884cc 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -1171,6 +1171,16 @@
*/
public static final int PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY = 0x00004000;
+ /**
+ * Flag to indicate that this window is not expected to be replaced across
+ * configuration change triggered activity relaunches. In general the WindowManager
+ * expects Windows to be replaced after relaunch, and thus it will preserve their surfaces
+ * until the replacement is ready to show in order to prevent visual glitch. However
+ * some windows, such as PopupWindows expect to be cleared across configuration change,
+ * and thus should hint to the WindowManager that it should not wait for a replacement.
+ * @hide
+ */
+ public static final int PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH = 0x00008000;
/**
* Control flags that are private to the platform.
diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java
index 7b9de79..f4c343a 100644
--- a/core/java/android/widget/PopupWindow.java
+++ b/core/java/android/widget/PopupWindow.java
@@ -16,6 +16,8 @@
package android.widget;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH;
+
import com.android.internal.R;
import android.annotation.NonNull;
@@ -1311,6 +1313,8 @@
p.width = mLastWidth = mWidth;
}
+ p.privateFlags = PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH;
+
// Used for debugging.
p.setTitle("PopupWindow:" + Integer.toHexString(hashCode()));
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 6c223c3..e38d82f 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -64,6 +64,11 @@
private static final boolean DEBUG = false;
/**
+ * The key to identify when the lock pattern enabled flag is being acccessed for legacy reasons.
+ */
+ public static final String LEGACY_LOCK_PATTERN_ENABLED = "legacy_lock_pattern_enabled";
+
+ /**
* The number of incorrect attempts before which we fall back on an alternative
* method of verifying the user, and resetting their lock pattern.
*/
@@ -1014,6 +1019,19 @@
return isLockPatternEnabled(getKeyguardStoredPasswordQuality(userId), userId);
}
+ @Deprecated
+ public boolean isLegacyLockPatternEnabled(int userId) {
+ // Note: this value should default to {@code true} to avoid any reset that might result.
+ // We must use a special key to read this value, since it will by default return the value
+ // based on the new logic.
+ return getBoolean(LEGACY_LOCK_PATTERN_ENABLED, true, userId);
+ }
+
+ @Deprecated
+ public void setLegacyLockPatternEnabled(int userId) {
+ setBoolean(Settings.Secure.LOCK_PATTERN_ENABLED, true, userId);
+ }
+
private boolean isLockPatternEnabled(int mode, int userId) {
return mode == DevicePolicyManager.PASSWORD_QUALITY_SOMETHING
&& savedPatternExists(userId);
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 4a94741..de7f6ed 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -369,6 +369,22 @@
<protected-broadcast android:name="ScheduleConditionProvider.EVALUATE" />
<protected-broadcast android:name="wifi_scan_available" />
+ <protected-broadcast android:name="action.cne.started" />
+ <protected-broadcast android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+ <protected-broadcast android:name="android.content.jobscheduler.JOB_DEADLINE_EXPIRED" />
+ <protected-broadcast android:name="android.intent.action.ACTION_UNSOL_RESPONSE_OEM_HOOK_RAW" />
+ <protected-broadcast android:name="android.location.HIGH_POWER_REQUEST_CHANGE" />
+ <protected-broadcast android:name="android.location.HIGH_POWER_REQUEST_CHANGE" />
+ <protected-broadcast android:name="android.net.conn.CONNECTIVITY_CHANGE_SUPL" />
+ <protected-broadcast android:name="android.os.action.ACTION_EFFECTS_SUPPRESSOR_CHANGED" />
+ <protected-broadcast android:name="android.os.action.LIGHT_DEVICE_IDLE_MODE_CHANGED" />
+ <protected-broadcast android:name="android.os.storage.action.VOLUME_STATE_CHANGED" />
+ <protected-broadcast android:name="com.android.server.action.UPDATE_TWILIGHT_STATE" />
+ <protected-broadcast android:name="com.android.server.device_idle.STEP_IDLE_STATE" />
+ <protected-broadcast android:name="com.android.server.device_idle.STEP_LIGHT_IDLE_STATE" />
+ <protected-broadcast android:name="com.android.server.Wifi.action.TOGGLE_PNO" />
+ <protected-broadcast android:name="intent.action.ACTION_RF_BAND_INFO" />
+
<!-- ====================================================================== -->
<!-- RUNTIME PERMISSIONS -->
<!-- ====================================================================== -->
diff --git a/packages/DocumentsUI/src/com/android/documentsui/CopyService.java b/packages/DocumentsUI/src/com/android/documentsui/CopyService.java
index b99c806..44ff079 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/CopyService.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/CopyService.java
@@ -170,8 +170,27 @@
setupCopyJob(srcs, stack, transferMode);
+ final String opDesc = transferMode == TRANSFER_MODE_COPY ? "copy" : "move";
+ DocumentInfo srcInfo;
+ DocumentInfo dstInfo;
for (int i = 0; i < srcs.size() && !mIsCancelled; ++i) {
- copy(srcs.get(i), stack.peek(), transferMode);
+ srcInfo = srcs.get(i);
+ dstInfo = stack.peek();
+
+ // Guard unsupported recursive operation.
+ if (dstInfo.equals(srcInfo) || isDescendentOf(srcInfo, dstInfo)) {
+ if (DEBUG) Log.d(TAG,
+ "Skipping recursive " + opDesc + " of directory " + dstInfo.derivedUri);
+ mFailedFiles.add(srcInfo);
+ continue;
+ }
+
+ if (DEBUG) Log.d(TAG,
+ "Performing " + opDesc + " of " + srcInfo.displayName
+ + " (" + srcInfo.derivedUri + ")" + " to " + dstInfo.displayName
+ + " (" + dstInfo.derivedUri + ")");
+
+ copy(srcInfo, dstInfo, transferMode);
}
} catch (Exception e) {
// Catch-all to prevent any copy errors from wedging the app.
@@ -447,22 +466,6 @@
*/
private void copy(DocumentInfo srcInfo, DocumentInfo dstDirInfo, int mode)
throws RemoteException {
-
- String opDesc = mode == TRANSFER_MODE_COPY ? "copy" : "move";
-
- // Guard unsupported recursive operation.
- if (dstDirInfo.equals(srcInfo) || isDescendentOf(srcInfo, dstDirInfo)) {
- if (DEBUG) Log.d(TAG,
- "Skipping recursive " + opDesc + " of directory " + dstDirInfo.derivedUri);
- mFailedFiles.add(srcInfo);
- return;
- }
-
- if (DEBUG) Log.d(TAG,
- "Performing " + opDesc + " of " + srcInfo.displayName
- + " (" + srcInfo.derivedUri + ")" + " to " + dstDirInfo.displayName
- + " (" + dstDirInfo.derivedUri + ")");
-
// When copying within the same provider, try to use optimized copying and moving.
// If not supported, then fallback to byte-by-byte copy/move.
if (srcInfo.authority.equals(dstDirInfo.authority)) {
@@ -490,6 +493,8 @@
}
}
+ // Create the target document (either a file or a directory), then copy recursively the
+ // contents (bytes or children).
final Uri dstUri = DocumentsContract.createDocument(mDstClient, dstDirInfo.derivedUri,
srcInfo.mimeType, srcInfo.displayName);
if (dstUri == null) {
@@ -498,10 +503,30 @@
return;
}
- if (srcInfo.isDirectory()) {
- copyDirectoryHelper(srcInfo.derivedUri, dstUri, mode);
+ DocumentInfo dstInfo = null;
+ try {
+ final DocumentInfo dstDocInfo = DocumentInfo.fromUri(getContentResolver(), dstUri);
+ } catch (FileNotFoundException e) {
+ mFailedFiles.add(srcInfo);
+ return;
+ }
+
+ if (Document.MIME_TYPE_DIR.equals(srcInfo.mimeType)) {
+ copyDirectoryHelper(srcInfo, dstInfo, mode);
} else {
- copyFileHelper(srcInfo.derivedUri, dstUri, mode);
+ copyFileHelper(srcInfo, dstInfo, mode);
+ }
+
+ if (mode == TRANSFER_MODE_MOVE) {
+ try {
+ DocumentsContract.deleteDocument(mSrcClient, srcInfo.derivedUri);
+ } catch (RemoteException e) {
+ // RemoteExceptions usually signal that the connection is dead, so there's no
+ // point attempting to continue. Propagate the exception up so the copy job is
+ // cancelled.
+ Log.w(TAG, "Failed to clean up after move: " + srcInfo.derivedUri, e);
+ throw e;
+ }
}
}
@@ -520,48 +545,29 @@
* Handles recursion into a directory and copying its contents. Note that in linux terms, this
* does the equivalent of "cp src/* dst", not "cp -r src dst".
*
- * @param srcDirUri URI of the directory to copy from. The routine will copy the directory's
+ * @param srcDirInfo Info of the directory to copy from. The routine will copy the directory's
* contents, not the directory itself.
- * @param dstDirUri URI of the directory to copy to. Must be created beforehand.
+ * @param dstDirInfo Info of the directory to copy to. Must be created beforehand.
* @throws RemoteException
*/
- private void copyDirectoryHelper(Uri srcDirUri, Uri dstDirUri, int mode)
+ private void copyDirectoryHelper(DocumentInfo srcDirInfo, DocumentInfo dstDirInfo, int mode)
throws RemoteException {
// Recurse into directories. Copy children into the new subdirectory.
final String queryColumns[] = new String[] {
Document.COLUMN_DISPLAY_NAME,
Document.COLUMN_DOCUMENT_ID,
Document.COLUMN_MIME_TYPE,
- Document.COLUMN_SIZE
+ Document.COLUMN_SIZE,
+ Document.COLUMN_FLAGS
};
- final Uri queryUri = DocumentsContract.buildChildDocumentsUri(srcDirUri.getAuthority(),
- DocumentsContract.getDocumentId(srcDirUri));
Cursor cursor = null;
try {
// Iterate over srcs in the directory; copy to the destination directory.
- cursor = mSrcClient.query(queryUri, queryColumns, null, null, null);
+ DocumentInfo srcInfo;
+ cursor = mSrcClient.query(srcDirInfo.derivedUri, queryColumns, null, null, null);
while (cursor.moveToNext()) {
- final String childMimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
- final Uri dstUri = DocumentsContract.createDocument(mDstClient, dstDirUri,
- childMimeType, getCursorString(cursor, Document.COLUMN_DISPLAY_NAME));
- final Uri childUri = DocumentsContract.buildDocumentUri(srcDirUri.getAuthority(),
- getCursorString(cursor, Document.COLUMN_DOCUMENT_ID));
- if (Document.MIME_TYPE_DIR.equals(childMimeType)) {
- copyDirectoryHelper(childUri, dstUri, mode);
- } else {
- copyFileHelper(childUri, dstUri, mode);
- }
- }
- if (mode == TRANSFER_MODE_MOVE) {
- try {
- DocumentsContract.deleteDocument(mSrcClient, srcDirUri);
- } catch (RemoteException e) {
- // RemoteExceptions usually signal that the connection is dead, so there's no
- // point attempting to continue. Propagate the exception up so the copy job is
- // cancelled.
- Log.w(TAG, "Failed to clean up after move: " + srcDirUri, e);
- throw e;
- }
+ srcInfo = DocumentInfo.fromCursor(cursor, srcDirInfo.authority);
+ copy(srcInfo, dstDirInfo, mode);
}
} finally {
IoUtils.closeQuietly(cursor);
@@ -571,11 +577,11 @@
/**
* Handles copying a single file.
*
- * @param srcUri URI of the file to copy from.
- * @param dstUri URI of the *file* to copy to. Must be created beforehand.
+ * @param srcUriInfo Info of the file to copy from.
+ * @param dstUriInfo Info of the *file* to copy to. Must be created beforehand.
* @throws RemoteException
*/
- private void copyFileHelper(Uri srcUri, Uri dstUri, int mode)
+ private void copyFileHelper(DocumentInfo srcInfo, DocumentInfo dstInfo, int mode)
throws RemoteException {
// Copy an individual file.
CancellationSignal canceller = new CancellationSignal();
@@ -586,8 +592,8 @@
IOException copyError = null;
try {
- srcFile = mSrcClient.openFile(srcUri, "r", canceller);
- dstFile = mDstClient.openFile(dstUri, "w", canceller);
+ srcFile = mSrcClient.openFile(srcInfo.derivedUri, "r", canceller);
+ dstFile = mDstClient.openFile(dstInfo.derivedUri, "w", canceller);
src = new ParcelFileDescriptor.AutoCloseInputStream(srcFile);
dst = new ParcelFileDescriptor.AutoCloseOutputStream(dstFile);
@@ -601,18 +607,7 @@
srcFile.checkError();
} catch (IOException e) {
copyError = e;
-
- try {
- DocumentInfo info = DocumentInfo.fromUri(getContentResolver(), srcUri);
- mFailedFiles.add(info);
- } catch (FileNotFoundException ignore) {
- // Generate a dummy DocumentInfo so an error still gets reflected in the UI for this
- // file.
- DocumentInfo info = new DocumentInfo();
- info.derivedUri = srcUri;
- info.displayName = "Unknown [" + srcUri + "]";
- mFailedFiles.add(info);
- }
+ mFailedFiles.add(srcInfo);
if (dstFile != null) {
try {
@@ -627,26 +622,17 @@
IoUtils.closeQuietly(dst);
}
-
if (copyError != null || mIsCancelled) {
// Clean up half-copied files.
canceller.cancel();
try {
- DocumentsContract.deleteDocument(mDstClient, dstUri);
+ DocumentsContract.deleteDocument(mDstClient, dstInfo.derivedUri);
} catch (RemoteException e) {
- Log.w(TAG, "Failed to clean up after copy error: " + dstUri, e);
+ Log.w(TAG, "Failed to clean up after copy error: " + dstInfo.derivedUri, e);
// RemoteExceptions usually signal that the connection is dead, so there's no point
// attempting to continue. Propagate the exception up so the copy job is cancelled.
throw e;
}
- } else if (mode == TRANSFER_MODE_MOVE) {
- // Clean up src files after a successful move.
- try {
- DocumentsContract.deleteDocument(mSrcClient, srcUri);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to clean up after move: " + srcUri, e);
- throw e;
- }
}
}
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java b/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java
index 6fa0df2..1d72647 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java
@@ -255,7 +255,7 @@
return;
}
if (mNumLoaded == 0) {
- mDatabase.getMapper().startAddingChildDocuments(mIdentifier.mDocumentId);
+ mDatabase.getMapper().startAddingDocuments(mIdentifier.mDocumentId);
}
try {
mDatabase.getMapper().putChildDocuments(
@@ -266,7 +266,7 @@
mNumLoaded = 0;
}
if (getState() != STATE_LOADING) {
- mDatabase.getMapper().stopAddingChildDocuments(mIdentifier.mDocumentId);
+ mDatabase.getMapper().stopAddingDocuments(mIdentifier.mDocumentId);
}
}
@@ -275,7 +275,7 @@
mError = message;
mNumLoaded = 0;
if (lastState == STATE_LOADING) {
- mDatabase.getMapper().stopAddingChildDocuments(mIdentifier.mDocumentId);
+ mDatabase.getMapper().stopAddingDocuments(mIdentifier.mDocumentId);
}
}
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java b/packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java
index 0d9d60c..15ebe99 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/Mapper.java
@@ -18,6 +18,7 @@
import static com.android.mtp.MtpDatabaseConstants.*;
+import android.annotation.Nullable;
import android.content.ContentValues;
import android.content.res.Resources;
import android.database.Cursor;
@@ -36,7 +37,6 @@
import static com.android.mtp.MtpDatabase.strings;
-
/**
* Mapping operations for MtpDatabase.
* Also see the comments of {@link MtpDatabase}.
@@ -45,8 +45,9 @@
private final MtpDatabase mDatabase;
/**
- * Mapping mode for roots/documents where we start adding child documents.
+ * Mapping mode for a parent. The key is document ID of parent, or null for root documents.
* Methods operate the state needs to be synchronized.
+ * TODO: Replace this with unboxing int map.
*/
private final Map<String, Integer> mMappingMode = new HashMap<>();
@@ -55,32 +56,6 @@
}
/**
- * Invokes {@link #startAddingDocuments} for root documents.
- * @param deviceId Device ID.
- */
- synchronized void startAddingRootDocuments(int deviceId) {
- final String mappingStateKey = getRootDocumentsMappingStateKey(deviceId);
- Preconditions.checkState(!mMappingMode.containsKey(mappingStateKey));
- mMappingMode.put(
- mappingStateKey,
- startAddingDocuments(
- SELECTION_ROOT_DOCUMENTS, Integer.toString(deviceId)));
- }
-
- /**
- * Invokes {@link #startAddingDocuments} for child of specific documents.
- * @param parentDocumentId Document ID for parent document.
- */
- @VisibleForTesting
- synchronized void startAddingChildDocuments(String parentDocumentId) {
- final String mappingStateKey = getChildDocumentsMappingStateKey(parentDocumentId);
- Preconditions.checkState(!mMappingMode.containsKey(mappingStateKey));
- mMappingMode.put(
- mappingStateKey,
- startAddingDocuments(SELECTION_CHILD_DOCUMENTS, parentDocumentId));
- }
-
- /**
* Puts root information to database.
* @param deviceId Device ID
* @param resources Resources required to localize root name.
@@ -93,9 +68,8 @@
try {
final boolean heuristic;
final String mapColumn;
- final String key = getRootDocumentsMappingStateKey(deviceId);
- Preconditions.checkState(mMappingMode.containsKey(key));
- switch (mMappingMode.get(key)) {
+ Preconditions.checkState(mMappingMode.containsKey(/* no parent for root */ null));
+ switch (mMappingMode.get(/* no parent for root */ null)) {
case MAP_BY_MTP_IDENTIFIER:
heuristic = false;
mapColumn = COLUMN_STORAGE_ID;
@@ -117,8 +91,8 @@
}
final boolean changed = putDocuments(
valuesList,
- SELECTION_ROOT_DOCUMENTS,
- Integer.toString(deviceId),
+ COLUMN_PARENT_DOCUMENT_ID + " IS NULL",
+ new String[0],
heuristic,
mapColumn);
final ContentValues values = new ContentValues();
@@ -156,9 +130,8 @@
synchronized void putChildDocuments(int deviceId, String parentId, MtpObjectInfo[] documents) {
final boolean heuristic;
final String mapColumn;
- final String key = getChildDocumentsMappingStateKey(parentId);
- Preconditions.checkState(mMappingMode.containsKey(key));
- switch (mMappingMode.get(key)) {
+ Preconditions.checkState(mMappingMode.containsKey(parentId));
+ switch (mMappingMode.get(parentId)) {
case MAP_BY_MTP_IDENTIFIER:
heuristic = false;
mapColumn = COLUMN_OBJECT_HANDLE;
@@ -177,59 +150,11 @@
valuesList[i], deviceId, parentId, documents[i]);
}
putDocuments(
- valuesList, SELECTION_CHILD_DOCUMENTS, parentId, heuristic, mapColumn);
- }
-
- /**
- * Stops adding root documents.
- * @param deviceId Device ID.
- * @return True if new rows are added/removed.
- */
- synchronized boolean stopAddingRootDocuments(int deviceId) {
- final String key = getRootDocumentsMappingStateKey(deviceId);
- Preconditions.checkState(mMappingMode.containsKey(key));
- switch (mMappingMode.get(key)) {
- case MAP_BY_MTP_IDENTIFIER:
- mMappingMode.remove(key);
- return stopAddingDocuments(
- SELECTION_ROOT_DOCUMENTS,
- Integer.toString(deviceId),
- COLUMN_STORAGE_ID);
- case MAP_BY_NAME:
- mMappingMode.remove(key);
- return stopAddingDocuments(
- SELECTION_ROOT_DOCUMENTS,
- Integer.toString(deviceId),
- Document.COLUMN_DISPLAY_NAME);
- default:
- throw new Error("Unexpected mapping state.");
- }
- }
-
- /**
- * Stops adding documents under the parent.
- * @param parentId Document ID of the parent.
- */
- synchronized void stopAddingChildDocuments(String parentId) {
- final String key = getChildDocumentsMappingStateKey(parentId);
- Preconditions.checkState(mMappingMode.containsKey(key));
- switch (mMappingMode.get(key)) {
- case MAP_BY_MTP_IDENTIFIER:
- stopAddingDocuments(
- SELECTION_CHILD_DOCUMENTS,
- parentId,
- COLUMN_OBJECT_HANDLE);
- break;
- case MAP_BY_NAME:
- stopAddingDocuments(
- SELECTION_CHILD_DOCUMENTS,
- parentId,
- Document.COLUMN_DISPLAY_NAME);
- break;
- default:
- throw new Error("Unexpected mapping state.");
- }
- mMappingMode.remove(key);
+ valuesList,
+ COLUMN_PARENT_DOCUMENT_ID + "=?",
+ strings(parentId),
+ heuristic,
+ mapColumn);
}
@VisibleForTesting
@@ -257,31 +182,42 @@
* identifier or not. If all the documents have MTP identifier, it uses the identifier to find
* a corresponding existing row. Otherwise it does heuristic.
*
- * @param selection Query matches valid documents.
- * @param arg Argument for selection.
- * @return Mapping mode.
+ * @param parentDocumentId Parent document ID or NULL for root documents.
*/
- private int startAddingDocuments(String selection, String arg) {
+ void startAddingDocuments(@Nullable String parentDocumentId) {
+ Preconditions.checkState(!mMappingMode.containsKey(parentDocumentId));
+ final String selection;
+ final String[] args;
+ if (parentDocumentId != null) {
+ selection = COLUMN_PARENT_DOCUMENT_ID + " = ?";
+ args = strings(parentDocumentId);
+ } else {
+ selection = COLUMN_PARENT_DOCUMENT_ID + " IS NULL";
+ args = new String[0];
+ }
+
final SQLiteDatabase database = mDatabase.getSQLiteDatabase();
database.beginTransaction();
try {
// Delete all pending rows.
mDatabase.deleteDocumentsAndRootsRecursively(
- selection + " AND " + COLUMN_ROW_STATE + "=?", strings(arg, ROW_STATE_PENDING));
+ selection + " AND " + COLUMN_ROW_STATE + "=?",
+ DatabaseUtils.appendSelectionArgs(args, strings(ROW_STATE_PENDING)));
// Set all documents as invalidated.
final ContentValues values = new ContentValues();
values.put(COLUMN_ROW_STATE, ROW_STATE_INVALIDATED);
- database.update(TABLE_DOCUMENTS, values, selection, new String[] { arg });
+ database.update(TABLE_DOCUMENTS, values, selection, args);
// If we have rows that does not have MTP identifier, do heuristic mapping by name.
final boolean useNameForResolving = DatabaseUtils.queryNumEntries(
database,
TABLE_DOCUMENTS,
selection + " AND " + COLUMN_STORAGE_ID + " IS NULL",
- new String[] { arg }) > 0;
+ args) > 0;
database.setTransactionSuccessful();
- return useNameForResolving ? MAP_BY_NAME : MAP_BY_MTP_IDENTIFIER;
+ mMappingMode.put(
+ parentDocumentId, useNameForResolving ? MAP_BY_NAME : MAP_BY_MTP_IDENTIFIER);
} finally {
database.endTransaction();
}
@@ -292,19 +228,19 @@
* If the mapping mode is not heuristic, it just adds the rows to the database or updates the
* existing rows with the new values. If the mapping mode is heuristic, it adds some new rows as
* 'pending' state when that rows may be corresponding to existing 'invalidated' rows. Then
- * {@link #stopAddingDocuments(String, String, String)} turns the pending rows into 'valid'
+ * {@link #stopAddingDocuments(String, String[], String)} turns the pending rows into 'valid'
* rows. If the methods adds rows to database, it updates valueList with correct document ID.
*
* @param valuesList Values for documents to be stored in the database.
* @param selection SQL where closure to select rows that shares the same parent.
- * @param arg Argument for selection SQL.
+ * @param args Argument for selection SQL.
* @param heuristic Whether the mapping mode is heuristic.
* @return Whether the method adds new rows.
*/
private boolean putDocuments(
ContentValues[] valuesList,
String selection,
- String arg,
+ String[] args,
boolean heuristic,
String mappingKey) {
final SQLiteDatabase database = mDatabase.getSQLiteDatabase();
@@ -318,7 +254,9 @@
selection + " AND " +
COLUMN_ROW_STATE + "=? AND " +
mappingKey + "=?",
- strings(arg, ROW_STATE_INVALIDATED, values.getAsString(mappingKey)),
+ DatabaseUtils.appendSelectionArgs(
+ args,
+ strings(ROW_STATE_INVALIDATED, values.getAsString(mappingKey))),
null,
null,
null,
@@ -362,12 +300,32 @@
* Maps 'pending' document and 'invalidated' document that shares the same column of groupKey.
* If the database does not find corresponding 'invalidated' document, it just removes
* 'invalidated' document from the database.
- * @param selection Query to select rows for resolving.
- * @param arg Argument for selection SQL.
- * @param groupKey Column name used to find corresponding rows.
+ * @param parentId Parent document ID or null for root documents.
* @return Whether the methods adds or removed visible rows.
*/
- private boolean stopAddingDocuments(String selection, String arg, String groupKey) {
+ boolean stopAddingDocuments(@Nullable String parentId) {
+ Preconditions.checkState(mMappingMode.containsKey(parentId));
+ final String selection;
+ final String[] args;
+ if (parentId != null) {
+ selection = COLUMN_PARENT_DOCUMENT_ID + "=?";
+ args = strings(parentId);
+ } else {
+ selection = COLUMN_PARENT_DOCUMENT_ID + " IS NULL";
+ args = new String[0];
+ }
+ final String groupKey;
+ switch (mMappingMode.get(parentId)) {
+ case MAP_BY_MTP_IDENTIFIER:
+ groupKey = parentId != null ? COLUMN_OBJECT_HANDLE : COLUMN_STORAGE_ID;
+ break;
+ case MAP_BY_NAME:
+ groupKey = Document.COLUMN_DISPLAY_NAME;
+ break;
+ default:
+ throw new Error("Unexpected mapping state.");
+ }
+ mMappingMode.remove(parentId);
final SQLiteDatabase database = mDatabase.getSQLiteDatabase();
database.beginTransaction();
try {
@@ -390,7 +348,7 @@
"group_concat(" + pendingIdQuery + ")"
},
selection,
- strings(arg),
+ args,
groupKey,
"count(" + invalidatedIdQuery + ") = 1 AND count(" + pendingIdQuery + ") = 1",
null);
@@ -439,7 +397,7 @@
// Delete all invalidated rows that cannot be mapped.
if (mDatabase.deleteDocumentsAndRootsRecursively(
COLUMN_ROW_STATE + " = ? AND " + selection,
- strings(ROW_STATE_INVALIDATED, arg))) {
+ DatabaseUtils.appendSelectionArgs(strings(ROW_STATE_INVALIDATED), args))) {
changed = true;
}
@@ -452,7 +410,7 @@
TABLE_DOCUMENTS,
values,
COLUMN_ROW_STATE + " = ? AND " + selection,
- strings(ROW_STATE_PENDING, arg)) != 0) {
+ DatabaseUtils.appendSelectionArgs(strings(ROW_STATE_PENDING), args)) != 0) {
changed = true;
}
database.setTransactionSuccessful();
@@ -494,20 +452,4 @@
return "CASE WHEN " + COLUMN_ROW_STATE + " = " + Integer.toString(state) +
" THEN " + a + " ELSE NULL END";
}
-
- /**
- * @param deviceId Device ID.
- * @return Key for {@link #mMappingMode}.
- */
- private static String getRootDocumentsMappingStateKey(int deviceId) {
- return "RootDocuments/" + deviceId;
- }
-
- /**
- * @param parentDocumentId Document ID for the parent document.
- * @return Key for {@link #mMappingMode}.
- */
- private static String getChildDocumentsMappingStateKey(String parentDocumentId) {
- return "ChildDocuments/" + parentDocumentId;
- }
}
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java
index 0e91fdd..eac9b98 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java
@@ -53,13 +53,13 @@
* by comparing the directory structure and object name.
*
* To start putting documents into the database, the client needs to call
- * {@link Mapper#startAddingChildDocuments(String)} with the parent document ID. Also it
- * needs to call {@link Mapper#stopAddingChildDocuments(String)} after putting all child
+ * {@link Mapper#startAddingDocuments(String)} with the parent document ID. Also it
+ * needs to call {@link Mapper#stopAddingDocuments(String)} after putting all child
* documents to the database. (All explanations are same for root documents)
*
- * database.getMapper().startAddingChildDocuments();
+ * database.getMapper().startAddingDocuments();
* database.getMapper().putChildDocuments();
- * database.getMapper().stopAddingChildDocuments();
+ * database.getMapper().stopAddingDocuments();
*
* To update the existing documents, the client code can repeat to call the three methods again.
* The newly added rows update corresponding existing rows that have same MTP identifier like
@@ -307,32 +307,6 @@
}
}
- /**
- * Returns the set of device ID stored in the database.
- */
- int[] getDeviceIds() {
- final Cursor cursor = mDatabase.query(
- true,
- TABLE_DOCUMENTS,
- strings(COLUMN_DEVICE_ID),
- null,
- null,
- null,
- null,
- null,
- null);
- try {
- final int[] ids = new int[cursor.getCount()];
- for (int i = 0; i < ids.length; i++) {
- cursor.moveToNext();
- ids[i] = cursor.getInt(0);
- }
- return ids;
- } finally {
- cursor.close();
- }
- }
-
private boolean deleteDocumentsAndRoots(String selection, String[] args) {
mDatabase.beginTransaction();
try {
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseConstants.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseConstants.java
index 72bd6ee..43dc8f5 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseConstants.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseConstants.java
@@ -103,9 +103,6 @@
static final String SELECTION_DOCUMENT_ID = Document.COLUMN_DOCUMENT_ID + " = ?";
static final String SELECTION_ROOT_ID = Root.COLUMN_ROOT_ID + " = ?";
- static final String SELECTION_ROOT_DOCUMENTS =
- COLUMN_DEVICE_ID + " = ? AND " + COLUMN_PARENT_DOCUMENT_ID + " IS NULL";
- static final String SELECTION_CHILD_DOCUMENTS = COLUMN_PARENT_DOCUMENT_ID + " = ?";
static final String QUERY_CREATE_DOCUMENTS =
"CREATE TABLE " + TABLE_DOCUMENTS + " (" +
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
index d5f00e6..9338c1b 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java
@@ -293,19 +293,11 @@
}
/**
- * Reopens MTP devices based on database state.
+ * Clears MTP identifier in the database.
*/
private void resume() {
synchronized (mDeviceListLock) {
mDatabase.getMapper().clearMapping();
- final int[] ids = mDatabase.getDeviceIds();
- for (final int id : ids) {
- try {
- openDevice(id);
- } catch (IOException exception) {
- mDatabase.removeDeviceRows(id);
- }
- }
}
}
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java b/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java
index df2ab01..52a751b 100644
--- a/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java
+++ b/packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java
@@ -110,19 +110,12 @@
return;
}
boolean changed = false;
+ mDatabase.getMapper().startAddingDocuments(null /* parentDocumentId */);
for (int deviceId : deviceIds) {
try {
final MtpRoot[] roots = mManager.getRoots(deviceId);
- mDatabase.getMapper().startAddingRootDocuments(deviceId);
- try {
- if (mDatabase.getMapper().putRootDocuments(
- deviceId, mResources, roots)) {
- changed = true;
- }
- } finally {
- if (mDatabase.getMapper().stopAddingRootDocuments(deviceId)) {
- changed = true;
- }
+ if (mDatabase.getMapper().putRootDocuments(deviceId, mResources, roots)) {
+ changed = true;
}
} catch (IOException | SQLiteException exception) {
// The error may happen on the device. We would like to continue getting
@@ -130,6 +123,9 @@
Log.e(MtpDocumentsProvider.TAG, exception.getMessage());
}
}
+ if (mDatabase.getMapper().stopAddingDocuments(null /* parentDocumentId */)) {
+ changed = true;
+ }
if (changed) {
notifyChange();
}
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java
index f0b4343..22d954d 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/DocumentLoaderTest.java
@@ -40,11 +40,11 @@
@Override
public void setUp() {
mDatabase = new MtpDatabase(getContext(), MtpDatabaseConstants.FLAG_DATABASE_IN_MEMORY);
- mDatabase.getMapper().startAddingRootDocuments(0);
+ mDatabase.getMapper().startAddingDocuments(null);
mDatabase.getMapper().putRootDocuments(0, new TestResources(), new MtpRoot[] {
new MtpRoot(0, 0, "Device", "Storage", 1000, 1000, "")
});
- mDatabase.getMapper().stopAddingRootDocuments(0);
+ mDatabase.getMapper().stopAddingDocuments(null);
mManager = new BlockableTestMtpManager(getContext());
mResolver = new TestContentResolver();
mLoader = new DocumentLoader(mManager, mResolver, mDatabase);
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
index 8166de1..18623e6 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java
@@ -61,7 +61,7 @@
}
public void testPutRootDocuments() throws Exception {
- mDatabase.getMapper().startAddingRootDocuments(0);
+ mDatabase.getMapper().startAddingDocuments(null);
mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 1, "Device", "Storage", 1000, 2000, ""),
new MtpRoot(0, 2, "Device", "Storage", 2000, 4000, ""),
@@ -155,7 +155,7 @@
}
public void testPutChildDocuments() throws Exception {
- mDatabase.getMapper().startAddingChildDocuments("parentId");
+ mDatabase.getMapper().startAddingDocuments("parentId");
mDatabase.getMapper().putChildDocuments(0, "parentId", new MtpObjectInfo[] {
createDocument(100, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
createDocument(101, "image.jpg", MtpConstants.FORMAT_EXIF_JPEG, 2 * 1024 * 1024),
@@ -236,7 +236,7 @@
Root.COLUMN_AVAILABLE_BYTES
};
- mDatabase.getMapper().startAddingRootDocuments(0);
+ mDatabase.getMapper().startAddingDocuments(null);
mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 100, "Device", "Storage A", 1000, 0, ""),
new MtpRoot(0, 101, "Device", "Storage B", 1001, 0, "")
@@ -296,7 +296,7 @@
cursor.close();
}
- mDatabase.getMapper().startAddingRootDocuments(0);
+ mDatabase.getMapper().startAddingDocuments(null);
mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 200, "Device", "Storage A", 2000, 0, ""),
new MtpRoot(0, 202, "Device", "Storage C", 2002, 0, "")
@@ -335,7 +335,7 @@
cursor.close();
}
- mDatabase.getMapper().stopAddingRootDocuments(0);
+ mDatabase.getMapper().stopAddingDocuments(null);
{
final Cursor cursor = mDatabase.queryRootDocuments(columns);
@@ -370,7 +370,7 @@
MtpDatabaseConstants.COLUMN_OBJECT_HANDLE,
DocumentsContract.Document.COLUMN_DISPLAY_NAME
};
- mDatabase.getMapper().startAddingChildDocuments("parentId");
+ mDatabase.getMapper().startAddingDocuments("parentId");
mDatabase.getMapper().putChildDocuments(0, "parentId", new MtpObjectInfo[] {
createDocument(100, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
createDocument(101, "image.jpg", MtpConstants.FORMAT_EXIF_JPEG, 2 * 1024 * 1024),
@@ -400,7 +400,7 @@
cursor.close();
}
- mDatabase.getMapper().startAddingChildDocuments("parentId");
+ mDatabase.getMapper().startAddingDocuments("parentId");
mDatabase.getMapper().putChildDocuments(0, "parentId", new MtpObjectInfo[] {
createDocument(200, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
createDocument(203, "video.mp4", MtpConstants.FORMAT_MP4_CONTAINER, 1024),
@@ -418,7 +418,7 @@
cursor.close();
}
- mDatabase.getMapper().stopAddingChildDocuments("parentId");
+ mDatabase.getMapper().stopAddingDocuments("parentId");
{
final Cursor cursor = mDatabase.queryChildDocuments(columns, "parentId");
@@ -437,7 +437,10 @@
}
}
- public void testRestoreIdForDifferentDevices() throws Exception {
+ /**
+ * TODO: Enable this test after introducing device documents.
+ */
+ public void disabled_testRestoreIdForDifferentDevices() throws Exception {
final String[] columns = new String[] {
DocumentsContract.Document.COLUMN_DOCUMENT_ID,
MtpDatabaseConstants.COLUMN_STORAGE_ID,
@@ -447,8 +450,7 @@
Root.COLUMN_ROOT_ID,
Root.COLUMN_AVAILABLE_BYTES
};
- mDatabase.getMapper().startAddingRootDocuments(0);
- mDatabase.getMapper().startAddingRootDocuments(1);
+ mDatabase.getMapper().startAddingDocuments(null);
mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 100, "Device", "Storage", 0, 0, "")
});
@@ -484,16 +486,14 @@
mDatabase.getMapper().clearMapping();
- mDatabase.getMapper().startAddingRootDocuments(0);
- mDatabase.getMapper().startAddingRootDocuments(1);
+ mDatabase.getMapper().startAddingDocuments(null);
mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 200, "Device", "Storage", 2000, 0, "")
});
mDatabase.getMapper().putRootDocuments(1, resources, new MtpRoot[] {
new MtpRoot(1, 300, "Device", "Storage", 3000, 0, "")
});
- mDatabase.getMapper().stopAddingRootDocuments(0);
- mDatabase.getMapper().stopAddingRootDocuments(1);
+ mDatabase.getMapper().stopAddingDocuments(null);
{
final Cursor cursor = mDatabase.queryRootDocuments(columns);
@@ -528,8 +528,8 @@
MtpDatabaseConstants.COLUMN_OBJECT_HANDLE
};
- mDatabase.getMapper().startAddingChildDocuments("parentId1");
- mDatabase.getMapper().startAddingChildDocuments("parentId2");
+ mDatabase.getMapper().startAddingDocuments("parentId1");
+ mDatabase.getMapper().startAddingDocuments("parentId2");
mDatabase.getMapper().putChildDocuments(0, "parentId1", new MtpObjectInfo[] {
createDocument(100, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
});
@@ -538,15 +538,15 @@
});
mDatabase.getMapper().clearMapping();
- mDatabase.getMapper().startAddingChildDocuments("parentId1");
- mDatabase.getMapper().startAddingChildDocuments("parentId2");
+ mDatabase.getMapper().startAddingDocuments("parentId1");
+ mDatabase.getMapper().startAddingDocuments("parentId2");
mDatabase.getMapper().putChildDocuments(0, "parentId1", new MtpObjectInfo[] {
createDocument(200, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
});
mDatabase.getMapper().putChildDocuments(0, "parentId2", new MtpObjectInfo[] {
createDocument(201, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
});
- mDatabase.getMapper().stopAddingChildDocuments("parentId1");
+ mDatabase.getMapper().stopAddingDocuments("parentId1");
{
final Cursor cursor = mDatabase.queryChildDocuments(columns, "parentId1");
@@ -577,23 +577,23 @@
Root.COLUMN_AVAILABLE_BYTES
};
- mDatabase.getMapper().startAddingRootDocuments(0);
+ mDatabase.getMapper().startAddingDocuments(null);
mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 100, "Device", "Storage", 0, 0, ""),
});
mDatabase.getMapper().clearMapping();
- mDatabase.getMapper().startAddingRootDocuments(0);
+ mDatabase.getMapper().startAddingDocuments(null);
mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 200, "Device", "Storage", 2000, 0, ""),
});
mDatabase.getMapper().clearMapping();
- mDatabase.getMapper().startAddingRootDocuments(0);
+ mDatabase.getMapper().startAddingDocuments(null);
mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 300, "Device", "Storage", 3000, 0, ""),
});
- mDatabase.getMapper().stopAddingRootDocuments(0);
+ mDatabase.getMapper().stopAddingDocuments(null);
{
final Cursor cursor = mDatabase.queryRootDocuments(columns);
@@ -625,18 +625,18 @@
Root.COLUMN_AVAILABLE_BYTES
};
- mDatabase.getMapper().startAddingRootDocuments(0);
+ mDatabase.getMapper().startAddingDocuments(null);
mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 100, "Device", "Storage", 0, 0, ""),
});
mDatabase.getMapper().clearMapping();
- mDatabase.getMapper().startAddingRootDocuments(0);
+ mDatabase.getMapper().startAddingDocuments(null);
mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 200, "Device", "Storage", 2000, 0, ""),
new MtpRoot(0, 201, "Device", "Storage", 2001, 0, ""),
});
- mDatabase.getMapper().stopAddingRootDocuments(0);
+ mDatabase.getMapper().stopAddingDocuments(null);
{
final Cursor cursor = mDatabase.queryRootDocuments(columns);
@@ -667,17 +667,17 @@
public void testReplaceExistingRoots() {
// The client code should be able to replace existing rows with new information.
// Add one.
- mDatabase.getMapper().startAddingRootDocuments(0);
+ mDatabase.getMapper().startAddingDocuments(null);
mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""),
});
- mDatabase.getMapper().stopAddingRootDocuments(0);
+ mDatabase.getMapper().stopAddingDocuments(null);
// Replace it.
- mDatabase.getMapper().startAddingRootDocuments(0);
+ mDatabase.getMapper().startAddingDocuments(null);
mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 100, "Device", "Storage B", 1000, 1000, ""),
});
- mDatabase.getMapper().stopAddingRootDocuments(0);
+ mDatabase.getMapper().stopAddingDocuments(null);
{
final String[] columns = new String[] {
DocumentsContract.Document.COLUMN_DOCUMENT_ID,
@@ -709,7 +709,7 @@
public void testFailToReplaceExisitingUnmappedRoots() {
// The client code should not be able to replace rows before resolving 'unmapped' rows.
// Add one.
- mDatabase.getMapper().startAddingRootDocuments(0);
+ mDatabase.getMapper().startAddingDocuments(null);
mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""),
});
@@ -718,7 +718,7 @@
assertEquals(1, oldCursor.getCount());
// Add one.
- mDatabase.getMapper().startAddingRootDocuments(0);
+ mDatabase.getMapper().startAddingDocuments(null);
mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 101, "Device", "Storage B", 1000, 1000, ""),
});
@@ -726,7 +726,7 @@
mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 102, "Device", "Storage B", 1000, 1000, ""),
});
- mDatabase.getMapper().stopAddingRootDocuments(0);
+ mDatabase.getMapper().stopAddingDocuments(null);
// Because the roots shares the same name, the roots should have new IDs.
final Cursor newCursor = mDatabase.queryRoots(strings(Root.COLUMN_ROOT_ID));
@@ -742,11 +742,11 @@
}
public void testQueryDocument() {
- mDatabase.getMapper().startAddingRootDocuments(0);
+ mDatabase.getMapper().startAddingDocuments(null);
mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""),
});
- mDatabase.getMapper().stopAddingRootDocuments(0);
+ mDatabase.getMapper().stopAddingDocuments(null);
final Cursor cursor = mDatabase.queryDocument("1", strings(Document.COLUMN_DISPLAY_NAME));
assertEquals(1, cursor.getCount());
@@ -756,48 +756,48 @@
}
public void testGetParentId() throws FileNotFoundException {
- mDatabase.getMapper().startAddingRootDocuments(0);
+ mDatabase.getMapper().startAddingDocuments(null);
mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""),
});
- mDatabase.getMapper().stopAddingRootDocuments(0);
+ mDatabase.getMapper().stopAddingDocuments(null);
- mDatabase.getMapper().startAddingChildDocuments("1");
+ mDatabase.getMapper().startAddingDocuments("1");
mDatabase.getMapper().putChildDocuments(
0,
"1",
new MtpObjectInfo[] {
createDocument(200, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
});
- mDatabase.getMapper().stopAddingChildDocuments("1");
+ mDatabase.getMapper().stopAddingDocuments("1");
assertEquals("1", mDatabase.getParentId("2"));
}
public void testDeleteDocument() {
- mDatabase.getMapper().startAddingRootDocuments(0);
+ mDatabase.getMapper().startAddingDocuments(null);
mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""),
});
- mDatabase.getMapper().stopAddingRootDocuments(0);
+ mDatabase.getMapper().stopAddingDocuments(null);
- mDatabase.getMapper().startAddingChildDocuments("1");
+ mDatabase.getMapper().startAddingDocuments("1");
mDatabase.getMapper().putChildDocuments(
0,
"1",
new MtpObjectInfo[] {
createDocument(200, "dir", MtpConstants.FORMAT_ASSOCIATION, 1024),
});
- mDatabase.getMapper().stopAddingChildDocuments("1");
+ mDatabase.getMapper().stopAddingDocuments("1");
- mDatabase.getMapper().startAddingChildDocuments("2");
+ mDatabase.getMapper().startAddingDocuments("2");
mDatabase.getMapper().putChildDocuments(
0,
"2",
new MtpObjectInfo[] {
createDocument(200, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
});
- mDatabase.getMapper().stopAddingChildDocuments("2");
+ mDatabase.getMapper().stopAddingDocuments("2");
mDatabase.deleteDocument("2");
@@ -819,11 +819,11 @@
}
public void testPutNewDocument() {
- mDatabase.getMapper().startAddingRootDocuments(0);
+ mDatabase.getMapper().startAddingDocuments(null);
mDatabase.getMapper().putRootDocuments(0, resources, new MtpRoot[] {
new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""),
});
- mDatabase.getMapper().stopAddingRootDocuments(0);
+ mDatabase.getMapper().stopAddingDocuments(null);
assertEquals(
"2",
@@ -841,12 +841,12 @@
// The new document should not be mapped with existing invalidated document.
mDatabase.getMapper().clearMapping();
- mDatabase.getMapper().startAddingChildDocuments("1");
+ mDatabase.getMapper().startAddingDocuments("1");
mDatabase.putNewDocument(
0,
"1",
createDocument(201, "note.txt", MtpConstants.FORMAT_TEXT, 1024));
- mDatabase.getMapper().stopAddingChildDocuments("1");
+ mDatabase.getMapper().stopAddingDocuments("1");
{
final Cursor cursor =
diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
index dc6f79e..597d51e 100644
--- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
+++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java
@@ -408,34 +408,6 @@
MtpDocumentsProvider.AUTHORITY, "1")));
}
- @MediumTest
- public void testPauseAndResume() throws Exception {
- setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_FILE);
- mMtpManager.addValidDevice(0);
- mProvider.openDevice(0);
- setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Device", "Storage", 0, 0, "")});
-
- {
- final Cursor cursor = mProvider.queryRoots(
- strings(DocumentsContract.Root.COLUMN_ROOT_ID));
- cursor.moveToNext();
- assertEquals(1, cursor.getInt(0));
- }
-
- mProvider.shutdown();
- setupProvider(MtpDatabaseConstants.FLAG_DATABASE_IN_FILE);
-
- {
- // We can still fetch roots after relaunching the provider.
- final Cursor cursor = mProvider.queryRoots(
- strings(DocumentsContract.Root.COLUMN_ROOT_ID));
- assertEquals(1, cursor.getCount());
- cursor.moveToNext();
- assertEquals(1, cursor.getInt(0));
- assertEquals(1, mMtpManager.getOpenedDeviceIds().length);
- }
- }
-
private void setupProvider(int flag) {
mDatabase = new MtpDatabase(getContext(), flag);
mProvider = new MtpDocumentsProvider();
diff --git a/services/core/java/com/android/server/LockSettingsService.java b/services/core/java/com/android/server/LockSettingsService.java
index 033a4b8..1a21f5c 100644
--- a/services/core/java/com/android/server/LockSettingsService.java
+++ b/services/core/java/com/android/server/LockSettingsService.java
@@ -356,6 +356,10 @@
}
}
+ if (LockPatternUtils.LEGACY_LOCK_PATTERN_ENABLED.equals(key)) {
+ key = Settings.Secure.LOCK_PATTERN_ENABLED;
+ }
+
return mStorage.readKeyValue(key, defaultValue, userId);
}
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index e8100f0..6c00e73 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -29,12 +29,12 @@
import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
+import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME;
import static android.content.pm.ActivityInfo.FLAG_SHOW_FOR_ALL_USERS;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
import static com.android.server.am.ActivityManagerDebugConfig.*;
import static com.android.server.am.ActivityManagerService.FIRST_SUPERVISOR_STACK_MSG;
import static com.android.server.am.ActivityRecord.HOME_ACTIVITY_TYPE;
@@ -76,7 +76,6 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
-import android.content.pm.PackageManagerInternal;
import android.content.pm.ResolveInfo;
import android.content.pm.UserInfo;
import android.content.res.Configuration;
@@ -107,7 +106,6 @@
import android.os.UserHandle;
import android.os.UserManager;
import android.os.WorkSource;
-import android.os.storage.StorageManager;
import android.provider.MediaStore;
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
@@ -123,7 +121,6 @@
import android.view.DisplayInfo;
import android.view.InputEvent;
import android.view.Surface;
-import android.widget.Toast;
import com.android.internal.app.HeavyWeightSwitcherActivity;
import com.android.internal.app.IVoiceInteractor;
@@ -1549,7 +1546,7 @@
// to ensure that it is safe to do so. If the upcoming activity will also
// be part of the voice session, we can only launch it if it has explicitly
// said it supports the VOICE category, or it is a part of the calling app.
- if ((launchFlags & Intent.FLAG_ACTIVITY_NEW_TASK) == 0
+ if ((launchFlags & FLAG_ACTIVITY_NEW_TASK) == 0
&& sourceRecord.info.applicationInfo.uid != aInfo.applicationInfo.uid) {
try {
intent.addCategory(Intent.CATEGORY_VOICE);
@@ -1596,63 +1593,9 @@
return err;
}
- boolean abort = false;
-
- final int startAnyPerm = mService.checkPermission(
- START_ANY_ACTIVITY, callingPid, callingUid);
-
- if (startAnyPerm != PERMISSION_GRANTED) {
- final int componentRestriction = getComponentRestrictionForCallingPackage(
- aInfo, callingPackage, callingPid, callingUid, ignoreTargetSecurity);
- final int actionRestriction = getActionRestrictionForCallingPackage(
- intent.getAction(), callingPackage, callingPid, callingUid);
-
- if (componentRestriction == ACTIVITY_RESTRICTION_PERMISSION
- || actionRestriction == ACTIVITY_RESTRICTION_PERMISSION) {
- if (resultRecord != null) {
- resultStack.sendActivityResultLocked(-1,
- resultRecord, resultWho, requestCode,
- Activity.RESULT_CANCELED, null);
- }
- String msg;
- if (actionRestriction == ACTIVITY_RESTRICTION_PERMISSION) {
- msg = "Permission Denial: starting " + intent.toString()
- + " from " + callerApp + " (pid=" + callingPid
- + ", uid=" + callingUid + ")" + " with revoked permission "
- + ACTION_TO_RUNTIME_PERMISSION.get(intent.getAction());
- } else if (!aInfo.exported) {
- msg = "Permission Denial: starting " + intent.toString()
- + " from " + callerApp + " (pid=" + callingPid
- + ", uid=" + callingUid + ")"
- + " not exported from uid " + aInfo.applicationInfo.uid;
- } else {
- msg = "Permission Denial: starting " + intent.toString()
- + " from " + callerApp + " (pid=" + callingPid
- + ", uid=" + callingUid + ")"
- + " requires " + aInfo.permission;
- }
- Slog.w(TAG, msg);
- throw new SecurityException(msg);
- }
-
- if (actionRestriction == ACTIVITY_RESTRICTION_APPOP) {
- String message = "Appop Denial: starting " + intent.toString()
- + " from " + callerApp + " (pid=" + callingPid
- + ", uid=" + callingUid + ")"
- + " requires " + AppOpsManager.permissionToOp(
- ACTION_TO_RUNTIME_PERMISSION.get(intent.getAction()));
- Slog.w(TAG, message);
- abort = true;
- } else if (componentRestriction == ACTIVITY_RESTRICTION_APPOP) {
- String message = "Appop Denial: starting " + intent.toString()
- + " from " + callerApp + " (pid=" + callingPid
- + ", uid=" + callingUid + ")"
- + " requires appop " + AppOpsManager.permissionToOp(aInfo.permission);
- Slog.w(TAG, message);
- abort = true;
- }
- }
-
+ boolean abort = !checkStartAnyActivityPermission(intent, aInfo, resultWho, requestCode,
+ callingPid, callingUid, callingPackage, ignoreTargetSecurity, callerApp,
+ resultRecord, resultStack);
abort |= !mService.mIntentFirewall.checkStartActivity(intent, callingUid,
callingPid, resolvedType, aInfo.applicationInfo);
@@ -1682,7 +1625,7 @@
| PendingIntent.FLAG_IMMUTABLE, null);
int flags = intent.getFlags();
intent = km.createConfirmDeviceCredentialIntent(null, null);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+ intent.addFlags(FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
intent.putExtra(Intent.EXTRA_PACKAGE_NAME, aInfo.packageName);
intent.putExtra(Intent.EXTRA_INTENT, new IntentSender(target));
@@ -1801,6 +1744,66 @@
return err;
}
+ private boolean checkStartAnyActivityPermission(Intent intent, ActivityInfo aInfo,
+ String resultWho, int requestCode, int callingPid, int callingUid,
+ String callingPackage, boolean ignoreTargetSecurity, ProcessRecord callerApp,
+ ActivityRecord resultRecord, ActivityStack resultStack) {
+ final int startAnyPerm = mService.checkPermission(START_ANY_ACTIVITY, callingPid,
+ callingUid);
+ if (startAnyPerm == PERMISSION_GRANTED) {
+ return true;
+ }
+ final int componentRestriction = getComponentRestrictionForCallingPackage(
+ aInfo, callingPackage, callingPid, callingUid, ignoreTargetSecurity);
+ final int actionRestriction = getActionRestrictionForCallingPackage(
+ intent.getAction(), callingPackage, callingPid, callingUid);
+ if (componentRestriction == ACTIVITY_RESTRICTION_PERMISSION
+ || actionRestriction == ACTIVITY_RESTRICTION_PERMISSION) {
+ if (resultRecord != null) {
+ resultStack.sendActivityResultLocked(-1,
+ resultRecord, resultWho, requestCode,
+ Activity.RESULT_CANCELED, null);
+ }
+ final String msg;
+ if (actionRestriction == ACTIVITY_RESTRICTION_PERMISSION) {
+ msg = "Permission Denial: starting " + intent.toString()
+ + " from " + callerApp + " (pid=" + callingPid
+ + ", uid=" + callingUid + ")" + " with revoked permission "
+ + ACTION_TO_RUNTIME_PERMISSION.get(intent.getAction());
+ } else if (!aInfo.exported) {
+ msg = "Permission Denial: starting " + intent.toString()
+ + " from " + callerApp + " (pid=" + callingPid
+ + ", uid=" + callingUid + ")"
+ + " not exported from uid " + aInfo.applicationInfo.uid;
+ } else {
+ msg = "Permission Denial: starting " + intent.toString()
+ + " from " + callerApp + " (pid=" + callingPid
+ + ", uid=" + callingUid + ")"
+ + " requires " + aInfo.permission;
+ }
+ Slog.w(TAG, msg);
+ throw new SecurityException(msg);
+ }
+
+ if (actionRestriction == ACTIVITY_RESTRICTION_APPOP) {
+ final String message = "Appop Denial: starting " + intent.toString()
+ + " from " + callerApp + " (pid=" + callingPid
+ + ", uid=" + callingUid + ")"
+ + " requires " + AppOpsManager.permissionToOp(
+ ACTION_TO_RUNTIME_PERMISSION.get(intent.getAction()));
+ Slog.w(TAG, message);
+ return false;
+ } else if (componentRestriction == ACTIVITY_RESTRICTION_APPOP) {
+ final String message = "Appop Denial: starting " + intent.toString()
+ + " from " + callerApp + " (pid=" + callingPid
+ + ", uid=" + callingUid + ")"
+ + " requires appop " + AppOpsManager.permissionToOp(aInfo.permission);
+ Slog.w(TAG, message);
+ return false;
+ }
+ return true;
+ }
+
private UserInfo getUserInfo(int userId) {
final long identity = Binder.clearCallingIdentity();
try {
@@ -1879,7 +1882,7 @@
return ACTIVITY_RESTRICTION_NONE;
}
- ActivityStack computeStackFocus(ActivityRecord r, boolean newTask, Rect bounds) {
+ private ActivityStack computeStackFocus(ActivityRecord r, boolean newTask, Rect bounds) {
final TaskRecord task = r.task;
if (!(r.isApplicationActivity() || (task != null && task.isApplicationTask()))) {
@@ -1994,7 +1997,7 @@
Slog.i(TAG, "Ignoring FLAG_ACTIVITY_NEW_DOCUMENT, launchMode is " +
"\"singleInstance\" or \"singleTask\"");
launchFlags &=
- ~(Intent.FLAG_ACTIVITY_NEW_DOCUMENT | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
+ ~(Intent.FLAG_ACTIVITY_NEW_DOCUMENT | FLAG_ACTIVITY_MULTIPLE_TASK);
} else {
switch (r.info.documentLaunchMode) {
case ActivityInfo.DOCUMENT_LAUNCH_NONE:
@@ -2006,7 +2009,7 @@
launchFlags |= Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
break;
case ActivityInfo.DOCUMENT_LAUNCH_NEVER:
- launchFlags &= ~Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
+ launchFlags &= ~FLAG_ACTIVITY_MULTIPLE_TASK;
break;
}
}
@@ -2015,7 +2018,7 @@
&& !launchSingleTask && !launchSingleInstance
&& (launchFlags & Intent.FLAG_ACTIVITY_NEW_DOCUMENT) != 0;
- if (r.resultTo != null && (launchFlags & Intent.FLAG_ACTIVITY_NEW_TASK) != 0
+ if (r.resultTo != null && (launchFlags & FLAG_ACTIVITY_NEW_TASK) != 0
&& r.resultTo.task.stack != null) {
// For whatever reason this activity is being launched into a new
// task... yet the caller has requested a result back. Well, that
@@ -2030,15 +2033,15 @@
}
if ((launchFlags & Intent.FLAG_ACTIVITY_NEW_DOCUMENT) != 0 && r.resultTo == null) {
- launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK;
+ launchFlags |= FLAG_ACTIVITY_NEW_TASK;
}
// If we are actually going to launch in to a new task, there are some cases where
// we further want to do multiple task.
- if ((launchFlags & Intent.FLAG_ACTIVITY_NEW_TASK) != 0) {
+ if ((launchFlags & FLAG_ACTIVITY_NEW_TASK) != 0) {
if (launchTaskBehind
|| r.info.documentLaunchMode == ActivityInfo.DOCUMENT_LAUNCH_ALWAYS) {
- launchFlags |= Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
+ launchFlags |= FLAG_ACTIVITY_MULTIPLE_TASK;
}
}
@@ -2107,8 +2110,8 @@
// If task is empty, then adopt the interesting intent launch flags in to the
// activity being started.
if (root == null) {
- final int flagsOfInterest = Intent.FLAG_ACTIVITY_NEW_TASK
- | Intent.FLAG_ACTIVITY_MULTIPLE_TASK | Intent.FLAG_ACTIVITY_NEW_DOCUMENT
+ final int flagsOfInterest = FLAG_ACTIVITY_NEW_TASK
+ | FLAG_ACTIVITY_MULTIPLE_TASK | Intent.FLAG_ACTIVITY_NEW_DOCUMENT
| Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS;
launchFlags = (launchFlags&~flagsOfInterest)
| (baseIntent.getFlags()&flagsOfInterest);
@@ -2119,7 +2122,7 @@
// If the task is not empty and the caller is asking to start it as the root
// of a new task, then we don't actually want to start this on the task. We
// will bring the task to the front, and possibly give it a new intent.
- } else if ((launchFlags & Intent.FLAG_ACTIVITY_NEW_TASK) != 0) {
+ } else if ((launchFlags & FLAG_ACTIVITY_NEW_TASK) != 0) {
addingToTask = false;
} else {
@@ -2143,20 +2146,20 @@
if (sourceRecord == null) {
// This activity is not being started from another... in this
// case we -always- start a new task.
- if ((launchFlags & Intent.FLAG_ACTIVITY_NEW_TASK) == 0 && inTask == null) {
+ if ((launchFlags & FLAG_ACTIVITY_NEW_TASK) == 0 && inTask == null) {
Slog.w(TAG, "startActivity called from non-Activity context; forcing " +
"Intent.FLAG_ACTIVITY_NEW_TASK for: " + intent);
- launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK;
+ launchFlags |= FLAG_ACTIVITY_NEW_TASK;
}
} else if (sourceRecord.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) {
// The original activity who is starting us is running as a single
// instance... this new activity it is starting must go on its
// own task.
- launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK;
+ launchFlags |= FLAG_ACTIVITY_NEW_TASK;
} else if (launchSingleInstance || launchSingleTask) {
// The activity being started is a single instance... it always
// gets launched into its own task.
- launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK;
+ launchFlags |= FLAG_ACTIVITY_NEW_TASK;
}
}
@@ -2170,10 +2173,10 @@
// so we don't want to blindly throw it in to that task. Instead we will take
// the NEW_TASK flow and try to find a task for it. But save the task information
// so it can be used when creating the new task.
- if ((launchFlags & Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {
+ if ((launchFlags & FLAG_ACTIVITY_NEW_TASK) == 0) {
Slog.w(TAG, "startActivity called from finishing " + sourceRecord
+ "; forcing " + "Intent.FLAG_ACTIVITY_NEW_TASK for: " + intent);
- launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK;
+ launchFlags |= FLAG_ACTIVITY_NEW_TASK;
newTaskInfo = sourceRecord.info;
newTaskIntent = sourceRecord.task.intent;
}
@@ -2192,216 +2195,192 @@
intent.setFlags(launchFlags);
final boolean noAnimation = (launchFlags & Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0;
- // We may want to try to place the new activity in to an existing task. We always
- // do this if the target activity is singleTask or singleInstance; we will also do
- // this if NEW_TASK has been requested, and there is not an additional qualifier telling
- // us to still place it in a new task: multi task, always doc mode, or being asked to
- // launch this as a new task behind the current one.
- if (((launchFlags & Intent.FLAG_ACTIVITY_NEW_TASK) != 0 &&
- (launchFlags & Intent.FLAG_ACTIVITY_MULTIPLE_TASK) == 0)
- || launchSingleInstance || launchSingleTask) {
- // If bring to front is requested, and no result is requested and we have not
- // been given an explicit task to launch in to, and
- // we can find a task that was started with this same
- // component, then instead of launching bring that one to the front.
- if (inTask == null && r.resultTo == null) {
- // See if there is a task to bring to the front. If this is
- // a SINGLE_INSTANCE activity, there can be one and only one
- // instance of it in the history, and it is always in its own
- // unique task, so we do a special search.
- ActivityRecord intentActivity = !launchSingleInstance ?
- findTaskLocked(r) : findActivityLocked(intent, r.info);
- if (intentActivity != null) {
- // When the flags NEW_TASK and CLEAR_TASK are set, then the task gets reused
- // but still needs to be a lock task mode violation since the task gets
- // cleared out and the device would otherwise leave the locked task.
- if (isLockTaskModeViolation(intentActivity.task,
- (launchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK))
- == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK))) {
- showLockTaskToast();
- Slog.e(TAG, "startActivityUnchecked: Attempt to violate Lock Task Mode");
- return ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION;
+ ActivityRecord intentActivity = getReusableIntentActivity(r, inTask, intent,
+ launchSingleInstance, launchSingleTask, launchFlags);
+ if (intentActivity != null) {
+ // When the flags NEW_TASK and CLEAR_TASK are set, then the task gets reused
+ // but still needs to be a lock task mode violation since the task gets
+ // cleared out and the device would otherwise leave the locked task.
+ if (isLockTaskModeViolation(intentActivity.task,
+ (launchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK))
+ == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK))) {
+ showLockTaskToast();
+ Slog.e(TAG, "startActivityUnchecked: Attempt to violate Lock Task Mode");
+ return ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION;
+ }
+ if (r.task == null) {
+ r.task = intentActivity.task;
+ }
+ if (intentActivity.task.intent == null) {
+ // This task was started because of movement of the activity based on affinity...
+ // Now that we are actually launching it, we can assign the base intent.
+ intentActivity.task.setIntent(r);
+ }
+ targetStack = intentActivity.task.stack;
+ targetStack.mLastPausedActivity = null;
+ // If the target task is not in the front, then we need
+ // to bring it to the front... except... well, with
+ // SINGLE_TASK_LAUNCH it's not entirely clear. We'd like
+ // to have the same behavior as if a new instance was
+ // being started, which means not bringing it to the front
+ // if the caller is not itself in the front.
+ final ActivityStack focusStack = getFocusedStack();
+ ActivityRecord curTop = (focusStack == null)
+ ? null : focusStack.topRunningNonDelayedActivityLocked(notTop);
+ boolean movedToFront = false;
+ if (curTop != null && (curTop.task != intentActivity.task ||
+ curTop.task != focusStack.topTask())) {
+ r.intent.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
+ if (sourceRecord == null || (sourceStack.topActivity() != null &&
+ sourceStack.topActivity().task == sourceRecord.task)) {
+ // We really do want to push this one into the user's face, right now.
+ if (launchTaskBehind && sourceRecord != null) {
+ intentActivity.setTaskToAffiliateWith(sourceRecord.task);
}
- if (r.task == null) {
- r.task = intentActivity.task;
+ movedHome = true;
+ targetStack.moveTaskToFrontLocked(intentActivity.task, noAnimation,
+ options, r.appTimeTracker, "bringingFoundTaskToFront");
+ movedToFront = true;
+ if ((launchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME))
+ == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME)) {
+ // Caller wants to appear on home activity.
+ intentActivity.task.setTaskToReturnTo(HOME_ACTIVITY_TYPE);
}
- if (intentActivity.task.intent == null) {
- // This task was started because of movement of
- // the activity based on affinity... now that we
- // are actually launching it, we can assign the
- // base intent.
- intentActivity.task.setIntent(r);
- }
- targetStack = intentActivity.task.stack;
- targetStack.mLastPausedActivity = null;
- // If the target task is not in the front, then we need
- // to bring it to the front... except... well, with
- // SINGLE_TASK_LAUNCH it's not entirely clear. We'd like
- // to have the same behavior as if a new instance was
- // being started, which means not bringing it to the front
- // if the caller is not itself in the front.
- final ActivityStack focusStack = getFocusedStack();
- ActivityRecord curTop = (focusStack == null)
- ? null : focusStack.topRunningNonDelayedActivityLocked(notTop);
- boolean movedToFront = false;
- if (curTop != null && (curTop.task != intentActivity.task ||
- curTop.task != focusStack.topTask())) {
- r.intent.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
- if (sourceRecord == null || (sourceStack.topActivity() != null &&
- sourceStack.topActivity().task == sourceRecord.task)) {
- // We really do want to push this one into the user's face, right now.
- if (launchTaskBehind && sourceRecord != null) {
- intentActivity.setTaskToAffiliateWith(sourceRecord.task);
- }
- movedHome = true;
- targetStack.moveTaskToFrontLocked(intentActivity.task, noAnimation,
- options, r.appTimeTracker, "bringingFoundTaskToFront");
- movedToFront = true;
- if ((launchFlags &
- (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME))
- == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME)) {
- // Caller wants to appear on home activity.
- intentActivity.task.setTaskToReturnTo(HOME_ACTIVITY_TYPE);
- }
- options = null;
- }
- }
- if (!movedToFront && doResume) {
- if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Bring to front target: " + targetStack
- + " from " + intentActivity);
- targetStack.moveToFront("intentActivityFound");
- }
-
- // If the caller has requested that the target task be
- // reset, then do so.
- if ((launchFlags&Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) {
- intentActivity = targetStack.resetTaskIfNeededLocked(intentActivity, r);
- }
- if ((startFlags & ActivityManager.START_FLAG_ONLY_IF_NEEDED) != 0) {
- // We don't need to start a new activity, and
- // the client said not to do anything if that
- // is the case, so this is it! And for paranoia, make
- // sure we have correctly resumed the top activity.
- if (doResume) {
- resumeTopActivitiesLocked(targetStack, null, options);
-
- // Make sure to notify Keyguard as well if we are not running an app
- // transition later.
- if (!movedToFront) {
- notifyActivityDrawnForKeyguard();
- }
- } else {
- ActivityOptions.abort(options);
- }
- updateUserStackLocked(r.userId, targetStack);
- return ActivityManager.START_RETURN_INTENT_TO_CALLER;
- }
- if ((launchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK))
- == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK)) {
- // The caller has requested to completely replace any
- // existing task with its new activity. Well that should
- // not be too hard...
- reuseTask = intentActivity.task;
- reuseTask.performClearTaskLocked();
- reuseTask.setIntent(r);
- } else if ((launchFlags & FLAG_ACTIVITY_CLEAR_TOP) != 0
- || launchSingleInstance || launchSingleTask) {
- // In this situation we want to remove all activities
- // from the task up to the one being started. In most
- // cases this means we are resetting the task to its
- // initial state.
- ActivityRecord top =
- intentActivity.task.performClearTaskLocked(r, launchFlags);
- if (top != null) {
- if (top.frontOfTask) {
- // Activity aliases may mean we use different
- // intents for the top activity, so make sure
- // the task now has the identity of the new
- // intent.
- top.task.setIntent(r);
- }
- ActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT,
- r, top.task);
- top.deliverNewIntentLocked(callingUid, r.intent, r.launchedFromPackage);
- } else {
- // A special case: we need to start the activity because it is not
- // currently running, and the caller has asked to clear the current
- // task to have this activity at the top.
- addingToTask = true;
- // Now pretend like this activity is being started by the top of its
- // task, so it is put in the right place.
- sourceRecord = intentActivity;
- TaskRecord task = sourceRecord.task;
- if (task != null && task.stack == null) {
- // Target stack got cleared when we all activities were removed
- // above. Go ahead and reset it.
- targetStack = computeStackFocus(
- sourceRecord, false /* newTask */, null /* bounds */);
- targetStack.addTask(task,
- !launchTaskBehind /* toTop */, "startActivityUnchecked");
- }
-
- }
- } else if (r.realActivity.equals(intentActivity.task.realActivity)) {
- // In this case the top activity on the task is the
- // same as the one being launched, so we take that
- // as a request to bring the task to the foreground.
- // If the top activity in the task is the root
- // activity, deliver this new intent to it if it
- // desires.
- if (((launchFlags&Intent.FLAG_ACTIVITY_SINGLE_TOP) != 0 || launchSingleTop)
- && intentActivity.realActivity.equals(r.realActivity)) {
- ActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT, r,
- intentActivity.task);
- if (intentActivity.frontOfTask) {
- intentActivity.task.setIntent(r);
- }
- intentActivity.deliverNewIntentLocked(callingUid, r.intent,
- r.launchedFromPackage);
- } else if (!r.intent.filterEquals(intentActivity.task.intent)) {
- // In this case we are launching the root activity
- // of the task, but with a different intent. We
- // should start a new instance on top.
- addingToTask = true;
- sourceRecord = intentActivity;
- }
- } else if ((launchFlags&Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) == 0) {
- // In this case an activity is being launched in to an
- // existing task, without resetting that task. This
- // is typically the situation of launching an activity
- // from a notification or shortcut. We want to place
- // the new activity on top of the current task.
- addingToTask = true;
- sourceRecord = intentActivity;
- } else if (!intentActivity.task.rootWasReset) {
- // In this case we are launching in to an existing task
- // that has not yet been started from its front door.
- // The current task has been brought to the front.
- // Ideally, we'd probably like to place this new task
- // at the bottom of its stack, but that's a little hard
- // to do with the current organization of the code so
- // for now we'll just drop it.
- intentActivity.task.setIntent(r);
- }
- if (!addingToTask && reuseTask == null) {
- // We didn't do anything... but it was needed (a.k.a., client
- // don't use that intent!) And for paranoia, make
- // sure we have correctly resumed the top activity.
- if (doResume) {
- targetStack.resumeTopActivityLocked(null, options);
- if (!movedToFront) {
- // Make sure to notify Keyguard as well if we are not running an app
- // transition later.
- notifyActivityDrawnForKeyguard();
- }
- } else {
- ActivityOptions.abort(options);
- }
- updateUserStackLocked(r.userId, targetStack);
- return ActivityManager.START_TASK_TO_FRONT;
- }
+ options = null;
}
}
+ if (!movedToFront && doResume) {
+ if (DEBUG_TASKS) Slog.d(TAG_TASKS, "Bring to front target: " + targetStack
+ + " from " + intentActivity);
+ targetStack.moveToFront("intentActivityFound");
+ }
+
+ // If the caller has requested that the target task be
+ // reset, then do so.
+ if ((launchFlags & Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) {
+ intentActivity = targetStack.resetTaskIfNeededLocked(intentActivity, r);
+ }
+ if ((startFlags & ActivityManager.START_FLAG_ONLY_IF_NEEDED) != 0) {
+ // We don't need to start a new activity, and
+ // the client said not to do anything if that
+ // is the case, so this is it! And for paranoia, make
+ // sure we have correctly resumed the top activity.
+ if (doResume) {
+ resumeTopActivitiesLocked(targetStack, null, options);
+
+ // Make sure to notify Keyguard as well if we are not running an app
+ // transition later.
+ if (!movedToFront) {
+ notifyActivityDrawnForKeyguard();
+ }
+ } else {
+ ActivityOptions.abort(options);
+ }
+ updateUserStackLocked(r.userId, targetStack);
+ return ActivityManager.START_RETURN_INTENT_TO_CALLER;
+ }
+ if ((launchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK))
+ == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK)) {
+ // The caller has requested to completely replace any
+ // existing task with its new activity. Well that should
+ // not be too hard...
+ reuseTask = intentActivity.task;
+ reuseTask.performClearTaskLocked();
+ reuseTask.setIntent(r);
+ } else if ((launchFlags & FLAG_ACTIVITY_CLEAR_TOP) != 0
+ || launchSingleInstance || launchSingleTask) {
+ // In this situation we want to remove all activities
+ // from the task up to the one being started. In most
+ // cases this means we are resetting the task to its
+ // initial state.
+ ActivityRecord top = intentActivity.task.performClearTaskLocked(r, launchFlags);
+ if (top != null) {
+ if (top.frontOfTask) {
+ // Activity aliases may mean we use different
+ // intents for the top activity, so make sure
+ // the task now has the identity of the new
+ // intent.
+ top.task.setIntent(r);
+ }
+ ActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT, r, top.task);
+ top.deliverNewIntentLocked(callingUid, r.intent, r.launchedFromPackage);
+ } else {
+ // A special case: we need to start the activity because it is not
+ // currently running, and the caller has asked to clear the current
+ // task to have this activity at the top.
+ addingToTask = true;
+ // Now pretend like this activity is being started by the top of its
+ // task, so it is put in the right place.
+ sourceRecord = intentActivity;
+ TaskRecord task = sourceRecord.task;
+ if (task != null && task.stack == null) {
+ // Target stack got cleared when we all activities were removed
+ // above. Go ahead and reset it.
+ targetStack = computeStackFocus(
+ sourceRecord, false /* newTask */, null /* bounds */);
+ targetStack.addTask(task,
+ !launchTaskBehind /* toTop */, "startActivityUnchecked");
+ }
+
+ }
+ } else if (r.realActivity.equals(intentActivity.task.realActivity)) {
+ // In this case the top activity on the task is the
+ // same as the one being launched, so we take that
+ // as a request to bring the task to the foreground.
+ // If the top activity in the task is the root
+ // activity, deliver this new intent to it if it
+ // desires.
+ if (((launchFlags&Intent.FLAG_ACTIVITY_SINGLE_TOP) != 0 || launchSingleTop)
+ && intentActivity.realActivity.equals(r.realActivity)) {
+ ActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT, r,
+ intentActivity.task);
+ if (intentActivity.frontOfTask) {
+ intentActivity.task.setIntent(r);
+ }
+ intentActivity.deliverNewIntentLocked(callingUid, r.intent,
+ r.launchedFromPackage);
+ } else if (!r.intent.filterEquals(intentActivity.task.intent)) {
+ // In this case we are launching the root activity
+ // of the task, but with a different intent. We
+ // should start a new instance on top.
+ addingToTask = true;
+ sourceRecord = intentActivity;
+ }
+ } else if ((launchFlags&Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) == 0) {
+ // In this case an activity is being launched in to an
+ // existing task, without resetting that task. This
+ // is typically the situation of launching an activity
+ // from a notification or shortcut. We want to place
+ // the new activity on top of the current task.
+ addingToTask = true;
+ sourceRecord = intentActivity;
+ } else if (!intentActivity.task.rootWasReset) {
+ // In this case we are launching in to an existing task
+ // that has not yet been started from its front door.
+ // The current task has been brought to the front.
+ // Ideally, we'd probably like to place this new task
+ // at the bottom of its stack, but that's a little hard
+ // to do with the current organization of the code so
+ // for now we'll just drop it.
+ intentActivity.task.setIntent(r);
+ }
+ if (!addingToTask && reuseTask == null) {
+ // We didn't do anything... but it was needed (a.k.a., client
+ // don't use that intent!) And for paranoia, make
+ // sure we have correctly resumed the top activity.
+ if (doResume) {
+ targetStack.resumeTopActivityLocked(null, options);
+ if (!movedToFront) {
+ // Make sure to notify Keyguard as well if we are not running an app
+ // transition later.
+ notifyActivityDrawnForKeyguard();
+ }
+ } else {
+ ActivityOptions.abort(options);
+ }
+ updateUserStackLocked(r.userId, targetStack);
+ return ActivityManager.START_TASK_TO_FRONT;
+ }
}
//String uri = r.intent.toURI();
@@ -2460,7 +2439,7 @@
// Should this be considered a new task?
if (r.resultTo == null && inTask == null && !addingToTask
- && (launchFlags & Intent.FLAG_ACTIVITY_NEW_TASK) != 0) {
+ && (launchFlags & FLAG_ACTIVITY_NEW_TASK) != 0) {
newTask = true;
targetStack = computeStackFocus(r, newTask, newBounds);
if (doResume) {
@@ -2646,6 +2625,37 @@
return ActivityManager.START_SUCCESS;
}
+ /**
+ * Decide whether the new activity should be inserted into an existing task. Returns null if not
+ * or an ActivityRecord with the task into which the new activity should be added.
+ */
+ private ActivityRecord getReusableIntentActivity(ActivityRecord r, TaskRecord inTask, Intent intent,
+ boolean launchSingleInstance, boolean launchSingleTask, int launchFlags) {
+ // We may want to try to place the new activity in to an existing task. We always
+ // do this if the target activity is singleTask or singleInstance; we will also do
+ // this if NEW_TASK has been requested, and there is not an additional qualifier telling
+ // us to still place it in a new task: multi task, always doc mode, or being asked to
+ // launch this as a new task behind the current one.
+ boolean putIntoExistingTask = ((launchFlags & FLAG_ACTIVITY_NEW_TASK) != 0 &&
+ (launchFlags & FLAG_ACTIVITY_MULTIPLE_TASK) == 0)
+ || launchSingleInstance || launchSingleTask;
+ // If bring to front is requested, and no result is requested and we have not
+ // been given an explicit task to launch in to, and
+ // we can find a task that was started with this same
+ // component, then instead of launching bring that one to the front.
+ putIntoExistingTask &= inTask == null && r.resultTo == null;
+ ActivityRecord intentActivity = null;
+ if (putIntoExistingTask) {
+ // See if there is a task to bring to the front. If this is
+ // a SINGLE_INSTANCE activity, there can be one and only one
+ // instance of it in the history, and it is always in its own
+ // unique task, so we do a special search.
+ intentActivity = launchSingleInstance ?
+ findActivityLocked(intent, r.info) : findTaskLocked(r);
+ }
+ return intentActivity;
+ }
+
final void doPendingActivityLaunchesLocked(boolean doResume) {
while (!mPendingActivityLaunches.isEmpty()) {
PendingActivityLaunch pal = mPendingActivityLaunches.remove(0);
@@ -4708,8 +4718,8 @@
}
class ActivityContainer extends android.app.IActivityContainer.Stub {
- final static int FORCE_NEW_TASK_FLAGS = Intent.FLAG_ACTIVITY_NEW_TASK |
- Intent.FLAG_ACTIVITY_MULTIPLE_TASK | Intent.FLAG_ACTIVITY_NO_ANIMATION;
+ final static int FORCE_NEW_TASK_FLAGS = FLAG_ACTIVITY_NEW_TASK |
+ FLAG_ACTIVITY_MULTIPLE_TASK | Intent.FLAG_ACTIVITY_NO_ANIMATION;
final int mStackId;
IActivityContainerCallback mCallback = null;
final ActivityStack mStack;
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 3a8a988..e0f85c5 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -436,12 +436,6 @@
final String[] mSeparateProcesses;
final boolean mIsUpgrade;
- // This is where all application persistent data goes.
- final File mAppDataDir;
-
- // This is where all application persistent data goes for secondary users.
- final File mUserAppDataDir;
-
/** The location for ASEC container files on internal storage. */
final String mAsecInternalPath;
@@ -953,7 +947,7 @@
// Recordkeeping of restore-after-install operations that are currently in flight
// between the Package Manager and the Backup Manager
- class PostInstallData {
+ static class PostInstallData {
public InstallArgs args;
public PackageInstalledInfo res;
@@ -1070,7 +1064,7 @@
}
long timeInMillis;
try {
- timeInMillis = Long.parseLong(timeInMillisString.toString());
+ timeInMillis = Long.parseLong(timeInMillisString);
} catch (NumberFormatException e) {
throw new IOException("Failed to parse " + timeInMillisString
+ " as a long.", e);
@@ -1987,12 +1981,10 @@
Watchdog.getInstance().addThread(mHandler, WATCHDOG_TIMEOUT);
File dataDir = Environment.getDataDirectory();
- mAppDataDir = new File(dataDir, "data");
mAppInstallDir = new File(dataDir, "app");
mAppLib32InstallDir = new File(dataDir, "app-lib");
mEphemeralInstallDir = new File(dataDir, "app-ephemeral");
mAsecInternalPath = new File(dataDir, "app-asec").getPath();
- mUserAppDataDir = new File(dataDir, "user");
mDrmAppPrivateInstallDir = new File(dataDir, "app-private");
sUserManager = new UserManagerService(context, this, mPackages);
@@ -3368,14 +3360,6 @@
}
}
- private void checkValidCaller(int uid, int userId) {
- if (UserHandle.getUserId(uid) == userId || uid == Process.SYSTEM_UID || uid == 0)
- return;
-
- throw new SecurityException("Caller uid=" + uid
- + " is not privileged to communicate with user=" + userId);
- }
-
@Override
public int checkPermission(String permName, String pkgName, int userId) {
if (!sUserManager.exists(userId)) {
@@ -4102,10 +4086,12 @@
synchronized (mPackages) {
if (mProtectedBroadcasts.contains(actionName)) {
return true;
- } else if (actionName != null
- && actionName.startsWith("android.net.netmon.lingerExpired")) {
- // TODO: remove this terrible hack
- return true;
+ } else if (actionName != null) {
+ // TODO: remove these terrible hacks
+ if (actionName.startsWith("android.net.netmon.lingerExpired")
+ || actionName.startsWith("com.android.server.sip.SipWakeupTimer")) {
+ return true;
+ }
}
}
return false;
@@ -10497,7 +10483,7 @@
ArrayList<IntentFilter> result = new ArrayList<>();
for (int n=0; n<count; n++) {
PackageParser.Activity activity = pkg.activities.get(n);
- if (activity.intents != null || activity.intents.size() > 0) {
+ if (activity.intents != null && activity.intents.size() > 0) {
result.addAll(activity.intents);
}
}
@@ -10916,7 +10902,7 @@
}
}
- class MoveInfo {
+ static class MoveInfo {
final int moveId;
final String fromUuid;
final String toUuid;
@@ -12222,7 +12208,7 @@
}
}
- class PackageInstalledInfo {
+ static class PackageInstalledInfo {
String name;
int uid;
// The set of users that originally had this package installed.
@@ -13163,10 +13149,6 @@
}
}
- private static boolean isMultiArch(PackageSetting ps) {
- return (ps.pkgFlags & ApplicationInfo.FLAG_MULTIARCH) != 0;
- }
-
private static boolean isMultiArch(ApplicationInfo info) {
return (info.flags & ApplicationInfo.FLAG_MULTIARCH) != 0;
}
@@ -13179,10 +13161,6 @@
return (ps.pkgFlags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0;
}
- private static boolean isExternal(ApplicationInfo info) {
- return (info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0;
- }
-
private static boolean isEphemeral(PackageParser.Package pkg) {
return pkg.applicationInfo.isEphemeralApp();
}
@@ -13866,7 +13844,7 @@
return ret;
}
- private final class ClearStorageConnection implements ServiceConnection {
+ private final static class ClearStorageConnection implements ServiceConnection {
IMediaContainerService mContainerService;
@Override
@@ -15116,7 +15094,9 @@
// First, verify that this is a valid class name.
PackageParser.Package pkg = pkgSetting.pkg;
if (pkg == null || !pkg.hasComponentClassName(className)) {
- if (pkg.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.JELLY_BEAN) {
+ if (pkg != null &&
+ pkg.applicationInfo.targetSdkVersion >=
+ Build.VERSION_CODES.JELLY_BEAN) {
throw new IllegalArgumentException("Component class " + className
+ " does not exist in " + packageName);
} else {
@@ -17326,7 +17306,7 @@
}
}
- private final class OnPermissionChangeListeners extends Handler {
+ private final static class OnPermissionChangeListeners extends Handler {
private static final int MSG_ON_PERMISSIONS_CHANGED = 1;
private final RemoteCallbackList<IOnPermissionsChangeListener> mPermissionListeners =
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index fd5c704..b49641f 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -19,6 +19,7 @@
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WINDOW_MOVEMENT;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -122,21 +123,6 @@
// True if the windows associated with this token should be cropped to their stack bounds.
boolean mCropWindowsToStack;
- // This application will have its window replaced due to relaunch. This allows window manager
- // to differentiate between simple removal of a window and replacement. In the latter case it
- // will preserve the old window until the new one is drawn.
- boolean mWillReplaceWindow;
- // If true, the replaced window was already requested to be removed.
- boolean mReplacingRemoveRequested;
- // Whether the replacement of the window should trigger app transition animation.
- boolean mAnimateReplacingWindow;
- // If not null, the window that will be used to replace the old one. This is being set when
- // the window is added and unset when this window reports its first draw.
- WindowState mReplacingWindow;
- // Whether the new window has replaced the old one, so the old one can be removed without
- // blinking.
- boolean mHasReplacedWindow;
-
AppWindowToken(WindowManagerService _service, IApplicationToken _token,
boolean _voiceInteraction) {
super(_service, _token.asBinder(),
@@ -392,6 +378,62 @@
}
}
+ void setReplacingWindows(boolean animate) {
+ if (DEBUG_ADD_REMOVE) Slog.d(TAG_WM, "Marking app token " + appWindowToken
+ + " with replacing windows.");
+
+ for (int i = allAppWindows.size() - 1; i >= 0; i--) {
+ final WindowState w = allAppWindows.get(i);
+ w.setReplacing(animate);
+ }
+ if (animate) {
+ // Set-up dummy animation so we can start treating windows associated with this
+ // token like they are in transition before the new app window is ready for us to
+ // run the real transition animation.
+ if (DEBUG_APP_TRANSITIONS) Slog.v(TAG_WM,
+ "setReplacingWindow() Setting dummy animation on: " + this);
+ mAppAnimator.setDummyAnimation();
+ }
+ }
+
+ void addWindow(WindowState w) {
+ for (int i = allAppWindows.size() - 1; i >= 0; i--) {
+ WindowState candidate = allAppWindows.get(i);
+ if (candidate.mWillReplaceWindow && candidate.mReplacingWindow == null &&
+ candidate.getWindowTag().equals(w.getWindowTag().toString())) {
+ candidate.mReplacingWindow = w;
+ }
+ }
+ allAppWindows.add(w);
+ }
+
+ boolean waitingForReplacement() {
+ for (int i = allAppWindows.size() -1; i >= 0; i--) {
+ WindowState candidate = allAppWindows.get(i);
+ if (candidate.mWillReplaceWindow) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ void clearTimedoutReplaceesLocked() {
+ for (int i = allAppWindows.size() - 1; i >= 0;
+ // removeWindowLocked at bottom of loop may remove multiple entries from
+ // allAppWindows if the window to be removed has child windows. It also may
+ // not remove any windows from allAppWindows at all if win is exiting and
+ // currently animating away. This ensures that winNdx is monotonically decreasing
+ // and never beyond allAppWindows bounds.
+ i = Math.min(i - 1, allAppWindows.size() - 1)) {
+ WindowState candidate = allAppWindows.get(i);
+ if (candidate.mWillReplaceWindow == false) {
+ continue;
+ }
+ candidate.mWillReplaceWindow = false;
+ service.removeWindowLocked(candidate);
+ }
+ }
+
@Override
void dump(PrintWriter pw, String prefix) {
super.dump(pw, prefix);
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 3db9ae0..2e424d0 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -506,7 +506,7 @@
inFreeformSpace = stack != null && stack.mStackId == FREEFORM_WORKSPACE_STACK_ID;
}
- replacing = replacing || (w.mAppToken != null && w.mAppToken.mWillReplaceWindow);
+ replacing = replacing || w.mWillReplaceWindow;
// If the app is executing an animation because the keyguard is going away,
// keep the wallpaper during the animation so it doesn't flicker out.
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index a6523a4..6a5183f 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -918,10 +918,6 @@
}
void requestRemovalOfReplacedWindows(WindowState win) {
- final AppWindowToken token = win.mAppToken;
- if (token != null && token.mWillReplaceWindow && token.mReplacingWindow == win) {
- token.mHasReplacedWindow = true;
- }
mRemoveReplacedWindows = true;
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 4190bd77..456c416 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -269,6 +269,9 @@
/** Amount of time (in milliseconds) to delay before declaring a window freeze timeout. */
static final int WINDOW_FREEZE_TIMEOUT_DURATION = 2000;
+ /** Amount of time (in milliseconds) to delay before declaring a window replacement timeout. */
+ static final int WINDOW_REPLACEMENT_TIMEOUT_DURATION = 2000;
+
/** Amount of time to allow a last ANR message to exist before freeing the memory. */
static final int LAST_ANR_LIFETIME_DURATION_MSECS = 2 * 60 * 60 * 1000; // Two hours
/**
@@ -1350,10 +1353,7 @@
final AppWindowToken appToken = win.mAppToken;
if (appToken != null) {
if (addToToken) {
- appToken.allAppWindows.add(win);
- }
- if (appToken.mWillReplaceWindow) {
- appToken.mReplacingWindow = win;
+ appToken.addWindow(win);
}
}
}
@@ -2088,14 +2088,15 @@
}
private void prepareWindowReplacementTransition(AppWindowToken atoken) {
- if (atoken == null || !atoken.mWillReplaceWindow || !atoken.mAnimateReplacingWindow) {
+ if (atoken == null) {
return;
}
atoken.allDrawn = false;
WindowState replacedWindow = null;
for (int i = atoken.windows.size() - 1; i >= 0 && replacedWindow == null; i--) {
WindowState candidate = atoken.windows.get(i);
- if (candidate.mExiting) {
+ if (candidate.mExiting && candidate.mWillReplaceWindow
+ && candidate.mAnimateReplacingWindow) {
replacedWindow = candidate;
}
}
@@ -2195,7 +2196,7 @@
+ " app-animation="
+ (win.mAppToken != null ? win.mAppToken.mAppAnimator.animation : null)
+ " mWillReplaceWindow="
- + (win.mAppToken != null ? win.mAppToken.mWillReplaceWindow : false)
+ + win.mWillReplaceWindow
+ " inPendingTransaction="
+ (win.mAppToken != null ? win.mAppToken.inPendingTransaction : false)
+ " mDisplayFrozen=" + mDisplayFrozen);
@@ -2207,13 +2208,13 @@
// animation wouldn't be seen.
if (win.mHasSurface && okToDisplay()) {
final AppWindowToken appToken = win.mAppToken;
- if (appToken != null && appToken.mWillReplaceWindow) {
+ if (win.mWillReplaceWindow) {
// This window is going to be replaced. We need to keep it around until the new one
// gets added, then we will get rid of this one.
if (DEBUG_ADD_REMOVE) Slog.v(TAG_WM, "Preserving " + win + " until the new one is "
+ "added");
win.mExiting = true;
- appToken.mReplacingRemoveRequested = true;
+ win.mReplacingRemoveRequested = true;
Binder.restoreCallingIdentity(origId);
return;
}
@@ -4052,7 +4053,7 @@
// transition animation
// * or this is an opening app and windows are being replaced.
if (wtoken.hidden == visible || (wtoken.hidden && wtoken.mIsExiting) ||
- (visible && wtoken.mWillReplaceWindow)) {
+ (visible && wtoken.waitingForReplacement())) {
boolean changed = false;
if (DEBUG_APP_TRANSITIONS) Slog.v(
TAG_WM, "Changing app " + wtoken + " hidden=" + wtoken.hidden
@@ -7514,6 +7515,8 @@
public static final int TWO_FINGER_SCROLL_START = 45;
public static final int SHOW_NON_RESIZEABLE_DOCK_TOAST = 46;
+ public static final int WINDOW_REPLACEMENT_TIMEOUT = 47;
+
/**
* Used to denote that an integer field in a message will not be used.
*/
@@ -8096,6 +8099,13 @@
toast.show();
}
break;
+ case WINDOW_REPLACEMENT_TIMEOUT: {
+ final AppWindowToken token = (AppWindowToken) msg.obj;
+ synchronized (mWindowMap) {
+ token.clearTimedoutReplaceesLocked();
+ }
+ }
+ break;
}
if (DEBUG_WINDOW_TRACE) {
Slog.v(TAG_WM, "handleMessage: exit");
@@ -8618,7 +8628,7 @@
final int numTokens = tokens.size();
for (int tokenNdx = 0; tokenNdx < numTokens; ++tokenNdx) {
final AppWindowToken wtoken = tokens.get(tokenNdx);
- if (wtoken.mIsExiting && !wtoken.mWillReplaceWindow) {
+ if (wtoken.mIsExiting && !wtoken.waitingForReplacement()) {
continue;
}
i = reAddAppWindowsLocked(displayContent, i, wtoken);
@@ -8727,8 +8737,8 @@
private void forceHigherLayerIfNeeded(WindowState w, WindowStateAnimator winAnimator,
AppWindowToken wtoken) {
boolean force = false;
- if (wtoken.mWillReplaceWindow && wtoken.mReplacingWindow != w
- && wtoken.mAnimateReplacingWindow) {
+
+ if (w.mWillReplaceWindow) {
// We know that we will be animating a relaunching window in the near future,
// which will receive a z-order increase. We want the replaced window to
// immediately receive the same treatment, e.g. to be above the dock divider.
@@ -10244,26 +10254,20 @@
* @param token Application token for which the activity will be relaunched.
*/
public void setReplacingWindow(IBinder token, boolean animate) {
+ AppWindowToken appWindowToken = null;
synchronized (mWindowMap) {
- AppWindowToken appWindowToken = findAppWindowToken(token);
+ appWindowToken = findAppWindowToken(token);
if (appWindowToken == null || !appWindowToken.isVisible()) {
Slog.w(TAG_WM, "Attempted to set replacing window on non-existing app token " + token);
return;
}
- if (DEBUG_ADD_REMOVE) Slog.d(TAG_WM, "Marking app token " + appWindowToken
- + " as replacing window.");
- appWindowToken.mWillReplaceWindow = true;
- appWindowToken.mHasReplacedWindow = false;
- appWindowToken.mAnimateReplacingWindow = animate;
+ appWindowToken.setReplacingWindows(animate);
+ }
- if (animate) {
- // Set-up dummy animation so we can start treating windows associated with this
- // token like they are in transition before the new app window is ready for us to
- // run the real transition animation.
- if (DEBUG_APP_TRANSITIONS) Slog.v(TAG_WM,
- "setReplacingWindow() Setting dummy animation on: " + appWindowToken);
- appWindowToken.mAppAnimator.setDummyAnimation();
- }
+ if (appWindowToken != null) {
+ mH.removeMessages(H.WINDOW_REPLACEMENT_TIMEOUT);
+ mH.sendMessageDelayed(mH.obtainMessage(H.WINDOW_REPLACEMENT_TIMEOUT, appWindowToken),
+ WINDOW_REPLACEMENT_TIMEOUT_DURATION);
}
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 5a589e3..e4a6806 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -75,6 +75,7 @@
import static android.view.WindowManager.LayoutParams.MATCH_PARENT;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
@@ -403,6 +404,18 @@
// used to start an entering animation earlier.
public boolean mSurfaceSaved = false;
+ // This window will be replaced due to relaunch. This allows window manager
+ // to differentiate between simple removal of a window and replacement. In the latter case it
+ // will preserve the old window until the new one is drawn.
+ boolean mWillReplaceWindow = false;
+ // If true, the replaced window was already requested to be removed.
+ boolean mReplacingRemoveRequested = false;
+ // Whether the replacement of the window should trigger app transition animation.
+ boolean mAnimateReplacingWindow = false;
+ // If not null, the window that will be used to replace the old one. This is being set when
+ // the window is added and unset when this window reports its first draw.
+ WindowState mReplacingWindow = null;
+
/**
* Wake lock for drawing.
* Even though it's slightly more expensive to do so, we will use a separate wake lock
@@ -580,8 +593,7 @@
@Override
public void computeFrameLw(Rect pf, Rect df, Rect of, Rect cf, Rect vf, Rect dcf, Rect sf,
Rect osf) {
- if (mAppToken != null && mAppToken.mWillReplaceWindow
- && (mExiting || !mAppToken.mReplacingRemoveRequested)) {
+ if (mWillReplaceWindow && (mExiting || !mReplacingRemoveRequested)) {
// This window is being replaced and either already got information that it's being
// removed or we are still waiting for some information. Because of this we don't
// want to apply any more changes to it, so it remains in this state until new window
@@ -1343,17 +1355,17 @@
}
void maybeRemoveReplacedWindow() {
- AppWindowToken token = mAppToken;
- if (token != null && token.mWillReplaceWindow && token.mReplacingWindow == this
- && token.mHasReplacedWindow) {
- if (DEBUG_ADD_REMOVE) Slog.d(TAG, "Removing replacing window: " + this);
- token.mWillReplaceWindow = false;
- token.mAnimateReplacingWindow = false;
- token.mReplacingRemoveRequested = false;
- token.mReplacingWindow = null;
- token.mHasReplacedWindow = false;
- for (int i = token.allAppWindows.size() - 1; i >= 0; i--) {
- final WindowState win = token.allAppWindows.get(i);
+ if (mAppToken == null) {
+ return;
+ }
+ for (int i = mAppToken.allAppWindows.size() - 1; i >= 0; i--) {
+ final WindowState win = mAppToken.allAppWindows.get(i);
+ if (DEBUG_ADD_REMOVE) Slog.d(TAG, "Removing replaced window: " + win);
+ if (win.mWillReplaceWindow && win.mReplacingWindow == this) {
+ win.mWillReplaceWindow = false;
+ win.mAnimateReplacingWindow = false;
+ win.mReplacingRemoveRequested = false;
+ win.mReplacingWindow = null;
if (win.mExiting) {
mService.removeWindowInnerLocked(win);
}
@@ -2161,7 +2173,7 @@
+ " " + getWindowTag();
}
- private CharSequence getWindowTag() {
+ CharSequence getWindowTag() {
CharSequence tag = mAttrs.getTitle();
if (tag == null || tag.length() <= 0) {
tag = mAttrs.packageName;
@@ -2259,4 +2271,12 @@
boolean isChildWindow() {
return mAttachedWindow != null;
}
+
+ void setReplacing(boolean animate) {
+ if ((mAttrs.privateFlags & PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH) == 0) {
+ mWillReplaceWindow = true;
+ mReplacingWindow = null;
+ mAnimateReplacingWindow = animate;
+ }
+ }
}
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index cd82a5f..d4001cd 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -1192,7 +1192,7 @@
// different stack. If we suddenly crop it to the new stack bounds, it might get cut off.
// We don't want it to happen, so we let it ignore the stack bounds until it gets removed.
// The window that will replace it will abide them.
- if (isAnimating() && (appToken.mWillReplaceWindow || w.inDockedWorkspace()
+ if (isAnimating() && (w.mWillReplaceWindow || w.inDockedWorkspace()
|| w.inFreeformWorkspace())) {
return;
}
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index 6001321..160c97f 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -1193,7 +1193,7 @@
// if app window is removed, or window relayout to invisible. We don't want to
// clear it out for windows that get replaced, because the animation depends on
// the flag to remove the replaced window.
- if (win.mAppToken == null || !win.mAppToken.mWillReplaceWindow) {
+ if (!win.mWillReplaceWindow) {
win.mExiting = false;
}
if (win.mWinAnimator.mAnimLayer > layer) {