Merge "Send ranking update on importance change."
diff --git a/api/current.txt b/api/current.txt
index 2fd32c5..8e4531e 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -34348,6 +34348,7 @@
package android.service.notification {
public class Condition implements android.os.Parcelable {
+ ctor public Condition(android.net.Uri, java.lang.String, java.lang.String, java.lang.String, int);
ctor public Condition(android.net.Uri, java.lang.String, java.lang.String, java.lang.String, int, int, int);
method public android.service.notification.Condition copy();
method public int describeContents();
@@ -34379,7 +34380,6 @@
method public final void notifyConditions(android.service.notification.Condition...);
method public android.os.IBinder onBind(android.content.Intent);
method public abstract void onConnected();
- method public abstract void onRequestConditions(int);
method public abstract void onSubscribe(android.net.Uri);
method public abstract void onUnsubscribe(android.net.Uri);
field public static final java.lang.String EXTRA_RULE_ID = "android.content.automatic.ruleId";
@@ -43245,7 +43245,7 @@
method public abstract void setNavigationBarColor(int);
method public void setReenterTransition(android.transition.Transition);
method public abstract void setResizingCaptionDrawable(android.graphics.drawable.Drawable);
- method public final void setRestrictedCaptionAreaListener(android.view.Window.RestrictedCaptionAreaListener);
+ method public final void setRestrictedCaptionAreaListener(android.view.Window.OnRestrictedCaptionAreaChangedListener);
method public void setReturnTransition(android.transition.Transition);
method public void setSharedElementEnterTransition(android.transition.Transition);
method public void setSharedElementExitTransition(android.transition.Transition);
@@ -43332,7 +43332,7 @@
method public abstract android.view.ActionMode onWindowStartingActionMode(android.view.ActionMode.Callback, int);
}
- public static abstract interface Window.RestrictedCaptionAreaListener {
+ public static abstract interface Window.OnRestrictedCaptionAreaChangedListener {
method public abstract void onRestrictedCaptionAreaChanged(android.graphics.Rect);
}
diff --git a/api/removed.txt b/api/removed.txt
index 2c6729d..0bf6594 100644
--- a/api/removed.txt
+++ b/api/removed.txt
@@ -199,6 +199,14 @@
}
+package android.service.notification {
+
+ public abstract class ConditionProviderService extends android.app.Service {
+ method public void onRequestConditions(int);
+ }
+
+}
+
package android.test.mock {
public deprecated class MockPackageManager extends android.content.pm.PackageManager {
diff --git a/api/system-current.txt b/api/system-current.txt
index 5b640cd..b34182f 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -36528,6 +36528,7 @@
package android.service.notification {
public class Condition implements android.os.Parcelable {
+ ctor public Condition(android.net.Uri, java.lang.String, java.lang.String, java.lang.String, int);
ctor public Condition(android.net.Uri, java.lang.String, java.lang.String, java.lang.String, int, int, int);
method public android.service.notification.Condition copy();
method public int describeContents();
@@ -36559,7 +36560,7 @@
method public final void notifyConditions(android.service.notification.Condition...);
method public android.os.IBinder onBind(android.content.Intent);
method public abstract void onConnected();
- method public abstract void onRequestConditions(int);
+ method public void onRequestConditions(int);
method public abstract void onSubscribe(android.net.Uri);
method public abstract void onUnsubscribe(android.net.Uri);
field public static final java.lang.String EXTRA_RULE_ID = "android.content.automatic.ruleId";
@@ -45663,7 +45664,7 @@
method public abstract void setNavigationBarColor(int);
method public void setReenterTransition(android.transition.Transition);
method public abstract void setResizingCaptionDrawable(android.graphics.drawable.Drawable);
- method public final void setRestrictedCaptionAreaListener(android.view.Window.RestrictedCaptionAreaListener);
+ method public final void setRestrictedCaptionAreaListener(android.view.Window.OnRestrictedCaptionAreaChangedListener);
method public void setReturnTransition(android.transition.Transition);
method public void setSharedElementEnterTransition(android.transition.Transition);
method public void setSharedElementExitTransition(android.transition.Transition);
@@ -45750,7 +45751,7 @@
method public abstract android.view.ActionMode onWindowStartingActionMode(android.view.ActionMode.Callback, int);
}
- public static abstract interface Window.RestrictedCaptionAreaListener {
+ public static abstract interface Window.OnRestrictedCaptionAreaChangedListener {
method public abstract void onRestrictedCaptionAreaChanged(android.graphics.Rect);
}
diff --git a/api/test-current.txt b/api/test-current.txt
index 47eec48..6a5917d 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -34363,6 +34363,7 @@
package android.service.notification {
public class Condition implements android.os.Parcelable {
+ ctor public Condition(android.net.Uri, java.lang.String, java.lang.String, java.lang.String, int);
ctor public Condition(android.net.Uri, java.lang.String, java.lang.String, java.lang.String, int, int, int);
method public android.service.notification.Condition copy();
method public int describeContents();
@@ -34394,7 +34395,6 @@
method public final void notifyConditions(android.service.notification.Condition...);
method public android.os.IBinder onBind(android.content.Intent);
method public abstract void onConnected();
- method public abstract void onRequestConditions(int);
method public abstract void onSubscribe(android.net.Uri);
method public abstract void onUnsubscribe(android.net.Uri);
field public static final java.lang.String EXTRA_RULE_ID = "android.content.automatic.ruleId";
@@ -43262,7 +43262,7 @@
method public abstract void setNavigationBarColor(int);
method public void setReenterTransition(android.transition.Transition);
method public abstract void setResizingCaptionDrawable(android.graphics.drawable.Drawable);
- method public final void setRestrictedCaptionAreaListener(android.view.Window.RestrictedCaptionAreaListener);
+ method public final void setRestrictedCaptionAreaListener(android.view.Window.OnRestrictedCaptionAreaChangedListener);
method public void setReturnTransition(android.transition.Transition);
method public void setSharedElementEnterTransition(android.transition.Transition);
method public void setSharedElementExitTransition(android.transition.Transition);
@@ -43349,7 +43349,7 @@
method public abstract android.view.ActionMode onWindowStartingActionMode(android.view.ActionMode.Callback, int);
}
- public static abstract interface Window.RestrictedCaptionAreaListener {
+ public static abstract interface Window.OnRestrictedCaptionAreaChangedListener {
method public abstract void onRestrictedCaptionAreaChanged(android.graphics.Rect);
}
diff --git a/api/test-removed.txt b/api/test-removed.txt
index 2c6729d..0bf6594 100644
--- a/api/test-removed.txt
+++ b/api/test-removed.txt
@@ -199,6 +199,14 @@
}
+package android.service.notification {
+
+ public abstract class ConditionProviderService extends android.app.Service {
+ method public void onRequestConditions(int);
+ }
+
+}
+
package android.test.mock {
public deprecated class MockPackageManager extends android.content.pm.PackageManager {
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 2c6b604..593fe2a 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -452,6 +452,9 @@
*/
boolean performDexOptIfNeeded(String packageName, String instructionSet);
+ boolean performDexOpt(String packageName, String instructionSet, boolean useProfiles,
+ boolean extractOnly, boolean force);
+
void forceDexOpt(String packageName);
/**
diff --git a/core/java/android/service/notification/Condition.java b/core/java/android/service/notification/Condition.java
index 2c7bf65..11737c6 100644
--- a/core/java/android/service/notification/Condition.java
+++ b/core/java/android/service/notification/Condition.java
@@ -16,6 +16,7 @@
package android.service.notification;
+import android.annotation.SystemApi;
import android.content.Context;
import android.net.Uri;
import android.os.Parcel;
@@ -24,7 +25,8 @@
import java.util.Objects;
/**
- * Condition information from condition providers.
+ * Condition information from condition providers. Used to tell the system to enter Do Not Disturb
+ * mode and request that the system exit Do Not Disturb mode.
*/
public class Condition implements Parcelable {
@@ -38,14 +40,47 @@
public static final int FLAG_RELEVANT_NOW = 1 << 0;
public static final int FLAG_RELEVANT_ALWAYS = 1 << 1;
+ /**
+ * The URI representing the condition being updated.
+ * See {@link android.app.AutomaticZenRule#getConditionId()}.
+ */
public final Uri id;
- public final String summary;
- public final String line1;
- public final String line2;
- public final int icon;
- public final int state;
- public final int flags;
+ /**
+ * A summary of what the rule encoded in {@link #id} means when it is enabled. User visible
+ * if the state of the condition is {@link #STATE_TRUE}.
+ */
+ public final String summary;
+
+ /**
+ * Additional information about what the rule encoded in {@link #id} means when it is enabled.
+ * User visible if the state of the condition is {@link #STATE_TRUE}.
+ */
+ public final String line1;
+
+ /**
+ * Additional information about what the rule encoded in {@link #id} means when it is enabled.
+ * User visible if the state of the condition is {@link #STATE_TRUE}.
+ */
+ public final String line2;
+
+ /**
+ * The state of this condition. {@link #STATE_TRUE} will enable Do Not Disturb mode. Any other
+ * state will turn Do Not Disturb off for this rule. Note that Do Not Disturb might still be
+ * enabled globally if other conditions are in a {@link #STATE_TRUE} state.
+ */
+ public final int state;
+
+ @SystemApi
+ public final int flags;
+ @SystemApi
+ public final int icon;
+
+ public Condition(Uri id, String summary, String line1, String line2, int state) {
+ this(id, summary, line1, line2, -1, state, FLAG_RELEVANT_ALWAYS);
+ }
+
+ @SystemApi
public Condition(Uri id, String summary, String line1, String line2, int icon,
int state, int flags) {
if (id == null) throw new IllegalArgumentException("id is required");
diff --git a/core/java/android/service/notification/ConditionProviderService.java b/core/java/android/service/notification/ConditionProviderService.java
index eff09d6..adcc9d6 100644
--- a/core/java/android/service/notification/ConditionProviderService.java
+++ b/core/java/android/service/notification/ConditionProviderService.java
@@ -17,6 +17,7 @@
package android.service.notification;
import android.annotation.SdkConstant;
+import android.annotation.SystemApi;
import android.app.INotificationManager;
import android.app.Service;
import android.content.ComponentName;
@@ -102,13 +103,10 @@
abstract public void onConnected();
/**
- * Called when the system wants to know the state of Conditions managed by this provider.
- *
- * Implementations should evaluate the state of all subscribed conditions, and provide updates
- * by calling {@link #notifyCondition(Condition)} or {@link #notifyConditions(Condition...)}.
- * @param relevance
+ * @removed
*/
- abstract public void onRequestConditions(int relevance);
+ @SystemApi
+ public void onRequestConditions(int relevance) {}
/**
* Called by the system when there is a new {@link Condition} to be managed by this provider.
@@ -131,7 +129,11 @@
}
/**
- * Informs the notification manager that the state of a Condition has changed.
+ * Informs the notification manager that the state of a Condition has changed. Use this method
+ * to put the system into Do Not Disturb mode or request that it exits Do Not Disturb mode. This
+ * call will be ignored unless there is an enabled {@link android.app.AutomaticZenRule} owned by
+ * service that has an {@link android.app.AutomaticZenRule#getConditionId()} equal to this
+ * {@link Condition#id}.
* @param condition the condition that has changed.
*/
public final void notifyCondition(Condition condition) {
@@ -140,7 +142,8 @@
}
/**
- * Informs the notification manager that the state of one or more Conditions has changed.
+ * Informs the notification manager that the state of one or more Conditions has changed. See
+ * {@link #notifyCondition(Condition)} for restrictions.
* @param conditions the changed conditions.
*/
public final void notifyConditions(Condition... conditions) {
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 96b63d1..c68a740 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -34,7 +34,6 @@
import android.media.session.MediaController;
import android.net.Uri;
import android.os.Bundle;
-import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.SystemProperties;
@@ -273,7 +272,7 @@
private Callback mCallback;
private OnWindowDismissedCallback mOnWindowDismissedCallback;
private WindowControllerCallback mWindowControllerCallback;
- private RestrictedCaptionAreaListener mRestrictedCaptionAreaListener;
+ private OnRestrictedCaptionAreaChangedListener mOnRestrictedCaptionAreaChangedListener;
private Rect mRestrictedCaptionAreaRect;
private WindowManager mWindowManager;
private IBinder mAppToken;
@@ -596,7 +595,7 @@
/**
* Callback for clients that want to be aware of where caption draws content.
*/
- public interface RestrictedCaptionAreaListener {
+ public interface OnRestrictedCaptionAreaChangedListener {
/**
* Called when the area where caption draws content changes.
*
@@ -856,8 +855,8 @@
*
* @param listener Callback that will be called when the area changes.
*/
- public final void setRestrictedCaptionAreaListener(RestrictedCaptionAreaListener listener) {
- mRestrictedCaptionAreaListener = listener;
+ public final void setRestrictedCaptionAreaListener(OnRestrictedCaptionAreaChangedListener listener) {
+ mOnRestrictedCaptionAreaChangedListener = listener;
mRestrictedCaptionAreaRect = listener != null ? new Rect() : null;
}
@@ -2126,17 +2125,20 @@
/** @hide */
public void notifyRestrictedCaptionAreaCallback(int left, int top, int right, int bottom) {
- if (mRestrictedCaptionAreaListener != null) {
+ if (mOnRestrictedCaptionAreaChangedListener != null) {
mRestrictedCaptionAreaRect.set(left, top, right, bottom);
- mRestrictedCaptionAreaListener.onRestrictedCaptionAreaChanged(
+ mOnRestrictedCaptionAreaChangedListener.onRestrictedCaptionAreaChanged(
mRestrictedCaptionAreaRect);
}
}
/**
* Set what color should the caption controls be. By default the system will try to determine
- * the color from the theme. You can overwrite this by using {@link #DECOR_CAPTION_SHADE_DARK}
- * or {@link #DECOR_CAPTION_SHADE_DARK}.
+ * the color from the theme. You can overwrite this by using {@link #DECOR_CAPTION_SHADE_DARK},
+ * {@link #DECOR_CAPTION_SHADE_LIGHT}, or {@link #DECOR_CAPTION_SHADE_AUTO}.
+ * @see #DECOR_CAPTION_SHADE_DARK
+ * @see #DECOR_CAPTION_SHADE_LIGHT
+ * @see #DECOR_CAPTION_SHADE_AUTO
*/
public abstract void setDecorCaptionShade(int decorCaptionShade);
diff --git a/core/java/com/android/internal/app/SuggestedLocaleAdapter.java b/core/java/com/android/internal/app/SuggestedLocaleAdapter.java
index c4ec714..478a56d 100644
--- a/core/java/com/android/internal/app/SuggestedLocaleAdapter.java
+++ b/core/java/com/android/internal/app/SuggestedLocaleAdapter.java
@@ -16,6 +16,7 @@
package com.android.internal.app;
+import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -165,6 +166,14 @@
localized.setVisibility(View.VISIBLE);
text.setTextLocale(Locale.getDefault());
}
+ if (mCountryMode) {
+ int layoutDir = TextUtils.getLayoutDirectionFromLocale(item.getParent());
+ //noinspection ResourceType
+ convertView.setLayoutDirection(layoutDir);
+ text.setTextDirection(layoutDir == View.LAYOUT_DIRECTION_RTL
+ ? View.TEXT_DIRECTION_RTL
+ : View.TEXT_DIRECTION_LTR);
+ }
}
return convertView;
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java
index 71e87cb..eea91a0 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java
@@ -129,9 +129,11 @@
@Override
public void onChanged() {
- // TODO: This is causing b/22765812
- mSelection.clear();
mModelIds = mAdapter.getModelIds();
+
+ // Update the selection to remove any disappeared IDs.
+ mSelection.cancelProvisionalSelection();
+ mSelection.intersect(mModelIds);
}
@Override
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/DocumentsProviderHelper.java b/packages/DocumentsUI/tests/src/com/android/documentsui/DocumentsProviderHelper.java
index 467d97e..5df4dca 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/DocumentsProviderHelper.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/DocumentsProviderHelper.java
@@ -105,6 +105,19 @@
return createDocument(root.documentId, mimeType, name);
}
+ public Uri createDocumentWithFlags(String documentId, String mimeType, String name, int flags)
+ throws RemoteException {
+ Bundle in = new Bundle();
+ in.putInt(StubProvider.EXTRA_FLAGS, flags);
+ in.putString(StubProvider.EXTRA_PARENT_ID, documentId);
+ in.putString(Document.COLUMN_MIME_TYPE, mimeType);
+ in.putString(Document.COLUMN_DISPLAY_NAME, name);
+
+ Bundle out = mClient.call("createDocumentWithFlags", null, in);
+ Uri uri = out.getParcelable(DocumentsContract.EXTRA_URI);
+ return uri;
+ }
+
public Uri createFolder(Uri parentUri, String name) {
return createDocument(parentUri, Document.MIME_TYPE_DIR, name);
}
@@ -286,4 +299,5 @@
return DocumentsContract.buildDocumentUri(mAuthority, documentId);
}
+
}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/DownloadsActivityUiTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/DownloadsActivityUiTest.java
index 737a8b6..0a91427 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/DownloadsActivityUiTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/DownloadsActivityUiTest.java
@@ -85,7 +85,7 @@
mDevice.wait(Until.hasObject(By.pkg(TARGET_PKG).depth(0)), TIMEOUT);
mDevice.waitForIdle();
- mBot = new UiBot(mDevice, TIMEOUT);
+ mBot = new UiBot(mDevice, mContext, TIMEOUT);
resetStorage(); // Just in case a test failed and tearDown didn't happen.
}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java
index f0dbfe9..379300b 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/FilesActivityUiTest.java
@@ -82,7 +82,7 @@
mDevice.wait(Until.hasObject(By.pkg(TARGET_PKG).depth(0)), TIMEOUT);
mDevice.waitForIdle();
- mBot = new UiBot(mDevice, TIMEOUT);
+ mBot = new UiBot(mDevice, mContext, TIMEOUT);
resetStorage(); // Just incase a test failed and tearDown didn't happen.
}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/RenameDocumentUiTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/RenameDocumentUiTest.java
new file mode 100644
index 0000000..5c6254f
--- /dev/null
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/RenameDocumentUiTest.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.documentsui;
+
+import static com.android.documentsui.StubProvider.ROOT_0_ID;
+import static com.android.documentsui.UiTestEnvironment.TIMEOUT;
+
+import android.support.test.uiautomator.UiObject;
+import android.support.test.uiautomator.UiObjectNotFoundException;
+import android.test.InstrumentationTestCase;
+import android.test.suitebuilder.annotation.LargeTest;
+
+@LargeTest
+public class RenameDocumentUiTest extends InstrumentationTestCase {
+
+ private static final String TAG = "RenamDocumentUiTest";
+
+ private final String newName = "kitties.log";
+
+ private UiTestEnvironment mHelper;
+
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ mHelper = new UiTestEnvironment(getInstrumentation());
+ mHelper.launch();
+ mHelper.initTestFiles();
+ mHelper.bot().openRoot(ROOT_0_ID);
+ }
+
+ public void testRenameEnabled_SingleSelection() throws Exception {
+ mHelper.bot().selectDocument(UiTestEnvironment.fileName1);
+ mHelper.bot().openOverflowMenu();
+ mHelper.bot().assertMenuEnabled(R.string.menu_rename, true);
+
+ // Dismiss more options window
+ mHelper.device().pressBack();
+ }
+
+ public void testNoRenameSupport_SingleSelection() throws Exception {
+ mHelper.bot().selectDocument(UiTestEnvironment.fileNameNoRename);
+ mHelper.bot().openOverflowMenu();
+ mHelper.bot().assertMenuEnabled(R.string.menu_rename, false);
+
+ // Dismiss more options window
+ mHelper.device().pressBack();
+ }
+
+ public void testOneHasRenameSupport_MultipleSelection() throws Exception {
+ mHelper.bot().selectDocument(UiTestEnvironment.fileName1);
+ mHelper.bot().selectDocument(UiTestEnvironment.fileNameNoRename);
+ mHelper.bot().openOverflowMenu();
+ mHelper.bot().assertMenuEnabled(R.string.menu_rename, false);
+
+ // Dismiss more options window
+ mHelper.device().pressBack();
+ }
+
+ public void testRenameDisabled_MultipleSelection() throws Exception {
+ mHelper.bot().selectDocument(UiTestEnvironment.fileName1);
+ mHelper.bot().selectDocument(UiTestEnvironment.fileName2);
+ mHelper.bot().openOverflowMenu();
+ mHelper.bot().assertMenuEnabled(R.string.menu_rename, false);
+
+ // Dismiss more options window
+ mHelper.device().pressBack();
+ }
+
+ public void testRenameFile_OkButton() throws Exception {
+ mHelper.bot().selectDocument(UiTestEnvironment.fileName1);
+ mHelper.bot().openOverflowMenu();
+ mHelper.bot().openDialog(R.string.menu_rename);
+ mHelper.bot().setDialogText(newName);
+ mHelper.bot().dismissKeyboardIfPresent();
+
+ mHelper.device().waitForIdle(TIMEOUT);
+ mHelper.bot().findRenameDialogOkButton().click();
+ mHelper.device().waitForIdle(TIMEOUT);
+
+ mHelper.bot().assertDocument(UiTestEnvironment.fileName1, false);
+ mHelper.bot().assertDocument(newName, true);
+ mHelper.bot().assertDocumentsCount(mHelper.getDocumentsCountDir0());
+ }
+
+ public void testRenameFile_Enter() throws Exception {
+ mHelper.bot().selectDocument(UiTestEnvironment.fileName1);
+ mHelper.bot().openOverflowMenu();
+ mHelper.bot().openDialog(R.string.menu_rename);
+ mHelper.bot().setDialogText(newName);
+
+ pressEnter();
+
+ mHelper.bot().assertDocument(UiTestEnvironment.fileName1, false);
+ mHelper.bot().assertDocument(newName, true);
+ mHelper.bot().assertDocumentsCount(mHelper.getDocumentsCountDir0());
+ }
+
+ public void testRenameFile_Cancel() throws Exception {
+ mHelper.bot().selectDocument(UiTestEnvironment.fileName1);
+ mHelper.bot().openOverflowMenu();
+ mHelper.bot().openDialog(R.string.menu_rename);
+ mHelper.bot().setDialogText(newName);
+ mHelper.bot().dismissKeyboardIfPresent();
+
+ mHelper.device().waitForIdle(TIMEOUT);
+ mHelper.bot().findRenameDialogCancelButton().click();
+ mHelper.device().waitForIdle(TIMEOUT);
+
+ mHelper.bot().assertDocument(UiTestEnvironment.fileName1, true);
+ mHelper.bot().assertDocument(newName, false);
+ mHelper.bot().assertDocumentsCount(mHelper.getDocumentsCountDir0());
+ }
+
+ public void testRenameDir() throws Exception {
+ String oldName = "Dir1";
+ String newName = "Dir123";
+
+ mHelper.bot().selectDocument(oldName);
+ mHelper.bot().openOverflowMenu();
+ mHelper.bot().openDialog(R.string.menu_rename);
+ mHelper.bot().setDialogText(newName);
+
+ pressEnter();
+
+ mHelper.bot().assertDocument(oldName, false);
+ mHelper.bot().assertDocument(newName, true);
+ mHelper.bot().assertDocumentsCount(mHelper.getDocumentsCountDir0());
+ }
+
+ public void testRename_NameExists() throws Exception {
+ // Check that document with the new name exists
+ mHelper.bot().assertDocument(UiTestEnvironment.fileName2, true);
+ mHelper.bot().selectDocument(UiTestEnvironment.fileName1);
+ mHelper.bot().openOverflowMenu();
+ mHelper.bot().openDialog(R.string.menu_rename);
+ mHelper.bot().setDialogText(UiTestEnvironment.fileName2);
+
+ pressEnter();
+
+ mHelper.bot().assertSnackbar(R.string.rename_error);
+ mHelper.bot().assertDocument(UiTestEnvironment.fileName1, true);
+ mHelper.bot().assertDocument(UiTestEnvironment.fileName2, true);
+ mHelper.bot().assertDocumentsCount(mHelper.getDocumentsCountDir0());
+ }
+
+ private void pressEnter() {
+ mHelper.device().waitForIdle(TIMEOUT);
+ mHelper.device().pressEnter();
+ mHelper.device().waitForIdle(TIMEOUT);
+ }
+
+}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/SearchViewUiTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/SearchViewUiTest.java
index 6c9c5d9..ce54725 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/SearchViewUiTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/SearchViewUiTest.java
@@ -16,278 +16,139 @@
package com.android.documentsui;
-import static com.android.documentsui.StubProvider.DEFAULT_AUTHORITY;
import static com.android.documentsui.StubProvider.ROOT_0_ID;
import static com.android.documentsui.StubProvider.ROOT_1_ID;
-import android.app.Instrumentation;
-import android.content.ContentProviderClient;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.os.RemoteException;
-import android.support.test.uiautomator.By;
-import android.support.test.uiautomator.Configurator;
-import android.support.test.uiautomator.UiDevice;
import android.support.test.uiautomator.UiObject;
import android.support.test.uiautomator.UiObjectNotFoundException;
-import android.support.test.uiautomator.Until;
import android.test.InstrumentationTestCase;
import android.test.suitebuilder.annotation.LargeTest;
-import android.util.Log;
-import android.view.MotionEvent;
-
-import com.android.documentsui.model.RootInfo;
@LargeTest
public class SearchViewUiTest extends InstrumentationTestCase {
- private static final int TIMEOUT = 5000;
private static final String TAG = "SearchViewUiTest";
- private static final String TARGET_PKG = "com.android.documentsui";
- private static final String LAUNCHER_PKG = "com.android.launcher";
- private UiBot mBot;
- private UiDevice mDevice;
- private Context mContext;
- private ContentResolver mResolver;
- private DocumentsProviderHelper mDocsHelper;
- private ContentProviderClient mClient;
- private RootInfo mRoot_0;
- private RootInfo mRoot_1;
+ private UiTestEnvironment mEnv;
- private UiObject mSearchView;
- private UiObject mSearchTextField;
private UiObject mDocsList;
private UiObject mMessageTextView;
- private UiObject mSearchIcon;
+ @Override
public void setUp() throws Exception {
- // Initialize UiDevice instance.
- Instrumentation instrumentation = getInstrumentation();
+ super.setUp();
+ mEnv = new UiTestEnvironment(getInstrumentation());
+ mEnv.launch();
- mDevice = UiDevice.getInstance(instrumentation);
-
- Configurator.getInstance().setToolType(MotionEvent.TOOL_TYPE_MOUSE);
-
- // Start from the home screen.
- mDevice.pressHome();
- mDevice.wait(Until.hasObject(By.pkg(LAUNCHER_PKG).depth(0)), TIMEOUT);
-
- // NOTE: Must be the "target" context, else security checks in content provider will fail.
- mContext = instrumentation.getTargetContext();
- mResolver = mContext.getContentResolver();
-
- mClient = mResolver.acquireUnstableContentProviderClient(DEFAULT_AUTHORITY);
- mDocsHelper = new DocumentsProviderHelper(DEFAULT_AUTHORITY, mClient);
-
- // Launch app.
- Intent intent = mContext.getPackageManager().getLaunchIntentForPackage(TARGET_PKG);
- intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
- mContext.startActivity(intent);
- // Wait for the app to appear.
- mDevice.wait(Until.hasObject(By.pkg(TARGET_PKG).depth(0)), TIMEOUT);
- mDevice.waitForIdle();
-
- mBot = new UiBot(mDevice, TIMEOUT);
-
- resetStorage(); // Just incase a test failed and tearDown didn't happen.
-
- initUiObjects();
}
@Override
protected void tearDown() throws Exception {
+ mEnv.device().pressBack();
super.tearDown();
- mDevice.pressBack();
- resetStorage();
- mClient.release();
}
- private void resetStorage() throws RemoteException {
- mClient.call("clear", null, null);
- // TODO: Would be nice to have an event to wait on here.
- mDevice.waitForIdle();
+ public void testSearchView_ExpandsOnClick() throws Exception {
+ mEnv.bot().openSearchView();
+ mEnv.bot().assertSearchTextFiledAndIcon(true, false);
}
- private void initTestFiles() throws RemoteException {
- mRoot_0 = mDocsHelper.getRoot(ROOT_0_ID);
- mRoot_1 = mDocsHelper.getRoot(ROOT_1_ID);
+ public void testSearchView_CollapsesOnBack() throws Exception {
+ mEnv.bot().openSearchView();
- mDocsHelper.createDocument(mRoot_0, "text/plain", "file10.log");
- mDocsHelper.createDocument(mRoot_0, "image/png", "file1.png");
- mDocsHelper.createDocument(mRoot_0, "text/csv", "file2.csv");
+ mEnv.device().pressBack();
- mDocsHelper.createDocument(mRoot_1, "text/plain", "anotherFile0.log");
- mDocsHelper.createDocument(mRoot_1, "text/plain", "poodles.text");
+ mEnv.bot().assertSearchTextFiledAndIcon(false, true);
}
- private void initUiObjects() {
- mSearchView = mBot.findSearchView();
- mSearchTextField = mBot.findSearchViewTextField();
- mDocsList = mBot.findDocumentsList();
- mMessageTextView = mBot.findMessageTextView();
- mSearchIcon = mBot.findSearchViewIcon();
- }
-
- public void testSearchViewExpandsOnClick() throws Exception {
- assertTrue(mSearchIcon.exists());
- assertFalse(mSearchTextField.exists());
-
- mSearchView.click();
-
- assertTrue(mSearchTextField.exists());
- assertTrue(mSearchTextField.isFocused());
- assertFalse(mSearchIcon.exists());
- }
-
- public void testSearchViewCollapsesOnBack() throws Exception {
- assertTrue(mSearchIcon.exists());
- assertFalse(mSearchTextField.exists());
-
- mSearchView.click();
-
- mDevice.pressBack();
-
- assertTrue(mSearchIcon.exists());
- assertFalse(mSearchTextField.exists());
- }
-
- public void testSearchViewClearsTextOnBack() throws Exception {
- assertTrue(mSearchIcon.exists());
- assertFalse(mSearchTextField.exists());
-
+ public void testSearchView_ClearsTextOnBack() throws Exception {
String query = "file2";
- mSearchView.click();
- mSearchTextField.setText(query);
+ mEnv.bot().openSearchView();
+ mEnv.bot().setSearchQuery(query);
- assertSearchTextField(true, query);
+ mEnv.device().pressBack();
- mDevice.pressBack();
-
- assertTrue(mSearchIcon.exists());
- assertFalse(mSearchTextField.exists());
+ mEnv.bot().assertSearchTextFiledAndIcon(false, true);
}
- public void testSearchFound() throws Exception {
- initTestFiles();
-
- mBot.openRoot(ROOT_0_ID);
-
- assertDefaultTestDir0();
+ public void testSearch_ResultsFound() throws Exception {
+ mEnv.initTestFiles();
+ mEnv.bot().openRoot(ROOT_0_ID);
+ mEnv.assertDefaultContentOfTestDir0();
String query = "file1";
- mSearchView.click();
- mSearchTextField.setText(query);
+ mEnv.bot().openSearchView();
+ mEnv.bot().setSearchQuery(query);
+ mEnv.bot().assertSearchTextField(true, query);
- assertTrue(mDocsList.exists());
- assertSearchTextField(true, query);
+ mEnv.device().pressEnter();
- mDevice.pressEnter();
-
- assertTrue(mDocsList.exists());
- assertEquals(2, mDocsList.getChildCount());
- mBot.assertHasDocuments("file1.png", "file10.log");
- assertSearchTextField(false, query);
+ mEnv.bot().assertDocumentsCountOnList(true, 2);
+ mEnv.bot().assertHasDocuments(UiTestEnvironment.fileName1, UiTestEnvironment.fileName2);
+ mEnv.bot().assertSearchTextField(false, query);
}
- public void testSearchFoundClearsOnBack() throws Exception {
- initTestFiles();
+ public void testSearchResultsFound_ClearsOnBack() throws Exception {
+ mEnv.initTestFiles();
+ mEnv.bot().openRoot(ROOT_0_ID);
+ mEnv.assertDefaultContentOfTestDir0();
- mBot.openRoot(ROOT_0_ID);
+ String query = UiTestEnvironment.fileName1;
+ mEnv.bot().openSearchView();
+ mEnv.bot().setSearchQuery(query);
- assertDefaultTestDir0();
-
- String query = "file1";
- mSearchView.click();
- mSearchTextField.setText(query);
-
- mDevice.pressEnter();
- mDevice.pressBack();
-
- assertDefaultTestDir0();
+ mEnv.device().pressEnter();
+ mEnv.device().pressBack();
+ mEnv.assertDefaultContentOfTestDir0();
}
- public void testSearchNoResults() throws Exception {
- initTestFiles();
-
- mBot.openRoot(ROOT_0_ID);
-
- assertDefaultTestDir0();
+ public void testSearch_NoResults() throws Exception {
+ mEnv.initTestFiles();
+ mEnv.bot().openRoot(ROOT_0_ID);
+ mEnv.assertDefaultContentOfTestDir0();
String query = "chocolate";
- mSearchView.click();
- mSearchTextField.setText(query);
+ mEnv.bot().openSearchView();
+ mEnv.bot().setSearchQuery(query);
- mDevice.pressEnter();
+ mEnv.device().pressEnter();
- assertFalse(mDocsList.exists());
- assertTrue(mMessageTextView.exists());
- String msg = String.valueOf(mContext.getString(R.string.no_results));
- assertEquals(String.format(msg, "TEST_ROOT_0"), mMessageTextView.getText());
- assertSearchTextField(false, query);
+ mEnv.bot().assertDocumentsCountOnList(false, 0);
+
+ String msg = String.valueOf(mEnv.context().getString(R.string.no_results));
+ mEnv.bot().assertMessageTextView(String.format(msg, "TEST_ROOT_0"));
+ mEnv.bot().assertSearchTextField(false, query);
}
- public void testSearchNoResultsClearsOnBack() throws Exception {
- initTestFiles();
-
- mBot.openRoot(ROOT_0_ID);
-
- assertDefaultTestDir0();
+ public void testSearchNoResults_ClearsOnBack() throws Exception {
+ mEnv.initTestFiles();
+ mEnv.bot().openRoot(ROOT_0_ID);
+ mEnv.assertDefaultContentOfTestDir0();
String query = "chocolate";
- mSearchView.click();
- mSearchTextField.setText(query);
+ mEnv.bot().openSearchView();
+ mEnv.bot().setSearchQuery(query);
- mDevice.pressEnter();
- mDevice.pressBack();
-
- assertDefaultTestDir0();
+ mEnv.device().pressEnter();
+ mEnv.device().pressBack();
+ mEnv.assertDefaultContentOfTestDir0();
}
- public void testSearchFoundClearsDirectoryChange() throws Exception {
- initTestFiles();
+ public void testSearchResultsFound_ClearsOnDirectoryChange() throws Exception {
+ mEnv.initTestFiles();
+ mEnv.bot().openRoot(ROOT_0_ID);
+ mEnv.assertDefaultContentOfTestDir0();
- mBot.openRoot(ROOT_0_ID);
+ String query = UiTestEnvironment.fileName1;
+ mEnv.bot().openSearchView();
+ mEnv.bot().setSearchQuery(query);
- assertDefaultTestDir0();
+ mEnv.device().pressEnter();
- String query = "file1";
- mSearchView.click();
- mSearchTextField.setText(query);
+ mEnv.bot().openRoot(ROOT_1_ID);
+ mEnv.assertDefaultContentOfTestDir1();
- mDevice.pressEnter();
-
- mBot.openRoot(ROOT_1_ID);
-
- assertDefaultTestDir1();
-
- mBot.openRoot(ROOT_0_ID);
-
- assertDefaultTestDir0();
- }
-
- private void assertDefaultTestDir0() throws UiObjectNotFoundException {
- assertTrue(mSearchIcon.exists());
- assertTrue(mDocsList.exists());
- assertFalse(mSearchTextField.exists());
- assertEquals(3, mDocsList.getChildCount());
- mBot.assertHasDocuments("file2.csv", "file1.png", "file10.log");
- }
-
- private void assertDefaultTestDir1() throws UiObjectNotFoundException {
- assertTrue(mSearchIcon.exists());
- assertFalse(mSearchTextField.exists());
- assertTrue(mDocsList.exists());
- assertEquals(2, mDocsList.getChildCount());
- mBot.assertHasDocuments("anotherFile0.log", "poodles.text");
- }
-
- private void assertSearchTextField(boolean isFocused, String query)
- throws UiObjectNotFoundException {
- assertFalse(mSearchIcon.exists());
- assertTrue(mSearchTextField.exists());
- assertEquals(isFocused, mSearchTextField.isFocused());
- assertEquals(query, mSearchTextField.getText());
+ mEnv.bot().openRoot(ROOT_0_ID);
+ mEnv.assertDefaultContentOfTestDir0();
}
}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java b/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java
index cc48786..980627b 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/StubProvider.java
@@ -16,6 +16,10 @@
package com.android.documentsui;
+import static com.android.documentsui.Shared.TAG;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.ProviderInfo;
@@ -34,6 +38,7 @@
import android.provider.DocumentsContract.Root;
import android.provider.DocumentsProvider;
import android.support.annotation.VisibleForTesting;
+import android.text.TextUtils;
import android.util.Log;
import libcore.io.IoUtils;
@@ -66,10 +71,13 @@
= "com.android.documentsui.stubprovider.STREAM_TYPES";
public static final String EXTRA_CONTENT = "com.android.documentsui.stubprovider.CONTENT";
+ public static final String EXTRA_FLAGS = "com.android.documentsui.stubprovider.FLAGS";
+ public static final String EXTRA_PARENT_ID = "com.android.documentsui.stubprovider.PARENT";
+
private static final String TAG = "StubProvider";
private static final String STORAGE_SIZE_KEY = "documentsui.stubprovider.size";
- private static int DEFAULT_ROOT_SIZE = 1024 * 1024 * 100; // 100 MB.
+ private static int DEFAULT_ROOT_SIZE = 1024 * 1024 * 100; // 100 MB.
private static final String[] DEFAULT_ROOT_PROJECTION = new String[] {
Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_TITLE, Root.COLUMN_DOCUMENT_ID,
@@ -176,44 +184,8 @@
@Override
public String createDocument(String parentId, String mimeType, String displayName)
throws FileNotFoundException {
-
- final StubDocument parent = mStorage.get(parentId);
- if (parent == null) {
- throw new IllegalArgumentException(
- "Can't create file " + displayName + " in null parent.");
- }
- if (!parent.file.isDirectory()) {
- throw new IllegalArgumentException(
- "Can't create file " + displayName + " inside non-directory parent "
- + parent.file.getName());
- }
-
- final File file = new File(parent.file, displayName);
- if (file.exists()) {
- throw new FileNotFoundException(
- "Duplicate file names not supported for " + file);
- }
-
- if (mimeType.equals(Document.MIME_TYPE_DIR)) {
- if (!file.mkdirs()) {
- throw new FileNotFoundException(
- "Failed to create directory(s): " + file);
- }
- Log.i(TAG, "Created new directory: " + file);
- } else {
- boolean created = false;
- try {
- created = file.createNewFile();
- } catch (IOException e) {
- // We'll throw an FNF exception later :)
- Log.e(TAG, "createNewFile operation failed for file: " + file, e);
- }
- if (!created) {
- throw new FileNotFoundException(
- "createNewFile operation failed for: " + file);
- }
- Log.i(TAG, "Created new file: " + file);
- }
+ StubDocument parent = mStorage.get(parentId);
+ File file = createFile(parent, mimeType, displayName);
final StubDocument document = StubDocument.createRegularDocument(file, mimeType, parent);
mStorage.put(document.documentId, document);
@@ -303,6 +275,45 @@
}
@Override
+ public String renameDocument(String documentId, String displayName)
+ throws FileNotFoundException {
+
+ StubDocument oldDoc = mStorage.get(documentId);
+
+ File before = oldDoc.file;
+ File after = new File(before.getParentFile(), displayName);
+
+ if (after.exists()) {
+ throw new IllegalStateException("Already exists " + after);
+ }
+
+ boolean result = before.renameTo(after);
+
+ if (!result) {
+ throw new IllegalStateException("Failed to rename to " + after);
+ }
+
+ StubDocument newDoc = StubDocument.createRegularDocument(after, oldDoc.mimeType,
+ mStorage.get(oldDoc.parentId));
+
+ mStorage.remove(documentId);
+ notifyParentChanged(oldDoc.parentId);
+ getContext().getContentResolver().notifyChange(
+ DocumentsContract.buildDocumentUri(mAuthority, oldDoc.documentId), null, false);
+
+ mStorage.put(newDoc.documentId, newDoc);
+ notifyParentChanged(newDoc.parentId);
+ getContext().getContentResolver().notifyChange(
+ DocumentsContract.buildDocumentUri(mAuthority, newDoc.documentId), null, false);
+
+ if (!TextUtils.equals(documentId, newDoc.documentId)) {
+ return newDoc.documentId;
+ } else {
+ return null;
+ }
+ }
+
+ @Override
public ParcelFileDescriptor openDocument(String docId, String mode, CancellationSignal signal)
throws FileNotFoundException {
@@ -468,6 +479,9 @@
case "simulateReadErrorsForFile":
simulateReadErrorsForFile(arg);
return null;
+ case "createDocumentWithFlags":
+ Bundle bundle = dispatchCreateDocumentWithFlags(extras);
+ return bundle;
default:
return super.call(method, arg, extras);
}
@@ -493,6 +507,81 @@
return null;
}
+ private Bundle dispatchCreateDocumentWithFlags(Bundle extras) {
+ String rootId = extras.getString(EXTRA_PARENT_ID);
+ String mimeType = extras.getString(Document.COLUMN_MIME_TYPE);
+ String name = extras.getString(Document.COLUMN_DISPLAY_NAME);
+ int flags = extras.getInt(EXTRA_FLAGS);
+
+ Bundle out = new Bundle();
+ String documentId = null;
+ try {
+ documentId = createDocument(rootId, mimeType, name, flags);
+ Uri uri = DocumentsContract.buildDocumentUri(mAuthority, documentId);
+ out.putParcelable(DocumentsContract.EXTRA_URI, uri);
+ } catch (FileNotFoundException e) {
+ Log.d(TAG, "Cretaing document with flags failed" + name);
+ }
+ return out;
+ }
+
+ public String createDocument(String parentId, String mimeType, String displayName, int flags)
+ throws FileNotFoundException {
+
+ StubDocument parent = mStorage.get(parentId);
+ File file = createFile(parent, mimeType, displayName);
+
+ final StubDocument document = StubDocument.createDocumentWithFlags(file, mimeType, parent,
+ flags);
+ mStorage.put(document.documentId, document);
+ Log.d(TAG, "Created document " + document.documentId);
+ notifyParentChanged(document.parentId);
+ getContext().getContentResolver().notifyChange(
+ DocumentsContract.buildDocumentUri(mAuthority, document.documentId),
+ null, false);
+
+ return document.documentId;
+ }
+
+ private File createFile(StubDocument parent, String mimeType, String displayName)
+ throws FileNotFoundException {
+ if (parent == null) {
+ throw new IllegalArgumentException(
+ "Can't create file " + displayName + " in null parent.");
+ }
+ if (!parent.file.isDirectory()) {
+ throw new IllegalArgumentException(
+ "Can't create file " + displayName + " inside non-directory parent "
+ + parent.file.getName());
+ }
+
+ final File file = new File(parent.file, displayName);
+ if (file.exists()) {
+ throw new FileNotFoundException(
+ "Duplicate file names not supported for " + file);
+ }
+
+ if (mimeType.equals(Document.MIME_TYPE_DIR)) {
+ if (!file.mkdirs()) {
+ throw new FileNotFoundException("Failed to create directory(s): " + file);
+ }
+ Log.i(TAG, "Created new directory: " + file);
+ } else {
+ boolean created = false;
+ try {
+ created = file.createNewFile();
+ } catch (IOException e) {
+ // We'll throw an FNF exception later :)
+ Log.e(TAG, "createNewFile operation failed for file: " + file, e);
+ }
+ if (!created) {
+ throw new FileNotFoundException("createNewFile operation failed for: " + file);
+ }
+ Log.i(TAG, "Created new file: " + file);
+ }
+ return file;
+ }
+
private void configure(String arg, Bundle extras) {
Log.d(TAG, "Configure " + arg);
String rootName = extras.getString(EXTRA_ROOT, ROOT_0_ID);
@@ -557,7 +646,7 @@
}
final StubDocument document = StubDocument.createRegularDocument(file, mimeType, parent);
mStorage.put(document.documentId, document);
- return DocumentsContract.buildDocumentUri(mAuthority, document.documentId);
+ return DocumentsContract.buildDocumentUri(mAuthority, document.documentId);
}
@VisibleForTesting
@@ -573,7 +662,7 @@
final StubDocument document = StubDocument.createVirtualDocument(
file, mimeType, streamTypes, parent);
mStorage.put(document.documentId, document);
- return DocumentsContract.buildDocumentUri(mAuthority, document.documentId);
+ return DocumentsContract.buildDocumentUri(mAuthority, document.documentId);
}
@VisibleForTesting
@@ -643,9 +732,8 @@
public final String parentId;
public final RootInfo rootInfo;
- private StubDocument(
- File file, String mimeType, List<String> streamTypes, int flags,
- StubDocument parent) {
+ private StubDocument(File file, String mimeType, List<String> streamTypes, int flags,
+ StubDocument parent) {
this.file = file;
this.documentId = getDocumentIdForFile(file);
this.mimeType = mimeType;
@@ -671,7 +759,7 @@
public static StubDocument createRegularDocument(
File file, String mimeType, StubDocument parent) {
- int flags = Document.FLAG_SUPPORTS_DELETE;
+ int flags = Document.FLAG_SUPPORTS_DELETE | Document.FLAG_SUPPORTS_RENAME;
if (file.isDirectory()) {
flags |= Document.FLAG_DIR_SUPPORTS_CREATE;
} else {
@@ -680,6 +768,11 @@
return new StubDocument(file, mimeType, new ArrayList<String>(), flags, parent);
}
+ public static StubDocument createDocumentWithFlags(
+ File file, String mimeType, StubDocument parent, int flags) {
+ return new StubDocument(file, mimeType, new ArrayList<String>(), flags, parent);
+ }
+
public static StubDocument createVirtualDocument(
File file, String mimeType, List<String> streamTypes, StubDocument parent) {
int flags = Document.FLAG_SUPPORTS_DELETE | Document.FLAG_SUPPORTS_WRITE
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/UiBot.java b/packages/DocumentsUI/tests/src/com/android/documentsui/UiBot.java
index c4def8f..f12ae10 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/UiBot.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/UiBot.java
@@ -17,7 +17,11 @@
package com.android.documentsui;
import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.assertFalse;
+import android.content.Context;
import android.support.test.uiautomator.By;
import android.support.test.uiautomator.BySelector;
import android.support.test.uiautomator.UiDevice;
@@ -28,11 +32,13 @@
import android.support.test.uiautomator.UiSelector;
import android.support.test.uiautomator.Until;
import android.util.Log;
+import android.view.inputmethod.InputMethodManager;
import junit.framework.Assert;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Iterator;
import java.util.List;
import java.util.regex.Pattern;
@@ -48,10 +54,12 @@
By.desc(Pattern.compile("^Deleting [0-9]+ file.+"));
private UiDevice mDevice;
+ private Context mContext;
private int mTimeout;
- public UiBot(UiDevice device, int timeout) {
+ public UiBot(UiDevice device, Context context, int timeout) {
mDevice = device;
+ mContext = context;
mTimeout = timeout;
}
@@ -109,25 +117,40 @@
}
}
- UiObject findDocument(String label) throws UiObjectNotFoundException {
- final UiSelector docList = new UiSelector().resourceId(
- "com.android.documentsui:id/container_directory").childSelector(
- new UiSelector().resourceId("com.android.documentsui:id/list"));
-
- // Wait for the first list item to appear
- new UiObject(docList.childSelector(new UiSelector())).waitForExists(mTimeout);
-
- // new UiScrollable(docList).scrollIntoView(new UiSelector().text(label));
- return mDevice.findObject(docList.childSelector(new UiSelector().text(label)));
+ void assertMenuEnabled(int id, boolean enabled) {
+ UiObject2 menu= findMenuWithName(mContext.getString(id));
+ assertNotNull(menu);
+ assertEquals(enabled, menu.isEnabled());
}
- boolean hasDocuments(String... labels) throws UiObjectNotFoundException {
- for (String label : labels) {
- if (!findDocument(label).exists()) {
- return false;
- }
+ void assertDocumentsCount(int count) throws UiObjectNotFoundException {
+ UiObject docsList = findDocumentsList();
+ assertEquals(count, docsList.getChildCount());
+ }
+
+ void assertDocumentsCount(String dir, int count) throws UiObjectNotFoundException {
+ openRoot(dir);
+ UiObject docsList = findDocumentsList();
+ assertEquals(count, docsList.getChildCount());
+ }
+
+ void assertSearchTextField(boolean isFocused, String query)
+ throws UiObjectNotFoundException {
+ UiObject textField = findSearchViewTextField();
+ UiObject searchIcon = findSearchViewIcon();
+
+ assertFalse(searchIcon.exists());
+ assertTrue(textField.exists());
+ assertEquals(isFocused, textField.isFocused());
+ if(query != null) {
+ assertEquals(query, textField.getText());
}
- return true;
+ }
+
+ void assertSearchTextFiledAndIcon(boolean searchTextFieldExists, boolean searchIconExists) {
+ assertEquals(searchTextFieldExists, findSearchViewTextField().exists());
+ assertEquals(searchIconExists, findSearchViewIcon().exists());
+
}
void assertHasDocuments(String... labels) throws UiObjectNotFoundException {
@@ -143,10 +166,77 @@
}
}
+ void assertDocument(String name, boolean exists) throws UiObjectNotFoundException {
+ UiObject doc = findDocument(name);
+ assertEquals(exists, doc.exists());
+ }
+
+ void assertDocumentsCountOnList(boolean exists, int count) throws UiObjectNotFoundException {
+ UiObject docsList = findDocumentsList();
+ assertEquals(exists, docsList.exists());
+ if(docsList.exists()) {
+ assertEquals(count, docsList.getChildCount());
+ }
+ }
+
+ void assertMessageTextView(String message) throws UiObjectNotFoundException {
+ UiObject messageTextView = findMessageTextView();
+ assertTrue(messageTextView.exists());
+
+ String msg = String.valueOf(message);
+ assertEquals(String.format(msg, "TEST_ROOT_0"), messageTextView.getText());
+
+ }
+ void assertSnackbar(int id) {
+ assertNotNull(getSnackbar(mContext.getString(id)));
+ }
+
void clickDocument(String label) throws UiObjectNotFoundException {
findDocument(label).click();
}
+ void openSearchView() throws UiObjectNotFoundException {
+ UiObject searchView = findSearchView();
+ searchView.click();
+ assertTrue(searchView.exists());
+ }
+
+ void setSearchQuery(String query) throws UiObjectNotFoundException {
+ UiObject searchView = findSearchView();
+ assertTrue(searchView.exists());
+ UiObject searchTextField = findSearchViewTextField();
+ searchTextField.setText(query);
+ assertSearchTextField(true, query);
+ }
+
+ UiObject openOverflowMenu() throws UiObjectNotFoundException {
+ UiObject obj = findMenuMoreOptions();
+ obj.click();
+ mDevice.waitForIdle(mTimeout);
+ return obj;
+ }
+
+ void openDialog(int id) {
+ UiObject2 menu= findMenuWithName(mContext.getString(id));
+ assertNotNull(menu);
+ assertEquals(true, menu.isEnabled());
+ menu.click();
+ }
+
+ void setDialogText(String text) throws UiObjectNotFoundException {
+ findDialogEditText().setText(text);
+ }
+
+ UiObject selectDocument(String label) throws UiObjectNotFoundException {
+ UiObject doc = findDocument(label);
+ doc.longClick();
+ return doc;
+ }
+
+ UiObject2 getSnackbar(String message) {
+ return mDevice.wait(Until.findObject(By.text(message)), mTimeout);
+ }
+
void waitForDeleteSnackbar() {
mDevice.wait(Until.findObject(SNACK_DELETE), mTimeout);
}
@@ -200,6 +290,27 @@
return mDevice.findObject(selector);
}
+ UiObject findDocument(String label) throws UiObjectNotFoundException {
+ final UiSelector docList = new UiSelector().resourceId(
+ "com.android.documentsui:id/container_directory").childSelector(
+ new UiSelector().resourceId("com.android.documentsui:id/list"));
+
+ // Wait for the first list item to appear
+ new UiObject(docList.childSelector(new UiSelector())).waitForExists(mTimeout);
+
+ // new UiScrollable(docList).scrollIntoView(new UiSelector().text(label));
+ return mDevice.findObject(docList.childSelector(new UiSelector().text(label)));
+ }
+
+ boolean hasDocuments(String... labels) throws UiObjectNotFoundException {
+ for (String label : labels) {
+ if (!findDocument(label).exists()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
UiObject findDocumentsList() {
return findObject(
"com.android.documentsui:id/container_directory",
@@ -224,4 +335,60 @@
"com.android.documentsui:id/message");
}
+ UiObject findActionModeBar() {
+ return findObject("android:id/action_mode_bar");
+ }
+
+ UiObject findDialogEditText() {
+ return findObject("android:id/content", "android:id/text1");
+ }
+
+ UiObject findRenameDialogOkButton() {
+ return findObject("android:id/content", "android:id/button1");
+ }
+
+ UiObject findRenameDialogCancelButton() {
+ return findObject("android:id/content", "android:id/button2");
+ }
+
+ UiObject findMenuLabelWithName(String label) {
+ UiSelector selector = new UiSelector().text(label);
+ return mDevice.findObject(selector);
+ }
+
+ UiObject2 findMenuWithName(String label) {
+ List<UiObject2> menuItems = mDevice.findObjects(By.clazz("android.widget.LinearLayout"));
+ Iterator<UiObject2> it = menuItems.iterator();
+
+ UiObject2 menuItem = null;
+ while(it.hasNext()) {
+ menuItem = it.next();
+ UiObject2 text = menuItem.findObject(By.text(label));
+ if(text != null) {
+ break;
+ }
+ }
+ return menuItem;
+ }
+
+ UiObject findMenuMoreOptions() {
+ UiSelector selector = new UiSelector().className("android.widget.ImageButton")
+ .descriptionContains("More options");
+ //TODO: use the system string ? android.R.string.action_menu_overflow_description
+ return mDevice.findObject(selector);
+ }
+
+ // Indirect way to detect the keyboard.
+ boolean isKeyboardPresent() {
+ InputMethodManager inputManager = (InputMethodManager) mContext
+ .getSystemService(Context.INPUT_METHOD_SERVICE);
+ return inputManager.isAcceptingText();
+ }
+
+ void dismissKeyboardIfPresent() {
+ if(isKeyboardPresent()) {
+ mDevice.pressBack();
+ }
+ }
+
}
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/UiTestEnvironment.java b/packages/DocumentsUI/tests/src/com/android/documentsui/UiTestEnvironment.java
new file mode 100644
index 0000000..9e30589
--- /dev/null
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/UiTestEnvironment.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.documentsui;
+
+import static com.android.documentsui.StubProvider.DEFAULT_AUTHORITY;
+import static com.android.documentsui.StubProvider.ROOT_0_ID;
+import static com.android.documentsui.StubProvider.ROOT_1_ID;
+
+import android.app.Instrumentation;
+import android.content.ContentProviderClient;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.RemoteException;
+import android.provider.DocumentsContract.Document;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.Configurator;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObjectNotFoundException;
+import android.support.test.uiautomator.Until;
+import android.view.MotionEvent;
+
+import com.android.documentsui.model.RootInfo;
+
+/**
+ * Provides basic test environment for UI tests:
+ * - Launches activity
+ * - Creates and gives access to test root directories and test files
+ * - Cleans up the test environment
+ */
+class UiTestEnvironment {
+
+ public static final int TIMEOUT = 5000;
+ public static final String NO_RENAME = "NO_RENAME";
+
+ public static final String dirName1 = "Dir1";
+ public static final String fileName1 = "file1.log";
+ public static final String fileName2 = "file12.png";
+ public static final String fileName3 = "anotherFile0.log";
+ public static final String fileName4 = "poodles.text";
+ public static final String fileNameNoRename = NO_RENAME + "file.txt";
+
+ private static final String TARGET_PKG = "com.android.documentsui";
+ private static final String LAUNCHER_PKG = "com.android.launcher";
+
+ private final UiBot mBot;
+ private final UiDevice mDevice;
+ private final Context mContext;
+
+ private RootInfo mRootDir0;
+ private RootInfo mRootDir1;
+ private int mDocsCountDir0;
+ private int mDocsCountDir1;
+
+ private ContentResolver mResolver;
+ private DocumentsProviderHelper mDocsHelper;
+ private ContentProviderClient mClient;
+
+ private final Instrumentation mInstrumentation;
+
+ public UiTestEnvironment(Instrumentation instrumentation) {
+ mInstrumentation = instrumentation;
+ mDevice = UiDevice.getInstance(mInstrumentation);
+ // NOTE: Must be the "target" context, else security checks in content provider will fail.
+ mContext = mInstrumentation.getTargetContext();
+ mBot = new UiBot(mDevice, mContext, TIMEOUT);
+ }
+
+/**
+ * Launches default activity and waits for the application to appear.
+ * @throws Exception
+ */
+ public void launch() throws Exception {
+ Intent intent = mContext.getPackageManager().getLaunchIntentForPackage(TARGET_PKG);
+ launch(intent);
+ }
+
+ /**
+ * Launches activity specified by intent and waits for the application to appear.
+ * @param intent Intent describing activity to launch.
+ * @throws Exception
+ */
+ public void launch(Intent intent) throws Exception {
+ Configurator.getInstance().setToolType(MotionEvent.TOOL_TYPE_MOUSE);
+ // Start from the home screen.
+ mDevice.pressHome();
+ mDevice.wait(Until.hasObject(By.pkg(LAUNCHER_PKG).depth(0)), TIMEOUT);
+
+ mResolver = mContext.getContentResolver();
+ mClient = mResolver.acquireUnstableContentProviderClient(DEFAULT_AUTHORITY);
+ mDocsHelper = new DocumentsProviderHelper(DEFAULT_AUTHORITY, mClient);
+
+ // Launch app.
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK);
+ mContext.startActivity(intent);
+ // Wait for the app to appear.
+ mDevice.wait(Until.hasObject(By.pkg(TARGET_PKG).depth(0)), TIMEOUT);
+ mDevice.waitForIdle();
+
+ resetStorage(); // Just incase a test failed and tearDown didn't happen.
+ }
+
+ public void cleanUp() throws Exception {
+ resetStorage();
+ mClient.release();
+ }
+
+ public void resetStorage() throws RemoteException {
+ mClient.call("clear", null, null);
+ mDevice.waitForIdle();
+ }
+
+ public void initTestFiles() throws RemoteException {
+ mRootDir0 = mDocsHelper.getRoot(ROOT_0_ID);
+ mRootDir1 = mDocsHelper.getRoot(ROOT_1_ID);
+
+ mDocsHelper.createFolder(mRootDir0, dirName1);
+ mDocsHelper.createDocument(mRootDir0, "text/plain", fileName1);
+ mDocsHelper.createDocument(mRootDir0, "image/png", fileName2);
+ mDocsHelper.createDocumentWithFlags(mRootDir0.documentId, "text/plain", fileNameNoRename,
+ Document.FLAG_SUPPORTS_WRITE);
+ mDocsCountDir0 = 4;
+
+ mDocsHelper.createDocument(mRootDir1, "text/plain", fileName3);
+ mDocsHelper.createDocument(mRootDir1, "text/plain", fileName4);
+ mDocsCountDir1 = 2;
+ }
+
+ public void assertDefaultContentOfTestDir0() throws UiObjectNotFoundException {
+ bot().assertDocumentsCount(ROOT_0_ID, getDocumentsCountDir0());
+ bot().assertHasDocuments(UiTestEnvironment.fileName1, UiTestEnvironment.fileName2,
+ UiTestEnvironment.dirName1, UiTestEnvironment.fileNameNoRename);
+ }
+
+ public void assertDefaultContentOfTestDir1() throws UiObjectNotFoundException {
+ bot().assertDocumentsCount(ROOT_1_ID, getDocumentsCountDir1());
+ bot().assertHasDocuments(UiTestEnvironment.fileName3, UiTestEnvironment.fileName4);
+ }
+
+ public UiBot bot() {
+ return mBot;
+ }
+
+ public UiDevice device() {
+ return mDevice;
+ }
+
+ public Context context() {
+ return mContext;
+ }
+
+ public RootInfo getRootDir0() {
+ return mRootDir0;
+ }
+
+ public int getDocumentsCountDir0() {
+ return mDocsCountDir0;
+ }
+
+ public int getDocumentsCountDir1() {
+ return mDocsCountDir1;
+ }
+}
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index ed26e0c..3ad6b83 100755
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -654,7 +654,6 @@
_intent.getCategories().size() == 1 &&
_intent.getData() == null &&
_intent.getType() == null &&
- (intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) != 0 &&
!isResolverActivity()) {
// This sure looks like a home activity!
mActivityType = HOME_ACTIVITY_TYPE;
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 577ca48..0e970b3 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -1673,11 +1673,37 @@
TaskRecord task, ActivityRecord r) {
if (r.fullscreen) {
if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Fullscreen: at " + r
- + " stackInvisible=" + stackInvisible
- + " behindFullscreenActivity=" + behindFullscreenActivity);
+ + " stackInvisible=" + stackInvisible
+ + " behindFullscreenActivity=" + behindFullscreenActivity);
// At this point, nothing else needs to be shown in this task.
behindFullscreenActivity = true;
- } else if (!isHomeStack() && r.frontOfTask && task.isOverHomeStack()) {
+ } else if (isHomeStack()) {
+ if (r.isHomeActivity()) {
+ if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Home activity: at " + r
+ + " stackInvisible=" + stackInvisible
+ + " behindFullscreenActivity=" + behindFullscreenActivity);
+ // No other activity in the home stack should be visible behind the home activity.
+ // Home activities is usually a translucent activity with the wallpaper behind them.
+ // However, when they don't have the wallpaper behind them, we want to show
+ // activities in the next application stack behind them vs. another activity in the
+ // home stack like recents.
+ behindFullscreenActivity = true;
+ } else if (r.isRecentsActivity()
+ && task.getTaskToReturnTo() == APPLICATION_ACTIVITY_TYPE) {
+ if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY,
+ "Recents activity returning to app: at " + r
+ + " stackInvisible=" + stackInvisible
+ + " behindFullscreenActivity=" + behindFullscreenActivity);
+ // We don't want any other activities in the home stack visible if the recents
+ // activity is going to be returning to an application activity type.
+ // We do this to preserve the visible order the user used to get into the recents
+ // activity. The recents activity is normally translucent and if it doesn't have
+ // the wallpaper behind it the next activity in the home stack shouldn't be visible
+ // when the home stack is brought to the front to display the recents activity from
+ // an app.
+ behindFullscreenActivity = true;
+ }
+ } else if (r.frontOfTask && task.isOverHomeStack()) {
if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Showing home: at " + r
+ " stackInvisible=" + stackInvisible
+ " behindFullscreenActivity=" + behindFullscreenActivity);
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index b8d94eb..98eebea 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -48,6 +48,7 @@
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.am.ActivityManagerService.ANIMATE;
+import static com.android.server.am.ActivityRecord.APPLICATION_ACTIVITY_TYPE;
import static com.android.server.am.ActivityRecord.HOME_ACTIVITY_TYPE;
import static com.android.server.am.ActivityRecord.RECENTS_ACTIVITY_TYPE;
import static com.android.server.am.ActivityStack.ActivityState.RESUMED;
@@ -941,12 +942,8 @@
Slog.e(TAG, "Attempted Lock Task Mode violation mStartActivity=" + mStartActivity);
return START_RETURN_LOCK_TASK_MODE_VIOLATION;
}
- if (!mMovedHome
- && (mLaunchFlags & (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, so before starting
- // their own activity we will bring home to the front.
- mStartActivity.task.setTaskToReturnTo(HOME_ACTIVITY_TYPE);
+ if (!mMovedHome) {
+ updateTaskReturnToType(mStartActivity.task, mLaunchFlags, topStack);
}
} else if (mSourceRecord != null) {
if (mSupervisor.isLockTaskModeViolation(mSourceRecord.task)) {
@@ -1287,11 +1284,7 @@
mOptions, mStartActivity.appTimeTracker, "bringingFoundTaskToFront");
mMovedToFront = true;
}
- if ((mLaunchFlags & (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);
- }
+ updateTaskReturnToType(intentActivity.task, mLaunchFlags, focusStack);
mOptions = null;
}
}
@@ -1308,6 +1301,23 @@
return intentActivity;
}
+ private void updateTaskReturnToType(
+ TaskRecord task, int launchFlags, ActivityStack focusedStack) {
+ 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.
+ task.setTaskToReturnTo(HOME_ACTIVITY_TYPE);
+ return;
+ } else if (focusedStack == null || focusedStack.mStackId == HOME_STACK_ID) {
+ // Task will be launched over the home stack, so return home.
+ task.setTaskToReturnTo(HOME_ACTIVITY_TYPE);
+ return;
+ }
+
+ // Else we are coming from an application stack so return to an application.
+ task.setTaskToReturnTo(APPLICATION_ACTIVITY_TYPE);
+ }
+
private void setTaskFromIntentActivity(ActivityRecord intentActivity) {
if ((mLaunchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK))
== (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK)) {
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 272f26f..f39ab2f 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -128,8 +128,6 @@
private static final String TASK_THUMBNAIL_SUFFIX = "_task_thumbnail";
- static final boolean IGNORE_RETURN_TO_RECENTS = true;
-
static final int INVALID_TASK_ID = -1;
final int taskId; // Unique identifier for this task.
@@ -459,8 +457,8 @@
}
void setTaskToReturnTo(int taskToReturnTo) {
- mTaskToReturnTo = (IGNORE_RETURN_TO_RECENTS && taskToReturnTo == RECENTS_ACTIVITY_TYPE)
- ? HOME_ACTIVITY_TYPE : taskToReturnTo;
+ mTaskToReturnTo = (taskToReturnTo == RECENTS_ACTIVITY_TYPE)
+ ? HOME_ACTIVITY_TYPE : taskToReturnTo;
}
int getTaskToReturnTo() {
diff --git a/services/core/java/com/android/server/notification/CountdownConditionProvider.java b/services/core/java/com/android/server/notification/CountdownConditionProvider.java
index 07903e9..c1a7ac6 100644
--- a/services/core/java/com/android/server/notification/CountdownConditionProvider.java
+++ b/services/core/java/com/android/server/notification/CountdownConditionProvider.java
@@ -107,11 +107,6 @@
}
@Override
- public void onRequestConditions(int relevance) {
- // by convention
- }
-
- @Override
public void onSubscribe(Uri conditionId) {
if (DEBUG) Slog.d(TAG, "onSubscribe " + conditionId);
mTime = ZenModeConfig.tryParseCountdownConditionId(conditionId);
diff --git a/services/core/java/com/android/server/notification/EventConditionProvider.java b/services/core/java/com/android/server/notification/EventConditionProvider.java
index a4d5bce..ab3cb83 100644
--- a/services/core/java/com/android/server/notification/EventConditionProvider.java
+++ b/services/core/java/com/android/server/notification/EventConditionProvider.java
@@ -134,12 +134,6 @@
}
@Override
- public void onRequestConditions(int relevance) {
- if (DEBUG) Slog.d(TAG, "onRequestConditions relevance=" + relevance);
- // does not advertise conditions
- }
-
- @Override
public void onSubscribe(Uri conditionId) {
if (DEBUG) Slog.d(TAG, "onSubscribe " + conditionId);
if (!ZenModeConfig.isValidEventConditionId(conditionId)) {
diff --git a/services/core/java/com/android/server/notification/ScheduleConditionProvider.java b/services/core/java/com/android/server/notification/ScheduleConditionProvider.java
index 44448cc..c9b0ebb 100644
--- a/services/core/java/com/android/server/notification/ScheduleConditionProvider.java
+++ b/services/core/java/com/android/server/notification/ScheduleConditionProvider.java
@@ -107,12 +107,6 @@
}
@Override
- public void onRequestConditions(int relevance) {
- if (DEBUG) Slog.d(TAG, "onRequestConditions relevance=" + relevance);
- // does not advertise conditions
- }
-
- @Override
public void onSubscribe(Uri conditionId) {
if (DEBUG) Slog.d(TAG, "onSubscribe " + conditionId);
if (!ZenModeConfig.isValidScheduleConditionId(conditionId)) {
diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
index 89e89b0..0eacd13 100644
--- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java
+++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
@@ -94,7 +94,7 @@
continue;
}
if (!pm.performDexOpt(pkg, /* instruction set */ null, useJitProfiles,
- /* extractOnly */ false)) {
+ /* extractOnly */ false, /* force */ false)) {
// there was a problem running dexopt,
// remember this so we do not keep retrying.
sFailedPackageNames.add(pkg);
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index 4d66e10..fe0f141 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -81,7 +81,7 @@
* {@link PackageManagerService#mInstallLock}.
*/
int performDexOpt(PackageParser.Package pkg, String[] instructionSets,
- boolean inclDependencies, boolean useProfiles, boolean extractOnly) {
+ boolean inclDependencies, boolean useProfiles, boolean extractOnly, boolean force) {
ArraySet<String> done;
if (inclDependencies && (pkg.usesLibraries != null || pkg.usesOptionalLibraries != null)) {
done = new ArraySet<String>();
@@ -97,7 +97,7 @@
}
try {
return performDexOptLI(pkg, instructionSets, done, useProfiles,
- extractOnly);
+ extractOnly, force);
} finally {
if (useLock) {
mDexoptWakeLock.release();
@@ -107,7 +107,7 @@
}
private int performDexOptLI(PackageParser.Package pkg, String[] targetInstructionSets,
- ArraySet<String> done, boolean useProfiles, boolean extractOnly) {
+ ArraySet<String> done, boolean useProfiles, boolean extractOnly, boolean force) {
final String[] instructionSets = targetInstructionSets != null ?
targetInstructionSets : getAppDexInstructionSets(pkg.applicationInfo);
@@ -128,34 +128,38 @@
final boolean vmSafeMode = (pkg.applicationInfo.flags & ApplicationInfo.FLAG_VM_SAFE_MODE) != 0;
final boolean debuggable = (pkg.applicationInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
+ if (useProfiles) {
+ // If we do a profile guided compilation then we might recompile
+ // the same package if more profile information is available.
+ force = true;
+ }
+
final List<String> paths = pkg.getAllCodePathsExcludingResourceOnly();
boolean performedDexOpt = false;
final String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets);
for (String dexCodeInstructionSet : dexCodeInstructionSets) {
- if (!useProfiles && pkg.mDexOptPerformed.contains(dexCodeInstructionSet)) {
- // Skip only if we do not use profiles since they might trigger a recompilation.
+ if (!force && pkg.mDexOptPerformed.contains(dexCodeInstructionSet)) {
continue;
}
for (String path : paths) {
int dexoptNeeded;
- try {
- dexoptNeeded = DexFile.getDexOptNeeded(path, pkg.packageName,
- dexCodeInstructionSet, /* defer */false);
- } catch (IOException ioe) {
- Slog.w(TAG, "IOException reading apk: " + path, ioe);
- return DEX_OPT_FAILED;
+
+ if (force) {
+ dexoptNeeded = DexFile.DEX2OAT_NEEDED;
+ } else {
+ try {
+ dexoptNeeded = DexFile.getDexOptNeeded(path, pkg.packageName,
+ dexCodeInstructionSet, /* defer */false);
+ } catch (IOException ioe) {
+ Slog.w(TAG, "IOException reading apk: " + path, ioe);
+ return DEX_OPT_FAILED;
+ }
}
if (dexoptNeeded == DexFile.NO_DEXOPT_NEEDED) {
- if (useProfiles) {
- // If we do a profile guided compilation then we might recompile the same
- // package if more profile information is available.
- dexoptNeeded = DexFile.DEX2OAT_NEEDED;
- } else {
- // No dexopt needed and we don't use profiles. Nothing to do.
- continue;
- }
+ // No dexopt needed and we don't use profiles. Nothing to do.
+ continue;
}
final String dexoptType;
String oatDir = null;
@@ -252,7 +256,7 @@
// TODO: Analyze and investigate if we (should) profile libraries.
// Currently this will do a full compilation of the library.
performDexOptLI(libPkg, instructionSets, done, /*useProfiles*/ false,
- /* extractOnly */ false);
+ /* extractOnly */ false, /* force */ false);
}
}
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 45455cf..b6905b4 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -6639,7 +6639,7 @@
if (pkgs != null) {
for (String pkg : pkgs) {
performDexOpt(pkg, null /* instructionSet */, false /* useProfiles */,
- true /* extractOnly */);
+ true /* extractOnly */, false /* force */);
}
}
}
@@ -6676,26 +6676,28 @@
@Override
public boolean performDexOptIfNeeded(String packageName, String instructionSet) {
return performDexOptTraced(packageName, instructionSet, false /* useProfiles */,
- false /* extractOnly */);
+ false /* extractOnly */, false /* force */);
}
+ @Override
public boolean performDexOpt(String packageName, String instructionSet, boolean useProfiles,
- boolean extractOnly) {
- return performDexOptTraced(packageName, instructionSet, useProfiles, extractOnly);
+ boolean extractOnly, boolean force) {
+ return performDexOptTraced(packageName, instructionSet, useProfiles, extractOnly, force);
}
private boolean performDexOptTraced(String packageName, String instructionSet,
- boolean useProfiles, boolean extractOnly) {
+ boolean useProfiles, boolean extractOnly, boolean force) {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt");
try {
- return performDexOptInternal(packageName, instructionSet, useProfiles, extractOnly);
+ return performDexOptInternal(packageName, instructionSet, useProfiles, extractOnly,
+ force);
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
}
private boolean performDexOptInternal(String packageName, String instructionSet,
- boolean useProfiles, boolean extractOnly) {
+ boolean useProfiles, boolean extractOnly, boolean force) {
PackageParser.Package p;
final String targetInstructionSet;
synchronized (mPackages) {
@@ -6707,7 +6709,7 @@
targetInstructionSet = instructionSet != null ? instructionSet :
getPrimaryInstructionSet(p.applicationInfo);
- if (!useProfiles && p.mDexOptPerformed.contains(targetInstructionSet)) {
+ if (!force && !useProfiles && p.mDexOptPerformed.contains(targetInstructionSet)) {
// Skip only if we do not use profiles since they might trigger a recompilation.
return false;
}
@@ -6717,7 +6719,7 @@
synchronized (mInstallLock) {
final String[] instructionSets = new String[] { targetInstructionSet };
int result = mPackageDexOptimizer.performDexOpt(p, instructionSets,
- true /* inclDependencies */, useProfiles, extractOnly);
+ true /* inclDependencies */, useProfiles, extractOnly, force);
return result == PackageDexOptimizer.DEX_OPT_PERFORMED;
}
} finally {
@@ -6763,7 +6765,7 @@
// Don't use profiles since that may cause compilation to be skipped.
final int res = mPackageDexOptimizer.performDexOpt(pkg, instructionSets,
true /* inclDependencies */, false /* useProfiles */,
- false /* extractOnly */);
+ false /* extractOnly */, true /* force */);
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
if (res != PackageDexOptimizer.DEX_OPT_PERFORMED) {
@@ -13031,7 +13033,7 @@
// method because `pkg` is not in `mPackages` yet.
int result = mPackageDexOptimizer.performDexOpt(pkg, null /* instructionSets */,
false /* inclDependencies */, false /* useProfiles */,
- true /* extractOnly */);
+ true /* extractOnly */, false /* force */);
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
if (result == PackageDexOptimizer.DEX_OPT_FAILED) {
String msg = "Extracking package failed for " + pkgName;
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 43e4b84..d8845d8 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -44,6 +44,7 @@
import android.os.Bundle;
import android.os.RemoteException;
import android.os.ShellCommand;
+import android.os.SystemProperties;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.PrintWriterPrinter;
@@ -96,6 +97,8 @@
return runInstallCreate();
case "install-write":
return runInstallWrite();
+ case "compile":
+ return runCompile();
case "list":
return runList();
case "uninstall":
@@ -227,6 +230,67 @@
return doWriteSession(sessionId, path, sizeBytes, splitName, true /*logSuccess*/);
}
+ private int runCompile() throws RemoteException {
+ final PrintWriter pw = getOutPrintWriter();
+ boolean useJitProfiles = false;
+ boolean extractOnly = false;
+ boolean forceCompilation = false;
+ String compilationMode = "default";
+
+ String opt;
+ while ((opt = getNextOption()) != null) {
+ switch (opt) {
+ case "-m":
+ compilationMode = getNextArgRequired();
+ break;
+ case "-f":
+ forceCompilation = true;
+ break;
+ default:
+ pw.println("Error: Unknown option: " + opt);
+ return 1;
+ }
+ }
+
+ switch (compilationMode) {
+ case "default":
+ useJitProfiles = SystemProperties.getBoolean("dalvik.vm.usejitprofiles", false);
+ extractOnly = false;
+ break;
+ case "all":
+ useJitProfiles = false;
+ extractOnly = false;
+ break;
+ case "profile":
+ useJitProfiles = true;
+ extractOnly = false;
+ break;
+ case "extract":
+ useJitProfiles = false;
+ extractOnly = true;
+ break;
+ default:
+ pw.println("Error: Unknown compilation mode: " + compilationMode);
+ return 1;
+ }
+
+ String packageName = getNextArg();
+ if (packageName == null) {
+ pw.println("Error: package name not specified");
+ return 1;
+ }
+
+ boolean success = mInterface.performDexOpt(packageName, null /* instructionSet */,
+ useJitProfiles, extractOnly, forceCompilation);
+ if (success) {
+ pw.println("Success");
+ return 0;
+ } else {
+ pw.println("Failure: package " + packageName + " could not be compiled");
+ return 1;
+ }
+ }
+
private int runList() throws RemoteException {
final PrintWriter pw = getOutPrintWriter();
final String type = getNextArg();
@@ -1069,6 +1133,12 @@
pw.println(" help");
pw.println(" Print this help text.");
pw.println("");
+ pw.println(" compile [-m MODE] [-f] TARGET-PACKAGE");
+ pw.println(" Trigger compilation of TARGET-PACKAGE.");
+ pw.println(" Options:");
+ pw.println(" -m: select compilation mode");
+ pw.println(" MODE can be one of \"default\", \"all\", \"profile\", and \"extract\"");
+ pw.println(" -f: force compilation even if not needed");
pw.println(" list features");
pw.println(" Prints all features of the system.");
pw.println(" list instrumentation [-f] [TARGET-PACKAGE]");