Merge "Print job files and print job records not always cleaned up." into klp-dev
diff --git a/api/current.txt b/api/current.txt
index c6ffc3b..37881e6 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -3972,7 +3972,6 @@
}
public static class Notification.Action implements android.os.Parcelable {
- ctor public Notification.Action();
ctor public Notification.Action(int, java.lang.CharSequence, android.app.PendingIntent);
method public android.app.Notification.Action clone();
method public int describeContents();
@@ -3988,7 +3987,6 @@
ctor public Notification.BigPictureStyle(android.app.Notification.Builder);
method public android.app.Notification.BigPictureStyle bigLargeIcon(android.graphics.Bitmap);
method public android.app.Notification.BigPictureStyle bigPicture(android.graphics.Bitmap);
- method public android.app.Notification build();
method public android.app.Notification.BigPictureStyle setBigContentTitle(java.lang.CharSequence);
method public android.app.Notification.BigPictureStyle setSummaryText(java.lang.CharSequence);
}
@@ -3997,7 +3995,6 @@
ctor public Notification.BigTextStyle();
ctor public Notification.BigTextStyle(android.app.Notification.Builder);
method public android.app.Notification.BigTextStyle bigText(java.lang.CharSequence);
- method public android.app.Notification build();
method public android.app.Notification.BigTextStyle setBigContentTitle(java.lang.CharSequence);
method public android.app.Notification.BigTextStyle setSummaryText(java.lang.CharSequence);
}
@@ -4042,14 +4039,13 @@
ctor public Notification.InboxStyle();
ctor public Notification.InboxStyle(android.app.Notification.Builder);
method public android.app.Notification.InboxStyle addLine(java.lang.CharSequence);
- method public android.app.Notification build();
method public android.app.Notification.InboxStyle setBigContentTitle(java.lang.CharSequence);
method public android.app.Notification.InboxStyle setSummaryText(java.lang.CharSequence);
}
public static abstract class Notification.Style {
ctor public Notification.Style();
- method public abstract android.app.Notification build();
+ method public android.app.Notification build();
method protected void checkBuilder();
method protected android.widget.RemoteViews getStandardView(int);
method protected void internalSetBigContentTitle(java.lang.CharSequence);
@@ -10908,7 +10904,6 @@
method public abstract android.hardware.camera2.CaptureRequest.Builder createCaptureRequest(int) throws android.hardware.camera2.CameraAccessException;
method public abstract void flush() throws android.hardware.camera2.CameraAccessException;
method public abstract java.lang.String getId();
- method public abstract android.hardware.camera2.CameraCharacteristics getProperties() throws android.hardware.camera2.CameraAccessException;
method public abstract int setRepeatingBurst(java.util.List<android.hardware.camera2.CaptureRequest>, android.hardware.camera2.CameraDevice.CaptureListener, android.os.Handler) throws android.hardware.camera2.CameraAccessException;
method public abstract int setRepeatingRequest(android.hardware.camera2.CaptureRequest, android.hardware.camera2.CameraDevice.CaptureListener, android.os.Handler) throws android.hardware.camera2.CameraAccessException;
method public abstract void stopRepeating() throws android.hardware.camera2.CameraAccessException;
@@ -20993,7 +20988,6 @@
field public static final java.lang.String COLUMN_MIME_TYPE = "mime_type";
field public static final java.lang.String COLUMN_SIZE = "_size";
field public static final java.lang.String COLUMN_SUMMARY = "summary";
- field public static final int FLAG_DIR_HIDE_GRID_TITLES = 64; // 0x40
field public static final int FLAG_DIR_PREFERS_GRID = 16; // 0x10
field public static final int FLAG_DIR_PREFERS_LAST_MODIFIED = 32; // 0x20
field public static final int FLAG_DIR_SUPPORTS_CREATE = 8; // 0x8
@@ -21010,18 +21004,12 @@
field public static final java.lang.String COLUMN_ICON = "icon";
field public static final java.lang.String COLUMN_MIME_TYPES = "mime_types";
field public static final java.lang.String COLUMN_ROOT_ID = "root_id";
- field public static final java.lang.String COLUMN_ROOT_TYPE = "root_type";
field public static final java.lang.String COLUMN_SUMMARY = "summary";
field public static final java.lang.String COLUMN_TITLE = "title";
- field public static final int FLAG_ADVANCED = 4; // 0x4
- field public static final int FLAG_EMPTY = 32; // 0x20
field public static final int FLAG_LOCAL_ONLY = 2; // 0x2
field public static final int FLAG_SUPPORTS_CREATE = 1; // 0x1
- field public static final int FLAG_SUPPORTS_RECENTS = 8; // 0x8
- field public static final int FLAG_SUPPORTS_SEARCH = 16; // 0x10
- field public static final int ROOT_TYPE_DEVICE = 3; // 0x3
- field public static final int ROOT_TYPE_SERVICE = 1; // 0x1
- field public static final int ROOT_TYPE_SHORTCUT = 2; // 0x2
+ field public static final int FLAG_SUPPORTS_RECENTS = 4; // 0x4
+ field public static final int FLAG_SUPPORTS_SEARCH = 8; // 0x8
}
public abstract class DocumentsProvider extends android.content.ContentProvider {
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index e70ad1c..c63e586 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -432,28 +432,119 @@
/**
* Additional semantic data to be carried around with this Notification.
+ * <p>
+ * The extras keys defined here are intended to capture the original inputs to {@link Builder}
+ * APIs, and are intended to be used by
+ * {@link android.service.notification.NotificationListenerService} implementations to extract
+ * detailed information from notification objects.
*/
public Bundle extras = new Bundle();
- // extras keys for Builder inputs
+ /**
+ * {@link #extras} key: this is the title of the notification,
+ * as supplied to {@link Builder#setContentTitle(CharSequence)}.
+ */
public static final String EXTRA_TITLE = "android.title";
+
+ /**
+ * {@link #extras} key: this is the title of the notification when shown in expanded form,
+ * e.g. as supplied to {@link BigTextStyle#setBigContentTitle(CharSequence)}.
+ */
public static final String EXTRA_TITLE_BIG = EXTRA_TITLE + ".big";
+
+ /**
+ * {@link #extras} key: this is the main text payload, as supplied to
+ * {@link Builder#setContentText(CharSequence)}.
+ */
public static final String EXTRA_TEXT = "android.text";
+
+ /**
+ * {@link #extras} key: this is a third line of text, as supplied to
+ * {@link Builder#setSubText(CharSequence)}.
+ */
public static final String EXTRA_SUB_TEXT = "android.subText";
+
+ /**
+ * {@link #extras} key: this is a small piece of additional text as supplied to
+ * {@link Builder#setContentInfo(CharSequence)}.
+ */
public static final String EXTRA_INFO_TEXT = "android.infoText";
+
+ /**
+ * {@link #extras} key: this is a line of summary information intended to be shown
+ * alongside expanded notifications, as supplied to (e.g.)
+ * {@link BigTextStyle#setSummaryText(CharSequence)}.
+ */
public static final String EXTRA_SUMMARY_TEXT = "android.summaryText";
+
+ /**
+ * {@link #extras} key: this is the resource ID of the notification's main small icon, as
+ * supplied to {@link Builder#setSmallIcon(int)}.
+ */
public static final String EXTRA_SMALL_ICON = "android.icon";
+
+ /**
+ * {@link #extras} key: this is a bitmap to be used instead of the small icon when showing the
+ * notification payload, as
+ * supplied to {@link Builder#setLargeIcon(android.graphics.Bitmap)}.
+ */
public static final String EXTRA_LARGE_ICON = "android.largeIcon";
+
+ /**
+ * {@link #extras} key: this is a bitmap to be used instead of the one from
+ * {@link Builder#setLargeIcon(android.graphics.Bitmap)} when the notification is
+ * shown in its expanded form, as supplied to
+ * {@link BigPictureStyle#bigLargeIcon(android.graphics.Bitmap)}.
+ */
public static final String EXTRA_LARGE_ICON_BIG = EXTRA_LARGE_ICON + ".big";
+
+ /**
+ * {@link #extras} key: this is the progress value supplied to
+ * {@link Builder#setProgress(int, int, boolean)}.
+ */
public static final String EXTRA_PROGRESS = "android.progress";
+
+ /**
+ * {@link #extras} key: this is the maximum value supplied to
+ * {@link Builder#setProgress(int, int, boolean)}.
+ */
public static final String EXTRA_PROGRESS_MAX = "android.progressMax";
+
+ /**
+ * {@link #extras} key: whether the progress bar is indeterminate, supplied to
+ * {@link Builder#setProgress(int, int, boolean)}.
+ */
public static final String EXTRA_PROGRESS_INDETERMINATE = "android.progressIndeterminate";
+
+ /**
+ * {@link #extras} key: whether {@link #when} should be shown as a count-up timer (specifically
+ * a {@link android.widget.Chronometer}) instead of a timestamp, as supplied to
+ * {@link Builder#setUsesChronometer(boolean)}.
+ */
public static final String EXTRA_SHOW_CHRONOMETER = "android.showChronometer";
+
+ /**
+ * {@link #extras} key: whether {@link #when} should be shown,
+ * as supplied to {@link Builder#setShowWhen(boolean)}.
+ */
public static final String EXTRA_SHOW_WHEN = "android.showWhen";
+
+ /**
+ * {@link #extras} key: this is a bitmap to be shown in {@link BigPictureStyle} expanded
+ * notifications, supplied to {@link BigPictureStyle#bigPicture(android.graphics.Bitmap)}.
+ */
public static final String EXTRA_PICTURE = "android.picture";
+
+ /**
+ * {@link #extras} key: An array of CharSequences to show in {@link InboxStyle} expanded
+ * notifications, each of which was supplied to {@link InboxStyle#addLine(CharSequence)}.
+ */
public static final String EXTRA_TEXT_LINES = "android.textLines";
- // extras keys for other interesting pieces of information
+ /**
+ * {@link #extras} key: An array of people that this notification relates to, specified
+ * by contacts provider contact URI.
+ */
public static final String EXTRA_PEOPLE = "android.people";
/**
@@ -464,38 +555,53 @@
public static final String EXTRA_SCORE_MODIFIED = "android.scoreModified";
/**
- * Notification extra to specify heads up display preference.
+ * Not used.
* @hide
*/
public static final String EXTRA_AS_HEADS_UP = "headsup";
/**
- * Value for {@link #EXTRA_AS_HEADS_UP} indicating that heads up display is not appropriate.
+ * Value for {@link #EXTRA_AS_HEADS_UP}.
* @hide
*/
public static final int HEADS_UP_NEVER = 0;
/**
- * Default value for {@link #EXTRA_AS_HEADS_UP} indicating that heads up display is appropriate.
+ * Default value for {@link #EXTRA_AS_HEADS_UP}.
* @hide
*/
public static final int HEADS_UP_ALLOWED = 1;
/**
- * Value for {@link #EXTRA_AS_HEADS_UP} that advocates for heads up display.
+ * Value for {@link #EXTRA_AS_HEADS_UP}.
* @hide
*/
public static final int HEADS_UP_REQUESTED = 2;
/**
- * Structure to encapsulate an "action", including title and icon, that can be attached to a Notification.
+ * Structure to encapsulate a named action that can be shown as part of this notification.
+ * It must include an icon, a label, and a {@link PendingIntent} to be fired when the action is
+ * selected by the user.
+ * <p>
+ * Apps should use {@link Builder#addAction(int, CharSequence, PendingIntent)} to create and
+ * attach actions.
*/
public static class Action implements Parcelable {
+ /**
+ * Small icon representing the action.
+ */
public int icon;
+ /**
+ * Title of the action.
+ */
public CharSequence title;
+ /**
+ * Intent to send when the user invokes this action. May be null, in which case the action
+ * may be rendered in a disabled presentation by the system UI.
+ */
public PendingIntent actionIntent;
- @SuppressWarnings("unused")
- public Action() { }
+
+ private Action() { }
private Action(Parcel in) {
icon = in.readInt();
title = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
@@ -503,16 +609,20 @@
actionIntent = PendingIntent.CREATOR.createFromParcel(in);
}
}
- public Action(int icon_, CharSequence title_, PendingIntent intent_) {
- this.icon = icon_;
- this.title = title_;
- this.actionIntent = intent_;
+ /**
+ * Use {@link Builder#addAction(int, CharSequence, PendingIntent)}.
+ */
+ public Action(int icon, CharSequence title, PendingIntent intent) {
+ this.icon = icon;
+ this.title = title;
+ this.actionIntent = intent;
}
+
@Override
public Action clone() {
return new Action(
this.icon,
- this.title.toString(),
+ this.title,
this.actionIntent // safe to alias
);
}
@@ -542,6 +652,12 @@
};
}
+ /**
+ * Array of all {@link Action} structures attached to this notification by
+ * {@link Builder#addAction(int, CharSequence, PendingIntent)}. Mostly useful for instances of
+ * {@link android.service.notification.NotificationListenerService} that provide an alternative
+ * interface for invoking actions.
+ */
public Action[] actions;
/**
@@ -1468,8 +1584,15 @@
/**
* Add an action to this notification. Actions are typically displayed by
* the system as a button adjacent to the notification content.
- * <br>
- * A notification displays up to 3 actions, from left to right in the order they were added.
+ * <p>
+ * Every action must have an icon (32dp square and matching the
+ * <a href="{@docRoot}design/style/iconography.html#action-bar">Holo
+ * Dark action bar</a> visual style), a textual label, and a {@link PendingIntent}.
+ * <p>
+ * A notification in its expanded form can display up to 3 actions, from left to right in
+ * the order they were added. Actions will not be displayed when the notification is
+ * collapsed, however, so be sure that any essential functions may be accessed by the user
+ * in some other way (for example, in the Activity pointed to by {@link #contentIntent}).
*
* @param icon Resource ID of a drawable that represents the action.
* @param title Text describing the action.
@@ -1666,8 +1789,9 @@
/**
* Apply the unstyled operations and return a new {@link Notification} object.
+ * @hide
*/
- private Notification buildUnstyled() {
+ public Notification buildUnstyled() {
Notification n = new Notification();
n.when = mWhen;
n.icon = mSmallIcon;
@@ -1745,12 +1869,10 @@
* object.
*/
public Notification build() {
- final Notification n;
+ Notification n = buildUnstyled();
if (mStyle != null) {
- n = mStyle.build();
- } else {
- n = buildUnstyled();
+ n = mStyle.buildStyled(n);
}
n.extras = mExtras != null ? new Bundle(mExtras) : new Bundle();
@@ -1860,7 +1982,21 @@
}
}
- public abstract Notification build();
+ /**
+ * @hide
+ */
+ public abstract Notification buildStyled(Notification wip);
+
+ /**
+ * Calls {@link android.app.Notification.Builder#build()} on the Builder this Style is
+ * attached to.
+ *
+ * @return the fully constructed Notification.
+ */
+ public Notification build() {
+ checkBuilder();
+ return mBuilder.build();
+ }
}
/**
@@ -1946,10 +2082,11 @@
extras.putParcelable(EXTRA_PICTURE, mPicture);
}
+ /**
+ * @hide
+ */
@Override
- public Notification build() {
- checkBuilder();
- Notification wip = mBuilder.buildUnstyled();
+ public Notification buildStyled(Notification wip) {
if (mBigLargeIconSet ) {
mBuilder.mLargeIcon = mBigLargeIcon;
}
@@ -2039,10 +2176,11 @@
return contentView;
}
+ /**
+ * @hide
+ */
@Override
- public Notification build() {
- checkBuilder();
- Notification wip = mBuilder.buildUnstyled();
+ public Notification buildStyled(Notification wip) {
wip.bigContentView = makeBigContentView();
wip.extras.putCharSequence(EXTRA_TEXT, mBigText);
@@ -2150,10 +2288,11 @@
return contentView;
}
+ /**
+ * @hide
+ */
@Override
- public Notification build() {
- checkBuilder();
- Notification wip = mBuilder.buildUnstyled();
+ public Notification buildStyled(Notification wip) {
wip.bigContentView = makeBigContentView();
return wip;
diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java
index ec89041..a9a72b0 100644
--- a/core/java/android/hardware/camera2/CameraDevice.java
+++ b/core/java/android/hardware/camera2/CameraDevice.java
@@ -16,11 +16,9 @@
package android.hardware.camera2;
-import android.view.Surface;
import android.os.Handler;
-import android.util.Log;
+import android.view.Surface;
-import java.lang.AutoCloseable;
import java.util.List;
/**
@@ -127,24 +125,11 @@
* @return the ID for this camera device
*
* @see CameraManager#getCameraCharacteristics
- * @see CameraManager#getDeviceIdList
+ * @see CameraManager#getCameraIdList
*/
public String getId();
/**
- * Get the static properties for this camera. These are identical to the
- * properties returned by {@link CameraManager#getCameraCharacteristics}.
- *
- * @return the static properties of the camera
- *
- * @throws CameraAccessException if the camera device is no longer connected or has
- * encountered a fatal error
- * @throws IllegalStateException if the camera device has been closed
- *
- * @see CameraManager#getCameraCharacteristics
- */
- public CameraCharacteristics getProperties() throws CameraAccessException;
- /**
* <p>Set up a new output set of Surfaces for the camera device.</p>
*
* <p>The configuration determines the set of potential output Surfaces for
diff --git a/core/java/android/hardware/camera2/impl/CameraDevice.java b/core/java/android/hardware/camera2/impl/CameraDevice.java
index f126472..70a6f44 100644
--- a/core/java/android/hardware/camera2/impl/CameraDevice.java
+++ b/core/java/android/hardware/camera2/impl/CameraDevice.java
@@ -88,24 +88,6 @@
}
@Override
- public CameraCharacteristics getProperties() throws CameraAccessException {
-
- CameraMetadataNative info = new CameraMetadataNative();
-
- try {
- mRemoteDevice.getCameraInfo(/*out*/info);
- } catch(CameraRuntimeException e) {
- throw e.asChecked();
- } catch(RemoteException e) {
- // impossible
- return null;
- }
-
- CameraCharacteristics properties = new CameraCharacteristics(info);
- return properties;
- }
-
- @Override
public void configureOutputs(List<Surface> outputs) throws CameraAccessException {
synchronized (mLock) {
HashSet<Surface> addSet = new HashSet<Surface>(outputs); // Streams to create
diff --git a/core/java/android/print/PrinterDiscoverySession.java b/core/java/android/print/PrinterDiscoverySession.java
index c6dbc16..6432a37 100644
--- a/core/java/android/print/PrinterDiscoverySession.java
+++ b/core/java/android/print/PrinterDiscoverySession.java
@@ -192,22 +192,46 @@
}
}
- private void handlePrintersAdded(List<PrinterInfo> printers) {
+ private void handlePrintersAdded(List<PrinterInfo> addedPrinters) {
if (isDestroyed()) {
return;
}
- boolean printersChanged = false;
- final int addedPrinterCount = printers.size();
- for (int i = 0; i < addedPrinterCount; i++) {
- PrinterInfo addedPrinter = printers.get(i);
- PrinterInfo oldPrinter = mPrinters.put(addedPrinter.getId(), addedPrinter);
- if (oldPrinter == null || !oldPrinter.equals(addedPrinter)) {
- printersChanged = true;
+
+ // No old printers - do not bother keeping their position.
+ if (mPrinters.isEmpty()) {
+ final int printerCount = addedPrinters.size();
+ for (int i = 0; i < printerCount; i++) {
+ PrinterInfo printer = addedPrinters.get(i);
+ mPrinters.put(printer.getId(), printer);
+ }
+ notifyOnPrintersChanged();
+ return;
+ }
+
+ // Add the printers to a map.
+ ArrayMap<PrinterId, PrinterInfo> addedPrintersMap =
+ new ArrayMap<PrinterId, PrinterInfo>();
+ final int printerCount = addedPrinters.size();
+ for (int i = 0; i < printerCount; i++) {
+ PrinterInfo printer = addedPrinters.get(i);
+ addedPrintersMap.put(printer.getId(), printer);
+ }
+
+ // Update printers we already have.
+ final int oldPrinterCount = mPrinters.size();
+ for (int i = 0; i < oldPrinterCount; i++) {
+ PrinterId oldPrinterId = mPrinters.keyAt(i);
+ PrinterInfo updatedPrinter = addedPrintersMap.remove(oldPrinterId);
+ if (updatedPrinter != null) {
+ mPrinters.put(oldPrinterId, updatedPrinter);
}
}
- if (printersChanged) {
- notifyOnPrintersChanged();
- }
+
+ // Add the new printers, i.e. what is left.
+ mPrinters.putAll(addedPrintersMap);
+
+ // Announce the change.
+ notifyOnPrintersChanged();
}
private void handlePrintersRemoved(List<PrinterId> printerIds) {
diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index 4c9af19..85ec803 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -99,7 +99,7 @@
/**
* Unique ID of a document. This ID is both provided by and interpreted
* by a {@link DocumentsProvider}, and should be treated as an opaque
- * value by client applications.
+ * value by client applications. This column is required.
* <p>
* Each document must have a unique ID within a provider, but that
* single document may be included as a child of multiple directories.
@@ -117,7 +117,7 @@
* Concrete MIME type of a document. For example, "image/png" or
* "application/pdf" for openable files. A document can also be a
* directory containing additional documents, which is represented with
- * the {@link #MIME_TYPE_DIR} MIME type.
+ * the {@link #MIME_TYPE_DIR} MIME type. This column is required.
* <p>
* Type: STRING
*
@@ -127,15 +127,15 @@
/**
* Display name of a document, used as the primary title displayed to a
- * user.
+ * user. This column is required.
* <p>
* Type: STRING
*/
public static final String COLUMN_DISPLAY_NAME = OpenableColumns.DISPLAY_NAME;
/**
- * Summary of a document, which may be shown to a user. The summary may
- * be {@code null}.
+ * Summary of a document, which may be shown to a user. This column is
+ * optional, and may be {@code null}.
* <p>
* Type: STRING
*/
@@ -143,9 +143,9 @@
/**
* Timestamp when a document was last modified, in milliseconds since
- * January 1, 1970 00:00:00.0 UTC, or {@code null} if unknown. A
- * {@link DocumentsProvider} can update this field using events from
- * {@link OnCloseListener} or other reliable
+ * January 1, 1970 00:00:00.0 UTC. This column is required, and may be
+ * {@code null} if unknown. A {@link DocumentsProvider} can update this
+ * field using events from {@link OnCloseListener} or other reliable
* {@link ParcelFileDescriptor} transports.
* <p>
* Type: INTEGER (long)
@@ -155,15 +155,16 @@
public static final String COLUMN_LAST_MODIFIED = "last_modified";
/**
- * Specific icon resource ID for a document, or {@code null} to use
- * platform default icon based on {@link #COLUMN_MIME_TYPE}.
+ * Specific icon resource ID for a document. This column is optional,
+ * and may be {@code null} to use a platform-provided default icon based
+ * on {@link #COLUMN_MIME_TYPE}.
* <p>
* Type: INTEGER (int)
*/
public static final String COLUMN_ICON = "icon";
/**
- * Flags that apply to a document.
+ * Flags that apply to a document. This column is required.
* <p>
* Type: INTEGER (int)
*
@@ -171,12 +172,13 @@
* @see #FLAG_SUPPORTS_DELETE
* @see #FLAG_SUPPORTS_THUMBNAIL
* @see #FLAG_DIR_PREFERS_GRID
- * @see #FLAG_DIR_SUPPORTS_CREATE
+ * @see #FLAG_DIR_PREFERS_LAST_MODIFIED
*/
public static final String COLUMN_FLAGS = "flags";
/**
- * Size of a document, in bytes, or {@code null} if unknown.
+ * Size of a document, in bytes, or {@code null} if unknown. This column
+ * is required.
* <p>
* Type: INTEGER (long)
*/
@@ -211,7 +213,7 @@
* writability of a document may change over time, for example due to
* remote access changes. This flag indicates that a document client can
* expect {@link ContentResolver#openOutputStream(Uri)} to succeed.
- *
+ *
* @see #COLUMN_FLAGS
*/
public static final int FLAG_SUPPORTS_WRITE = 1 << 1;
@@ -265,8 +267,9 @@
*
* @see #COLUMN_FLAGS
* @see #FLAG_DIR_PREFERS_GRID
+ * @hide
*/
- public static final int FLAG_DIR_HIDE_GRID_TITLES = 1 << 6;
+ public static final int FLAG_DIR_HIDE_GRID_TITLES = 1 << 16;
}
/**
@@ -282,31 +285,17 @@
/**
* Unique ID of a root. This ID is both provided by and interpreted by a
* {@link DocumentsProvider}, and should be treated as an opaque value
- * by client applications.
+ * by client applications. This column is required.
* <p>
* Type: STRING
*/
public static final String COLUMN_ROOT_ID = "root_id";
/**
- * Type of a root, used for clustering when presenting multiple roots to
- * a user.
+ * Flags that apply to a root. This column is required.
* <p>
* Type: INTEGER (int)
*
- * @see #ROOT_TYPE_SERVICE
- * @see #ROOT_TYPE_SHORTCUT
- * @see #ROOT_TYPE_DEVICE
- */
- public static final String COLUMN_ROOT_TYPE = "root_type";
-
- /**
- * Flags that apply to a root.
- * <p>
- * Type: INTEGER (int)
- *
- * @see #FLAG_ADVANCED
- * @see #FLAG_EMPTY
* @see #FLAG_LOCAL_ONLY
* @see #FLAG_SUPPORTS_CREATE
* @see #FLAG_SUPPORTS_RECENTS
@@ -315,22 +304,23 @@
public static final String COLUMN_FLAGS = "flags";
/**
- * Icon resource ID for a root.
+ * Icon resource ID for a root. This column is required.
* <p>
* Type: INTEGER (int)
*/
public static final String COLUMN_ICON = "icon";
/**
- * Title for a root, which will be shown to a user.
+ * Title for a root, which will be shown to a user. This column is
+ * required.
* <p>
* Type: STRING
*/
public static final String COLUMN_TITLE = "title";
/**
- * Summary for this root, which may be shown to a user. The summary may
- * be {@code null}.
+ * Summary for this root, which may be shown to a user. This column is
+ * optional, and may be {@code null}.
* <p>
* Type: STRING
*/
@@ -338,7 +328,7 @@
/**
* Document which is a directory that represents the top directory of
- * this root.
+ * this root. This column is required.
* <p>
* Type: STRING
*
@@ -347,20 +337,20 @@
public static final String COLUMN_DOCUMENT_ID = "document_id";
/**
- * Number of bytes available in this root, or {@code null} if unknown or
- * unbounded.
+ * Number of bytes available in this root. This column is optional, and
+ * may be {@code null} if unknown or unbounded.
* <p>
* Type: INTEGER (long)
*/
public static final String COLUMN_AVAILABLE_BYTES = "available_bytes";
/**
- * MIME types supported by this root, or {@code null} if the root
- * supports all MIME types. Multiple MIME types can be separated by a
- * newline. For example, a root supporting audio might use
- * "audio/*\napplication/x-flac".
+ * MIME types supported by this root. This column is optional, and if
+ * {@code null} the root is assumed to support all MIME types. Multiple
+ * MIME types can be separated by a newline. For example, a root
+ * supporting audio might return "audio/*\napplication/x-flac".
* <p>
- * Type: String
+ * Type: STRING
*/
public static final String COLUMN_MIME_TYPES = "mime_types";
@@ -368,29 +358,6 @@
public static final String MIME_TYPE_ITEM = "vnd.android.document/root";
/**
- * Type of root that represents a storage service, such as a cloud-based
- * service.
- *
- * @see #COLUMN_ROOT_TYPE
- */
- public static final int ROOT_TYPE_SERVICE = 1;
-
- /**
- * Type of root that represents a shortcut to content that may be
- * available elsewhere through another storage root.
- *
- * @see #COLUMN_ROOT_TYPE
- */
- public static final int ROOT_TYPE_SHORTCUT = 2;
-
- /**
- * Type of root that represents a physical storage device.
- *
- * @see #COLUMN_ROOT_TYPE
- */
- public static final int ROOT_TYPE_DEVICE = 3;
-
- /**
* Flag indicating that at least one directory under this root supports
* creating content. Roots with this flag will be shown when an
* application interacts with {@link Intent#ACTION_CREATE_DOCUMENT}.
@@ -409,21 +376,13 @@
public static final int FLAG_LOCAL_ONLY = 1 << 1;
/**
- * Flag indicating that this root should only be visible to advanced
- * users.
- *
- * @see #COLUMN_FLAGS
- */
- public static final int FLAG_ADVANCED = 1 << 2;
-
- /**
* Flag indicating that this root can report recently modified
* documents.
*
* @see #COLUMN_FLAGS
* @see DocumentsContract#buildRecentDocumentsUri(String, String)
*/
- public static final int FLAG_SUPPORTS_RECENTS = 1 << 3;
+ public static final int FLAG_SUPPORTS_RECENTS = 1 << 2;
/**
* Flag indicating that this root supports search.
@@ -432,19 +391,31 @@
* @see DocumentsProvider#querySearchDocuments(String, String,
* String[])
*/
- public static final int FLAG_SUPPORTS_SEARCH = 1 << 4;
+ public static final int FLAG_SUPPORTS_SEARCH = 1 << 3;
/**
* Flag indicating that this root is currently empty. This may be used
* to hide the root when opening documents, but the root will still be
* shown when creating documents and {@link #FLAG_SUPPORTS_CREATE} is
- * also set.
+ * also set. If the value of this flag changes, such as when a root
+ * becomes non-empty, you must send a content changed notification for
+ * {@link DocumentsContract#buildRootsUri(String)}.
*
* @see #COLUMN_FLAGS
- * @see DocumentsProvider#querySearchDocuments(String, String,
- * String[])
+ * @see ContentResolver#notifyChange(Uri,
+ * android.database.ContentObserver, boolean)
+ * @hide
*/
- public static final int FLAG_EMPTY = 1 << 5;
+ public static final int FLAG_EMPTY = 1 << 16;
+
+ /**
+ * Flag indicating that this root should only be visible to advanced
+ * users.
+ *
+ * @see #COLUMN_FLAGS
+ * @hide
+ */
+ public static final int FLAG_ADVANCED = 1 << 17;
}
/**
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 38f28ae..28f7480 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -4825,8 +4825,8 @@
enqueueInputEvent(new KeyEvent(event.getDownTime(), event.getEventTime(),
event.getAction(), keyCode, event.getRepeatCount(), event.getMetaState(),
- event.getScanCode(), event.getFlags() | KeyEvent.FLAG_FALLBACK,
- event.getSource()));
+ event.getDeviceId(), event.getScanCode(),
+ event.getFlags() | KeyEvent.FLAG_FALLBACK, event.getSource()));
return true;
}
diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java
index e7d84c2..59330ca 100644
--- a/core/java/android/view/inputmethod/InputConnection.java
+++ b/core/java/android/view/inputmethod/InputConnection.java
@@ -337,14 +337,17 @@
public boolean deleteSurroundingText(int beforeLength, int afterLength);
/**
- * Set composing text around the current cursor position with the
- * given text, and set the new cursor position. Any composing text
- * set previously will be removed automatically.
+ * Replace the currently composing text with the given text, and
+ * set the new cursor position. Any composing text set previously
+ * will be removed automatically.
*
* <p>If there is any composing span currently active, all
* characters that it comprises are removed. The passed text is
* added in its place, and a composing span is added to this
- * text. Finally, the cursor is moved to the location specified by
+ * text. If there is no composing span active, the passed text is
+ * added at the cursor position (removing selected characters
+ * first if any), and a composing span is added on the new text.
+ * Finally, the cursor is moved to the location specified by
* <code>newCursorPosition</code>.</p>
*
* <p>This is usually called by IMEs to add or remove or change
@@ -447,8 +450,10 @@
*
* <p>This method removes the contents of the currently composing
* text and replaces it with the passed CharSequence, and then
- * moves the cursor according to {@code newCursorPosition}.
- * This behaves like calling
+ * moves the cursor according to {@code newCursorPosition}. If there
+ * is no composing text when this method is called, the new text is
+ * inserted at the cursor position, removing text inside the selection
+ * if any. This behaves like calling
* {@link #setComposingText(CharSequence, int) setComposingText(text, newCursorPosition)}
* then {@link #finishComposingText()}.</p>
*
@@ -461,15 +466,16 @@
* but be careful to wait until the batch edit is over if one is
* in progress.</p>
*
- * @param text The committed text. This may include styles.
- * @param newCursorPosition The new cursor position around the text. If
- * > 0, this is relative to the end of the text - 1; if <= 0, this
- * is relative to the start of the text. So a value of 1 will
- * always advance you to the position after the full text being
- * inserted. Note that this means you can't position the cursor
- * within the text, because the editor can make modifications to
- * the text you are providing so it is not possible to correctly
- * specify locations there.
+ * @param text The text to commit. This may include styles.
+ * @param newCursorPosition The new cursor position around the text,
+ * in Java characters. If > 0, this is relative to the end
+ * of the text - 1; if <= 0, this is relative to the start
+ * of the text. So a value of 1 will always advance the cursor
+ * to the position after the full text being inserted. Note that
+ * this means you can't position the cursor within the text,
+ * because the editor can make modifications to the text
+ * you are providing so it is not possible to correctly specify
+ * locations there.
* @return true on success, false if the input connection is no longer
* valid.
*/
diff --git a/core/java/android/widget/CursorAdapter.java b/core/java/android/widget/CursorAdapter.java
index 6c4c39d..d4c5be0 100644
--- a/core/java/android/widget/CursorAdapter.java
+++ b/core/java/android/widget/CursorAdapter.java
@@ -26,9 +26,13 @@
import android.view.ViewGroup;
/**
- * Adapter that exposes data from a {@link android.database.Cursor Cursor} to a
- * {@link android.widget.ListView ListView} widget. The Cursor must include
- * a column named "_id" or this class will not work.
+ * Adapter that exposes data from a {@link android.database.Cursor Cursor} to a
+ * {@link android.widget.ListView ListView} widget.
+ * <p>
+ * The Cursor must include a column named "_id" or this class will not work.
+ * Additionally, using {@link android.database.MergeCursor} with this class will
+ * not work if the merged Cursors have overlapping values in their "_id"
+ * columns.
*/
public abstract class CursorAdapter extends BaseAdapter implements Filterable,
CursorFilter.CursorFilterClient {
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 97cb815..61e071b 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -4679,8 +4679,6 @@
assumeLayout();
}
- boolean changed = false;
-
if (mMovement != null) {
/* This code also provides auto-scrolling when a cursor is moved using a
* CursorController (insertion point or selection limits).
@@ -4703,10 +4701,10 @@
}
if (curs >= 0) {
- changed = bringPointIntoView(curs);
+ bringPointIntoView(curs);
}
} else {
- changed = bringTextIntoView();
+ bringTextIntoView();
}
// This has to be checked here since:
@@ -4727,7 +4725,7 @@
getViewTreeObserver().removeOnPreDrawListener(this);
mPreDrawRegistered = false;
- return !changed;
+ return true;
}
@Override
diff --git a/core/java/android/widget/VideoView.java b/core/java/android/widget/VideoView.java
index 009b729..fbdf318 100644
--- a/core/java/android/widget/VideoView.java
+++ b/core/java/android/widget/VideoView.java
@@ -33,6 +33,7 @@
import android.media.SubtitleTrack.RenderingWidget;
import android.media.WebVttRenderer;
import android.net.Uri;
+import android.os.Looper;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Pair;
@@ -879,4 +880,10 @@
invalidate();
}
+
+ /** @hide */
+ @Override
+ public Looper getSubtitleLooper() {
+ return Looper.getMainLooper();
+ }
}
diff --git a/docs/html/guide/topics/ui/controls/checkbox.jd b/docs/html/guide/topics/ui/controls/checkbox.jd
index 99140b5..2a64e38 100644
--- a/docs/html/guide/topics/ui/controls/checkbox.jd
+++ b/docs/html/guide/topics/ui/controls/checkbox.jd
@@ -94,7 +94,7 @@
android.view.View} that was clicked)</li>
</ul>
-<p class="note"><strong>Tip:</strong> If you need to change the radio button state
+<p class="note"><strong>Tip:</strong> If you need to change the checkbox state
yourself (such as when loading a saved {@link android.preference.CheckBoxPreference}),
use the {@link android.widget.CompoundButton#setChecked(boolean)} or {@link
android.widget.CompoundButton#toggle()} method.</p>
\ No newline at end of file
diff --git a/docs/html/guide/topics/ui/dialogs.jd b/docs/html/guide/topics/ui/dialogs.jd
index 09767bf..d934c4b 100644
--- a/docs/html/guide/topics/ui/dialogs.jd
+++ b/docs/html/guide/topics/ui/dialogs.jd
@@ -593,7 +593,7 @@
.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
// Send the negative button event back to the host activity
- mListener.onDialogPositiveClick(NoticeDialogFragment.this);
+ mListener.onDialogNegativeClick(NoticeDialogFragment.this);
}
});
return builder.create();
diff --git a/docs/html/training/articles/security-ssl.jd b/docs/html/training/articles/security-ssl.jd
index d3f68e2..f52865a 100644
--- a/docs/html/training/articles/security-ssl.jd
+++ b/docs/html/training/articles/security-ssl.jd
@@ -250,7 +250,7 @@
This is similar to an unknown certificate authority, so you can use the
same approach from the previous section.</p>
-<p>You can create yout own {@link javax.net.ssl.TrustManager},
+<p>You can create your own {@link javax.net.ssl.TrustManager},
this time trusting the server certificate directly. This has all of the
downsides discussed earlier of tying your app directly to a certificate, but can be done
securely. However, you should be careful to make sure your self-signed certificate has a
diff --git a/docs/html/training/articles/smp.jd b/docs/html/training/articles/smp.jd
index 0f667d7..7240eec 100644
--- a/docs/html/training/articles/smp.jd
+++ b/docs/html/training/articles/smp.jd
@@ -1057,7 +1057,7 @@
fix them. Before we do that, we need to discuss the use of a basic language
feature.</p>
-<h4 id="volatile">C/C+++ and "volatile"</h4>
+<h4 id="volatile">C/C++ and "volatile"</h4>
<p>When writing single-threaded code, declaring a variable “volatile” can be
very useful. The compiler will not omit or reorder accesses to volatile
diff --git a/docs/html/training/monitoring-device-state/connectivity-monitoring.jd b/docs/html/training/monitoring-device-state/connectivity-monitoring.jd
index 11a05e1..fb5096d 100644
--- a/docs/html/training/monitoring-device-state/connectivity-monitoring.jd
+++ b/docs/html/training/monitoring-device-state/connectivity-monitoring.jd
@@ -49,7 +49,8 @@
(ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
-boolean isConnected = activeNetwork.isConnectedOrConnecting();</pre>
+boolean isConnected = activeNetwork != null &&
+ activeNetwork.isConnectedOrConnecting();</pre>
<h2 id="DetermineType">Determine the Type of your Internet Connection</h2>
diff --git a/docs/html/training/training_toc.cs b/docs/html/training/training_toc.cs
index a3c9dac..77ac235 100644
--- a/docs/html/training/training_toc.cs
+++ b/docs/html/training/training_toc.cs
@@ -576,29 +576,6 @@
</li>
</ul>
</li>
- <li class="nav-section">
- <div class="nav-section-header">
- <a href="<?cs var:toroot ?>training/id-auth/index.html"
- description=
- "How to remember the user by account, authenticate the user, acquire user permission
- for the user's online data, and create custom accounts on the device."
- >Remembering Users</a>
- </div>
- <ul>
- <li><a href="<?cs var:toroot ?>training/id-auth/identify.html">
- Remembering Your User
- </a>
- </li>
- <li><a href="<?cs var:toroot ?>training/id-auth/authenticate.html">
- Authenticating to OAuth2 Services
- </a>
- </li>
- <li><a href="<?cs var:toroot ?>training/id-auth/custom_auth.html">
- Creating a Custom Account Type
- </a>
- </li>
- </ul>
- </li>
<li class="nav-section">
<div class="nav-section-header">
diff --git a/media/java/android/media/MediaPlayer.java b/media/java/android/media/MediaPlayer.java
index deba2cc..def9aa7 100644
--- a/media/java/android/media/MediaPlayer.java
+++ b/media/java/android/media/MediaPlayer.java
@@ -1772,6 +1772,9 @@
mSelectedSubtitleTrackIndex = -1;
}
setOnSubtitleDataListener(null);
+ if (track == null) {
+ return;
+ }
for (int i = 0; i < mInbandSubtitleTracks.length; i++) {
if (mInbandSubtitleTracks[i] == track) {
Log.v(TAG, "Selecting subtitle track " + i);
diff --git a/media/java/android/media/SoundPool.java b/media/java/android/media/SoundPool.java
index 5127479..06af5de 100644
--- a/media/java/android/media/SoundPool.java
+++ b/media/java/android/media/SoundPool.java
@@ -374,7 +374,7 @@
* Called when a sound has completed loading.
*
* @param soundPool SoundPool object from the load() method
- * @param soundPool the sample ID of the sound loaded.
+ * @param sampleId the sample ID of the sound loaded.
* @param status the status of the load operation (0 = success)
*/
public void onLoadComplete(SoundPool soundPool, int sampleId, int status);
diff --git a/media/java/android/media/SubtitleController.java b/media/java/android/media/SubtitleController.java
index 8090561..13205bc 100644
--- a/media/java/android/media/SubtitleController.java
+++ b/media/java/android/media/SubtitleController.java
@@ -21,6 +21,9 @@
import android.content.Context;
import android.media.SubtitleTrack.RenderingWidget;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
import android.view.accessibility.CaptioningManager;
/**
@@ -37,6 +40,34 @@
private SubtitleTrack mSelectedTrack;
private boolean mShowing;
private CaptioningManager mCaptioningManager;
+ private Handler mHandler;
+
+ private static final int WHAT_SHOW = 1;
+ private static final int WHAT_HIDE = 2;
+ private static final int WHAT_SELECT_TRACK = 3;
+ private static final int WHAT_SELECT_DEFAULT_TRACK = 4;
+
+ private final Handler.Callback mCallback = new Handler.Callback() {
+ @Override
+ public boolean handleMessage(Message msg) {
+ switch (msg.what) {
+ case WHAT_SHOW:
+ doShow();
+ return true;
+ case WHAT_HIDE:
+ doHide();
+ return true;
+ case WHAT_SELECT_TRACK:
+ doSelectTrack((SubtitleTrack)msg.obj);
+ return true;
+ case WHAT_SELECT_DEFAULT_TRACK:
+ doSelectDefaultTrack();
+ return true;
+ default:
+ return false;
+ }
+ }
+ };
private CaptioningManager.CaptioningChangeListener mCaptioningChangeListener =
new CaptioningManager.CaptioningChangeListener() {
@@ -112,7 +143,7 @@
* in-band data from the {@link MediaPlayer}. However, this does
* not change the subtitle visibility.
*
- * Must be called from the UI thread.
+ * Should be called from the anchor's (UI) thread. {@see #Anchor.getSubtitleLooper}
*
* @param track The subtitle track to select. This must be one of the
* tracks in {@link #getTracks}.
@@ -122,9 +153,15 @@
if (track != null && !mTracks.contains(track)) {
return false;
}
+
+ processOnAnchor(mHandler.obtainMessage(WHAT_SELECT_TRACK, track));
+ return true;
+ }
+
+ private void doSelectTrack(SubtitleTrack track) {
mTrackIsExplicit = true;
if (mSelectedTrack == track) {
- return true;
+ return;
}
if (mSelectedTrack != null) {
@@ -145,7 +182,6 @@
if (mListener != null) {
mListener.onSubtitleTrackSelected(track);
}
- return true;
}
/**
@@ -170,8 +206,6 @@
*
* The default values for these flags are DEFAULT=no, AUTOSELECT=yes
* and FORCED=no.
- *
- * Must be called from the UI thread.
*/
public SubtitleTrack getDefaultTrack() {
SubtitleTrack bestTrack = null;
@@ -226,8 +260,12 @@
private boolean mTrackIsExplicit = false;
private boolean mVisibilityIsExplicit = false;
- /** @hide - called from UI thread */
+ /** @hide - should be called from anchor thread */
public void selectDefaultTrack() {
+ processOnAnchor(mHandler.obtainMessage(WHAT_SELECT_DEFAULT_TRACK));
+ }
+
+ private void doSelectDefaultTrack() {
if (mTrackIsExplicit) {
// If track selection is explicit, but visibility
// is not, it falls back to the captioning setting
@@ -259,8 +297,9 @@
}
}
- /** @hide - called from UI thread */
+ /** @hide - must be called from anchor thread */
public void reset() {
+ checkAnchorLooper();
hide();
selectTrack(null);
mTracks.clear();
@@ -301,9 +340,13 @@
/**
* Show the selected (or default) subtitle track.
*
- * Must be called from the UI thread.
+ * Should be called from the anchor's (UI) thread. {@see #Anchor.getSubtitleLooper}
*/
public void show() {
+ processOnAnchor(mHandler.obtainMessage(WHAT_SHOW));
+ }
+
+ private void doShow() {
mShowing = true;
mVisibilityIsExplicit = true;
if (mSelectedTrack != null) {
@@ -314,9 +357,13 @@
/**
* Hide the selected (or default) subtitle track.
*
- * Must be called from the UI thread.
+ * Should be called from the anchor's (UI) thread. {@see #Anchor.getSubtitleLooper}
*/
public void hide() {
+ processOnAnchor(mHandler.obtainMessage(WHAT_HIDE));
+ }
+
+ private void doHide() {
mVisibilityIsExplicit = true;
if (mSelectedTrack != null) {
mSelectedTrack.hide();
@@ -384,25 +431,53 @@
* @hide
*/
public void setSubtitleWidget(RenderingWidget subtitleWidget);
+
+ /**
+ * Anchors provide the looper on which all track visibility changes
+ * (track.show/hide, setSubtitleWidget) will take place.
+ * @hide
+ */
+ public Looper getSubtitleLooper();
}
private Anchor mAnchor;
- /** @hide - called from UI thread */
+ /**
+ * @hide - called from anchor's looper (if any, both when unsetting and
+ * setting)
+ */
public void setAnchor(Anchor anchor) {
if (mAnchor == anchor) {
return;
}
if (mAnchor != null) {
+ checkAnchorLooper();
mAnchor.setSubtitleWidget(null);
}
mAnchor = anchor;
+ mHandler = null;
if (mAnchor != null) {
+ mHandler = new Handler(mAnchor.getSubtitleLooper(), mCallback);
+ checkAnchorLooper();
mAnchor.setSubtitleWidget(getRenderingWidget());
}
}
+ private void checkAnchorLooper() {
+ assert mHandler != null : "Should have a looper already";
+ assert Looper.myLooper() == mHandler.getLooper() : "Must be called from the anchor's looper";
+ }
+
+ private void processOnAnchor(Message m) {
+ assert mHandler != null : "Should have a looper already";
+ if (Looper.myLooper() == mHandler.getLooper()) {
+ mHandler.dispatchMessage(m);
+ } else {
+ mHandler.sendMessage(m);
+ }
+ }
+
public interface Listener {
/**
* Called when a subtitle track has been selected.
diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_dir_shadow.9.png b/packages/DocumentsUI/res/drawable-hdpi/ic_dir_shadow.9.png
index 0240874..904d525 100644
--- a/packages/DocumentsUI/res/drawable-hdpi/ic_dir_shadow.9.png
+++ b/packages/DocumentsUI/res/drawable-hdpi/ic_dir_shadow.9.png
Binary files differ
diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_drawer_hairline_divider.9.png b/packages/DocumentsUI/res/drawable-hdpi/ic_drawer_hairline_divider.9.png
new file mode 100644
index 0000000..0d75172
--- /dev/null
+++ b/packages/DocumentsUI/res/drawable-hdpi/ic_drawer_hairline_divider.9.png
Binary files differ
diff --git a/packages/DocumentsUI/res/drawable-hdpi/ic_drawer_tall_divider.9.png b/packages/DocumentsUI/res/drawable-hdpi/ic_drawer_tall_divider.9.png
new file mode 100644
index 0000000..403eddb
--- /dev/null
+++ b/packages/DocumentsUI/res/drawable-hdpi/ic_drawer_tall_divider.9.png
Binary files differ
diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_dir_shadow.9.png b/packages/DocumentsUI/res/drawable-mdpi/ic_dir_shadow.9.png
index 0240874..068619b 100644
--- a/packages/DocumentsUI/res/drawable-mdpi/ic_dir_shadow.9.png
+++ b/packages/DocumentsUI/res/drawable-mdpi/ic_dir_shadow.9.png
Binary files differ
diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_drawer_hairline_divider.9.png b/packages/DocumentsUI/res/drawable-mdpi/ic_drawer_hairline_divider.9.png
new file mode 100644
index 0000000..0d75172
--- /dev/null
+++ b/packages/DocumentsUI/res/drawable-mdpi/ic_drawer_hairline_divider.9.png
Binary files differ
diff --git a/packages/DocumentsUI/res/drawable-mdpi/ic_drawer_tall_divider.9.png b/packages/DocumentsUI/res/drawable-mdpi/ic_drawer_tall_divider.9.png
new file mode 100644
index 0000000..9a9cf5e
--- /dev/null
+++ b/packages/DocumentsUI/res/drawable-mdpi/ic_drawer_tall_divider.9.png
Binary files differ
diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_dir_shadow.9.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_dir_shadow.9.png
index 0240874..e38a868 100644
--- a/packages/DocumentsUI/res/drawable-xhdpi/ic_dir_shadow.9.png
+++ b/packages/DocumentsUI/res/drawable-xhdpi/ic_dir_shadow.9.png
Binary files differ
diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_drawer_hairline_divider.9.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_drawer_hairline_divider.9.png
new file mode 100644
index 0000000..0d75172
--- /dev/null
+++ b/packages/DocumentsUI/res/drawable-xhdpi/ic_drawer_hairline_divider.9.png
Binary files differ
diff --git a/packages/DocumentsUI/res/drawable-xhdpi/ic_drawer_tall_divider.9.png b/packages/DocumentsUI/res/drawable-xhdpi/ic_drawer_tall_divider.9.png
new file mode 100644
index 0000000..205c34b
--- /dev/null
+++ b/packages/DocumentsUI/res/drawable-xhdpi/ic_drawer_tall_divider.9.png
Binary files differ
diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_dir_shadow.9.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_dir_shadow.9.png
index 0240874..0b332e4 100644
--- a/packages/DocumentsUI/res/drawable-xxhdpi/ic_dir_shadow.9.png
+++ b/packages/DocumentsUI/res/drawable-xxhdpi/ic_dir_shadow.9.png
Binary files differ
diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_drawer_hairline_divider.9.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_drawer_hairline_divider.9.png
new file mode 100644
index 0000000..32b5f98
--- /dev/null
+++ b/packages/DocumentsUI/res/drawable-xxhdpi/ic_drawer_hairline_divider.9.png
Binary files differ
diff --git a/packages/DocumentsUI/res/drawable-xxhdpi/ic_drawer_tall_divider.9.png b/packages/DocumentsUI/res/drawable-xxhdpi/ic_drawer_tall_divider.9.png
new file mode 100644
index 0000000..f47d50a
--- /dev/null
+++ b/packages/DocumentsUI/res/drawable-xxhdpi/ic_drawer_tall_divider.9.png
Binary files differ
diff --git a/packages/DocumentsUI/res/layout-sw720dp-land/item_doc_list.xml b/packages/DocumentsUI/res/layout-sw720dp-land/item_doc_list.xml
index 3bea166..851061f 100644
--- a/packages/DocumentsUI/res/layout-sw720dp-land/item_doc_list.xml
+++ b/packages/DocumentsUI/res/layout-sw720dp-land/item_doc_list.xml
@@ -21,17 +21,18 @@
android:minHeight="?android:attr/listPreferredItemHeight"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
- android:paddingTop="8dip"
- android:paddingBottom="8dip"
- android:orientation="horizontal">
+ android:paddingTop="8dp"
+ android:paddingBottom="8dp"
+ android:gravity="center_vertical"
+ android:orientation="horizontal"
+ android:baselineAligned="false">
<FrameLayout
android:id="@android:id/icon"
android:layout_width="@dimen/icon_size"
android:layout_height="@dimen/icon_size"
android:layout_marginStart="12dp"
- android:layout_marginEnd="20dp"
- android:layout_gravity="center_vertical">
+ android:layout_marginEnd="20dp">
<ImageView
android:id="@+id/icon_mime"
@@ -49,11 +50,11 @@
</FrameLayout>
+ <!-- This is the one special case where we want baseline alignment! -->
<LinearLayout
- android:layout_width="0dip"
+ android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
- android:layout_gravity="center_vertical"
android:orientation="horizontal">
<TextView
diff --git a/packages/DocumentsUI/res/layout-sw720dp/activity.xml b/packages/DocumentsUI/res/layout-sw720dp/activity.xml
index 78735fd..9286277 100644
--- a/packages/DocumentsUI/res/layout-sw720dp/activity.xml
+++ b/packages/DocumentsUI/res/layout-sw720dp/activity.xml
@@ -17,7 +17,8 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:orientation="horizontal">
+ android:orientation="horizontal"
+ android:baselineAligned="false">
<FrameLayout
android:layout_width="wrap_content"
@@ -47,7 +48,7 @@
<com.android.documentsui.DirectoryContainerView
android:id="@+id/container_directory"
android:layout_width="match_parent"
- android:layout_height="0dip"
+ android:layout_height="0dp"
android:layout_weight="1" />
<FrameLayout
diff --git a/packages/DocumentsUI/res/layout/activity.xml b/packages/DocumentsUI/res/layout/activity.xml
index 9937c39..2ef7e9c 100644
--- a/packages/DocumentsUI/res/layout/activity.xml
+++ b/packages/DocumentsUI/res/layout/activity.xml
@@ -27,7 +27,7 @@
<com.android.documentsui.DirectoryContainerView
android:id="@+id/container_directory"
android:layout_width="match_parent"
- android:layout_height="0dip"
+ android:layout_height="0dp"
android:layout_weight="1" />
<FrameLayout
diff --git a/packages/DocumentsUI/res/layout/fragment_roots.xml b/packages/DocumentsUI/res/layout/fragment_roots.xml
index 09782d9..c3a3da0 100644
--- a/packages/DocumentsUI/res/layout/fragment_roots.xml
+++ b/packages/DocumentsUI/res/layout/fragment_roots.xml
@@ -18,4 +18,4 @@
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:divider="@null" />
+ android:divider="@drawable/ic_drawer_hairline_divider" />
diff --git a/packages/DocumentsUI/res/layout/fragment_save.xml b/packages/DocumentsUI/res/layout/fragment_save.xml
index 570b517..891f0a0 100644
--- a/packages/DocumentsUI/res/layout/fragment_save.xml
+++ b/packages/DocumentsUI/res/layout/fragment_save.xml
@@ -29,6 +29,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
+ android:baselineAligned="false"
android:gravity="center_vertical"
android:background="#ddd"
android:minHeight="?android:attr/listPreferredItemHeightSmall">
@@ -44,7 +45,7 @@
<EditText
android:id="@android:id/title"
- android:layout_width="0dip"
+ android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:singleLine="true"
diff --git a/packages/DocumentsUI/res/layout/item_doc_grid.xml b/packages/DocumentsUI/res/layout/item_doc_grid.xml
index b745bb9..bb5dce1 100644
--- a/packages/DocumentsUI/res/layout/item_doc_grid.xml
+++ b/packages/DocumentsUI/res/layout/item_doc_grid.xml
@@ -28,37 +28,25 @@
<FrameLayout
android:layout_width="match_parent"
- android:layout_height="0dip"
+ android:layout_height="0dp"
android:layout_weight="1"
android:layout_marginBottom="6dp"
- android:background="#fff">
-
- <FrameLayout
- android:id="@android:id/icon"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
-
- <ImageView
- android:id="@+id/icon_mime"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:scaleType="centerInside"
- android:contentDescription="@null" />
-
- <ImageView
- android:id="@+id/icon_thumb"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:scaleType="centerCrop"
- android:contentDescription="@null" />
-
- </FrameLayout>
+ android:background="#fff"
+ android:foreground="@drawable/ic_grid_gradient_bg"
+ android:foregroundGravity="fill">
<ImageView
+ android:id="@+id/icon_mime"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:scaleType="fitXY"
- android:src="@drawable/ic_grid_gradient_bg"
+ android:scaleType="centerInside"
+ android:contentDescription="@null" />
+
+ <ImageView
+ android:id="@+id/icon_thumb"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:scaleType="centerCrop"
android:contentDescription="@null" />
</FrameLayout>
@@ -67,7 +55,9 @@
android:id="@+id/line1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:gravity="center_vertical"
android:orientation="horizontal"
+ android:baselineAligned="false"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
@@ -85,7 +75,7 @@
android:id="@android:id/icon1"
android:layout_width="@dimen/root_icon_size"
android:layout_height="@dimen/root_icon_size"
- android:layout_marginStart="8dip"
+ android:layout_marginStart="8dp"
android:scaleType="centerInside"
android:contentDescription="@null" />
@@ -95,16 +85,17 @@
android:id="@+id/line2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:gravity="center_vertical"
android:orientation="horizontal"
+ android:baselineAligned="false"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
<TextView
android:id="@+id/date"
- android:layout_width="wrap_content"
+ android:layout_width="0dp"
android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
- android:minWidth="80dp"
+ android:layout_weight="0.5"
android:singleLine="true"
android:ellipsize="marquee"
android:textAlignment="viewStart"
@@ -112,26 +103,20 @@
<TextView
android:id="@+id/size"
- android:layout_width="wrap_content"
+ android:layout_width="0dp"
android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
+ android:layout_weight="0.5"
android:layout_marginStart="8dp"
- android:minWidth="80dp"
android:singleLine="true"
android:ellipsize="marquee"
android:textAlignment="viewStart"
style="@style/TextAppearance.Small" />
- <Space
- android:layout_width="0dp"
- android:layout_height="0dp"
- android:layout_weight="1" />
-
<ImageView
android:id="@android:id/icon2"
android:layout_width="@dimen/root_icon_size"
android:layout_height="@dimen/root_icon_size"
- android:layout_marginStart="8dip"
+ android:layout_marginStart="8dp"
android:scaleType="centerInside"
android:contentDescription="@null"
android:visibility="gone" />
diff --git a/packages/DocumentsUI/res/layout/item_doc_list.xml b/packages/DocumentsUI/res/layout/item_doc_list.xml
index 84fda9d..4c5b2e3 100644
--- a/packages/DocumentsUI/res/layout/item_doc_list.xml
+++ b/packages/DocumentsUI/res/layout/item_doc_list.xml
@@ -21,17 +21,18 @@
android:minHeight="?android:attr/listPreferredItemHeight"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
- android:paddingTop="8dip"
- android:paddingBottom="8dip"
- android:orientation="horizontal">
+ android:paddingTop="8dp"
+ android:paddingBottom="8dp"
+ android:gravity="center_vertical"
+ android:orientation="horizontal"
+ android:baselineAligned="false">
<FrameLayout
android:id="@android:id/icon"
android:layout_width="@dimen/icon_size"
android:layout_height="@dimen/icon_size"
android:layout_marginStart="12dp"
- android:layout_marginEnd="20dp"
- android:layout_gravity="center_vertical">
+ android:layout_marginEnd="20dp">
<ImageView
android:id="@+id/icon_mime"
@@ -50,20 +51,20 @@
</FrameLayout>
<LinearLayout
- android:layout_width="0dip"
+ android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
- android:layout_gravity="center_vertical"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:orientation="horizontal">
+ android:orientation="horizontal"
+ android:baselineAligned="false">
<TextView
android:id="@android:id/title"
- android:layout_width="0dip"
+ android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:singleLine="true"
@@ -75,7 +76,7 @@
android:id="@android:id/icon1"
android:layout_width="@dimen/root_icon_size"
android:layout_height="@dimen/root_icon_size"
- android:layout_marginStart="8dip"
+ android:layout_marginStart="8dp"
android:scaleType="centerInside"
android:contentDescription="@null" />
@@ -85,13 +86,15 @@
android:id="@+id/line2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:orientation="horizontal">
+ android:gravity="center_vertical"
+ android:orientation="horizontal"
+ android:baselineAligned="false">
<TextView
android:id="@+id/date"
- android:layout_width="wrap_content"
+ android:layout_width="0dp"
android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
+ android:layout_weight="0.25"
android:minWidth="70dp"
android:singleLine="true"
android:ellipsize="marquee"
@@ -100,11 +103,11 @@
<TextView
android:id="@+id/size"
- android:layout_width="wrap_content"
+ android:layout_width="0dp"
android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
- android:minWidth="70dp"
+ android:layout_weight="0.25"
android:layout_marginStart="8dp"
+ android:minWidth="70dp"
android:singleLine="true"
android:ellipsize="marquee"
android:textAlignment="viewStart"
@@ -114,8 +117,7 @@
android:id="@android:id/summary"
android:layout_width="0dp"
android:layout_height="wrap_content"
- android:layout_weight="1"
- android:layout_gravity="center_vertical"
+ android:layout_weight="0.5"
android:layout_marginStart="8dp"
android:singleLine="true"
android:ellipsize="marquee"
diff --git a/packages/DocumentsUI/res/layout/item_loading_grid.xml b/packages/DocumentsUI/res/layout/item_loading_grid.xml
index 21be137..0bf6137 100644
--- a/packages/DocumentsUI/res/layout/item_loading_grid.xml
+++ b/packages/DocumentsUI/res/layout/item_loading_grid.xml
@@ -20,8 +20,8 @@
android:minHeight="?android:attr/listPreferredItemHeight"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
- android:paddingTop="8dip"
- android:paddingBottom="8dip"
+ android:paddingTop="8dp"
+ android:paddingBottom="8dp"
android:orientation="horizontal">
<ProgressBar
diff --git a/packages/DocumentsUI/res/layout/item_loading_list.xml b/packages/DocumentsUI/res/layout/item_loading_list.xml
index 7da71e3..cdcd01d 100644
--- a/packages/DocumentsUI/res/layout/item_loading_list.xml
+++ b/packages/DocumentsUI/res/layout/item_loading_list.xml
@@ -20,9 +20,8 @@
android:minHeight="?android:attr/listPreferredItemHeight"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
- android:paddingTop="8dip"
- android:paddingBottom="8dip"
- android:orientation="horizontal">
+ android:paddingTop="8dp"
+ android:paddingBottom="8dp">
<ProgressBar
android:layout_width="wrap_content"
diff --git a/packages/DocumentsUI/res/layout/item_message_list.xml b/packages/DocumentsUI/res/layout/item_message_list.xml
index ffda98c..2bcbc2d 100644
--- a/packages/DocumentsUI/res/layout/item_message_list.xml
+++ b/packages/DocumentsUI/res/layout/item_message_list.xml
@@ -21,15 +21,16 @@
android:minHeight="?android:attr/listPreferredItemHeight"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
- android:paddingTop="8dip"
- android:paddingBottom="8dip"
- android:orientation="horizontal">
+ android:paddingTop="8dp"
+ android:paddingBottom="8dp"
+ android:orientation="horizontal"
+ android:baselineAligned="false">
<ImageView
android:id="@android:id/icon"
android:layout_width="@android:dimen/app_icon_size"
android:layout_height="@android:dimen/app_icon_size"
- android:layout_marginEnd="8dip"
+ android:layout_marginEnd="8dp"
android:layout_gravity="center_vertical"
android:scaleType="centerInside"
android:contentDescription="@null" />
diff --git a/packages/DocumentsUI/res/layout/item_root.xml b/packages/DocumentsUI/res/layout/item_root.xml
index 98d78da..9b52d85 100644
--- a/packages/DocumentsUI/res/layout/item_root.xml
+++ b/packages/DocumentsUI/res/layout/item_root.xml
@@ -22,13 +22,14 @@
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:gravity="center_vertical"
android:orientation="horizontal"
+ android:baselineAligned="false"
android:background="@drawable/item_root">
<ImageView
android:id="@android:id/icon"
android:layout_width="@dimen/icon_size"
android:layout_height="@dimen/icon_size"
- android:layout_marginEnd="8dip"
+ android:layout_marginEnd="8dp"
android:scaleType="centerInside"
android:contentDescription="@null" />
diff --git a/packages/DocumentsUI/res/layout/item_root_spacer.xml b/packages/DocumentsUI/res/layout/item_root_spacer.xml
new file mode 100644
index 0000000..7d96ac8
--- /dev/null
+++ b/packages/DocumentsUI/res/layout/item_root_spacer.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 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.
+-->
+
+<View xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="@drawable/ic_drawer_tall_divider" />
diff --git a/packages/DocumentsUI/res/layout/item_title.xml b/packages/DocumentsUI/res/layout/item_title.xml
index 7eb100a..58016f1 100644
--- a/packages/DocumentsUI/res/layout/item_title.xml
+++ b/packages/DocumentsUI/res/layout/item_title.xml
@@ -21,7 +21,8 @@
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:gravity="center_vertical"
- android:orientation="horizontal">
+ android:orientation="horizontal"
+ android:baselineAligned="false">
<ImageView
android:id="@+id/subdir"
diff --git a/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java
index 9d92cd8..48bfaf0 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java
@@ -25,6 +25,7 @@
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.net.Uri;
+import android.os.AsyncTask;
import android.os.Bundle;
import android.provider.DocumentsContract;
import android.provider.DocumentsContract.Document;
@@ -35,6 +36,8 @@
import com.android.documentsui.model.DocumentInfo;
+import java.io.FileNotFoundException;
+
/**
* Dialog to create a new directory.
*/
@@ -64,24 +67,45 @@
@Override
public void onClick(DialogInterface dialog, int which) {
final String displayName = text1.getText().toString();
-
- final DocumentsActivity activity = (DocumentsActivity) getActivity();
- final DocumentInfo cwd = activity.getCurrentDirectory();
-
- try {
- final Uri childUri = DocumentsContract.createDocument(
- resolver, cwd.derivedUri, Document.MIME_TYPE_DIR, displayName);
-
- // Navigate into newly created child
- final DocumentInfo childDoc = DocumentInfo.fromUri(resolver, childUri);
- activity.onDocumentPicked(childDoc);
- } catch (Exception e) {
- Toast.makeText(context, R.string.create_error, Toast.LENGTH_SHORT).show();
- }
+ new CreateDirectoryTask(displayName).execute();
}
});
builder.setNegativeButton(android.R.string.cancel, null);
return builder.create();
}
+
+ private class CreateDirectoryTask extends AsyncTask<Void, Void, DocumentInfo> {
+ private final String mDisplayName;
+
+ public CreateDirectoryTask(String displayName) {
+ mDisplayName = displayName;
+ }
+
+ @Override
+ protected DocumentInfo doInBackground(Void... params) {
+ final DocumentsActivity activity = (DocumentsActivity) getActivity();
+ final ContentResolver resolver = activity.getContentResolver();
+
+ final DocumentInfo cwd = activity.getCurrentDirectory();
+ final Uri childUri = DocumentsContract.createDocument(
+ resolver, cwd.derivedUri, Document.MIME_TYPE_DIR, mDisplayName);
+ try {
+ return DocumentInfo.fromUri(resolver, childUri);
+ } catch (FileNotFoundException e) {
+ return null;
+ }
+ }
+
+ @Override
+ protected void onPostExecute(DocumentInfo result) {
+ final DocumentsActivity activity = (DocumentsActivity) getActivity();
+ if (result != null) {
+ // Navigate into newly created child
+ activity.onDocumentPicked(result);
+ } else {
+ Toast.makeText(activity, R.string.create_error, Toast.LENGTH_SHORT).show();
+ }
+ }
+ }
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
index 138f523..1f11aed 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
@@ -742,7 +742,6 @@
final View line1 = convertView.findViewById(R.id.line1);
final View line2 = convertView.findViewById(R.id.line2);
- final View icon = convertView.findViewById(android.R.id.icon);
final ImageView iconMime = (ImageView) convertView.findViewById(R.id.icon_mime);
final ImageView iconThumb = (ImageView) convertView.findViewById(R.id.icon_thumb);
final TextView title = (TextView) convertView.findViewById(android.R.id.title);
@@ -786,10 +785,12 @@
// loaded in background.
if (cacheHit) {
iconMime.setAlpha(0f);
+ iconMime.setImageDrawable(null);
iconThumb.setAlpha(1f);
} else {
iconMime.setAlpha(1f);
iconThumb.setAlpha(0f);
+ iconThumb.setImageDrawable(null);
if (docIcon != 0) {
iconMime.setImageDrawable(
IconUtils.loadPackageIcon(context, docAuthority, docIcon));
@@ -895,12 +896,14 @@
final boolean enabled = isDocumentEnabled(docMimeType, docFlags);
if (enabled) {
setEnabledRecursive(convertView, true);
- icon.setAlpha(1f);
+ iconMime.setAlpha(1f);
+ iconThumb.setAlpha(1f);
if (icon1 != null) icon1.setAlpha(1f);
if (icon2 != null) icon2.setAlpha(1f);
} else {
setEnabledRecursive(convertView, false);
- icon.setAlpha(0.5f);
+ iconMime.setAlpha(0.5f);
+ iconThumb.setAlpha(0.5f);
if (icon1 != null) icon1.setAlpha(0.5f);
if (icon2 != null) icon2.setAlpha(0.5f);
}
@@ -991,10 +994,11 @@
mIconThumb.setTag(null);
mIconThumb.setImageBitmap(result);
- mIconMime.setAlpha(1f);
+ final float targetAlpha = mIconMime.isEnabled() ? 1f : 0.5f;
+ mIconMime.setAlpha(targetAlpha);
mIconMime.animate().alpha(0f).start();
mIconThumb.setAlpha(0f);
- mIconThumb.animate().alpha(1f).start();
+ mIconThumb.animate().alpha(targetAlpha).start();
}
}
}
@@ -1060,16 +1064,16 @@
private boolean isDocumentEnabled(String docMimeType, int docFlags) {
final State state = getDisplayState(DirectoryFragment.this);
- // Read-only files are disabled when creating
- if (state.action == ACTION_CREATE && (docFlags & Document.FLAG_SUPPORTS_WRITE) == 0) {
- return false;
- }
-
// Directories are always enabled
if (Document.MIME_TYPE_DIR.equals(docMimeType)) {
return true;
}
+ // Read-only files are disabled when creating
+ if (state.action == ACTION_CREATE && (docFlags & Document.FLAG_SUPPORTS_WRITE) == 0) {
+ return false;
+ }
+
return MimePredicate.mimeMatches(state.acceptMimes, docMimeType);
}
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java
index 8627ecf..0b3ecf8 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java
@@ -63,6 +63,9 @@
}
public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> {
+
+ private static final String[] SEARCH_REJECT_MIMES = new String[] { Document.MIME_TYPE_DIR };
+
private final ForceLoadContentObserver mObserver = new ForceLoadContentObserver();
private final int mType;
@@ -164,8 +167,7 @@
if (mType == DirectoryFragment.TYPE_SEARCH) {
// Filter directories out of search results, for now
- cursor = new FilteringCursorWrapper(cursor, null, new String[] {
- Document.MIME_TYPE_DIR });
+ cursor = new FilteringCursorWrapper(cursor, null, SEARCH_REJECT_MIMES);
} else {
// Normal directories should have sorting applied
cursor = new SortingCursorWrapper(cursor, result.sortOrder);
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
index 72fdc57..4caec8f 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
@@ -854,14 +854,7 @@
mState.stackTouched = true;
if (!mRoots.isRecentsRoot(root)) {
- try {
- final Uri uri = DocumentsContract.buildDocumentUri(root.authority, root.documentId);
- final DocumentInfo doc = DocumentInfo.fromUri(getContentResolver(), uri);
- mState.stack.push(doc);
- mState.stackTouched = true;
- onCurrentDirectoryChanged(ANIM_SIDE);
- } catch (FileNotFoundException e) {
- }
+ new PickRootTask(root).execute();
} else {
onCurrentDirectoryChanged(ANIM_SIDE);
}
@@ -871,6 +864,34 @@
}
}
+ private class PickRootTask extends AsyncTask<Void, Void, DocumentInfo> {
+ private RootInfo mRoot;
+
+ public PickRootTask(RootInfo root) {
+ mRoot = root;
+ }
+
+ @Override
+ protected DocumentInfo doInBackground(Void... params) {
+ try {
+ final Uri uri = DocumentsContract.buildDocumentUri(
+ mRoot.authority, mRoot.documentId);
+ return DocumentInfo.fromUri(getContentResolver(), uri);
+ } catch (FileNotFoundException e) {
+ return null;
+ }
+ }
+
+ @Override
+ protected void onPostExecute(DocumentInfo result) {
+ if (result != null) {
+ mState.stack.push(result);
+ mState.stackTouched = true;
+ onCurrentDirectoryChanged(ANIM_SIDE);
+ }
+ }
+ }
+
public void onAppPicked(ResolveInfo info) {
final Intent intent = new Intent(getIntent());
intent.setFlags(intent.getFlags() & ~Intent.FLAG_ACTIVITY_FORWARD_RESULT);
@@ -909,7 +930,7 @@
onCurrentDirectoryChanged(ANIM_DOWN);
} else if (mState.action == ACTION_OPEN || mState.action == ACTION_GET_CONTENT) {
// Explicit file picked, return
- onFinished(doc.derivedUri);
+ new ExistingFinishTask(doc.derivedUri).execute();
} else if (mState.action == ACTION_CREATE) {
// Replace selected file
SaveFragment.get(fm).setReplaceTarget(doc);
@@ -943,29 +964,19 @@
for (int i = 0; i < size; i++) {
uris[i] = docs.get(i).derivedUri;
}
- onFinished(uris);
+ new ExistingFinishTask(uris).execute();
}
}
public void onSaveRequested(DocumentInfo replaceTarget) {
- onFinished(replaceTarget.derivedUri);
+ new ExistingFinishTask(replaceTarget.derivedUri).execute();
}
public void onSaveRequested(String mimeType, String displayName) {
- final DocumentInfo cwd = getCurrentDirectory();
-
- final Uri childUri = DocumentsContract.createDocument(
- getContentResolver(), cwd.derivedUri, mimeType, displayName);
- if (childUri != null) {
- onFinished(childUri);
- } else {
- Toast.makeText(this, R.string.save_error, Toast.LENGTH_SHORT).show();
- }
+ new CreateFinishTask(mimeType, displayName).execute();
}
- private void onFinished(Uri... uris) {
- Log.d(TAG, "onFinished() " + Arrays.toString(uris));
-
+ private void saveStackBlocking() {
final ContentResolver resolver = getContentResolver();
final ContentValues values = new ContentValues();
@@ -973,6 +984,7 @@
if (mState.action == ACTION_CREATE) {
// Remember stack for last create
values.clear();
+ values.put(RecentColumns.KEY, mState.stack.buildKey());
values.put(RecentColumns.STACK, rawStack);
resolver.insert(RecentsProvider.buildRecent(), values);
}
@@ -983,6 +995,10 @@
values.put(ResumeColumns.STACK, rawStack);
values.put(ResumeColumns.EXTERNAL, 0);
resolver.insert(RecentsProvider.buildResume(packageName), values);
+ }
+
+ private void onFinished(Uri... uris) {
+ Log.d(TAG, "onFinished() " + Arrays.toString(uris));
final Intent intent = new Intent();
if (uris.length == 1) {
@@ -1008,6 +1024,56 @@
finish();
}
+ private class CreateFinishTask extends AsyncTask<Void, Void, Uri> {
+ private final String mMimeType;
+ private final String mDisplayName;
+
+ public CreateFinishTask(String mimeType, String displayName) {
+ mMimeType = mimeType;
+ mDisplayName = displayName;
+ }
+
+ @Override
+ protected Uri doInBackground(Void... params) {
+ final DocumentInfo cwd = getCurrentDirectory();
+ final Uri childUri = DocumentsContract.createDocument(
+ getContentResolver(), cwd.derivedUri, mMimeType, mDisplayName);
+ if (childUri != null) {
+ saveStackBlocking();
+ }
+ return childUri;
+ }
+
+ @Override
+ protected void onPostExecute(Uri result) {
+ if (result != null) {
+ onFinished(result);
+ } else {
+ Toast.makeText(DocumentsActivity.this, R.string.save_error, Toast.LENGTH_SHORT)
+ .show();
+ }
+ }
+ }
+
+ private class ExistingFinishTask extends AsyncTask<Void, Void, Void> {
+ private final Uri[] mUris;
+
+ public ExistingFinishTask(Uri... uris) {
+ mUris = uris;
+ }
+
+ @Override
+ protected Void doInBackground(Void... params) {
+ saveStackBlocking();
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void result) {
+ onFinished(mUris);
+ }
+ }
+
public static class State implements android.os.Parcelable {
public int action;
public String[] acceptMimes;
diff --git a/packages/DocumentsUI/src/com/android/documentsui/FilteringCursorWrapper.java b/packages/DocumentsUI/src/com/android/documentsui/FilteringCursorWrapper.java
index 5f56963..52d816f 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/FilteringCursorWrapper.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/FilteringCursorWrapper.java
@@ -34,10 +34,15 @@
private int mCount;
public FilteringCursorWrapper(Cursor cursor, String[] acceptMimes) {
- this(cursor, acceptMimes, null);
+ this(cursor, acceptMimes, null, Long.MIN_VALUE);
}
public FilteringCursorWrapper(Cursor cursor, String[] acceptMimes, String[] rejectMimes) {
+ this(cursor, acceptMimes, rejectMimes, Long.MIN_VALUE);
+ }
+
+ public FilteringCursorWrapper(
+ Cursor cursor, String[] acceptMimes, String[] rejectMimes, long rejectBefore) {
mCursor = cursor;
final int count = cursor.getCount();
@@ -47,9 +52,14 @@
while (cursor.moveToNext()) {
final String mimeType = cursor.getString(
cursor.getColumnIndex(Document.COLUMN_MIME_TYPE));
+ final long lastModified = cursor.getLong(
+ cursor.getColumnIndex(Document.COLUMN_LAST_MODIFIED));
if (rejectMimes != null && MimePredicate.mimeMatches(rejectMimes, mimeType)) {
continue;
}
+ if (lastModified < rejectBefore) {
+ continue;
+ }
if (MimePredicate.mimeMatches(acceptMimes, mimeType)) {
mPosition[mCount++] = cursor.getPosition();
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java b/packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java
index e390456..9a4fb7d 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java
@@ -26,9 +26,11 @@
import android.database.Cursor;
import android.database.MergeCursor;
import android.net.Uri;
+import android.os.Bundle;
import android.provider.DocumentsContract;
import android.provider.DocumentsContract.Document;
import android.provider.DocumentsContract.Root;
+import android.text.format.DateUtils;
import android.util.Log;
import com.android.documentsui.DocumentsActivity.State;
@@ -54,17 +56,23 @@
public class RecentLoader extends AsyncTaskLoader<DirectoryResult> {
private static final boolean LOGD = true;
- public static final int MAX_OUTSTANDING_RECENTS = 2;
+ // TODO: adjust for svelte devices
+ // TODO: add support for oneway queries to avoid wedging loader
+ private static final int MAX_OUTSTANDING_RECENTS = 2;
/**
* Time to wait for first pass to complete before returning partial results.
*/
- public static final int MAX_FIRST_PASS_WAIT_MILLIS = 500;
+ private static final int MAX_FIRST_PASS_WAIT_MILLIS = 500;
- /**
- * Maximum documents from a single root.
- */
- public static final int MAX_DOCS_FROM_ROOT = 64;
+ /** Maximum documents from a single root. */
+ private static final int MAX_DOCS_FROM_ROOT = 64;
+
+ /** Ignore documents older than this age. */
+ private static final long REJECT_OLDER_THAN = 45 * DateUtils.DAY_IN_MILLIS;
+
+ /** MIME types that should always be excluded from recents. */
+ private static final String[] RECENT_REJECT_MIMES = new String[] { Document.MIME_TYPE_DIR };
private static final ExecutorService sExecutor = buildExecutor();
@@ -173,6 +181,8 @@
}
}
+ final long rejectBefore = System.currentTimeMillis() - REJECT_OLDER_THAN;
+
// Collect all finished tasks
List<Cursor> cursors = Lists.newArrayList();
for (RecentTask task : mTasks.values()) {
@@ -180,7 +190,7 @@
try {
final Cursor cursor = task.get();
final FilteringCursorWrapper filtered = new FilteringCursorWrapper(
- cursor, mState.acceptMimes, new String[] { Document.MIME_TYPE_DIR }) {
+ cursor, mState.acceptMimes, RECENT_REJECT_MIMES, rejectBefore) {
@Override
public void close() {
// Ignored, since we manage cursor lifecycle internally
@@ -203,11 +213,22 @@
final DirectoryResult result = new DirectoryResult();
result.sortOrder = SORT_ORDER_LAST_MODIFIED;
- if (cursors.size() > 0) {
- final MergeCursor merged = new MergeCursor(cursors.toArray(new Cursor[cursors.size()]));
- final SortingCursorWrapper sorted = new SortingCursorWrapper(merged, result.sortOrder);
- result.cursor = sorted;
+ // Hint to UI if we're still loading
+ final Bundle extras = new Bundle();
+ if (cursors.size() != mTasks.size()) {
+ extras.putBoolean(DocumentsContract.EXTRA_LOADING, true);
}
+
+ final MergeCursor merged = new MergeCursor(cursors.toArray(new Cursor[cursors.size()]));
+ final SortingCursorWrapper sorted = new SortingCursorWrapper(merged, result.sortOrder) {
+ @Override
+ public Bundle getExtras() {
+ return extras;
+ }
+ };
+
+ result.cursor = sorted;
+
return result;
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java b/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java
index c975382..3954173 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java
@@ -66,6 +66,7 @@
*/
public class RecentsCreateFragment extends Fragment {
+ private View mEmptyView;
private ListView mListView;
private DocumentStackAdapter mAdapter;
@@ -87,6 +88,8 @@
final View view = inflater.inflate(R.layout.fragment_directory, container, false);
+ mEmptyView = view.findViewById(android.R.id.empty);
+
mListView = (ListView) view.findViewById(R.id.list);
mListView.setOnItemClickListener(mItemListener);
@@ -189,6 +192,13 @@
public void swapStacks(List<DocumentStack> stacks) {
mStacks = stacks;
+
+ if (isEmpty()) {
+ mEmptyView.setVisibility(View.VISIBLE);
+ } else {
+ mEmptyView.setVisibility(View.GONE);
+ }
+
notifyDataSetChanged();
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java b/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java
index 7386cae..4313fa7 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java
@@ -33,7 +33,7 @@
public class RecentsProvider extends ContentProvider {
private static final String TAG = "RecentsProvider";
- public static final long MAX_HISTORY_IN_MILLIS = DateUtils.DAY_IN_MILLIS * 45;
+ public static final long MAX_HISTORY_IN_MILLIS = 45 * DateUtils.DAY_IN_MILLIS;
private static final String AUTHORITY = "com.android.documentsui.recents";
@@ -56,6 +56,7 @@
public static final String TABLE_RESUME = "resume";
public static class RecentColumns {
+ public static final String KEY = "key";
public static final String STACK = "stack";
public static final String TIMESTAMP = "timestamp";
}
@@ -99,16 +100,18 @@
private static final int VERSION_INIT = 1;
private static final int VERSION_AS_BLOB = 3;
private static final int VERSION_ADD_EXTERNAL = 4;
+ private static final int VERSION_ADD_RECENT_KEY = 5;
public DatabaseHelper(Context context) {
- super(context, DB_NAME, null, VERSION_ADD_EXTERNAL);
+ super(context, DB_NAME, null, VERSION_ADD_RECENT_KEY);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("CREATE TABLE " + TABLE_RECENT + " (" +
- RecentColumns.STACK + " BLOB PRIMARY KEY ON CONFLICT REPLACE," +
+ RecentColumns.KEY + " TEXT PRIMARY KEY ON CONFLICT REPLACE," +
+ RecentColumns.STACK + " BLOB DEFAULT NULL," +
RecentColumns.TIMESTAMP + " INTEGER" +
")");
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java
index 15af8aa..e3908e9 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java
@@ -99,7 +99,8 @@
*/
public void updateAsync() {
// Special root for recents
- mRecentsRoot.rootType = Root.ROOT_TYPE_SHORTCUT;
+ mRecentsRoot.authority = null;
+ mRecentsRoot.rootId = null;
mRecentsRoot.icon = R.drawable.ic_root_recent;
mRecentsRoot.flags = Root.FLAG_LOCAL_ONLY | Root.FLAG_SUPPORTS_CREATE;
mRecentsRoot.title = mContext.getString(R.string.root_recent);
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
index d602622..2fb12bb 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java
@@ -26,7 +26,6 @@
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
-import android.provider.DocumentsContract.Root;
import android.text.TextUtils;
import android.text.format.Formatter;
import android.view.LayoutInflater;
@@ -37,16 +36,16 @@
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.ListView;
-import android.widget.Space;
import android.widget.TextView;
import com.android.documentsui.DocumentsActivity.State;
-import com.android.documentsui.SectionedListAdapter.SectionAdapter;
import com.android.documentsui.model.DocumentInfo;
import com.android.documentsui.model.RootInfo;
import com.android.internal.util.Objects;
+import com.google.common.collect.Lists;
import java.util.Collection;
+import java.util.Collections;
import java.util.Comparator;
import java.util.List;
@@ -56,7 +55,7 @@
public class RootsFragment extends Fragment {
private ListView mList;
- private SectionedRootsAdapter mAdapter;
+ private RootsAdapter mAdapter;
private LoaderCallbacks<Collection<RootInfo>> mCallbacks;
@@ -112,7 +111,7 @@
final Intent includeApps = getArguments().getParcelable(EXTRA_INCLUDE_APPS);
- mAdapter = new SectionedRootsAdapter(context, result, includeApps);
+ mAdapter = new RootsAdapter(context, result, includeApps);
mList.setAdapter(mAdapter);
onCurrentRootChanged();
@@ -154,136 +153,148 @@
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
final DocumentsActivity activity = DocumentsActivity.get(RootsFragment.this);
- final Object item = mAdapter.getItem(position);
- if (item instanceof RootInfo) {
- activity.onRootPicked((RootInfo) item, true);
- } else if (item instanceof ResolveInfo) {
- activity.onAppPicked((ResolveInfo) item);
+ final Item item = mAdapter.getItem(position);
+ if (item instanceof RootItem) {
+ activity.onRootPicked(((RootItem) item).root, true);
+ } else if (item instanceof AppItem) {
+ activity.onAppPicked(((AppItem) item).info);
} else {
throw new IllegalStateException("Unknown root: " + item);
}
}
};
- private static class RootsAdapter extends ArrayAdapter<RootInfo> implements SectionAdapter {
- public RootsAdapter(Context context) {
- super(context, 0);
+ private static abstract class Item {
+ private final int mLayoutId;
+
+ public Item(int layoutId) {
+ mLayoutId = layoutId;
+ }
+
+ public View getView(View convertView, ViewGroup parent) {
+ if (convertView == null) {
+ convertView = LayoutInflater.from(parent.getContext())
+ .inflate(mLayoutId, parent, false);
+ }
+ bindView(convertView);
+ return convertView;
+ }
+
+ public abstract void bindView(View convertView);
+ }
+
+ private static class RootItem extends Item {
+ public final RootInfo root;
+
+ public RootItem(RootInfo root) {
+ super(R.layout.item_root);
+ this.root = root;
}
@Override
- public View getView(int position, View convertView, ViewGroup parent) {
- final Context context = parent.getContext();
- if (convertView == null) {
- convertView = LayoutInflater.from(context)
- .inflate(R.layout.item_root, parent, false);
- }
-
+ public void bindView(View convertView) {
final ImageView icon = (ImageView) convertView.findViewById(android.R.id.icon);
final TextView title = (TextView) convertView.findViewById(android.R.id.title);
final TextView summary = (TextView) convertView.findViewById(android.R.id.summary);
- final RootInfo root = getItem(position);
+ final Context context = convertView.getContext();
icon.setImageDrawable(root.loadIcon(context));
title.setText(root.title);
- // Device summary is always available space
- final String summaryText;
- if (root.rootType == Root.ROOT_TYPE_DEVICE && root.availableBytes >= 0) {
+ // Show available space if no summary
+ String summaryText = root.summary;
+ if (TextUtils.isEmpty(summaryText) && root.availableBytes >= 0) {
summaryText = context.getString(R.string.root_available_bytes,
Formatter.formatFileSize(context, root.availableBytes));
- } else {
- summaryText = root.summary;
}
summary.setText(summaryText);
summary.setVisibility(TextUtils.isEmpty(summaryText) ? View.GONE : View.VISIBLE);
-
- return convertView;
- }
-
- @Override
- public View getHeaderView(View convertView, ViewGroup parent) {
- if (convertView == null) {
- convertView = new Space(parent.getContext());
- }
- return convertView;
}
}
- private static class AppsAdapter extends ArrayAdapter<ResolveInfo> implements SectionAdapter {
- public AppsAdapter(Context context) {
- super(context, 0);
+ private static class SpacerItem extends Item {
+ public SpacerItem() {
+ super(R.layout.item_root_spacer);
}
@Override
- public View getView(int position, View convertView, ViewGroup parent) {
- final Context context = parent.getContext();
- final PackageManager pm = context.getPackageManager();
- if (convertView == null) {
- convertView = LayoutInflater.from(context)
- .inflate(R.layout.item_root, parent, false);
- }
+ public void bindView(View convertView) {
+ // Nothing to bind
+ }
+ }
+ private static class AppItem extends Item {
+ public final ResolveInfo info;
+
+ public AppItem(ResolveInfo info) {
+ super(R.layout.item_root);
+ this.info = info;
+ }
+
+ @Override
+ public void bindView(View convertView) {
final ImageView icon = (ImageView) convertView.findViewById(android.R.id.icon);
final TextView title = (TextView) convertView.findViewById(android.R.id.title);
final TextView summary = (TextView) convertView.findViewById(android.R.id.summary);
- final ResolveInfo info = getItem(position);
+ final PackageManager pm = convertView.getContext().getPackageManager();
icon.setImageDrawable(info.loadIcon(pm));
title.setText(info.loadLabel(pm));
// TODO: match existing summary behavior from disambig dialog
summary.setVisibility(View.GONE);
-
- return convertView;
- }
-
- @Override
- public View getHeaderView(View convertView, ViewGroup parent) {
- if (convertView == null) {
- convertView = LayoutInflater.from(parent.getContext())
- .inflate(R.layout.item_root_header, parent, false);
- }
-
- final TextView title = (TextView) convertView.findViewById(android.R.id.title);
- title.setText(R.string.root_type_apps);
-
- return convertView;
}
}
- private static class SectionedRootsAdapter extends SectionedListAdapter {
- private final RootsAdapter mRecent;
- private final RootsAdapter mServices;
- private final RootsAdapter mShortcuts;
- private final RootsAdapter mDevices;
- private final AppsAdapter mApps;
+ private static class RootsAdapter extends ArrayAdapter<Item> {
+ public RootsAdapter(Context context, Collection<RootInfo> roots, Intent includeApps) {
+ super(context, 0);
- public SectionedRootsAdapter(
- Context context, Collection<RootInfo> roots, Intent includeApps) {
- mRecent = new RootsAdapter(context);
- mServices = new RootsAdapter(context);
- mShortcuts = new RootsAdapter(context);
- mDevices = new RootsAdapter(context);
- mApps = new AppsAdapter(context);
+ RootItem recents = null;
+ RootItem images = null;
+ RootItem videos = null;
+ RootItem audio = null;
+ RootItem downloads = null;
+
+ final List<RootInfo> clouds = Lists.newArrayList();
+ final List<RootInfo> locals = Lists.newArrayList();
for (RootInfo root : roots) {
- if (root.authority == null) {
- mRecent.add(root);
- continue;
+ if (root.isRecents()) {
+ recents = new RootItem(root);
+ } else if (root.isExternalStorage()) {
+ locals.add(root);
+ } else if (root.isDownloads()) {
+ downloads = new RootItem(root);
+ } else if (root.isImages()) {
+ images = new RootItem(root);
+ } else if (root.isVideos()) {
+ videos = new RootItem(root);
+ } else if (root.isAudio()) {
+ audio = new RootItem(root);
+ } else {
+ clouds.add(root);
}
+ }
- switch (root.rootType) {
- case Root.ROOT_TYPE_SERVICE:
- mServices.add(root);
- break;
- case Root.ROOT_TYPE_SHORTCUT:
- mShortcuts.add(root);
- break;
- case Root.ROOT_TYPE_DEVICE:
- mDevices.add(root);
- break;
- }
+ final RootComparator comp = new RootComparator();
+ Collections.sort(clouds, comp);
+ Collections.sort(locals, comp);
+
+ if (recents != null) add(recents);
+
+ for (RootInfo cloud : clouds) {
+ add(new RootItem(cloud));
+ }
+
+ if (images != null) add(images);
+ if (videos != null) add(videos);
+ if (audio != null) add(audio);
+ if (downloads != null) add(downloads);
+
+ for (RootInfo local : locals) {
+ add(new RootItem(local));
}
if (includeApps != null) {
@@ -291,34 +302,53 @@
final List<ResolveInfo> infos = pm.queryIntentActivities(
includeApps, PackageManager.MATCH_DEFAULT_ONLY);
+ final List<AppItem> apps = Lists.newArrayList();
+
// Omit ourselves from the list
for (ResolveInfo info : infos) {
if (!context.getPackageName().equals(info.activityInfo.packageName)) {
- mApps.add(info);
+ apps.add(new AppItem(info));
+ }
+ }
+
+ if (apps.size() > 0) {
+ add(new SpacerItem());
+ for (Item item : apps) {
+ add(item);
}
}
}
+ }
- final RootComparator comp = new RootComparator();
- mServices.sort(comp);
- mShortcuts.sort(comp);
- mDevices.sort(comp);
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ final Item item = getItem(position);
+ return item.getView(convertView, parent);
+ }
- if (mRecent.getCount() > 0) {
- addSection(mRecent);
+ @Override
+ public boolean areAllItemsEnabled() {
+ return false;
+ }
+
+ @Override
+ public boolean isEnabled(int position) {
+ return getItemViewType(position) != 1;
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ final Item item = getItem(position);
+ if (item instanceof RootItem || item instanceof AppItem) {
+ return 0;
+ } else {
+ return 1;
}
- if (mServices.getCount() > 0) {
- addSection(mServices);
- }
- if (mShortcuts.getCount() > 0) {
- addSection(mShortcuts);
- }
- if (mDevices.getCount() > 0) {
- addSection(mDevices);
- }
- if (mApps.getCount() > 0) {
- addSection(mApps);
- }
+ }
+
+ @Override
+ public int getViewTypeCount() {
+ return 2;
}
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/TestActivity.java b/packages/DocumentsUI/src/com/android/documentsui/TestActivity.java
index 57fc7e4..1a47308 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/TestActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/TestActivity.java
@@ -213,8 +213,13 @@
if (DocumentsContract.isDocumentUri(this, uri)) {
result += "; DOC_ID";
}
- getContentResolver()
- .takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ try {
+ getContentResolver().takePersistableUriPermission(
+ uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ } catch (SecurityException e) {
+ result += "; FAILED TO TAKE";
+ Log.e(TAG, "Failed to take", e);
+ }
InputStream is = null;
try {
is = getContentResolver().openInputStream(uri);
@@ -222,7 +227,7 @@
result += "; read length=" + length;
} catch (Exception e) {
result += "; ERROR";
- Log.w(TAG, "Failed to read " + uri, e);
+ Log.e(TAG, "Failed to read " + uri, e);
} finally {
IoUtils.closeQuietly(is);
}
@@ -235,15 +240,20 @@
if (DocumentsContract.isDocumentUri(this, uri)) {
result += "; DOC_ID";
}
- getContentResolver()
- .takePersistableUriPermission(uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+ try {
+ getContentResolver().takePersistableUriPermission(
+ uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+ } catch (SecurityException e) {
+ result += "; FAILED TO TAKE";
+ Log.e(TAG, "Failed to take", e);
+ }
OutputStream os = null;
try {
os = getContentResolver().openOutputStream(uri);
os.write("THE COMPLETE WORKS OF SHAKESPEARE".getBytes());
} catch (Exception e) {
result += "; ERROR";
- Log.w(TAG, "Failed to write " + uri, e);
+ Log.e(TAG, "Failed to write " + uri, e);
} finally {
IoUtils.closeQuietly(os);
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentStack.java b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentStack.java
index 0a378c0..28bab6c 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentStack.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentStack.java
@@ -71,6 +71,25 @@
}
}
+ /**
+ * Build key that uniquely identifies this stack. It omits most of the raw
+ * details included in {@link #write(DataOutputStream)}, since they change
+ * too regularly to be used as a key.
+ */
+ public String buildKey() {
+ final StringBuilder builder = new StringBuilder();
+ if (root != null) {
+ builder.append(root.authority).append('#');
+ builder.append(root.rootId).append('#');
+ } else {
+ builder.append("[null]").append('#');
+ }
+ for (DocumentInfo doc : this) {
+ builder.append(doc.documentId).append('#');
+ }
+ return builder.toString();
+ }
+
@Override
public void reset() {
clear();
diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java b/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java
index 014901a..e220c9e 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java
@@ -42,10 +42,10 @@
*/
public class RootInfo implements Durable, Parcelable {
private static final int VERSION_INIT = 1;
+ private static final int VERSION_DROP_TYPE = 2;
public String authority;
public String rootId;
- public int rootType;
public int flags;
public int icon;
public String title;
@@ -67,7 +67,6 @@
public void reset() {
authority = null;
rootId = null;
- rootType = 0;
flags = 0;
icon = 0;
title = null;
@@ -85,10 +84,9 @@
public void read(DataInputStream in) throws IOException {
final int version = in.readInt();
switch (version) {
- case VERSION_INIT:
+ case VERSION_DROP_TYPE:
authority = DurableUtils.readNullableString(in);
rootId = DurableUtils.readNullableString(in);
- rootType = in.readInt();
flags = in.readInt();
icon = in.readInt();
title = DurableUtils.readNullableString(in);
@@ -105,10 +103,9 @@
@Override
public void write(DataOutputStream out) throws IOException {
- out.writeInt(VERSION_INIT);
+ out.writeInt(VERSION_DROP_TYPE);
DurableUtils.writeNullableString(out, authority);
DurableUtils.writeNullableString(out, rootId);
- out.writeInt(rootType);
out.writeInt(flags);
out.writeInt(icon);
DurableUtils.writeNullableString(out, title);
@@ -146,7 +143,6 @@
final RootInfo root = new RootInfo();
root.authority = authority;
root.rootId = getCursorString(cursor, Root.COLUMN_ROOT_ID);
- root.rootType = getCursorInt(cursor, Root.COLUMN_ROOT_TYPE);
root.flags = getCursorInt(cursor, Root.COLUMN_FLAGS);
root.icon = getCursorInt(cursor, Root.COLUMN_ICON);
root.title = getCursorString(cursor, Root.COLUMN_TITLE);
@@ -162,25 +158,44 @@
derivedMimeTypes = (mimeTypes != null) ? mimeTypes.split("\n") : null;
// TODO: remove these special case icons
- if ("com.android.externalstorage.documents".equals(authority)) {
- if ("documents".equals(rootId)) {
- derivedIcon = R.drawable.ic_doc_text;
- } else {
- derivedIcon = R.drawable.ic_root_sdcard;
- }
- }
- if ("com.android.providers.downloads.documents".equals(authority)) {
+ if (isExternalStorage()) {
+ derivedIcon = R.drawable.ic_root_sdcard;
+ } else if (isDownloads()) {
derivedIcon = R.drawable.ic_root_download;
+ } else if (isImages()) {
+ derivedIcon = R.drawable.ic_doc_image;
+ } else if (isVideos()) {
+ derivedIcon = R.drawable.ic_doc_video;
+ } else if (isAudio()) {
+ derivedIcon = R.drawable.ic_doc_audio;
}
- if ("com.android.providers.media.documents".equals(authority)) {
- if ("images_root".equals(rootId)) {
- derivedIcon = R.drawable.ic_doc_image;
- } else if ("videos_root".equals(rootId)) {
- derivedIcon = R.drawable.ic_doc_video;
- } else if ("audio_root".equals(rootId)) {
- derivedIcon = R.drawable.ic_doc_audio;
- }
- }
+ }
+
+ public boolean isRecents() {
+ return authority == null && rootId == null;
+ }
+
+ public boolean isExternalStorage() {
+ return "com.android.externalstorage.documents".equals(authority);
+ }
+
+ public boolean isDownloads() {
+ return "com.android.providers.downloads.documents".equals(authority);
+ }
+
+ public boolean isImages() {
+ return "com.android.providers.media.documents".equals(authority)
+ && "images_root".equals(rootId);
+ }
+
+ public boolean isVideos() {
+ return "com.android.providers.media.documents".equals(authority)
+ && "videos_root".equals(rootId);
+ }
+
+ public boolean isAudio() {
+ return "com.android.providers.media.documents".equals(authority)
+ && "audio_root".equals(rootId);
}
@Override
diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
index ed28da5..9328b33 100644
--- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
+++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
@@ -47,9 +47,8 @@
// docId format: root:path/to/file
private static final String[] DEFAULT_ROOT_PROJECTION = new String[] {
- Root.COLUMN_ROOT_ID, Root.COLUMN_ROOT_TYPE, Root.COLUMN_FLAGS, Root.COLUMN_ICON,
- Root.COLUMN_TITLE, Root.COLUMN_SUMMARY, Root.COLUMN_DOCUMENT_ID,
- Root.COLUMN_AVAILABLE_BYTES,
+ Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_ICON, Root.COLUMN_TITLE,
+ Root.COLUMN_DOCUMENT_ID, Root.COLUMN_AVAILABLE_BYTES,
};
private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[] {
@@ -59,7 +58,6 @@
private static class RootInfo {
public String rootId;
- public int rootType;
public int flags;
public String title;
public String docId;
@@ -84,7 +82,6 @@
final RootInfo root = new RootInfo();
root.rootId = rootId;
- root.rootType = Root.ROOT_TYPE_DEVICE;
root.flags = Root.FLAG_SUPPORTS_CREATE | Root.FLAG_LOCAL_ONLY | Root.FLAG_ADVANCED
| Root.FLAG_SUPPORTS_SEARCH;
root.title = getContext().getString(R.string.root_internal_storage);
@@ -198,7 +195,6 @@
final RowBuilder row = result.newRow();
row.add(Root.COLUMN_ROOT_ID, root.rootId);
- row.add(Root.COLUMN_ROOT_TYPE, root.rootType);
row.add(Root.COLUMN_FLAGS, root.flags);
row.add(Root.COLUMN_TITLE, root.title);
row.add(Root.COLUMN_DOCUMENT_ID, root.docId);
diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/TestDocumentsProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/TestDocumentsProvider.java
index e6fbb1b..5a15cd2 100644
--- a/packages/ExternalStorageProvider/src/com/android/externalstorage/TestDocumentsProvider.java
+++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/TestDocumentsProvider.java
@@ -65,7 +65,7 @@
private static final String MY_DOC_NULL = "myNull";
private static final String[] DEFAULT_ROOT_PROJECTION = new String[] {
- Root.COLUMN_ROOT_ID, Root.COLUMN_ROOT_TYPE, Root.COLUMN_FLAGS, Root.COLUMN_ICON,
+ Root.COLUMN_ROOT_ID, Root.COLUMN_FLAGS, Root.COLUMN_ICON,
Root.COLUMN_TITLE, Root.COLUMN_SUMMARY, Root.COLUMN_DOCUMENT_ID,
Root.COLUMN_AVAILABLE_BYTES,
};
@@ -114,7 +114,6 @@
final MatrixCursor result = new MatrixCursor(resolveRootProjection(projection));
final RowBuilder row = result.newRow();
row.add(Root.COLUMN_ROOT_ID, MY_ROOT_ID);
- row.add(Root.COLUMN_ROOT_TYPE, Root.ROOT_TYPE_SERVICE);
row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_RECENTS);
row.add(Root.COLUMN_TITLE, "_Test title which is really long");
row.add(Root.COLUMN_SUMMARY,
diff --git a/packages/Keyguard/res/layout-land/keyguard_host_view.xml b/packages/Keyguard/res/layout-land/keyguard_host_view.xml
index 87b8b59..eeb9ee7 100644
--- a/packages/Keyguard/res/layout-land/keyguard_host_view.xml
+++ b/packages/Keyguard/res/layout-land/keyguard_host_view.xml
@@ -51,6 +51,11 @@
androidprv:layout_maxHeight="480dp" />
<include layout="@layout/keyguard_multi_user_selector"/>
+ <View android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ androidprv:layout_childType="scrim"
+ android:background="#99000000" />
+
<com.android.keyguard.KeyguardSecurityContainer
android:id="@+id/keyguard_security_container"
android:layout_width="wrap_content"
diff --git a/packages/Keyguard/res/layout-port/keyguard_host_view.xml b/packages/Keyguard/res/layout-port/keyguard_host_view.xml
index 355739e..8498dcf 100644
--- a/packages/Keyguard/res/layout-port/keyguard_host_view.xml
+++ b/packages/Keyguard/res/layout-port/keyguard_host_view.xml
@@ -55,6 +55,11 @@
android:layout_gravity="center"/>
</FrameLayout>
+ <View android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ androidprv:layout_childType="scrim"
+ android:background="#99000000" />
+
<com.android.keyguard.KeyguardSecurityContainer
android:id="@+id/keyguard_security_container"
android:layout_width="wrap_content"
diff --git a/packages/Keyguard/res/layout-sw600dp-port/keyguard_host_view.xml b/packages/Keyguard/res/layout-sw600dp-port/keyguard_host_view.xml
index 42dbe9d..77bc9b5 100644
--- a/packages/Keyguard/res/layout-sw600dp-port/keyguard_host_view.xml
+++ b/packages/Keyguard/res/layout-sw600dp-port/keyguard_host_view.xml
@@ -52,6 +52,11 @@
<include layout="@layout/keyguard_multi_user_selector"/>
+ <View android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ androidprv:layout_childType="scrim"
+ android:background="#99000000" />
+
<com.android.keyguard.KeyguardSecurityContainer
android:id="@+id/keyguard_security_container"
android:layout_width="wrap_content"
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardHostView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardHostView.java
index a9e9d3a..07d4d1b 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardHostView.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardHostView.java
@@ -123,6 +123,8 @@
protected boolean mShowSecurityWhenReturn;
+ private final Rect mInsets = new Rect();
+
/*package*/ interface UserSwitcherCallback {
void hideSecurityView(int duration);
void showSecurityView();
@@ -405,11 +407,6 @@
updateSecurityViews();
}
- public void setScrimView(View scrim) {
- if (mSlidingChallengeLayout != null) mSlidingChallengeLayout.setScrimView(scrim);
- if (mMultiPaneChallengeLayout != null) mMultiPaneChallengeLayout.setScrimView(scrim);
- }
-
private void setBackButtonEnabled(boolean enabled) {
if (mContext instanceof Activity) return; // always enabled in activity mode
setSystemUiVisibility(enabled ?
@@ -1351,6 +1348,7 @@
static class SavedState extends BaseSavedState {
int transportState;
int appWidgetToShow = AppWidgetManager.INVALID_APPWIDGET_ID;
+ Rect insets = new Rect();
SavedState(Parcelable superState) {
super(superState);
@@ -1360,6 +1358,7 @@
super(in);
this.transportState = in.readInt();
this.appWidgetToShow = in.readInt();
+ this.insets = in.readParcelable(null);
}
@Override
@@ -1367,6 +1366,7 @@
super.writeToParcel(out, flags);
out.writeInt(this.transportState);
out.writeInt(this.appWidgetToShow);
+ out.writeParcelable(insets, 0);
}
public static final Parcelable.Creator<SavedState> CREATOR
@@ -1391,6 +1391,7 @@
&& mAppWidgetContainer.getWidgetPageIndex(mTransportControl) >= 0;
ss.transportState = showing ? TRANSPORT_VISIBLE : mTransportState;
ss.appWidgetToShow = mAppWidgetToShow;
+ ss.insets.set(mInsets);
return ss;
}
@@ -1404,11 +1405,24 @@
super.onRestoreInstanceState(ss.getSuperState());
mTransportState = (ss.transportState);
mAppWidgetToShow = ss.appWidgetToShow;
+ setInsets(ss.insets);
if (DEBUG) Log.d(TAG, "onRestoreInstanceState, transport=" + mTransportState);
post(mSwitchPageRunnable);
}
@Override
+ protected boolean fitSystemWindows(Rect insets) {
+ setInsets(insets);
+ return true;
+ }
+
+ private void setInsets(Rect insets) {
+ mInsets.set(insets);
+ if (mSlidingChallengeLayout != null) mSlidingChallengeLayout.setInsets(mInsets);
+ if (mMultiPaneChallengeLayout != null) mMultiPaneChallengeLayout.setInsets(mInsets);
+ }
+
+ @Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
super.onWindowFocusChanged(hasWindowFocus);
if (DEBUG) Log.d(TAG, "Window is " + (hasWindowFocus ? "focused" : "unfocused"));
diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardViewManager.java b/packages/Keyguard/src/com/android/keyguard/KeyguardViewManager.java
index a0e44d7..b96ef88 100644
--- a/packages/Keyguard/src/com/android/keyguard/KeyguardViewManager.java
+++ b/packages/Keyguard/src/com/android/keyguard/KeyguardViewManager.java
@@ -139,6 +139,7 @@
class ViewManagerHost extends FrameLayout {
private static final int BACKGROUND_COLOR = 0x70000000;
+
// This is a faster way to draw the background on devices without hardware acceleration
private final Drawable mBackgroundDrawable = new Drawable() {
@Override
@@ -159,54 +160,10 @@
return PixelFormat.TRANSLUCENT;
}
};
- private final View mScrimView;
- private boolean mExtendIntoPadding;
- public ViewManagerHost(Context context, boolean extendIntoPadding) {
+
+ public ViewManagerHost(Context context) {
super(context);
- mExtendIntoPadding = extendIntoPadding;
- setFitsSystemWindows(true);
- setClipToPadding(!mExtendIntoPadding);
setBackground(mBackgroundDrawable);
-
- mScrimView = new View(context);
- mScrimView.setVisibility(View.GONE);
- mScrimView.setBackgroundColor(0x99000000);
- addView(mScrimView);
- }
-
- private boolean considerPadding(View child) {
- return !mExtendIntoPadding || child instanceof KeyguardHostView;
- }
-
- @Override
- protected void measureChildWithMargins(View child,
- int parentWidthMeasureSpec, int widthUsed,
- int parentHeightMeasureSpec, int heightUsed) {
- if (considerPadding(child)) {
- // don't extend into padding (default behavior)
- super.measureChildWithMargins(child,
- parentWidthMeasureSpec, widthUsed,
- parentHeightMeasureSpec, heightUsed);
- } else {
- // allowed to extend into padding (scrim / camera preview)
- child.measure(parentWidthMeasureSpec, parentHeightMeasureSpec);
- }
- }
-
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
- final int count = getChildCount();
- for (int i = 0; i < count; i++) {
- final View child = getChildAt(i);
- int cl = l, ct = t, cr = r, cb = b;
- if (considerPadding(child)) {
- cl += mPaddingLeft;
- ct += mPaddingTop;
- cr -= mPaddingRight;
- cb -= mPaddingBottom;
- }
- child.layout(cl, ct, cr, cb);
- }
}
@Override
@@ -252,7 +209,7 @@
if (mKeyguardHost == null) {
if (DEBUG) Log.d(TAG, "keyguard host is null, creating it...");
- mKeyguardHost = new ViewManagerHost(mContext, shouldEnableTransparentBars());
+ mKeyguardHost = new ViewManagerHost(mContext);
int flags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
| WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR
@@ -285,6 +242,7 @@
}
if (force || mKeyguardView == null) {
+ mKeyguardHost.removeAllViews();
inflateKeyguardView(options);
mKeyguardView.requestFocus();
}
@@ -306,7 +264,6 @@
mKeyguardView.setViewMediatorCallback(mViewMediatorCallback);
mKeyguardView.initializeSwitchingUserState(options != null &&
options.getBoolean(IS_SWITCHING_USER));
- mKeyguardView.setScrimView(mKeyguardHost.mScrimView);
// HACK
// The keyguard view will have set up window flags in onFinishInflate before we set
diff --git a/packages/Keyguard/src/com/android/keyguard/MultiPaneChallengeLayout.java b/packages/Keyguard/src/com/android/keyguard/MultiPaneChallengeLayout.java
index 76a7fe3..67d0d5a 100644
--- a/packages/Keyguard/src/com/android/keyguard/MultiPaneChallengeLayout.java
+++ b/packages/Keyguard/src/com/android/keyguard/MultiPaneChallengeLayout.java
@@ -28,6 +28,7 @@
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
+import android.view.View.MeasureSpec;
import android.widget.LinearLayout;
public class MultiPaneChallengeLayout extends ViewGroup implements ChallengeLayout {
@@ -47,6 +48,7 @@
private final Rect mTempRect = new Rect();
private final Rect mZeroPadding = new Rect();
+ private final Rect mInsets = new Rect();
private final DisplayMetrics mDisplayMetrics;
@@ -80,6 +82,10 @@
setSystemUiVisibility(SYSTEM_UI_FLAG_LAYOUT_STABLE);
}
+ public void setInsets(Rect insets) {
+ mInsets.set(insets);
+ }
+
@Override
public boolean isChallengeShowing() {
return true;
@@ -187,7 +193,7 @@
// This calculation is super dodgy and relies on several assumptions.
// Specifically that the root of the window will be padded in for insets
// and that the window is LAYOUT_IN_SCREEN.
- virtualHeight = mDisplayMetrics.heightPixels - root.getPaddingTop();
+ virtualHeight = mDisplayMetrics.heightPixels - root.getPaddingTop() - mInsets.top;
}
if (lp.childType == LayoutParams.CHILD_TYPE_WIDGET ||
lp.childType == LayoutParams.CHILD_TYPE_USER_SWITCHER) {
@@ -213,6 +219,9 @@
final int height = MeasureSpec.getSize(heightSpec);
setMeasuredDimension(width, height);
+ final int insetHeight = height - mInsets.top - mInsets.bottom;
+ final int insetHeightSpec = MeasureSpec.makeMeasureSpec(insetHeight, MeasureSpec.EXACTLY);
+
int widthUsed = 0;
int heightUsed = 0;
@@ -245,14 +254,14 @@
if (child.getVisibility() == GONE) continue;
int adjustedWidthSpec = widthSpec;
- int adjustedHeightSpec = heightSpec;
+ int adjustedHeightSpec = insetHeightSpec;
if (lp.maxWidth >= 0) {
adjustedWidthSpec = MeasureSpec.makeMeasureSpec(
Math.min(lp.maxWidth, width), MeasureSpec.EXACTLY);
}
if (lp.maxHeight >= 0) {
adjustedHeightSpec = MeasureSpec.makeMeasureSpec(
- Math.min(lp.maxHeight, height), MeasureSpec.EXACTLY);
+ Math.min(lp.maxHeight, insetHeight), MeasureSpec.EXACTLY);
}
// measureChildWithMargins will resolve layout direction for the LayoutParams
measureChildWithMargins(child, adjustedWidthSpec, 0, adjustedHeightSpec, 0);
@@ -282,7 +291,7 @@
continue;
}
- final int virtualHeight = getVirtualHeight(lp, height, heightUsed);
+ final int virtualHeight = getVirtualHeight(lp, insetHeight, heightUsed);
int adjustedWidthSpec;
int adjustedHeightSpec;
@@ -330,11 +339,12 @@
padding.bottom = getPaddingBottom();
final int width = r - l;
final int height = b - t;
+ final int insetHeight = height - mInsets.top - mInsets.bottom;
// Reserve extra space in layout for the user switcher by modifying
// local padding during this layout pass
if (mUserSwitcherView != null && mUserSwitcherView.getVisibility() != GONE) {
- layoutWithGravity(width, height, mUserSwitcherView, padding, true);
+ layoutWithGravity(width, insetHeight, mUserSwitcherView, padding, true);
}
final int count = getChildCount();
@@ -349,11 +359,11 @@
child.layout(0, 0, width, height);
continue;
} else if (lp.childType == LayoutParams.CHILD_TYPE_PAGE_DELETE_DROP_TARGET) {
- layoutWithGravity(width, height, child, mZeroPadding, false);
+ layoutWithGravity(width, insetHeight, child, mZeroPadding, false);
continue;
}
- layoutWithGravity(width, height, child, padding, false);
+ layoutWithGravity(width, insetHeight, child, padding, false);
}
}
@@ -445,6 +455,8 @@
right = left + childWidth;
break;
}
+ top += mInsets.top;
+ bottom += mInsets.top;
child.layout(left, top, right, bottom);
}
diff --git a/packages/Keyguard/src/com/android/keyguard/SlidingChallengeLayout.java b/packages/Keyguard/src/com/android/keyguard/SlidingChallengeLayout.java
index 4a4e7fa..2e47768 100644
--- a/packages/Keyguard/src/com/android/keyguard/SlidingChallengeLayout.java
+++ b/packages/Keyguard/src/com/android/keyguard/SlidingChallengeLayout.java
@@ -24,6 +24,7 @@
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
+import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.FloatProperty;
@@ -125,6 +126,7 @@
private ObjectAnimator mFrameAnimation;
private boolean mHasGlowpad;
+ private final Rect mInsets = new Rect();
// We have an internal and external version, and we and them together.
private boolean mChallengeInteractiveExternal = true;
@@ -261,6 +263,10 @@
setSystemUiVisibility(SYSTEM_UI_FLAG_LAYOUT_STABLE);
}
+ public void setInsets(Rect insets) {
+ mInsets.set(insets);
+ }
+
public void setHandleAlpha(float alpha) {
if (mExpandChallengeView != null) {
mExpandChallengeView.setAlpha(alpha);
@@ -797,11 +803,13 @@
throw new IllegalArgumentException(
"SlidingChallengeLayout must be measured with an exact size");
}
-
final int width = MeasureSpec.getSize(widthSpec);
final int height = MeasureSpec.getSize(heightSpec);
setMeasuredDimension(width, height);
+ final int insetHeight = height - mInsets.top - mInsets.bottom;
+ final int insetHeightSpec = MeasureSpec.makeMeasureSpec(insetHeight, MeasureSpec.EXACTLY);
+
// Find one and only one challenge view.
final View oldChallengeView = mChallengeView;
final View oldExpandChallengeView = mChallengeView;
@@ -861,13 +869,13 @@
// We base this on the layout_maxHeight on the challenge view. If it comes out
// negative or zero, either we didn't have a maxHeight or we're totally out of space,
// so give up and measure as if this rule weren't there.
- int challengeHeightSpec = heightSpec;
+ int challengeHeightSpec = insetHeightSpec;
final View root = getRootView();
if (root != null) {
final LayoutParams lp = (LayoutParams) mChallengeView.getLayoutParams();
- final int specSize = MeasureSpec.getSize(heightSpec);
- final int windowHeight = mDisplayMetrics.heightPixels - root.getPaddingTop();
- final int diff = windowHeight - specSize;
+ final int windowHeight = mDisplayMetrics.heightPixels
+ - root.getPaddingTop() - mInsets.top;
+ final int diff = windowHeight - insetHeight;
final int maxChallengeHeight = lp.maxHeight - diff;
if (maxChallengeHeight > 0) {
challengeHeightSpec = makeChildMeasureSpec(maxChallengeHeight, lp.height);
@@ -887,7 +895,7 @@
// Measure children. Widget frame measures special, so that we can ignore
// insets for the IME.
- int parentWidthSpec = widthSpec, parentHeightSpec = heightSpec;
+ int parentWidthSpec = widthSpec, parentHeightSpec = insetHeightSpec;
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (lp.childType == LayoutParams.CHILD_TYPE_WIDGETS) {
final View root = getRootView();
@@ -896,12 +904,17 @@
// Specifically that the root of the window will be padded in for insets
// and that the window is LAYOUT_IN_SCREEN.
final int windowWidth = mDisplayMetrics.widthPixels;
- final int windowHeight = mDisplayMetrics.heightPixels - root.getPaddingTop();
+ final int windowHeight = mDisplayMetrics.heightPixels
+ - root.getPaddingTop() - mInsets.top;
parentWidthSpec = MeasureSpec.makeMeasureSpec(
windowWidth, MeasureSpec.EXACTLY);
parentHeightSpec = MeasureSpec.makeMeasureSpec(
windowHeight, MeasureSpec.EXACTLY);
}
+ } else if (lp.childType == LayoutParams.CHILD_TYPE_SCRIM) {
+ // Allow scrim views to extend into the insets
+ parentWidthSpec = widthSpec;
+ parentHeightSpec = heightSpec;
}
measureChildWithMargins(child, parentWidthSpec, 0, parentHeightSpec, 0);
}
@@ -931,7 +944,7 @@
final int childWidth = child.getMeasuredWidth();
final int childHeight = child.getMeasuredHeight();
final int left = center - childWidth / 2;
- final int layoutBottom = height - paddingBottom - lp.bottomMargin;
+ final int layoutBottom = height - paddingBottom - lp.bottomMargin - mInsets.bottom;
// We use the top of the challenge view to position the handle, so
// we never want less than the handle size showing at the bottom.
final int bottom = layoutBottom + (int) ((childHeight - mChallengeBottomBound)
@@ -942,15 +955,18 @@
final int center = (paddingLeft + width - paddingRight) / 2;
final int left = center - child.getMeasuredWidth() / 2;
final int right = left + child.getMeasuredWidth();
- final int bottom = height - paddingBottom - lp.bottomMargin;
+ final int bottom = height - paddingBottom - lp.bottomMargin - mInsets.bottom;
final int top = bottom - child.getMeasuredHeight();
child.layout(left, top, right, bottom);
+ } else if (lp.childType == LayoutParams.CHILD_TYPE_SCRIM) {
+ // Scrim views use the entire area, including padding & insets
+ child.layout(0, 0, getMeasuredWidth(), getMeasuredHeight());
} else {
// Non-challenge views lay out from the upper left, layered.
child.layout(paddingLeft + lp.leftMargin,
- paddingTop + lp.topMargin,
+ paddingTop + lp.topMargin + mInsets.top,
paddingLeft + child.getMeasuredWidth(),
- paddingTop + child.getMeasuredHeight());
+ paddingTop + child.getMeasuredHeight() + mInsets.top);
}
}
@@ -1076,7 +1092,7 @@
final int layoutBottom = getLayoutBottom();
final int challengeHeight = mChallengeView.getMeasuredHeight();
- return layoutBottom - challengeHeight;
+ return layoutBottom - challengeHeight - mInsets.top;
}
/**
@@ -1125,7 +1141,8 @@
final int bottomMargin = (mChallengeView == null)
? 0
: ((LayoutParams) mChallengeView.getLayoutParams()).bottomMargin;
- final int layoutBottom = getMeasuredHeight() - getPaddingBottom() - bottomMargin;
+ final int layoutBottom = getMeasuredHeight() - getPaddingBottom() - bottomMargin
+ - mInsets.bottom;
return layoutBottom;
}
diff --git a/packages/PrintSpooler/Android.mk b/packages/PrintSpooler/Android.mk
index f65fe4b..9e7b969 100644
--- a/packages/PrintSpooler/Android.mk
+++ b/packages/PrintSpooler/Android.mk
@@ -24,7 +24,4 @@
LOCAL_JAVA_LIBRARIES := framework-base
-LOCAL_PROGUARD_ENABLED := disabled
-
include $(BUILD_PACKAGE)
-
diff --git a/packages/PrintSpooler/res/layout/printer_dropdown_item.xml b/packages/PrintSpooler/res/layout/printer_dropdown_item.xml
new file mode 100644
index 0000000..6439b49
--- /dev/null
+++ b/packages/PrintSpooler/res/layout/printer_dropdown_item.xml
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2013 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:paddingStart="16dip"
+ android:paddingEnd="16dip"
+ android:minHeight="?android:attr/listPreferredItemHeightSmall"
+ android:orientation="horizontal"
+ android:gravity="start|center_vertical">
+
+ <ImageView
+ android:id="@+id/icon"
+ android:layout_width="32dip"
+ android:layout_height="32dip"
+ android:layout_gravity="center_vertical"
+ android:layout_marginEnd="8dip"
+ android:duplicateParentState="true"
+ android:contentDescription="@null"
+ android:visibility="gone">
+ </ImageView>
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:singleLine="true"
+ android:ellipsize="end"
+ android:textIsSelectable="false"
+ android:gravity="top|start"
+ android:textColor="@color/item_text_color"
+ android:duplicateParentState="true">
+ </TextView>
+
+ <TextView
+ android:id="@+id/subtitle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:singleLine="true"
+ android:ellipsize="end"
+ android:textIsSelectable="false"
+ android:visibility="gone"
+ android:textColor="@color/print_option_title"
+ android:duplicateParentState="true">
+ </TextView>
+
+ </LinearLayout>
+
+</LinearLayout>
diff --git a/packages/PrintSpooler/res/values/strings.xml b/packages/PrintSpooler/res/values/strings.xml
index 9fe7e00..5ee8d8c 100644
--- a/packages/PrintSpooler/res/values/strings.xml
+++ b/packages/PrintSpooler/res/values/strings.xml
@@ -109,6 +109,9 @@
<!-- Label for an unknown reason for failed or blocked print job. [CHAR LIMIT=25] -->
<string name="reason_unknown">unknown</string>
+ <!-- Label for a printer that is not available. [CHAR LIMIT=25] -->
+ <string name="printer_unavailable"><xliff:g id="print_job_name" example="Canon-123GHT">%1$s</xliff:g> – unavailable</string>
+
<!-- Arrays -->
<!-- Color mode labels. -->
diff --git a/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java b/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java
index 8d11a93..44362d4 100644
--- a/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java
+++ b/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java
@@ -75,11 +75,10 @@
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.EditText;
+import android.widget.ImageView;
import android.widget.Spinner;
import android.widget.TextView;
-import libcore.io.IoUtils;
-
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
@@ -96,6 +95,8 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import libcore.io.IoUtils;
+
/**
* Activity for configuring a print job.
*/
@@ -818,8 +819,6 @@
private PrinterInfo mCurrentPrinter;
- private boolean mRequestedCurrentPrinterRefresh;
-
private final OnItemSelectedListener mOnItemSelectedListener =
new AdapterView.OnItemSelectedListener() {
@Override
@@ -839,7 +838,7 @@
return;
}
- mRequestedCurrentPrinterRefresh = false;
+ mCapabilitiesTimeout.remove();
mCurrentPrinter = (PrinterInfo) mDestinationSpinnerAdapter
.getItem(position);
@@ -854,8 +853,7 @@
PrinterCapabilitiesInfo capabilities = mCurrentPrinter.getCapabilities();
if (capabilities == null) {
- // TODO: We need a timeout for the update.
- mRequestedCurrentPrinterRefresh = true;
+ mCapabilitiesTimeout.post();
updateUi();
refreshCurrentPrinter();
} else {
@@ -1128,6 +1126,9 @@
}
};
+ private final WaitForPrinterCapabilitiesTimeout mCapabilitiesTimeout =
+ new WaitForPrinterCapabilitiesTimeout();
+
private int mEditorState;
private boolean mIgnoreNextDestinationChange;
@@ -1173,16 +1174,16 @@
if (mCurrentPrinter.getStatus() == PrinterInfo.STATUS_UNAVAILABLE
&& printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE
&& printer.getCapabilities() == null
- && !mRequestedCurrentPrinterRefresh) {
- mRequestedCurrentPrinterRefresh = true;
+ && !mCapabilitiesTimeout.isPosted()) {
+ mCapabilitiesTimeout.post();
refreshCurrentPrinter();
return;
}
// We just refreshed the current printer.
if (printer.getCapabilities() != null
- && mRequestedCurrentPrinterRefresh) {
- mRequestedCurrentPrinterRefresh = false;
+ && mCapabilitiesTimeout.isPosted()) {
+ mCapabilitiesTimeout.remove();
updatePrintAttributes(printer.getCapabilities());
updateUi();
mController.update();
@@ -1971,6 +1972,43 @@
}
}
+ private final class WaitForPrinterCapabilitiesTimeout implements Runnable {
+ private static final long GET_CAPABILITIES_TIMEOUT_MILLIS = 10000; // 10sec
+
+ private boolean mIsPosted;
+
+ public void post() {
+ if (!mIsPosted) {
+ mDestinationSpinner.postDelayed(this,
+ GET_CAPABILITIES_TIMEOUT_MILLIS);
+ mIsPosted = true;
+ }
+ }
+
+ public void remove() {
+ if (mIsPosted) {
+ mIsPosted = false;
+ mDestinationSpinner.removeCallbacks(this);
+ }
+ }
+
+ public boolean isPosted() {
+ return mIsPosted;
+ }
+
+ @Override
+ public void run() {
+ mIsPosted = false;
+ if (mDestinationSpinner.getSelectedItemPosition() >= 0) {
+ View itemView = mDestinationSpinner.getSelectedView();
+ TextView titleView = (TextView) itemView.findViewById(R.id.title);
+ String title = getString(R.string.printer_unavailable,
+ mCurrentPrinter.getName());
+ titleView.setText(title);
+ }
+ }
+ }
+
private final class DestinationAdapter extends BaseAdapter
implements LoaderManager.LoaderCallbacks<List<PrinterInfo>>{
private final List<PrinterInfo> mPrinters = new ArrayList<PrinterInfo>();
@@ -2066,11 +2104,12 @@
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = getLayoutInflater().inflate(
- R.layout.spinner_dropdown_item, parent, false);
+ R.layout.printer_dropdown_item, parent, false);
}
CharSequence title = null;
CharSequence subtitle = null;
+ Drawable icon = null;
if (mPrinters.isEmpty()) {
if (position == 0) {
@@ -2092,6 +2131,7 @@
PackageInfo packageInfo = getPackageManager().getPackageInfo(
printer.getId().getServiceName().getPackageName(), 0);
subtitle = packageInfo.applicationInfo.loadLabel(getPackageManager());
+ icon = packageInfo.applicationInfo.loadIcon(getPackageManager());
} catch (NameNotFoundException nnfe) {
/* ignore */
}
@@ -2110,6 +2150,14 @@
subtitleView.setVisibility(View.GONE);
}
+ ImageView iconView = (ImageView) convertView.findViewById(R.id.icon);
+ if (icon != null) {
+ iconView.setImageDrawable(icon);
+ iconView.setVisibility(View.VISIBLE);
+ } else {
+ iconView.setVisibility(View.GONE);
+ }
+
return convertView;
}
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index fb73d39..260a3be 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -197,9 +197,10 @@
android:name=".DessertCase"
android:exported="true"
android:label="@string/dessert_case"
- android:theme="@android:style/Theme.Wallpaper.NoTitleBar.Fullscreen"
+ android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen"
android:hardwareAccelerated="true"
android:launchMode="singleInstance"
+ android:configChanges="orientation|screenSize"
android:excludeFromRecents="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
diff --git a/packages/SystemUI/res/drawable-nodpi/dessert_android.png b/packages/SystemUI/res/drawable-nodpi/dessert_android.png
new file mode 100644
index 0000000..2b47c19
--- /dev/null
+++ b/packages/SystemUI/res/drawable-nodpi/dessert_android.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-nodpi/dessert_cupcake.png b/packages/SystemUI/res/drawable-nodpi/dessert_cupcake.png
new file mode 100644
index 0000000..7b48c10
--- /dev/null
+++ b/packages/SystemUI/res/drawable-nodpi/dessert_cupcake.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-nodpi/dessert_dandroid.png b/packages/SystemUI/res/drawable-nodpi/dessert_dandroid.png
new file mode 100644
index 0000000..8be85c5
--- /dev/null
+++ b/packages/SystemUI/res/drawable-nodpi/dessert_dandroid.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-nodpi/dessert_donut.png b/packages/SystemUI/res/drawable-nodpi/dessert_donut.png
new file mode 100644
index 0000000..167ced7
--- /dev/null
+++ b/packages/SystemUI/res/drawable-nodpi/dessert_donut.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-nodpi/dessert_donutburger.png b/packages/SystemUI/res/drawable-nodpi/dessert_donutburger.png
new file mode 100644
index 0000000..9d77518a
--- /dev/null
+++ b/packages/SystemUI/res/drawable-nodpi/dessert_donutburger.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-nodpi/dessert_eclair.png b/packages/SystemUI/res/drawable-nodpi/dessert_eclair.png
new file mode 100644
index 0000000..8d463eb
--- /dev/null
+++ b/packages/SystemUI/res/drawable-nodpi/dessert_eclair.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-nodpi/dessert_flan.png b/packages/SystemUI/res/drawable-nodpi/dessert_flan.png
new file mode 100644
index 0000000..d05e3de
--- /dev/null
+++ b/packages/SystemUI/res/drawable-nodpi/dessert_flan.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-nodpi/dessert_froyo.png b/packages/SystemUI/res/drawable-nodpi/dessert_froyo.png
new file mode 100644
index 0000000..ffd9994
--- /dev/null
+++ b/packages/SystemUI/res/drawable-nodpi/dessert_froyo.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-nodpi/dessert_gingerbread.png b/packages/SystemUI/res/drawable-nodpi/dessert_gingerbread.png
new file mode 100644
index 0000000..22bffbb
--- /dev/null
+++ b/packages/SystemUI/res/drawable-nodpi/dessert_gingerbread.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-nodpi/dessert_honeycomb.png b/packages/SystemUI/res/drawable-nodpi/dessert_honeycomb.png
new file mode 100644
index 0000000..0f51a43
--- /dev/null
+++ b/packages/SystemUI/res/drawable-nodpi/dessert_honeycomb.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-nodpi/dessert_ics.png b/packages/SystemUI/res/drawable-nodpi/dessert_ics.png
new file mode 100644
index 0000000..bdec60e
--- /dev/null
+++ b/packages/SystemUI/res/drawable-nodpi/dessert_ics.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-nodpi/dessert_jandycane.png b/packages/SystemUI/res/drawable-nodpi/dessert_jandycane.png
new file mode 100644
index 0000000..ba1c7eb
--- /dev/null
+++ b/packages/SystemUI/res/drawable-nodpi/dessert_jandycane.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-nodpi/dessert_jellybean.png b/packages/SystemUI/res/drawable-nodpi/dessert_jellybean.png
new file mode 100644
index 0000000..5a2bcaa
--- /dev/null
+++ b/packages/SystemUI/res/drawable-nodpi/dessert_jellybean.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-nodpi/dessert_keylimepie.png b/packages/SystemUI/res/drawable-nodpi/dessert_keylimepie.png
new file mode 100644
index 0000000..a8741ec
--- /dev/null
+++ b/packages/SystemUI/res/drawable-nodpi/dessert_keylimepie.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-nodpi/dessert_kitkat.png b/packages/SystemUI/res/drawable-nodpi/dessert_kitkat.png
new file mode 100644
index 0000000..4f2b03b
--- /dev/null
+++ b/packages/SystemUI/res/drawable-nodpi/dessert_kitkat.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-nodpi/dessert_petitfour.png b/packages/SystemUI/res/drawable-nodpi/dessert_petitfour.png
new file mode 100644
index 0000000..3dc9d95
--- /dev/null
+++ b/packages/SystemUI/res/drawable-nodpi/dessert_petitfour.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-nodpi/dessert_zombiegingerbread.png b/packages/SystemUI/res/drawable-nodpi/dessert_zombiegingerbread.png
new file mode 100644
index 0000000..7962c21
--- /dev/null
+++ b/packages/SystemUI/res/drawable-nodpi/dessert_zombiegingerbread.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-nodpi/jandycane.png b/packages/SystemUI/res/drawable-nodpi/jandycane.png
deleted file mode 100644
index 278cfec..0000000
--- a/packages/SystemUI/res/drawable-nodpi/jandycane.png
+++ /dev/null
Binary files differ
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index f5356a8..cc78cb4 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -217,4 +217,7 @@
<!-- The width of the notification panel window: match_parent below sw600dp -->
<dimen name="notification_panel_width">-1dp</dimen>
+
+ <!-- used by DessertCase -->
+ <dimen name="dessert_case_cell_size">192dp</dimen>
</resources>
diff --git a/packages/SystemUI/src/com/android/systemui/DessertCase.java b/packages/SystemUI/src/com/android/systemui/DessertCase.java
index b6424af0..dd4c018 100644
--- a/packages/SystemUI/src/com/android/systemui/DessertCase.java
+++ b/packages/SystemUI/src/com/android/systemui/DessertCase.java
@@ -16,22 +16,51 @@
package com.android.systemui;
+import android.animation.ObjectAnimator;
import android.app.Activity;
import android.content.ComponentName;
import android.content.pm.PackageManager;
+import android.os.Handler;
import android.util.Slog;
+import android.view.animation.DecelerateInterpolator;
public class DessertCase extends Activity {
+ DessertCaseView mView;
@Override
public void onStart() {
super.onStart();
- Slog.v("DessertCase", "ACHIEVEMENT UNLOCKED");
PackageManager pm = getPackageManager();
- pm.setComponentEnabledSetting(new ComponentName(this, DessertCaseDream.class),
- PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0);
+ final ComponentName cn = new ComponentName(this, DessertCaseDream.class);
+ if (pm.getComponentEnabledSetting(cn) != PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
+ Slog.v("DessertCase", "ACHIEVEMENT UNLOCKED");
+ pm.setComponentEnabledSetting(cn,
+ PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0);
+ }
- finish();
+ mView = new DessertCaseView(this);
+
+ DessertCaseView.RescalingContainer container = new DessertCaseView.RescalingContainer(this);
+
+ container.setView(mView);
+
+ setContentView(container);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ mView.postDelayed(new Runnable() {
+ public void run() {
+ mView.start();
+ }
+ }, 1000);
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ mView.stop();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/DessertCaseDream.java b/packages/SystemUI/src/com/android/systemui/DessertCaseDream.java
index 022e4d8..a627cf6 100644
--- a/packages/SystemUI/src/com/android/systemui/DessertCaseDream.java
+++ b/packages/SystemUI/src/com/android/systemui/DessertCaseDream.java
@@ -19,21 +19,36 @@
import android.service.dreams.DreamService;
public class DessertCaseDream extends DreamService {
+ private DessertCaseView mView;
+ private DessertCaseView.RescalingContainer mContainer;
@Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
- setInteractive(true);
- setFullscreen(true);
+ setInteractive(false);
+
+ mView = new DessertCaseView(this);
+
+ mContainer = new DessertCaseView.RescalingContainer(this);
+
+ mContainer.setView(mView);
+
+ setContentView(mContainer);
}
@Override
public void onDreamingStarted() {
super.onDreamingStarted();
+ mView.postDelayed(new Runnable() {
+ public void run() {
+ mView.start();
+ }
+ }, 1000);
}
@Override
public void onDreamingStopped() {
super.onDreamingStopped();
+ mView.stop();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/DessertCaseView.java b/packages/SystemUI/src/com/android/systemui/DessertCaseView.java
new file mode 100644
index 0000000..99c59d5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/DessertCaseView.java
@@ -0,0 +1,505 @@
+/*
+ * Copyright (C) 2013 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.systemui;
+
+import android.animation.Animator;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.*;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.View;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.AnticipateOvershootInterpolator;
+import android.view.animation.DecelerateInterpolator;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+
+import java.util.HashSet;
+import java.util.Set;
+
+public class DessertCaseView extends FrameLayout {
+ private static final String TAG = DessertCaseView.class.getSimpleName();
+
+ private static final boolean DEBUG = false;
+
+ static final int START_DELAY = 5000;
+ static final int DELAY = 2000;
+ static final int DURATION = 500;
+
+ private static final int TAG_POS = 0x2000001;
+ private static final int TAG_SPAN = 0x2000002;
+
+ private static final int[] PASTRIES = {
+ R.drawable.dessert_kitkat, // used with permission
+ R.drawable.dessert_android, // thx irina
+ };
+
+ private static final int[] RARE_PASTRIES = {
+ R.drawable.dessert_cupcake, // 2009
+ R.drawable.dessert_donut, // 2009
+ R.drawable.dessert_eclair, // 2009
+ R.drawable.dessert_froyo, // 2010
+ R.drawable.dessert_gingerbread, // 2010
+ R.drawable.dessert_honeycomb, // 2011
+ R.drawable.dessert_ics, // 2011
+ R.drawable.dessert_jellybean, // 2012
+ };
+
+ private static final int[] XRARE_PASTRIES = {
+ R.drawable.dessert_petitfour, // the original and still delicious
+
+ R.drawable.dessert_donutburger, // remember kids, this was long before cronuts
+
+ R.drawable.dessert_flan, // sholes final approach
+ // landing gear punted to flan
+ // runway foam glistens
+ // -- mcleron
+
+ R.drawable.dessert_keylimepie, // from an alternative timeline
+ };
+ private static final int[] XXRARE_PASTRIES = {
+ R.drawable.dessert_zombiegingerbread, // thx hackbod
+ R.drawable.dessert_dandroid, // thx morrildl
+ R.drawable.dessert_jandycane, // thx nes
+ };
+
+ private static final int NUM_PASTRIES = PASTRIES.length + RARE_PASTRIES.length
+ + XRARE_PASTRIES.length + XXRARE_PASTRIES.length;
+
+ private SparseArray<Drawable> mDrawables = new SparseArray<Drawable>(NUM_PASTRIES);
+
+ private static final float[] MASK = {
+ 0f, 0f, 0f, 0f, 255f,
+ 0f, 0f, 0f, 0f, 255f,
+ 0f, 0f, 0f, 0f, 255f,
+ 1f, 0f, 0f, 0f, 0f
+ };
+
+ private static final float[] WHITE_MASK = {
+ 0f, 0f, 0f, 0f, 255f,
+ 0f, 0f, 0f, 0f, 255f,
+ 0f, 0f, 0f, 0f, 255f,
+ -1f, 0f, 0f, 0f, 255f
+ };
+
+ public static final float SCALE = 0.25f; // natural display size will be SCALE*mCellSize
+
+ private static final float PROB_2X = 0.33f;
+ private static final float PROB_3X = 0.1f;
+ private static final float PROB_4X = 0.01f;
+
+ private boolean mStarted;
+
+ private int mCellSize;
+ private int mWidth, mHeight;
+ private int mRows, mColumns;
+ private View[] mCells;
+
+ private final Set<Point> mFreeList = new HashSet<Point>();
+
+ private final Handler mHandler = new Handler();
+
+ private final Runnable mJuggle = new Runnable() {
+ @Override
+ public void run() {
+ final int N = getChildCount();
+
+ final int K = 1; //irand(1,3);
+ for (int i=0; i<K; i++) {
+ final View child = getChildAt((int) (Math.random() * N));
+ place(child, true);
+ }
+
+ fillFreeList();
+
+ if (mStarted) {
+ mHandler.postDelayed(mJuggle, DELAY);
+ }
+ }
+ };
+
+ public DessertCaseView(Context context) {
+ this(context, null);
+ }
+
+ public DessertCaseView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public DessertCaseView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+
+ final Resources res = getResources();
+
+ mStarted = false;
+
+ mCellSize = res.getDimensionPixelSize(R.dimen.dessert_case_cell_size);
+ final BitmapFactory.Options opts = new BitmapFactory.Options();
+ if (mCellSize < 512) { // assuming 512x512 images
+ opts.inSampleSize = 2;
+ }
+ for (int[] list : new int[][] { PASTRIES, RARE_PASTRIES, XRARE_PASTRIES, XXRARE_PASTRIES }) {
+ for (int resid : list) {
+ final BitmapDrawable d = new BitmapDrawable(res,
+ BitmapFactory.decodeResource(res, resid, opts));
+ d.setColorFilter(new ColorMatrixColorFilter(MASK));
+ d.setBounds(0, 0, mCellSize, mCellSize);
+ mDrawables.append(resid, d);
+ }
+ }
+ if (DEBUG) setWillNotDraw(false);
+ }
+
+ public void start() {
+ if (!mStarted) {
+ mStarted = true;
+ fillFreeList(DURATION * 4);
+ }
+ mHandler.postDelayed(mJuggle, START_DELAY);
+ }
+
+ public void stop() {
+ mStarted = false;
+ mHandler.removeCallbacks(mJuggle);
+ }
+
+ int pick(int[] a) {
+ return a[(int)(Math.random()*a.length)];
+ }
+
+ <T> T pick(T[] a) {
+ return a[(int)(Math.random()*a.length)];
+ }
+
+ <T> T pick(SparseArray<T> sa) {
+ return sa.valueAt((int)(Math.random()*sa.size()));
+ }
+
+ float[] hsv = new float[] { 0, 1f, .85f };
+ int random_color() {
+// return 0xFF000000 | (int) (Math.random() * (float) 0xFFFFFF); // totally random
+ final int COLORS = 12;
+ hsv[0] = irand(0,COLORS) * (360f/COLORS);
+ return Color.HSVToColor(hsv);
+ }
+
+ @Override
+ protected synchronized void onSizeChanged (int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+ if (mWidth == w && mHeight == h) return;
+
+ final boolean wasStarted = mStarted;
+ if (wasStarted) {
+ stop();
+ }
+
+ mWidth = w;
+ mHeight = h;
+
+ mCells = null;
+ removeAllViewsInLayout();
+ mFreeList.clear();
+
+ mRows = mHeight / mCellSize;
+ mColumns = mWidth / mCellSize;
+
+ mCells = new View[mRows * mColumns];
+
+ if (DEBUG) Log.v(TAG, String.format("New dimensions: %dx%d", mColumns, mRows));
+
+ setScaleX(SCALE);
+ setScaleY(SCALE);
+ setTranslationX(0.5f * (mWidth - mCellSize * mColumns) * SCALE);
+ setTranslationY(0.5f * (mHeight - mCellSize * mRows) * SCALE);
+
+ for (int j=0; j<mRows; j++) {
+ for (int i=0; i<mColumns; i++) {
+ mFreeList.add(new Point(i,j));
+ }
+ }
+
+ if (wasStarted) {
+ start();
+ }
+ }
+
+ public void fillFreeList() {
+ fillFreeList(DURATION);
+ }
+
+ public synchronized void fillFreeList(int animationLen) {
+ final Context ctx = getContext();
+ final FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(mCellSize, mCellSize);
+
+ while (! mFreeList.isEmpty()) {
+ Point pt = mFreeList.iterator().next();
+ mFreeList.remove(pt);
+ final int i=pt.x;
+ final int j=pt.y;
+
+ if (mCells[j*mColumns+i] != null) continue;
+ final ImageView v = new ImageView(ctx);
+ v.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ place(v, true);
+ postDelayed(new Runnable() { public void run() { fillFreeList(); } }, DURATION/2);
+ }
+ });
+
+ final int c = random_color();
+ v.setBackgroundColor(c);
+
+ final float which = frand();
+ final Drawable d;
+ if (which < 0.001f) {
+ d = mDrawables.get(pick(XXRARE_PASTRIES));
+ } else if (which < 0.01f) {
+ d = mDrawables.get(pick(XRARE_PASTRIES));
+ } else if (which < 0.5f) {
+ d = mDrawables.get(pick(RARE_PASTRIES));
+ } else if (which < 0.7f) {
+ d = mDrawables.get(pick(PASTRIES));
+ } else {
+ d = null;
+ }
+ if (d != null) {
+ v.getOverlay().add(d);
+ }
+
+ final Paint paint = new Paint();
+ v.setLayerType(View.LAYER_TYPE_HARDWARE, paint);
+
+ lp.width = lp.height = mCellSize;
+ addView(v, lp);
+ place(v, pt, false);
+ if (animationLen > 0) {
+ final float s = (Integer) v.getTag(TAG_SPAN);
+ v.setScaleX(0.5f * s);
+ v.setScaleY(0.5f * s);
+ v.setAlpha(0f);
+ v.animate().scaleX(s).scaleY(s).alpha(1f).setDuration(animationLen);
+ }
+ }
+ }
+
+ public void place(View v, boolean animate) {
+ place(v, new Point(irand(0, mColumns), irand(0, mRows)), animate);
+ }
+
+ private final HashSet<View> tmpSet = new HashSet<View>();
+ public synchronized void place(View v, Point pt, boolean animate) {
+ final int i = pt.x;
+ final int j = pt.y;
+ final float rnd = frand();
+ if (v.getTag(TAG_POS) != null) {
+ for (final Point oc : getOccupied(v)) {
+ mFreeList.add(oc);
+ mCells[oc.y*mColumns + oc.x] = null;
+ }
+ }
+ int scale = 1;
+ if (rnd < PROB_4X) {
+ if (!(i >= mColumns-3 || j >= mRows-3)) {
+ scale = 4;
+ }
+ } else if (rnd < PROB_3X) {
+ if (!(i >= mColumns-2 || j >= mRows-2)) {
+ scale = 3;
+ }
+ } else if (rnd < PROB_2X) {
+ if (!(i == mColumns-1 || j == mRows-1)) {
+ scale = 2;
+ }
+ }
+
+ v.setTag(TAG_POS, pt);
+ v.setTag(TAG_SPAN, scale);
+
+ tmpSet.clear();
+
+ final Point[] occupied = getOccupied(v);
+ for (final Point oc : occupied) {
+ final View squatter = mCells[oc.y*mColumns + oc.x];
+ if (squatter != null) {
+ tmpSet.add(squatter);
+ }
+ }
+
+ for (final View squatter : tmpSet) {
+ for (final Point sq : getOccupied(squatter)) {
+ mFreeList.add(sq);
+ mCells[sq.y*mColumns + sq.x] = null;
+ }
+ if (squatter != v) {
+ squatter.setTag(TAG_POS, null);
+ if (animate) {
+ squatter.animate().scaleX(0.5f).scaleY(0.5f).alpha(0)
+ .setDuration(DURATION)
+ .setInterpolator(new AccelerateInterpolator())
+ .setListener(new Animator.AnimatorListener() {
+ public void onAnimationStart(Animator animator) { }
+ public void onAnimationEnd(Animator animator) {
+ removeView(squatter);
+ }
+ public void onAnimationCancel(Animator animator) { }
+ public void onAnimationRepeat(Animator animator) { }
+ })
+ .start();
+ } else {
+ removeView(squatter);
+ }
+ }
+ }
+
+ for (final Point oc : occupied) {
+ mCells[oc.y*mColumns + oc.x] = v;
+ mFreeList.remove(oc);
+ }
+
+ final float rot = (float)irand(0, 4) * 90f;
+
+ if (animate) {
+ v.bringToFront();
+ AnimatorSet set1 = new AnimatorSet();
+ set1.playTogether(
+ ObjectAnimator.ofFloat(v, View.SCALE_X, (float) scale),
+ ObjectAnimator.ofFloat(v, View.SCALE_Y, (float) scale)
+ );
+ set1.setInterpolator(new AnticipateOvershootInterpolator());
+ set1.setDuration(DURATION);
+ set1.start();
+
+ AnimatorSet set2 = new AnimatorSet();
+ set2.playTogether(
+ ObjectAnimator.ofFloat(v, View.ROTATION, rot),
+ ObjectAnimator.ofFloat(v, View.X, i* mCellSize + (scale-1) * mCellSize /2),
+ ObjectAnimator.ofFloat(v, View.Y, j* mCellSize + (scale-1) * mCellSize /2)
+ );
+ set2.setInterpolator(new DecelerateInterpolator());
+ set2.setDuration(DURATION);
+ set2.start();
+ } else {
+ v.setX(i * mCellSize + (scale-1) * mCellSize /2);
+ v.setY(j * mCellSize + (scale-1) * mCellSize /2);
+ v.setScaleX((float) scale);
+ v.setScaleY((float) scale);
+ v.setRotation(rot);
+ }
+ }
+
+ private Point[] getOccupied(View v) {
+ final int scale = (Integer) v.getTag(TAG_SPAN);
+ final Point pt = (Point)v.getTag(TAG_POS);
+ if (pt == null || scale == 0) return new Point[0];
+
+ final Point[] result = new Point[scale * scale];
+ int p=0;
+ for (int i=0; i<scale; i++) {
+ for (int j=0; j<scale; j++) {
+ result[p++] = new Point(pt.x + i, pt.y + j);
+ }
+ }
+ return result;
+ }
+
+ static float frand() {
+ return (float)(Math.random());
+ }
+
+ static float frand(float a, float b) {
+ return (frand() * (b-a) + a);
+ }
+
+ static int irand(int a, int b) {
+ return (int)(frand(a, b));
+ }
+
+ @Override
+ public void onDraw(Canvas c) {
+ super.onDraw(c);
+ if (!DEBUG) return;
+
+ Paint pt = new Paint();
+ pt.setStyle(Paint.Style.STROKE);
+ pt.setColor(0xFFCCCCCC);
+ pt.setStrokeWidth(2.0f);
+
+ final Rect check = new Rect();
+ final int N = getChildCount();
+ for (int i = 0; i < N; i++) {
+ View stone = getChildAt(i);
+
+ stone.getHitRect(check);
+
+ c.drawRect(check, pt);
+ }
+ }
+
+ public static class RescalingContainer extends FrameLayout {
+ private static final int SYSTEM_UI_MODE_800 = 0x00000800;
+ private DessertCaseView mView;
+ private float mDarkness;
+
+ public RescalingContainer(Context context) {
+ super(context);
+
+ setSystemUiVisibility(0
+ | View.SYSTEM_UI_FLAG_FULLSCREEN
+ | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
+ | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+ | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+ | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+ | SYSTEM_UI_MODE_800
+ );
+ }
+
+ public void setView(DessertCaseView v) {
+ addView(v);
+ mView = v;
+ }
+
+ @Override
+ protected void onLayout (boolean changed, int left, int top, int right, int bottom) {
+ final float w = right-left;
+ final float h = bottom-top;
+ final int w2 = (int) (w / mView.SCALE / 2);
+ final int h2 = (int) (h / mView.SCALE / 2);
+ final int cx = (int) (left + w * 0.5f);
+ final int cy = (int) (top + h * 0.5f);
+ mView.layout(cx - w2, cy - h2, cx + w2, cy + h2);
+ }
+
+ public void setDarkness(float p) {
+ mDarkness = p;
+ getDarkness();
+ final int x = (int) (p * 0xff);
+ setBackgroundColor(x << 24 & 0xFF000000);
+ }
+
+ public float getDarkness() {
+ return mDarkness;
+ }
+ }
+}
diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java
index 18ff819..a7fc995 100644
--- a/services/java/com/android/server/am/ActivityStack.java
+++ b/services/java/com/android/server/am/ActivityStack.java
@@ -334,20 +334,16 @@
mCurrentUser = service.mCurrentUserId;
}
- private boolean okToShow(ActivityRecord r) {
+ boolean okToShow(ActivityRecord r) {
return r.userId == mCurrentUser
|| (r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0;
}
final ActivityRecord topRunningActivityLocked(ActivityRecord notTop) {
for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
- final TaskRecord task = mTaskHistory.get(taskNdx);
- final ArrayList<ActivityRecord> activities = task.mActivities;
- for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
- ActivityRecord r = activities.get(activityNdx);
- if (!r.finishing && r != notTop && okToShow(r)) {
- return r;
- }
+ ActivityRecord r = mTaskHistory.get(taskNdx).topRunningActivityLocked(notTop);
+ if (r != null) {
+ return r;
}
}
return null;
@@ -3405,11 +3401,16 @@
}
}
- void handleAppDiedLocked(ProcessRecord app, boolean restarting) {
+ /**
+ * Reset local parameters because an app's activity died.
+ * @param app The app of the activity that died.
+ * @return true if home should be launched next.
+ */
+ boolean handleAppDiedLocked(ProcessRecord app) {
if (!containsApp(app)) {
- return;
+ return false;
}
- // TODO: handle the case where an app spans multiple stacks.
+
if (mPausingActivity != null && mPausingActivity.app == app) {
if (DEBUG_PAUSE || DEBUG_CLEANUP) Slog.v(TAG,
"App died while pausing: " + mPausingActivity);
@@ -3419,28 +3420,32 @@
mLastPausedActivity = null;
mLastNoHistoryActivity = null;
}
- final ActivityRecord top = topRunningActivityLocked(null);
- final boolean launchHomeTaskNext =
- top != null && top.app == app && top.task.mOnTopOfHome;
- // Remove this application's activities from active lists.
- boolean hasVisibleActivities = removeHistoryRecordsForAppLocked(app);
-
- if (!restarting) {
- ActivityStack stack = mStackSupervisor.getFocusedStack();
- if (stack == null || launchHomeTaskNext) {
- mStackSupervisor.resumeHomeActivity(null);
- } else if (!mStackSupervisor.resumeTopActivitiesLocked(stack, null, null)) {
- // If there was nothing to resume, and we are not already
- // restarting this process, but there is a visible activity that
- // is hosted by the process... then make sure all visible
- // activities are running, taking care of restarting this
- // process.
- if (hasVisibleActivities) {
- mStackSupervisor.ensureActivitiesVisibleLocked(null, 0);
- }
+ // Determine if the top task is exiting and should return to home. Do this before it gets
+ // removed in removeHistoryRecordsForAppsLocked.
+ boolean launchHomeNext = false;
+ int top = mTaskHistory.size() - 1;
+ while (top >= 0) {
+ final TaskRecord topTask = mTaskHistory.get(top);
+ if (topTask.mActivities.isEmpty()) {
+ // Not possible, but just in case.
+ --top;
+ continue;
}
+ ActivityRecord r = topTask.topRunningActivityLocked(null);
+ if (r != null) {
+ // r will be launched next.
+ break;
+ }
+ // There is an activity in topTask that is finishing. If topTask belongs to the app
+ // return to home depending on the task flag.
+ launchHomeNext = topTask.mOnTopOfHome;
+ break;
}
+
+ removeHistoryRecordsForAppLocked(app);
+
+ return launchHomeNext;
}
void handleAppCrashLocked(ProcessRecord app) {
diff --git a/services/java/com/android/server/am/ActivityStackSupervisor.java b/services/java/com/android/server/am/ActivityStackSupervisor.java
index 1ee13ec..bf91904 100644
--- a/services/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/java/com/android/server/am/ActivityStackSupervisor.java
@@ -1932,10 +1932,28 @@
}
void handleAppDiedLocked(ProcessRecord app, boolean restarting) {
- // Just in case.
+ boolean launchHomeTaskNext = false;
+ final ActivityStack focusedStack = getFocusedStack();
final int numStacks = mStacks.size();
for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) {
- mStacks.get(stackNdx).handleAppDiedLocked(app, restarting);
+ final ActivityStack stack = mStacks.get(stackNdx);
+ // Only update launchHomeTaskNext for the focused stack.
+ launchHomeTaskNext |= (stack == focusedStack && stack.handleAppDiedLocked(app));
+ }
+
+ if (!restarting) {
+ if (launchHomeTaskNext) {
+ resumeHomeActivity(null);
+ } else {
+ if (!resumeTopActivitiesLocked(focusedStack, null, null)) {
+ // If there was nothing to resume, and we are not already
+ // restarting this process, but there is a visible activity that
+ // is hosted by the process... then make sure all visible
+ // activities are running, taking care of restarting this
+ // process.
+ ensureActivitiesVisibleLocked(null, 0);
+ }
+ }
}
}
diff --git a/services/java/com/android/server/am/TaskRecord.java b/services/java/com/android/server/am/TaskRecord.java
index f0bba4f..8a9324c 100644
--- a/services/java/com/android/server/am/TaskRecord.java
+++ b/services/java/com/android/server/am/TaskRecord.java
@@ -139,6 +139,16 @@
return null;
}
+ ActivityRecord topRunningActivityLocked(ActivityRecord notTop) {
+ for (int activityNdx = mActivities.size() - 1; activityNdx >= 0; --activityNdx) {
+ ActivityRecord r = mActivities.get(activityNdx);
+ if (!r.finishing && r != notTop && stack.okToShow(r)) {
+ return r;
+ }
+ }
+ return null;
+ }
+
/**
* Reorder the history stack so that the activity at the given index is
* brought to the front.
diff --git a/tools/aapt/Command.cpp b/tools/aapt/Command.cpp
index 1e3b058..632efe0 100644
--- a/tools/aapt/Command.cpp
+++ b/tools/aapt/Command.cpp
@@ -346,6 +346,8 @@
LABEL_ATTR = 0x01010001,
ICON_ATTR = 0x01010002,
NAME_ATTR = 0x01010003,
+ PERMISSION_ATTR = 0x01010006,
+ RESOURCE_ATTR = 0x01010025,
DEBUGGABLE_ATTR = 0x0101000f,
VERSION_CODE_ATTR = 0x0101021b,
VERSION_NAME_ATTR = 0x0101021c,
@@ -372,6 +374,7 @@
COMPATIBLE_WIDTH_LIMIT_DP_ATTR = 0x01010365,
LARGEST_WIDTH_LIMIT_DP_ATTR = 0x01010366,
PUBLIC_KEY_ATTR = 0x010103a6,
+ CATEGORY_ATTR = 0x010103e8,
};
const char *getComponentName(String8 &pkgName, String8 &componentName) {
@@ -424,6 +427,61 @@
printf("\n");
}
+Vector<String8> getNfcAidCategories(AssetManager& assets, String8 xmlPath, bool offHost,
+ String8 *outError = NULL)
+{
+ Asset* aidAsset = assets.openNonAsset(xmlPath, Asset::ACCESS_BUFFER);
+ if (aidAsset == NULL) {
+ if (outError != NULL) *outError = "xml resource does not exist";
+ return Vector<String8>();
+ }
+
+ const String8 serviceTagName(offHost ? "offhost-apdu-service" : "host-apdu-service");
+
+ bool withinApduService = false;
+ Vector<String8> categories;
+
+ String8 error;
+ ResXMLTree tree;
+ tree.setTo(aidAsset->getBuffer(true), aidAsset->getLength());
+
+ size_t len;
+ int depth = 0;
+ ResXMLTree::event_code_t code;
+ while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) {
+ if (code == ResXMLTree::END_TAG) {
+ depth--;
+ String8 tag(tree.getElementName(&len));
+
+ if (depth == 0 && tag == serviceTagName) {
+ withinApduService = false;
+ }
+
+ } else if (code == ResXMLTree::START_TAG) {
+ depth++;
+ String8 tag(tree.getElementName(&len));
+
+ if (depth == 1) {
+ if (tag == serviceTagName) {
+ withinApduService = true;
+ }
+ } else if (depth == 2 && withinApduService) {
+ if (tag == "aid-group") {
+ String8 category = getAttribute(tree, CATEGORY_ATTR, &error);
+ if (error != "") {
+ if (outError != NULL) *outError = error;
+ return Vector<String8>();
+ }
+
+ categories.add(category);
+ }
+ }
+ }
+ }
+ aidAsset->close();
+ return categories;
+}
+
/*
* Handle the "dump" command, to extract select data from an archive.
*/
@@ -631,12 +689,31 @@
bool hasOtherServices = false;
bool hasWallpaperService = false;
bool hasImeService = false;
+ bool hasAccessibilityService = false;
+ bool hasPrintService = false;
bool hasWidgetReceivers = false;
+ bool hasDeviceAdminReceiver = false;
bool hasIntentFilter = false;
+ bool hasPaymentService = false;
bool actMainActivity = false;
bool actWidgetReceivers = false;
+ bool actDeviceAdminEnabled = false;
bool actImeService = false;
bool actWallpaperService = false;
+ bool actAccessibilityService = false;
+ bool actPrintService = false;
+ bool actHostApduService = false;
+ bool actOffHostApduService = false;
+ bool hasMetaHostPaymentCategory = false;
+ bool hasMetaOffHostPaymentCategory = false;
+
+ // These permissions are required by services implementing services
+ // the system binds to (IME, Accessibility, PrintServices, etc.)
+ bool hasBindDeviceAdminPermission = false;
+ bool hasBindInputMethodPermission = false;
+ bool hasBindAccessibilityServicePermission = false;
+ bool hasBindPrintServicePermission = false;
+ bool hasBindNfcServicePermission = false;
// These two implement the implicit permissions that are granted
// to pre-1.6 applications.
@@ -747,6 +824,13 @@
hasOtherActivities |= withinActivity;
hasOtherReceivers |= withinReceiver;
hasOtherServices |= withinService;
+ } else {
+ if (withinService) {
+ hasPaymentService |= (actHostApduService && hasMetaHostPaymentCategory &&
+ hasBindNfcServicePermission);
+ hasPaymentService |= (actOffHostApduService && hasMetaOffHostPaymentCategory &&
+ hasBindNfcServicePermission);
+ }
}
withinActivity = false;
withinService = false;
@@ -760,11 +844,18 @@
hasOtherActivities |= !actMainActivity;
} else if (withinReceiver) {
hasWidgetReceivers |= actWidgetReceivers;
- hasOtherReceivers |= !actWidgetReceivers;
+ hasDeviceAdminReceiver |= (actDeviceAdminEnabled &&
+ hasBindDeviceAdminPermission);
+ hasOtherReceivers |= (!actWidgetReceivers && !actDeviceAdminEnabled);
} else if (withinService) {
hasImeService |= actImeService;
hasWallpaperService |= actWallpaperService;
- hasOtherServices |= (!actImeService && !actWallpaperService);
+ hasAccessibilityService |= (actAccessibilityService &&
+ hasBindAccessibilityServicePermission);
+ hasPrintService |= (actPrintService && hasBindPrintServicePermission);
+ hasOtherServices |= (!actImeService && !actWallpaperService &&
+ !actAccessibilityService && !actPrintService &&
+ !actHostApduService && !actOffHostApduService);
}
}
withinIntentFilter = false;
@@ -1109,6 +1200,13 @@
withinReceiver = false;
withinService = false;
hasIntentFilter = false;
+ hasMetaHostPaymentCategory = false;
+ hasMetaOffHostPaymentCategory = false;
+ hasBindDeviceAdminPermission = false;
+ hasBindInputMethodPermission = false;
+ hasBindAccessibilityServicePermission = false;
+ hasBindPrintServicePermission = false;
+ hasBindNfcServicePermission = false;
if (withinApplication) {
if(tag == "activity") {
withinActivity = true;
@@ -1166,6 +1264,16 @@
" %s\n", error.string());
goto bail;
}
+
+ String8 permission = getAttribute(tree, PERMISSION_ATTR, &error);
+ if (error == "") {
+ if (permission == "android.permission.BIND_DEVICE_ADMIN") {
+ hasBindDeviceAdminPermission = true;
+ }
+ } else {
+ fprintf(stderr, "ERROR getting 'android:permission' attribute for"
+ " receiver '%s': %s\n", receiverName.string(), error.string());
+ }
} else if (tag == "service") {
withinService = true;
serviceName = getAttribute(tree, NAME_ATTR, &error);
@@ -1175,6 +1283,22 @@
" service: %s\n", error.string());
goto bail;
}
+
+ String8 permission = getAttribute(tree, PERMISSION_ATTR, &error);
+ if (error == "") {
+ if (permission == "android.permission.BIND_INPUT_METHOD") {
+ hasBindInputMethodPermission = true;
+ } else if (permission == "android.permission.BIND_ACCESSIBILITY_SERVICE") {
+ hasBindAccessibilityServicePermission = true;
+ } else if (permission == "android.permission.BIND_PRINT_SERVICE") {
+ hasBindPrintServicePermission = true;
+ } else if (permission == "android.permission.BIND_NFC_SERVICE") {
+ hasBindNfcServicePermission = true;
+ }
+ } else {
+ fprintf(stderr, "ERROR getting 'android:permission' attribute for"
+ " service '%s': %s\n", serviceName.string(), error.string());
+ }
}
} else if (withinSupportsInput && tag == "input-type") {
String8 name = getAttribute(tree, NAME_ATTR, &error);
@@ -1186,10 +1310,60 @@
goto bail;
}
}
- } else if ((depth == 4) && (tag == "intent-filter")) {
- hasIntentFilter = true;
- withinIntentFilter = true;
- actMainActivity = actWidgetReceivers = actImeService = actWallpaperService = false;
+ } else if (depth == 4) {
+ if (tag == "intent-filter") {
+ hasIntentFilter = true;
+ withinIntentFilter = true;
+ actMainActivity = false;
+ actWidgetReceivers = false;
+ actImeService = false;
+ actWallpaperService = false;
+ actAccessibilityService = false;
+ actPrintService = false;
+ actDeviceAdminEnabled = false;
+ actHostApduService = false;
+ actOffHostApduService = false;
+ } else if (withinService && tag == "meta-data") {
+ String8 name = getAttribute(tree, NAME_ATTR, &error);
+ if (error != "") {
+ fprintf(stderr, "ERROR getting 'android:name' attribute for"
+ " meta-data tag in service '%s': %s\n", serviceName.string(), error.string());
+ goto bail;
+ }
+
+ if (name == "android.nfc.cardemulation.host_apdu_service" ||
+ name == "android.nfc.cardemulation.off_host_apdu_service") {
+ bool offHost = true;
+ if (name == "android.nfc.cardemulation.host_apdu_service") {
+ offHost = false;
+ }
+
+ String8 xmlPath = getResolvedAttribute(&res, tree, RESOURCE_ATTR, &error);
+ if (error != "") {
+ fprintf(stderr, "ERROR getting 'android:resource' attribute for"
+ " meta-data tag in service '%s': %s\n", serviceName.string(), error.string());
+ goto bail;
+ }
+
+ Vector<String8> categories = getNfcAidCategories(assets, xmlPath,
+ offHost, &error);
+ if (error != "") {
+ fprintf(stderr, "ERROR getting AID category for service '%s'\n",
+ serviceName.string());
+ goto bail;
+ }
+
+ const size_t catLen = categories.size();
+ for (size_t i = 0; i < catLen; i++) {
+ bool paymentCategory = (categories[i] == "payment");
+ if (offHost) {
+ hasMetaOffHostPaymentCategory |= paymentCategory;
+ } else {
+ hasMetaHostPaymentCategory |= paymentCategory;
+ }
+ }
+ }
+ }
} else if ((depth == 5) && withinIntentFilter){
String8 action;
if (tag == "action") {
@@ -1206,12 +1380,22 @@
} else if (withinReceiver) {
if (action == "android.appwidget.action.APPWIDGET_UPDATE") {
actWidgetReceivers = true;
+ } else if (action == "android.app.action.DEVICE_ADMIN_ENABLED") {
+ actDeviceAdminEnabled = true;
}
} else if (withinService) {
if (action == "android.view.InputMethod") {
actImeService = true;
} else if (action == "android.service.wallpaper.WallpaperService") {
actWallpaperService = true;
+ } else if (action == "android.accessibilityservice.AccessibilityService") {
+ actAccessibilityService = true;
+ } else if (action == "android.printservice.PrintService") {
+ actPrintService = true;
+ } else if (action == "android.nfc.cardemulation.action.HOST_APDU_SERVICE") {
+ actHostApduService = true;
+ } else if (action == "android.nfc.cardemulation.action.OFF_HOST_APDU_SERVICE") {
+ actOffHostApduService = true;
}
}
if (action == "android.intent.action.SEARCH") {
@@ -1411,12 +1595,24 @@
if (hasWidgetReceivers) {
printf("app-widget\n");
}
+ if (hasDeviceAdminReceiver) {
+ printf("device-admin\n");
+ }
if (hasImeService) {
printf("ime\n");
}
if (hasWallpaperService) {
printf("wallpaper\n");
}
+ if (hasAccessibilityService) {
+ printf("accessibility\n");
+ }
+ if (hasPrintService) {
+ printf("print\n");
+ }
+ if (hasPaymentService) {
+ printf("payment\n");
+ }
if (hasOtherActivities) {
printf("other-activities\n");
}