Merge "Update search box and text handles styling for Quantum"
diff --git a/api/current.txt b/api/current.txt
index 2eea8ac..2274ea0 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -15808,10 +15808,13 @@
public final class NsdServiceInfo implements android.os.Parcelable {
ctor public NsdServiceInfo();
method public int describeContents();
+ method public java.util.Map<java.lang.String, byte[]> getAttributes();
method public java.net.InetAddress getHost();
method public int getPort();
method public java.lang.String getServiceName();
method public java.lang.String getServiceType();
+ method public void removeAttribute(java.lang.String);
+ method public void setAttribute(java.lang.String, java.lang.String);
method public void setHost(java.net.InetAddress);
method public void setPort(int);
method public void setServiceName(java.lang.String);
@@ -23352,6 +23355,67 @@
field public static final java.lang.String TYPE = "type";
}
+ public final class TvContract {
+ method public static final android.net.Uri buildChannelUri(long);
+ method public static final android.net.Uri buildProgramUri(long);
+ field public static final java.lang.String AUTHORITY = "com.android.tv";
+ }
+
+ public static abstract interface TvContract.BaseTvColumns implements android.provider.BaseColumns {
+ field public static final java.lang.String PACKAGE_NAME = "package_name";
+ }
+
+ public static final class TvContract.Channels implements android.provider.TvContract.BaseTvColumns {
+ field public static final java.lang.String BROWSABLE = "browsable";
+ field public static final java.lang.String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.com.android.tv.channels";
+ field public static final java.lang.String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.com.android.tv.channels";
+ field public static final android.net.Uri CONTENT_URI;
+ field public static final java.lang.String DATA = "data";
+ field public static final java.lang.String DESCRIPTION = "description";
+ field public static final java.lang.String DISPLAY_NAME = "display_name";
+ field public static final java.lang.String DISPLAY_NUMBER = "display_number";
+ field public static final java.lang.String SERVICE_NAME = "service_name";
+ field public static final java.lang.String TRANSPORT_STREAM_ID = "transport_stream_id";
+ field public static final java.lang.String TYPE = "type";
+ field public static final int TYPE_1SEG = 263168; // 0x40400
+ field public static final int TYPE_ATSC = 196608; // 0x30000
+ field public static final int TYPE_ATSC_2_0 = 196609; // 0x30001
+ field public static final int TYPE_ATSC_M_H = 196864; // 0x30100
+ field public static final int TYPE_CMMB = 327936; // 0x50100
+ field public static final int TYPE_DTMB = 327680; // 0x50000
+ field public static final int TYPE_DVB_C = 131584; // 0x20200
+ field public static final int TYPE_DVB_C2 = 131585; // 0x20201
+ field public static final int TYPE_DVB_H = 131840; // 0x20300
+ field public static final int TYPE_DVB_S = 131328; // 0x20100
+ field public static final int TYPE_DVB_S2 = 131329; // 0x20101
+ field public static final int TYPE_DVB_SH = 132096; // 0x20400
+ field public static final int TYPE_DVB_T = 131072; // 0x20000
+ field public static final int TYPE_DVB_T2 = 131073; // 0x20001
+ field public static final int TYPE_ISDB_C = 262912; // 0x40300
+ field public static final int TYPE_ISDB_S = 262656; // 0x40200
+ field public static final int TYPE_ISDB_T = 262144; // 0x40000
+ field public static final int TYPE_ISDB_TB = 262400; // 0x40100
+ field public static final int TYPE_OTHER = 0; // 0x0
+ field public static final int TYPE_PASSTHROUGH = 65536; // 0x10000
+ field public static final int TYPE_S_DMB = 393472; // 0x60100
+ field public static final int TYPE_T_DMB = 393216; // 0x60000
+ field public static final java.lang.String VERSION_NUMBER = "version_number";
+ }
+
+ public static final class TvContract.Programs implements android.provider.TvContract.BaseTvColumns {
+ field public static final java.lang.String CHANNEL_ID = "channel_id";
+ field public static final java.lang.String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.com.android.tv.programs";
+ field public static final java.lang.String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.com.android.tv.programs";
+ field public static final android.net.Uri CONTENT_URI;
+ field public static final java.lang.String DATA = "data";
+ field public static final java.lang.String DESCRIPTION = "description";
+ field public static final java.lang.String END_TIME_UTC_MILLIS = "end_time_utc_millis";
+ field public static final java.lang.String LONG_DESCRIPTION = "long_description";
+ field public static final java.lang.String START_TIME_UTC_MILLIS = "start_time_utc_millis";
+ field public static final java.lang.String TITLE = "title";
+ field public static final java.lang.String VERSION_NUMBER = "version_number";
+ }
+
public class UserDictionary {
ctor public UserDictionary();
field public static final java.lang.String AUTHORITY = "user_dictionary";
diff --git a/core/java/android/app/ActionBar.java b/core/java/android/app/ActionBar.java
index 34b0f3a..9818c33 100644
--- a/core/java/android/app/ActionBar.java
+++ b/core/java/android/app/ActionBar.java
@@ -1239,6 +1239,8 @@
public LayoutParams(int width, int height, int gravity) {
super(width, height);
+
+ this.gravity = gravity;
}
public LayoutParams(int gravity) {
diff --git a/core/java/android/net/nsd/NsdServiceInfo.java b/core/java/android/net/nsd/NsdServiceInfo.java
index 205a21d..6fdb0d0 100644
--- a/core/java/android/net/nsd/NsdServiceInfo.java
+++ b/core/java/android/net/nsd/NsdServiceInfo.java
@@ -18,8 +18,15 @@
import android.os.Parcelable;
import android.os.Parcel;
+import android.util.Log;
+import android.util.ArrayMap;
+import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
+import java.nio.charset.StandardCharsets;
+import java.util.Collections;
+import java.util.Map;
+
/**
* A class representing service information for network service discovery
@@ -27,11 +34,13 @@
*/
public final class NsdServiceInfo implements Parcelable {
+ private static final String TAG = "NsdServiceInfo";
+
private String mServiceName;
private String mServiceType;
- private DnsSdTxtRecord mTxtRecord;
+ private final ArrayMap<String, byte[]> mTxtRecord = new ArrayMap<String, byte[]>();
private InetAddress mHost;
@@ -41,10 +50,9 @@
}
/** @hide */
- public NsdServiceInfo(String sn, String rt, DnsSdTxtRecord tr) {
+ public NsdServiceInfo(String sn, String rt) {
mServiceName = sn;
mServiceType = rt;
- mTxtRecord = tr;
}
/** Get the service name */
@@ -67,16 +75,6 @@
mServiceType = s;
}
- /** @hide */
- public DnsSdTxtRecord getTxtRecord() {
- return mTxtRecord;
- }
-
- /** @hide */
- public void setTxtRecord(DnsSdTxtRecord t) {
- mTxtRecord = new DnsSdTxtRecord(t);
- }
-
/** Get the host address. The host address is valid for a resolved service. */
public InetAddress getHost() {
return mHost;
@@ -97,14 +95,134 @@
mPort = p;
}
+ /** @hide */
+ public void setAttribute(String key, byte[] value) {
+ // Key must be printable US-ASCII, excluding =.
+ for (int i = 0; i < key.length(); ++i) {
+ char character = key.charAt(i);
+ if (character < 0x20 || character > 0x7E) {
+ throw new IllegalArgumentException("Key strings must be printable US-ASCII");
+ } else if (character == 0x3D) {
+ throw new IllegalArgumentException("Key strings must not include '='");
+ }
+ }
+
+ // Key length + value length must be < 255.
+ if (key.length() + (value == null ? 0 : value.length) >= 255) {
+ throw new IllegalArgumentException("Key length + value length must be < 255 bytes");
+ }
+
+ // Warn if key is > 9 characters, as recommended by RFC 6763 section 6.4.
+ if (key.length() > 9) {
+ Log.w(TAG, "Key lengths > 9 are discouraged: " + key);
+ }
+
+ // Check against total TXT record size limits.
+ // Arbitrary 400 / 1300 byte limits taken from RFC 6763 section 6.2.
+ int txtRecordSize = getTxtRecordSize();
+ int futureSize = txtRecordSize + key.length() + (value == null ? 0 : value.length) + 2;
+ if (futureSize > 1300) {
+ throw new IllegalArgumentException("Total length of attributes must be < 1300 bytes");
+ } else if (futureSize > 400) {
+ Log.w(TAG, "Total length of all attributes exceeds 400 bytes; truncation may occur");
+ }
+
+ mTxtRecord.put(key, value);
+ }
+
+ /**
+ * Add a service attribute as a key/value pair.
+ *
+ * <p> Service attributes are included as DNS-SD TXT record pairs.
+ *
+ * <p> The key must be US-ASCII printable characters, excluding the '=' character. Values may
+ * be UTF-8 strings or null. The total length of key + value must be less than 255 bytes.
+ *
+ * <p> Keys should be short, ideally no more than 9 characters, and unique per instance of
+ * {@link NsdServiceInfo}. Calling {@link #setAttribute} twice with the same key will overwrite
+ * first value.
+ */
+ public void setAttribute(String key, String value) {
+ try {
+ setAttribute(key, value == null ? (byte []) null : value.getBytes("UTF-8"));
+ } catch (UnsupportedEncodingException e) {
+ throw new IllegalArgumentException("Value must be UTF-8");
+ }
+ }
+
+ /** Remove an attribute by key */
+ public void removeAttribute(String key) {
+ mTxtRecord.remove(key);
+ }
+
+ /**
+ * Retrive attributes as a map of String keys to byte[] values.
+ *
+ * <p> The returned map is unmodifiable; changes must be made through {@link #setAttribute} and
+ * {@link #removeAttribute}.
+ */
+ public Map<String, byte[]> getAttributes() {
+ return Collections.unmodifiableMap(mTxtRecord);
+ }
+
+ private int getTxtRecordSize() {
+ int txtRecordSize = 0;
+ for (Map.Entry<String, byte[]> entry : mTxtRecord.entrySet()) {
+ txtRecordSize += 2; // One for the length byte, one for the = between key and value.
+ txtRecordSize += entry.getKey().length();
+ byte[] value = entry.getValue();
+ txtRecordSize += value == null ? 0 : value.length;
+ }
+ return txtRecordSize;
+ }
+
+ /** @hide */
+ public byte[] getTxtRecord() {
+ int txtRecordSize = getTxtRecordSize();
+ if (txtRecordSize == 0) {
+ return null;
+ }
+
+ byte[] txtRecord = new byte[txtRecordSize];
+ int ptr = 0;
+ for (Map.Entry<String, byte[]> entry : mTxtRecord.entrySet()) {
+ String key = entry.getKey();
+ byte[] value = entry.getValue();
+
+ // One byte to record the length of this key/value pair.
+ txtRecord[ptr++] = (byte) (key.length() + (value == null ? 0 : value.length) + 1);
+
+ // The key, in US-ASCII.
+ // Note: use the StandardCharsets const here because it doesn't raise exceptions and we
+ // already know the key is ASCII at this point.
+ System.arraycopy(key.getBytes(StandardCharsets.US_ASCII), 0, txtRecord, ptr,
+ key.length());
+ ptr += key.length();
+
+ // US-ASCII '=' character.
+ txtRecord[ptr++] = (byte)'=';
+
+ // The value, as any raw bytes.
+ if (value != null) {
+ System.arraycopy(value, 0, txtRecord, ptr, value.length);
+ ptr += value.length;
+ }
+ }
+ return txtRecord;
+ }
+
public String toString() {
StringBuffer sb = new StringBuffer();
- sb.append("name: ").append(mServiceName).
- append("type: ").append(mServiceType).
- append("host: ").append(mHost).
- append("port: ").append(mPort).
- append("txtRecord: ").append(mTxtRecord);
+ sb.append("name: ").append(mServiceName)
+ .append(", type: ").append(mServiceType)
+ .append(", host: ").append(mHost)
+ .append(", port: ").append(mPort);
+
+ byte[] txtRecord = getTxtRecord();
+ if (txtRecord != null) {
+ sb.append(", txtRecord: ").append(new String(txtRecord, StandardCharsets.UTF_8));
+ }
return sb.toString();
}
@@ -117,14 +235,27 @@
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(mServiceName);
dest.writeString(mServiceType);
- dest.writeParcelable(mTxtRecord, flags);
if (mHost != null) {
- dest.writeByte((byte)1);
+ dest.writeInt(1);
dest.writeByteArray(mHost.getAddress());
} else {
- dest.writeByte((byte)0);
+ dest.writeInt(0);
}
dest.writeInt(mPort);
+
+ // TXT record key/value pairs.
+ dest.writeInt(mTxtRecord.size());
+ for (String key : mTxtRecord.keySet()) {
+ byte[] value = mTxtRecord.get(key);
+ if (value != null) {
+ dest.writeInt(1);
+ dest.writeInt(value.length);
+ dest.writeByteArray(value);
+ } else {
+ dest.writeInt(0);
+ }
+ dest.writeString(key);
+ }
}
/** Implement the Parcelable interface */
@@ -134,15 +265,26 @@
NsdServiceInfo info = new NsdServiceInfo();
info.mServiceName = in.readString();
info.mServiceType = in.readString();
- info.mTxtRecord = in.readParcelable(null);
- if (in.readByte() == 1) {
+ if (in.readInt() == 1) {
try {
info.mHost = InetAddress.getByAddress(in.createByteArray());
} catch (java.net.UnknownHostException e) {}
}
info.mPort = in.readInt();
+
+ // TXT record key/value pairs.
+ int recordCount = in.readInt();
+ for (int i = 0; i < recordCount; ++i) {
+ byte[] valueArray = null;
+ if (in.readInt() == 1) {
+ int valueLength = in.readInt();
+ valueArray = new byte[valueLength];
+ in.readByteArray(valueArray);
+ }
+ info.mTxtRecord.put(in.readString(), valueArray);
+ }
return info;
}
diff --git a/core/java/android/provider/TvContract.java b/core/java/android/provider/TvContract.java
new file mode 100644
index 0000000..233e0ca
--- /dev/null
+++ b/core/java/android/provider/TvContract.java
@@ -0,0 +1,449 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.provider;
+
+import android.content.ContentUris;
+import android.net.Uri;
+
+/**
+ * <p>
+ * The contract between the TV provider and applications. Contains definitions for the supported
+ * URIs and columns.
+ * </p>
+ * <h3>Overview</h3>
+ * <p>
+ * TvContract defines a basic database of TV content metadata such as channel and program
+ * information. The information is stored in {@link Channels} and {@link Programs} tables.
+ * </p>
+ * <ul>
+ * <li>A row in the {@link Channels} table represents information about a TV channel. The data
+ * format can vary greatly from standard to standard or according to service provider, thus
+ * the columns here are mostly comprised of basic entities that are usually seen to users
+ * regardless of standard such as channel number and name.</li>
+ * <li>A row in the {@link Programs} table represents a set of data describing a TV program such
+ * as program title and start time.</li>
+ * </ul>
+ */
+public final class TvContract {
+ /** The authority for the TV provider. */
+ public static final String AUTHORITY = "com.android.tv";
+
+ /**
+ * Builds a URI that points to a specific channel.
+ *
+ * @param channelId The ID of the channel to point to.
+ */
+ public static final Uri buildChannelUri(long channelId) {
+ return ContentUris.withAppendedId(Channels.CONTENT_URI, channelId);
+ }
+
+ /**
+ * Builds a URI that points to a specific program.
+ *
+ * @param programId The ID of the program to point to.
+ */
+ public static final Uri buildProgramUri(long programId) {
+ return ContentUris.withAppendedId(Programs.CONTENT_URI, programId);
+ }
+
+ /**
+ * Builds a URI that points to a specific program the user watched.
+ *
+ * @param watchedProgramId The ID of the watched program to point to.
+ * @hide
+ */
+ public static final Uri buildWatchedProgramUri(long watchedProgramId) {
+ return ContentUris.withAppendedId(WatchedPrograms.CONTENT_URI, watchedProgramId);
+ }
+
+ private TvContract() {}
+
+ /**
+ * Common base for the tables of TV channels/programs.
+ */
+ public interface BaseTvColumns extends BaseColumns {
+ /**
+ * The name of the package that owns a row in each table.
+ * <p>
+ * The TV provider fills it in with the name of the package that provides the initial data
+ * of that row. If the package is later uninstalled, the rows it owns are automatically
+ * removed from the tables.
+ * </p><p>
+ * Type: TEXT
+ * </p>
+ */
+ public static final String PACKAGE_NAME = "package_name";
+ }
+
+ /** Column definitions for the TV channels table. */
+ public static final class Channels implements BaseTvColumns {
+
+ /** The content:// style URI for this table. */
+ public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/channel");
+
+ /** The MIME type of a directory of TV channels. */
+ public static final String CONTENT_TYPE =
+ "vnd.android.cursor.dir/vnd.com.android.tv.channels";
+
+ /** The MIME type of a single TV channel. */
+ public static final String CONTENT_ITEM_TYPE =
+ "vnd.android.cursor.item/vnd.com.android.tv.channels";
+
+ /** A generic channel type. */
+ public static final int TYPE_OTHER = 0x0;
+
+ /** The special channel type used for pass-through inputs such as HDMI. */
+ public static final int TYPE_PASSTHROUGH = 0x00010000;
+
+ /** The channel type for DVB-T (terrestrial). */
+ public static final int TYPE_DVB_T = 0x00020000;
+
+ /** The channel type for DVB-T2 (terrestrial). */
+ public static final int TYPE_DVB_T2 = 0x00020001;
+
+ /** The channel type for DVB-S (satellite). */
+ public static final int TYPE_DVB_S = 0x00020100;
+
+ /** The channel type for DVB-S2 (satellite). */
+ public static final int TYPE_DVB_S2 = 0x00020101;
+
+ /** The channel type for DVB-C (cable). */
+ public static final int TYPE_DVB_C = 0x00020200;
+
+ /** The channel type for DVB-C2 (cable). */
+ public static final int TYPE_DVB_C2 = 0x00020201;
+
+ /** The channel type for DVB-H (handheld). */
+ public static final int TYPE_DVB_H = 0x00020300;
+
+ /** The channel type for DVB-SH (satellite). */
+ public static final int TYPE_DVB_SH = 0x00020400;
+
+ /** The channel type for ATSC (terrestrial/cable). */
+ public static final int TYPE_ATSC = 0x00030000;
+
+ /** The channel type for ATSC 2.0. */
+ public static final int TYPE_ATSC_2_0 = 0x00030001;
+
+ /** The channel type for ATSC-M/H (mobile/handheld). */
+ public static final int TYPE_ATSC_M_H = 0x00030100;
+
+ /** The channel type for ISDB-T (terrestrial). */
+ public static final int TYPE_ISDB_T = 0x00040000;
+
+ /** The channel type for ISDB-Tb (Brazil). */
+ public static final int TYPE_ISDB_TB = 0x00040100;
+
+ /** The channel type for ISDB-S (satellite). */
+ public static final int TYPE_ISDB_S = 0x00040200;
+
+ /** The channel type for ISDB-C (cable). */
+ public static final int TYPE_ISDB_C = 0x00040300;
+
+ /** The channel type for 1seg (handheld). */
+ public static final int TYPE_1SEG = 0x00040400;
+
+ /** The channel type for DTMB (terrestrial). */
+ public static final int TYPE_DTMB = 0x00050000;
+
+ /** The channel type for CMMB (handheld). */
+ public static final int TYPE_CMMB = 0x00050100;
+
+ /** The channel type for T-DMB (terrestrial). */
+ public static final int TYPE_T_DMB = 0x00060000;
+
+ /** The channel type for S-DMB (satellite). */
+ public static final int TYPE_S_DMB = 0x00060100;
+
+ /**
+ * The name of the TV input service that provides this TV channel.
+ * <p>
+ * This is a required field.
+ * </p><p>
+ * Type: TEXT
+ * </p>
+ */
+ public static final String SERVICE_NAME = "service_name";
+
+ /**
+ * The predefined type of this TV channel.
+ * <p>
+ * This is used to indicate which broadcast standard (e.g. ATSC, DVB or ISDB) the current
+ * channel conforms to.
+ * </p><p>
+ * This is a required field.
+ * </p><p>
+ * Type: INTEGER
+ * </p>
+ */
+ public static final String TYPE = "type";
+
+ /**
+ * The transport stream ID as appeared in various broadcast standards.
+ * <p>
+ * This is not a required field but if provided, can significantly increase the accuracy of
+ * channel identification.
+ * </p><p>
+ * Type: INTEGER
+ * </p>
+ */
+ public static final String TRANSPORT_STREAM_ID = "transport_stream_id";
+
+ /**
+ * The channel number that is displayed to the user.
+ * <p>
+ * The format can vary depending on broadcast standard and product specification.
+ * </p><p>
+ * Type: INTEGER
+ * </p>
+ */
+ public static final String DISPLAY_NUMBER = "display_number";
+
+ /**
+ * The channel name that is displayed to the user.
+ * <p>
+ * A call sign is a good candidate to use for this purpose but any name that helps the user
+ * recognize the current channel will be enough. Can also be empty depending on broadcast
+ * standard.
+ * </p><p>
+ * Type: TEXT
+ * </p>
+ */
+ public static final String DISPLAY_NAME = "display_name";
+
+ /**
+ * The description of this TV channel.
+ * <p>
+ * Can be empty initially.
+ * </p><p>
+ * Type: TEXT
+ * </p>
+ */
+ public static final String DESCRIPTION = "description";
+
+ /**
+ * The flag indicating whether this TV channel is browsable or not.
+ * <p>
+ * A value of 1 indicates the channel is included in the channel list that applications use
+ * to browse channels, a value of 0 indicates the channel is not included in the list. If
+ * not specified, this value is set to 1 by default.
+ * </p><p>
+ * Type: INTEGER (boolean)
+ * </p>
+ */
+ public static final String BROWSABLE = "browsable";
+
+ /**
+ * Generic data used by individual TV input services.
+ * <p>
+ * Type: BLOB
+ * </p>
+ */
+ public static final String DATA = "data";
+
+
+ /**
+ * The version number of this row entry used by TV input services.
+ * <p>
+ * This is best used by sync adapters to identify the rows to update. The number can be
+ * defined by individual TV input services. One may assign the same value as
+ * {@code version_number} that appears in ETSI EN 300 468 or ATSC A/65, if the data are
+ * coming from a TV broadcast.
+ * </p><p>
+ * Type: INTEGER
+ * </p>
+ */
+ public static final String VERSION_NUMBER = "version_number";
+
+ private Channels() {}
+ }
+
+ /** Column definitions for the TV programs table. */
+ public static final class Programs implements BaseTvColumns {
+
+ /** The content:// style URI for this table. */
+ public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/program");
+
+ /** The MIME type of a directory of TV programs. */
+ public static final String CONTENT_TYPE =
+ "vnd.android.cursor.dir/vnd.com.android.tv.programs";
+
+ /** The MIME type of a single TV program. */
+ public static final String CONTENT_ITEM_TYPE =
+ "vnd.android.cursor.item/vnd.com.android.tv.programs";
+
+ /**
+ * The ID of the TV channel that contains this TV program.
+ * <p>
+ * This is a part of the channel URI and matches to {@link BaseColumns#_ID}.
+ * </p><p>
+ * Type: INTEGER (long)
+ * </p>
+ */
+ public static final String CHANNEL_ID = "channel_id";
+
+ /**
+ * The title of this TV program.
+ * <p>
+ * Type: TEXT
+ * </p>
+ **/
+ public static final String TITLE = "title";
+
+ /**
+ * The start time of this TV program, in milliseconds since the epoch.
+ * <p>
+ * Type: INTEGER (long)
+ * </p>
+ */
+ public static final String START_TIME_UTC_MILLIS = "start_time_utc_millis";
+
+ /**
+ * The end time of this TV program, in milliseconds since the epoch.
+ * <p>
+ * Type: INTEGER (long)
+ * </p>
+ */
+ public static final String END_TIME_UTC_MILLIS = "end_time_utc_millis";
+
+ /**
+ * The description of this TV program that is displayed to the user by default.
+ * <p>
+ * The maximum length of this field is 256 characters.
+ * </p><p>
+ * Type: TEXT
+ * </p>
+ */
+ public static final String DESCRIPTION = "description";
+
+ /**
+ * The detailed, lengthy description of this TV program that is displayed only when the user
+ * wants to see more information.
+ * <p>
+ * TV input services should leave this field empty if they have no additional
+ * details beyond {@link #DESCRIPTION}.
+ * </p><p>
+ * Type: TEXT
+ * </p>
+ */
+ public static final String LONG_DESCRIPTION = "long_description";
+
+ /**
+ * Generic data used by TV input services.
+ * <p>
+ * Type: BLOB
+ * </p>
+ */
+ public static final String DATA = "data";
+
+ /**
+ * The version number of this row entry used by TV input services.
+ * <p>
+ * This is best used by sync adapters to identify the rows to update. The number can be
+ * defined by individual TV input services. One may assign the same value as
+ * {@code version_number} in ETSI EN 300 468 or ATSC A/65, if the data are coming from a TV
+ * broadcast.
+ * </p><p>
+ * Type: INTEGER
+ * </p>
+ */
+ public static final String VERSION_NUMBER = "version_number";
+
+ private Programs() {}
+ }
+
+ /**
+ * Column definitions for the TV programs that the user watched. Applications do not have access
+ * to this table.
+ *
+ * @hide
+ */
+ public static final class WatchedPrograms implements BaseColumns {
+
+ /** The content:// style URI for this table. */
+ public static final Uri CONTENT_URI =
+ Uri.parse("content://" + AUTHORITY + "/watched_program");
+
+ /** The MIME type of a directory of watched programs. */
+ public static final String CONTENT_TYPE =
+ "vnd.android.cursor.dir/vnd.com.android.tv.watched_programs";
+
+ /** The MIME type of a single item in this table. */
+ public static final String CONTENT_ITEM_TYPE =
+ "vnd.android.cursor.item/vnd.com.android.tv.watched_programs";
+
+ /**
+ * The UTC time that the user started watching this TV program, in milliseconds since the
+ * epoch.
+ * <p>
+ * Type: INTEGER (long)
+ * </p>
+ */
+ public static final String WATCH_START_TIME_UTC_MILLIS = "watch_start_time_utc_millis";
+
+ /**
+ * The UTC time that the user stopped watching this TV program, in milliseconds since the
+ * epoch.
+ * <p>
+ * Type: INTEGER (long)
+ * </p>
+ */
+ public static final String WATCH_END_TIME_UTC_MILLIS = "watch_end_time_utc_millis";
+
+ /**
+ * The channel ID that contains this TV program.
+ * <p>
+ * Type: INTEGER (long)
+ * </p>
+ */
+ public static final String CHANNEL_ID = "channel_id";
+
+ /**
+ * The title of this TV program.
+ * <p>
+ * Type: TEXT
+ * </p>
+ */
+ public static final String TITLE = "title";
+
+ /**
+ * The start time of this TV program, in milliseconds since the epoch.
+ * <p>
+ * Type: INTEGER (long)
+ * </p>
+ */
+ public static final String START_TIME_UTC_MILLIS = "start_time_utc_millis";
+
+ /**
+ * The end time of this TV program, in milliseconds since the epoch.
+ * <p>
+ * Type: INTEGER (long)
+ * </p>
+ */
+ public static final String END_TIME_UTC_MILLIS = "end_time_utc_millis";
+
+ /**
+ * The description of this TV program.
+ * <p>
+ * Type: TEXT
+ * </p>
+ */
+ public static final String DESCRIPTION = "description";
+
+ private WatchedPrograms() {}
+ }
+}
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index cbb98e1..1429837 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -67,7 +67,7 @@
void destroy(boolean full) {
mInitialized = false;
updateEnabledState(null);
- nDestroyCanvas(mNativeProxy);
+ nDestroyCanvasAndSurface(mNativeProxy);
}
private void updateEnabledState(Surface surface) {
@@ -300,7 +300,7 @@
private static native void nDrawDisplayList(long nativeProxy, long displayList,
int dirtyLeft, int dirtyTop, int dirtyRight, int dirtyBottom);
private static native void nRunWithGlContext(long nativeProxy, Runnable runnable);
- private static native void nDestroyCanvas(long nativeProxy);
+ private static native void nDestroyCanvasAndSurface(long nativeProxy);
private static native void nInvokeFunctor(long nativeProxy, long functor, boolean waitForCompletion);
diff --git a/core/java/android/widget/ShareActionProvider.java b/core/java/android/widget/ShareActionProvider.java
index 64a1574..cde8080 100644
--- a/core/java/android/widget/ShareActionProvider.java
+++ b/core/java/android/widget/ShareActionProvider.java
@@ -161,9 +161,11 @@
@Override
public View onCreateActionView() {
// Create the view and set its data model.
- ActivityChooserModel dataModel = ActivityChooserModel.get(mContext, mShareHistoryFileName);
ActivityChooserView activityChooserView = new ActivityChooserView(mContext);
- activityChooserView.setActivityChooserModel(dataModel);
+ if (!activityChooserView.isInEditMode()) {
+ ActivityChooserModel dataModel = ActivityChooserModel.get(mContext, mShareHistoryFileName);
+ activityChooserView.setActivityChooserModel(dataModel);
+ }
// Lookup and set the expand action icon.
TypedValue outTypedValue = new TypedValue();
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 5e4c143..a7278da 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -714,19 +714,19 @@
break;
case com.android.internal.R.styleable.TextAppearance_shadowColor:
- shadowcolor = a.getInt(attr, 0);
+ shadowcolor = appearance.getInt(attr, 0);
break;
case com.android.internal.R.styleable.TextAppearance_shadowDx:
- dx = a.getFloat(attr, 0);
+ dx = appearance.getFloat(attr, 0);
break;
case com.android.internal.R.styleable.TextAppearance_shadowDy:
- dy = a.getFloat(attr, 0);
+ dy = appearance.getFloat(attr, 0);
break;
case com.android.internal.R.styleable.TextAppearance_shadowRadius:
- r = a.getFloat(attr, 0);
+ r = appearance.getFloat(attr, 0);
break;
}
}
diff --git a/core/java/com/android/internal/app/WindowDecorActionBar.java b/core/java/com/android/internal/app/WindowDecorActionBar.java
index c6afae0..fb93ddd 100644
--- a/core/java/com/android/internal/app/WindowDecorActionBar.java
+++ b/core/java/com/android/internal/app/WindowDecorActionBar.java
@@ -170,6 +170,15 @@
init(dialog.getWindow().getDecorView());
}
+ /**
+ * Only for edit mode.
+ * @hide
+ */
+ public WindowDecorActionBar(View layout) {
+ assert layout.isInEditMode();
+ init(layout);
+ }
+
private void init(View decor) {
mOverlayLayout = (ActionBarOverlayLayout) decor.findViewById(
com.android.internal.R.id.action_bar_overlay_layout);
@@ -559,8 +568,8 @@
return;
}
- final FragmentTransaction trans = mActivity.getFragmentManager().beginTransaction()
- .disallowAddToBackStack();
+ final FragmentTransaction trans = mActionView.isInEditMode() ? null :
+ mActivity.getFragmentManager().beginTransaction().disallowAddToBackStack();
if (mSelectedTab == tab) {
if (mSelectedTab != null) {
@@ -578,7 +587,7 @@
}
}
- if (!trans.isEmpty()) {
+ if (trans != null && !trans.isEmpty()) {
trans.commit();
}
}
diff --git a/core/jni/android_view_GLRenderer.cpp b/core/jni/android_view_GLRenderer.cpp
index 6ae6c8f..d0269a3 100644
--- a/core/jni/android_view_GLRenderer.cpp
+++ b/core/jni/android_view_GLRenderer.cpp
@@ -146,8 +146,8 @@
jlong renderNodePtr) {
using namespace android::uirenderer;
RenderNode* renderNode = reinterpret_cast<RenderNode*>(renderNodePtr);
- TreeInfo info = {0};
- renderNode->prepareTree(info);
+ TreeInfo ignoredInfo;
+ renderNode->prepareTree(ignoredInfo);
}
static void android_view_GLRenderer_invokeFunctor(JNIEnv* env, jobject clazz,
diff --git a/core/jni/android_view_HardwareLayer.cpp b/core/jni/android_view_HardwareLayer.cpp
index 2eb0d78..b2f17de 100644
--- a/core/jni/android_view_HardwareLayer.cpp
+++ b/core/jni/android_view_HardwareLayer.cpp
@@ -127,8 +127,8 @@
static jboolean android_view_HardwareLayer_flushChanges(JNIEnv* env, jobject clazz,
jlong layerUpdaterPtr) {
DeferredLayerUpdater* layer = reinterpret_cast<DeferredLayerUpdater*>(layerUpdaterPtr);
- bool ignoredHasFunctors;
- return layer->apply(&ignoredHasFunctors);
+ TreeInfo ignoredInfo;
+ return layer->apply(ignoredInfo);
}
static jlong android_view_HardwareLayer_getLayer(JNIEnv* env, jobject clazz,
diff --git a/core/jni/android_view_ThreadedRenderer.cpp b/core/jni/android_view_ThreadedRenderer.cpp
index 30d3e0c..b5f489d 100644
--- a/core/jni/android_view_ThreadedRenderer.cpp
+++ b/core/jni/android_view_ThreadedRenderer.cpp
@@ -121,10 +121,10 @@
proxy->drawDisplayList(displayList, dirtyLeft, dirtyTop, dirtyRight, dirtyBottom);
}
-static void android_view_ThreadedRenderer_destroyCanvas(JNIEnv* env, jobject clazz,
+static void android_view_ThreadedRenderer_destroyCanvasAndSurface(JNIEnv* env, jobject clazz,
jlong proxyPtr) {
RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
- proxy->destroyCanvas();
+ proxy->destroyCanvasAndSurface();
}
static void android_view_ThreadedRenderer_invokeFunctor(JNIEnv* env, jobject clazz,
@@ -194,7 +194,7 @@
{ "nPauseSurface", "(JLandroid/view/Surface;)V", (void*) android_view_ThreadedRenderer_pauseSurface },
{ "nSetup", "(JII)V", (void*) android_view_ThreadedRenderer_setup },
{ "nDrawDisplayList", "(JJIIII)V", (void*) android_view_ThreadedRenderer_drawDisplayList },
- { "nDestroyCanvas", "(J)V", (void*) android_view_ThreadedRenderer_destroyCanvas },
+ { "nDestroyCanvasAndSurface", "(J)V", (void*) android_view_ThreadedRenderer_destroyCanvasAndSurface },
{ "nInvokeFunctor", "(JJZ)V", (void*) android_view_ThreadedRenderer_invokeFunctor },
{ "nRunWithGlContext", "(JLjava/lang/Runnable;)V", (void*) android_view_ThreadedRenderer_runWithGlContext },
{ "nCreateDisplayListLayer", "(JII)J", (void*) android_view_ThreadedRenderer_createDisplayListLayer },
diff --git a/core/res/res/layout/action_bar_home_quantum.xml b/core/res/res/layout/action_bar_home_quantum.xml
new file mode 100644
index 0000000..3968429
--- /dev/null
+++ b/core/res/res/layout/action_bar_home_quantum.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 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"
+ class="com.android.internal.widget.ActionBarView$HomeView"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical|start">
+ <ImageView android:id="@android:id/up"
+ android:src="?android:attr/homeAsUpIndicator"
+ android:layout_gravity="center_vertical|start"
+ android:visibility="gone"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:scaleType="center" />
+ <ImageView android:id="@android:id/home"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:scaleType="fitCenter" />
+</view>
diff --git a/core/res/res/values-land/dimens_quantum.xml b/core/res/res/values-land/dimens_quantum.xml
new file mode 100644
index 0000000..7789219
--- /dev/null
+++ b/core/res/res/values-land/dimens_quantum.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 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.
+-->
+<resources>
+
+ <!-- Default height of an action bar. -->
+ <dimen name="action_bar_default_height_quantum">48dp</dimen>
+ <!-- Default padding of an action bar. -->
+ <dimen name="action_bar_default_padding_quantum">0dp</dimen>
+
+</resources>
diff --git a/core/res/res/values/dimens_quantum.xml b/core/res/res/values/dimens_quantum.xml
index 3913752..02e61e2 100644
--- a/core/res/res/values/dimens_quantum.xml
+++ b/core/res/res/values/dimens_quantum.xml
@@ -17,6 +17,8 @@
<!-- Default height of an action bar. -->
<dimen name="action_bar_default_height_quantum">56dp</dimen>
+ <!-- Default padding of an action bar. -->
+ <dimen name="action_bar_default_padding_quantum">4dp</dimen>
<!-- Vertical padding around action bar icons. -->
<dimen name="action_bar_icon_vertical_padding_quantum">16dp</dimen>
<!-- Text size for action bar titles -->
@@ -28,6 +30,10 @@
<!-- Bottom margin for action bar subtitles -->
<dimen name="action_bar_subtitle_bottom_margin_quantum">5dp</dimen>
+ <dimen name="action_button_min_width_quantum">48dp</dimen>
+ <dimen name="action_button_min_height_quantum">48dp</dimen>
+ <dimen name="action_overflow_min_width_quantum">36dp</dimen>
+
<dimen name="text_size_display_4_quantum">112sp</dimen>
<dimen name="text_size_display_3_quantum">56sp</dimen>
<dimen name="text_size_display_2_quantum">45sp</dimen>
diff --git a/core/res/res/values/styles_quantum.xml b/core/res/res/values/styles_quantum.xml
index 2720d61..595dc79 100644
--- a/core/res/res/values/styles_quantum.xml
+++ b/core/res/res/values/styles_quantum.xml
@@ -713,10 +713,9 @@
<style name="Widget.Quantum.PopupMenu" parent="Widget.Quantum.ListPopupWindow"/>
<style name="Widget.Quantum.ActionButton" parent="Widget.ActionButton">
- <item name="minWidth">@dimen/action_button_min_width</item>
+ <item name="minWidth">@dimen/action_button_min_width_quantum</item>
+ <item name="minHeight">@dimen/action_button_min_height_quantum</item>
<item name="gravity">center</item>
- <item name="paddingStart">12dip</item>
- <item name="paddingEnd">12dip</item>
<item name="scaleType">center</item>
<item name="maxLines">2</item>
</style>
@@ -729,6 +728,9 @@
<item name="src">@drawable/ic_menu_moreoverflow_quantum</item>
<item name="background">?attr/actionBarItemBackground</item>
<item name="contentDescription">@string/action_menu_overflow_description</item>
+ <item name="minWidth">@dimen/action_overflow_min_width_quantum</item>
+ <item name="minHeight">@dimen/action_button_min_height_quantum</item>
+ <item name="scaleType">center</item>
</style>
<style name="Widget.Quantum.ActionButton.TextButton" parent="Widget.Quantum.ButtonBar"/>
@@ -756,16 +758,19 @@
</style>
<style name="Widget.Quantum.ActionBar" parent="Widget.ActionBar">
- <item name="titleTextStyle">@style/TextAppearance.Quantum.Widget.ActionBar.Title</item>
- <item name="subtitleTextStyle">@style/TextAppearance.Quantum.Widget.ActionBar.Subtitle</item>
<item name="background">@null</item>
<item name="backgroundStacked">@null</item>
<item name="backgroundSplit">@null</item>
+ <item name="displayOptions">showHome|showTitle</item>
<item name="divider">?attr/dividerVertical</item>
+ <item name="titleTextStyle">@style/TextAppearance.Quantum.Widget.ActionBar.Title</item>
+ <item name="subtitleTextStyle">@style/TextAppearance.Quantum.Widget.ActionBar.Subtitle</item>
<item name="progressBarStyle">@style/Widget.Quantum.ProgressBar.Horizontal</item>
<item name="indeterminateProgressStyle">@style/Widget.Quantum.ProgressBar</item>
<item name="progressBarPadding">32dip</item>
<item name="itemPadding">8dip</item>
+ <item name="homeLayout">@layout/action_bar_home_quantum</item>
+ <item name="gravity">center_vertical</item>
</style>
<style name="Widget.Quantum.ActionBar.Solid">
diff --git a/core/res/res/values/themes_quantum.xml b/core/res/res/values/themes_quantum.xml
index 51e806a..58d95be 100644
--- a/core/res/res/values/themes_quantum.xml
+++ b/core/res/res/values/themes_quantum.xml
@@ -300,10 +300,10 @@
<item name="actionModeStyle">@style/Widget.Quantum.ActionMode</item>
<item name="actionModeCloseButtonStyle">@style/Widget.Quantum.ActionButton.CloseMode</item>
<item name="actionBarStyle">@style/Widget.Quantum.ActionBar.Solid</item>
- <item name="actionBarSize">@dimen/action_bar_default_height</item>
+ <item name="actionBarSize">@dimen/action_bar_default_height_quantum</item>
<item name="actionModePopupWindowStyle">@style/Widget.Quantum.PopupWindow.ActionMode</item>
<item name="actionBarWidgetTheme">@null</item>
- <item name="actionBarTheme">@null</item>
+ <item name="actionBarTheme">@style/Theme.Quantum.ActionBar</item>
<item name="actionBarItemBackground">@drawable/item_background_quantum</item>
<item name="actionModeCutDrawable">@drawable/ic_menu_cut_quantum</item>
@@ -645,10 +645,10 @@
<item name="actionModeStyle">@style/Widget.Quantum.Light.ActionMode</item>
<item name="actionModeCloseButtonStyle">@style/Widget.Quantum.Light.ActionButton.CloseMode</item>
<item name="actionBarStyle">@style/Widget.Quantum.Light.ActionBar.Solid</item>
- <item name="actionBarSize">@dimen/action_bar_default_height</item>
+ <item name="actionBarSize">@dimen/action_bar_default_height_quantum</item>
<item name="actionModePopupWindowStyle">@style/Widget.Quantum.Light.PopupWindow.ActionMode</item>
<item name="actionBarWidgetTheme">@null</item>
- <item name="actionBarTheme">@null</item>
+ <item name="actionBarTheme">@style/Theme.Quantum.Light.ActionBar</item>
<item name="actionBarItemBackground">@drawable/item_background_quantum</item>
<item name="actionModeCutDrawable">@drawable/ic_menu_cut_quantum</item>
@@ -722,12 +722,20 @@
<item name="colorButtonPressedColored">?attr/colorPrimaryDark</item>
</style>
+ <style name="Theme.Quantum.ActionBar">
+ <item name="colorControlActivated">?attr/colorControlNormal</item>
+ </style>
+
+ <style name="Theme.Quantum.Light.ActionBar">
+ <item name="colorControlActivated">?attr/colorControlNormal</item>
+ </style>
+
<!-- Variant of the quantum (light) theme that has a solid (opaque) action bar
with an inverse color profile. The dark action bar sharply stands out against
the light content. -->
<style name="Theme.Quantum.Light.DarkActionBar">
<item name="actionBarWidgetTheme">@null</item>
- <item name="actionBarTheme">@style/Theme.Quantum</item>
+ <item name="actionBarTheme">@style/Theme.Quantum.ActionBar</item>
</style>
<!-- Variant of the quantum (dark) theme with no action bar. -->
diff --git a/libs/hwui/DeferredLayerUpdater.cpp b/libs/hwui/DeferredLayerUpdater.cpp
index 8b23955..285c8c3 100644
--- a/libs/hwui/DeferredLayerUpdater.cpp
+++ b/libs/hwui/DeferredLayerUpdater.cpp
@@ -63,7 +63,7 @@
}
}
-bool DeferredLayerUpdater::apply(bool* hasFunctors) {
+bool DeferredLayerUpdater::apply(TreeInfo& info) {
bool success = true;
// These properties are applied the same to both layer types
mLayer->setColorFilter(mColorFilter);
@@ -74,11 +74,7 @@
success = LayerRenderer::resizeLayer(mLayer, mWidth, mHeight);
}
mLayer->setBlend(mBlend);
- TreeInfo info = {0};
mDisplayList->prepareTree(info);
- if (info.hasFunctors) {
- *hasFunctors = true;
- }
mLayer->updateDeferred(mDisplayList.get(),
mDirtyRect.left, mDirtyRect.top, mDirtyRect.right, mDirtyRect.bottom);
mDirtyRect.setEmpty();
diff --git a/libs/hwui/DeferredLayerUpdater.h b/libs/hwui/DeferredLayerUpdater.h
index 2cc9229..cc62caa 100644
--- a/libs/hwui/DeferredLayerUpdater.h
+++ b/libs/hwui/DeferredLayerUpdater.h
@@ -77,7 +77,7 @@
ANDROID_API void setPaint(const SkPaint* paint);
- ANDROID_API bool apply(bool* hasFunctors);
+ ANDROID_API bool apply(TreeInfo& info);
ANDROID_API Layer* backingLayer() {
return mLayer;
diff --git a/libs/hwui/DisplayListOp.h b/libs/hwui/DisplayListOp.h
index 06f675e..f19da9d 100644
--- a/libs/hwui/DisplayListOp.h
+++ b/libs/hwui/DisplayListOp.h
@@ -286,12 +286,6 @@
int getFlags() const { return mFlags; }
private:
- SaveOp() {}
- DisplayListOp* reinit(int flags) {
- mFlags = flags;
- return this;
- }
-
int mFlags;
};
@@ -318,12 +312,6 @@
virtual const char* name() { return "RestoreToCount"; }
private:
- RestoreToCountOp() {}
- DisplayListOp* reinit(int count) {
- mCount = count;
- return this;
- }
-
int mCount;
};
@@ -514,7 +502,6 @@
}
protected:
- ClipOp() {}
virtual bool isRect() { return false; }
SkRegion::Op mOp;
@@ -539,13 +526,6 @@
virtual bool isRect() { return true; }
private:
- ClipRectOp() {}
- DisplayListOp* reinit(float left, float top, float right, float bottom, SkRegion::Op op) {
- mOp = op;
- mArea.set(left, top, right, bottom);
- return this;
- }
-
Rect mArea;
};
diff --git a/libs/hwui/RenderNode.cpp b/libs/hwui/RenderNode.cpp
index c55ebd6..cf21834 100644
--- a/libs/hwui/RenderNode.cpp
+++ b/libs/hwui/RenderNode.cpp
@@ -109,7 +109,7 @@
mNeedsDisplayListDataSync = false;
// Do a push pass on the old tree to handle freeing DisplayListData
// that are no longer used
- TreeInfo oldTreeInfo = {0};
+ TreeInfo oldTreeInfo;
prepareSubTree(oldTreeInfo, mDisplayListData);
// TODO: The damage for the old tree should be accounted for
delete mDisplayListData;
@@ -120,8 +120,15 @@
void RenderNode::prepareSubTree(TreeInfo& info, DisplayListData* subtree) {
if (subtree) {
- if (!info.hasFunctors) {
- info.hasFunctors = subtree->functorCount;
+ TextureCache& cache = Caches::getInstance().textureCache;
+ info.hasFunctors |= subtree->functorCount;
+ // TODO: Fix ownedBitmapResources to not require disabling prepareTextures
+ // and thus falling out of async drawing path.
+ if (subtree->ownedBitmapResources.size()) {
+ info.prepareTextures = false;
+ }
+ for (size_t i = 0; info.prepareTextures && i < subtree->bitmapResources.size(); i++) {
+ info.prepareTextures = cache.prefetchAndMarkInUse(subtree->bitmapResources[i]);
}
for (size_t i = 0; i < subtree->children().size(); i++) {
RenderNode* childNode = subtree->children()[i]->mDisplayList;
diff --git a/libs/hwui/RenderNode.h b/libs/hwui/RenderNode.h
index 9e6ee3f..6688952 100644
--- a/libs/hwui/RenderNode.h
+++ b/libs/hwui/RenderNode.h
@@ -66,7 +66,13 @@
class DrawDisplayListOp;
struct TreeInfo {
+ TreeInfo()
+ : hasFunctors(false)
+ , prepareTextures(false)
+ {}
+
bool hasFunctors;
+ bool prepareTextures;
// TODO: Damage calculations? Flag to skip staging pushes for RT animations?
};
diff --git a/libs/hwui/Texture.cpp b/libs/hwui/Texture.cpp
index 7923ce7..e783905 100644
--- a/libs/hwui/Texture.cpp
+++ b/libs/hwui/Texture.cpp
@@ -25,14 +25,14 @@
namespace uirenderer {
Texture::Texture(): id(0), generation(0), blend(false), width(0), height(0),
- cleanup(false), bitmapSize(0), mipMap(false), uvMapper(NULL),
+ cleanup(false), bitmapSize(0), mipMap(false), uvMapper(NULL), isInUse(false),
mWrapS(GL_CLAMP_TO_EDGE), mWrapT(GL_CLAMP_TO_EDGE),
mMinFilter(GL_NEAREST), mMagFilter(GL_NEAREST),
mFirstFilter(true), mFirstWrap(true), mCaches(Caches::getInstance()) {
}
Texture::Texture(Caches& caches): id(0), generation(0), blend(false), width(0), height(0),
- cleanup(false), bitmapSize(0), mipMap(false), uvMapper(NULL),
+ cleanup(false), bitmapSize(0), mipMap(false), uvMapper(NULL), isInUse(false),
mWrapS(GL_CLAMP_TO_EDGE), mWrapT(GL_CLAMP_TO_EDGE),
mMinFilter(GL_NEAREST), mMagFilter(GL_NEAREST),
mFirstFilter(true), mFirstWrap(true), mCaches(caches) {
diff --git a/libs/hwui/Texture.h b/libs/hwui/Texture.h
index d48ec59..d5601f8 100644
--- a/libs/hwui/Texture.h
+++ b/libs/hwui/Texture.h
@@ -94,6 +94,12 @@
*/
const UvMapper* uvMapper;
+ /**
+ * Whether or not the Texture is marked in use and thus not evictable for
+ * the current frame. This is reset at the start of a new frame.
+ */
+ bool isInUse;
+
private:
/**
* Last wrap modes set on this texture. Defaults to GL_CLAMP_TO_EDGE.
diff --git a/libs/hwui/TextureCache.cpp b/libs/hwui/TextureCache.cpp
index 01d72d1..34e2265 100644
--- a/libs/hwui/TextureCache.cpp
+++ b/libs/hwui/TextureCache.cpp
@@ -121,29 +121,49 @@
// Caching
///////////////////////////////////////////////////////////////////////////////
-Texture* TextureCache::get(const SkBitmap* bitmap) {
+void TextureCache::resetMarkInUse() {
+ LruCache<const SkBitmap*, Texture*>::Iterator iter(mCache);
+ while (iter.next()) {
+ iter.value()->isInUse = false;
+ }
+}
+
+bool TextureCache::canMakeTextureFromBitmap(const SkBitmap* bitmap) {
+ if (bitmap->width() > mMaxTextureSize || bitmap->height() > mMaxTextureSize) {
+ ALOGW("Bitmap too large to be uploaded into a texture (%dx%d, max=%dx%d)",
+ bitmap->width(), bitmap->height(), mMaxTextureSize, mMaxTextureSize);
+ return false;
+ }
+ return true;
+}
+
+// Returns a prepared Texture* that either is already in the cache or can fit
+// in the cache (and is thus added to the cache)
+Texture* TextureCache::getCachedTexture(const SkBitmap* bitmap) {
Texture* texture = mCache.get(bitmap);
if (!texture) {
- if (bitmap->width() > mMaxTextureSize || bitmap->height() > mMaxTextureSize) {
- ALOGW("Bitmap too large to be uploaded into a texture (%dx%d, max=%dx%d)",
- bitmap->width(), bitmap->height(), mMaxTextureSize, mMaxTextureSize);
+ if (!canMakeTextureFromBitmap(bitmap)) {
return NULL;
}
const uint32_t size = bitmap->rowBytes() * bitmap->height();
+ bool canCache = size < mMaxSize;
// Don't even try to cache a bitmap that's bigger than the cache
- if (size < mMaxSize) {
- while (mSize + size > mMaxSize) {
+ while (canCache && mSize + size > mMaxSize) {
+ Texture* oldest = mCache.peekOldestValue();
+ if (oldest && !oldest->isInUse) {
mCache.removeOldest();
+ } else {
+ canCache = false;
}
}
- texture = new Texture();
- texture->bitmapSize = size;
- generateTexture(bitmap, texture, false);
+ if (canCache) {
+ texture = new Texture();
+ texture->bitmapSize = size;
+ generateTexture(bitmap, texture, false);
- if (size < mMaxSize) {
mSize += size;
TEXTURE_LOGD("TextureCache::get: create texture(%p): name, size, mSize = %d, %d, %d",
bitmap, texture->id, size, mSize);
@@ -151,16 +171,42 @@
ALOGD("Texture created, size = %d", size);
}
mCache.put(bitmap, texture);
- } else {
- texture->cleanup = true;
}
- } else if (bitmap->getGenerationID() != texture->generation) {
+ } else if (!texture->isInUse && bitmap->getGenerationID() != texture->generation) {
+ // Texture was in the cache but is dirty, re-upload
+ // TODO: Re-adjust the cache size if the bitmap's dimensions have changed
generateTexture(bitmap, texture, true);
}
return texture;
}
+bool TextureCache::prefetchAndMarkInUse(const SkBitmap* bitmap) {
+ Texture* texture = getCachedTexture(bitmap);
+ if (texture) {
+ texture->isInUse = true;
+ }
+ return texture;
+}
+
+Texture* TextureCache::get(const SkBitmap* bitmap) {
+ Texture* texture = getCachedTexture(bitmap);
+
+ if (!texture) {
+ if (!canMakeTextureFromBitmap(bitmap)) {
+ return NULL;
+ }
+
+ const uint32_t size = bitmap->rowBytes() * bitmap->height();
+ texture = new Texture();
+ texture->bitmapSize = size;
+ generateTexture(bitmap, texture, false);
+ texture->cleanup = true;
+ }
+
+ return texture;
+}
+
Texture* TextureCache::getTransient(const SkBitmap* bitmap) {
Texture* texture = new Texture();
texture->bitmapSize = bitmap->rowBytes() * bitmap->height();
diff --git a/libs/hwui/TextureCache.h b/libs/hwui/TextureCache.h
index e33c60d..48a10c2 100644
--- a/libs/hwui/TextureCache.h
+++ b/libs/hwui/TextureCache.h
@@ -62,6 +62,18 @@
void operator()(const SkBitmap*& bitmap, Texture*& texture);
/**
+ * Resets all Textures to not be marked as in use
+ */
+ void resetMarkInUse();
+
+ /**
+ * Attempts to precache the SkBitmap. Returns true if a Texture was successfully
+ * acquired for the bitmap, false otherwise. If a Texture was acquired it is
+ * marked as in use.
+ */
+ bool prefetchAndMarkInUse(const SkBitmap* bitmap);
+
+ /**
* Returns the texture associated with the specified bitmap. If the texture
* cannot be found in the cache, a new texture is generated.
*/
@@ -116,6 +128,11 @@
void setFlushRate(float flushRate);
private:
+
+ bool canMakeTextureFromBitmap(const SkBitmap* bitmap);
+
+ Texture* getCachedTexture(const SkBitmap* bitmap);
+
/**
* Generates the texture from a bitmap into the specified texture structure.
*
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 3638184..16baf77 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -318,10 +318,10 @@
}
CanvasContext::~CanvasContext() {
- destroyCanvas();
+ destroyCanvasAndSurface();
}
-void CanvasContext::destroyCanvas() {
+void CanvasContext::destroyCanvasAndSurface() {
if (mCanvas) {
delete mCanvas;
mCanvas = 0;
@@ -382,13 +382,18 @@
mCanvas->setViewport(width, height);
}
-void CanvasContext::processLayerUpdates(const Vector<DeferredLayerUpdater*>* layerUpdaters,
- bool* hasFunctors) {
- LOG_ALWAYS_FATAL_IF(!mCanvas, "Cannot process layer updates without a canvas!");
+void CanvasContext::makeCurrent() {
mGlobalContext->makeCurrent(mEglSurface);
+}
+
+void CanvasContext::processLayerUpdates(const Vector<DeferredLayerUpdater*>* layerUpdaters,
+ TreeInfo& info) {
+ LOG_ALWAYS_FATAL_IF(!mCanvas, "Cannot process layer updates without a canvas!");
+ makeCurrent();
for (size_t i = 0; i < layerUpdaters->size(); i++) {
DeferredLayerUpdater* update = layerUpdaters->itemAt(i);
- LOG_ALWAYS_FATAL_IF(!update->apply(hasFunctors), "Failed to update layer!");
+ bool success = update->apply(info);
+ LOG_ALWAYS_FATAL_IF(!success, "Failed to update layer!");
if (update->backingLayer()->deferredUpdateScheduled) {
mCanvas->pushLayerUpdate(update->backingLayer());
}
@@ -444,8 +449,8 @@
bool CanvasContext::copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap) {
requireGlContext();
- bool hasFunctors;
- layer->apply(&hasFunctors);
+ TreeInfo info;
+ layer->apply(info);
return LayerRenderer::copyLayer(layer->backingLayer(), bitmap);
}
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index dcb5957..a3fe591 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -23,6 +23,7 @@
#include <utils/Functor.h>
#include <utils/Vector.h>
+#include "../RenderNode.h"
#include "RenderTask.h"
#define FUNCTOR_PROCESS_DELAY 4
@@ -31,8 +32,6 @@
namespace uirenderer {
class DeferredLayerUpdater;
-class RenderNode;
-class DisplayListData;
class OpenGLRenderer;
class Rect;
class Layer;
@@ -54,9 +53,10 @@
void updateSurface(EGLNativeWindowType window);
void pauseSurface(EGLNativeWindowType window);
void setup(int width, int height);
- void processLayerUpdates(const Vector<DeferredLayerUpdater*>* layerUpdaters, bool* hasFunctors);
+ void makeCurrent();
+ void processLayerUpdates(const Vector<DeferredLayerUpdater*>* layerUpdaters, TreeInfo& info);
void drawDisplayList(RenderNode* displayList, Rect* dirty);
- void destroyCanvas();
+ void destroyCanvasAndSurface();
bool copyLayerInto(DeferredLayerUpdater* layer, SkBitmap* bitmap);
diff --git a/libs/hwui/renderthread/DrawFrameTask.cpp b/libs/hwui/renderthread/DrawFrameTask.cpp
index cf6c8db..f542d43 100644
--- a/libs/hwui/renderthread/DrawFrameTask.cpp
+++ b/libs/hwui/renderthread/DrawFrameTask.cpp
@@ -85,7 +85,6 @@
void DrawFrameTask::run() {
ATRACE_NAME("DrawFrame");
- // canUnblockUiThread is temporary until WebView has a solution for syncing frame state
bool canUnblockUiThread = syncFrameState();
// Grab a copy of everything we need
@@ -105,17 +104,20 @@
}
}
+static void prepareTreeInfo(TreeInfo& info) {
+ info.prepareTextures = true;
+}
+
bool DrawFrameTask::syncFrameState() {
ATRACE_CALL();
-
- bool hasFunctors = false;
- mContext->processLayerUpdates(&mLayers, &hasFunctors);
-
- TreeInfo info = {0};
+ mContext->makeCurrent();
+ Caches::getInstance().textureCache.resetMarkInUse();
+ TreeInfo info;
+ prepareTreeInfo(info);
+ mContext->processLayerUpdates(&mLayers, info);
mRenderNode->prepareTree(info);
- hasFunctors |= info.hasFunctors;
-
- return !hasFunctors;
+ // If prepareTextures is false, we ran out of texture cache space
+ return !info.hasFunctors && info.prepareTextures;
}
void DrawFrameTask::unblockUiThread() {
diff --git a/libs/hwui/renderthread/RenderProxy.cpp b/libs/hwui/renderthread/RenderProxy.cpp
index b233ae9..ce490f1 100644
--- a/libs/hwui/renderthread/RenderProxy.cpp
+++ b/libs/hwui/renderthread/RenderProxy.cpp
@@ -140,15 +140,18 @@
mDrawFrameTask.drawFrame(&mRenderThread);
}
-CREATE_BRIDGE1(destroyCanvas, CanvasContext* context) {
- args->context->destroyCanvas();
+CREATE_BRIDGE1(destroyCanvasAndSurface, CanvasContext* context) {
+ args->context->destroyCanvasAndSurface();
return NULL;
}
-void RenderProxy::destroyCanvas() {
- SETUP_TASK(destroyCanvas);
+void RenderProxy::destroyCanvasAndSurface() {
+ SETUP_TASK(destroyCanvasAndSurface);
args->context = mContext;
- post(task);
+ // destroyCanvasAndSurface() needs a fence as when it returns the
+ // underlying BufferQueue is going to be released from under
+ // the render thread.
+ postAndWait(task);
}
CREATE_BRIDGE2(invokeFunctor, CanvasContext* context, Functor* functor) {
diff --git a/libs/hwui/renderthread/RenderProxy.h b/libs/hwui/renderthread/RenderProxy.h
index 3eb8ed8..a112493 100644
--- a/libs/hwui/renderthread/RenderProxy.h
+++ b/libs/hwui/renderthread/RenderProxy.h
@@ -65,7 +65,7 @@
ANDROID_API void setup(int width, int height);
ANDROID_API void drawDisplayList(RenderNode* displayList,
int dirtyLeft, int dirtyTop, int dirtyRight, int dirtyBottom);
- ANDROID_API void destroyCanvas();
+ ANDROID_API void destroyCanvasAndSurface();
ANDROID_API void invokeFunctor(Functor* functor, bool waitForCompletion);
diff --git a/packages/SystemUI/proguard.flags b/packages/SystemUI/proguard.flags
index 48d9722..da37803 100644
--- a/packages/SystemUI/proguard.flags
+++ b/packages/SystemUI/proguard.flags
@@ -6,7 +6,7 @@
public void setGlowAlpha(float);
public void setGlowScale(float);
}
--keep class com.android.systemui.recents.views.TaskIconView {
+-keep class com.android.systemui.recents.views.TaskInfoView {
public void setCircularClipRadius(float);
public float getCircularClipRadius();
}
diff --git a/packages/SystemUI/res/layout/recents_task_view.xml b/packages/SystemUI/res/layout/recents_task_view.xml
index 8297878..7f64032 100644
--- a/packages/SystemUI/res/layout/recents_task_view.xml
+++ b/packages/SystemUI/res/layout/recents_task_view.xml
@@ -21,6 +21,21 @@
android:id="@+id/task_view_thumbnail"
android:layout_width="match_parent"
android:layout_height="match_parent" />
+ <com.android.systemui.recents.views.TaskInfoView
+ android:id="@+id/task_view_info_pane"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:visibility="invisible"
+ android:background="#e6444444">
+ <Button
+ android:id="@+id/task_view_app_info_button"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="20dp"
+ android:layout_marginEnd="20dp"
+ android:layout_gravity="top|center_horizontal"
+ android:text="@string/recents_app_info_button_label" />
+ </com.android.systemui.recents.views.TaskInfoView>
<com.android.systemui.recents.views.TaskBarView
android:id="@+id/task_view_bar"
android:layout_width="match_parent"
@@ -31,15 +46,15 @@
android:id="@+id/application_icon"
android:layout_width="@dimen/recents_task_view_application_icon_size"
android:layout_height="@dimen/recents_task_view_application_icon_size"
- android:layout_gravity="center_vertical|left"
+ android:layout_gravity="center_vertical|start"
android:padding="8dp" />
<TextView
android:id="@+id/activity_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|left"
- android:layout_marginLeft="@dimen/recents_task_view_application_icon_size"
- android:layout_marginRight="@dimen/recents_task_view_activity_icon_size"
+ android:layout_marginStart="@dimen/recents_task_view_application_icon_size"
+ android:layout_marginEnd="@dimen/recents_task_view_activity_icon_size"
android:textSize="24sp"
android:textColor="#ffffffff"
android:text="@string/recents_empty_message"
@@ -52,7 +67,7 @@
android:id="@+id/activity_icon"
android:layout_width="@dimen/recents_task_view_activity_icon_size"
android:layout_height="@dimen/recents_task_view_activity_icon_size"
- android:layout_gravity="center_vertical|right"
+ android:layout_gravity="center_vertical|end"
android:padding="12dp"
android:visibility="invisible" />
</com.android.systemui.recents.views.TaskBarView>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index e305d94..478c541 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -114,6 +114,8 @@
<integer name="recents_filter_animate_new_views_min_duration">125</integer>
<!-- The min animation duration for animating views that are newly visible. -->
<integer name="recents_animate_task_bar_enter_duration">200</integer>
+ <!-- The animation duration for animating in the info pane. -->
+ <integer name="recents_animate_task_view_info_pane_duration">150</integer>
<!-- The maximum count of notifications on Keyguard. The rest will be collapsed in an overflow
card. -->
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 5e7db8b..94d3541 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -239,6 +239,9 @@
<!-- The size of the activity icon in the recents task view. -->
<dimen name="recents_task_view_activity_icon_size">60dp</dimen>
+ <!-- The amount of space a user has to scroll to dismiss any info panes. -->
+ <dimen name="recents_task_stack_scroll_dismiss_info_pane_distance">50dp</dimen>
+
<!-- Used to calculate the translation animation duration, the expected amount of movement
in dps over one second of time. -->
<dimen name="recents_animation_movement_in_dps_per_second">800dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index d994a5b..73e5e19 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -509,6 +509,8 @@
<!-- Recents: The empty recents string. [CHAR LIMIT=NONE] -->
<string name="recents_empty_message">RECENTS</string>
+ <!-- Recents: The info panel app info button string. [CHAR LIMIT=NONE] -->
+ <string name="recents_app_info_button_label">Application Info</string>
<!-- Glyph to be overlaid atop the battery when the level is extremely low. Do not translate. -->
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Constants.java b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
index cde17f5e..64770a4 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Constants.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Constants.java
@@ -28,8 +28,9 @@
public static class App {
public static final boolean EnableTaskFiltering = true;
public static final boolean EnableTaskStackClipping = false;
- public static final boolean EnableToggleNewRecentsActivity = false;
- // This disables the bitmap and icon caches to
+ public static final boolean EnableInfoPane = true;
+
+ // This disables the bitmap and icon caches
public static final boolean DisableBackgroundCache = false;
// For debugging, this enables us to create mock recents tasks
public static final boolean EnableSystemServicesProxy = false;
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index f61c28c..f61c9f1 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -50,11 +50,7 @@
String action = intent.getAction();
Console.log(Constants.DebugFlags.App.SystemUIHandshake,
"[RecentsActivity|serviceBroadcast]", action, Console.AnsiRed);
- if (action.equals(RecentsService.ACTION_FINISH_RECENTS_ACTIVITY)) {
- if (Constants.DebugFlags.App.EnableToggleNewRecentsActivity) {
- finish();
- }
- } else if (action.equals(RecentsService.ACTION_TOGGLE_RECENTS_ACTIVITY)) {
+ if (action.equals(RecentsService.ACTION_TOGGLE_RECENTS_ACTIVITY)) {
// Try and unfilter and filtered stacks
if (!mRecentsView.unfilterFilteredStacks()) {
// If there are no filtered stacks, dismiss recents and launch the first task
@@ -190,7 +186,6 @@
// Register the broadcast receiver to handle messages from our service
IntentFilter filter = new IntentFilter();
filter.addAction(RecentsService.ACTION_TOGGLE_RECENTS_ACTIVITY);
- filter.addAction(RecentsService.ACTION_FINISH_RECENTS_ACTIVITY);
registerReceiver(mServiceBroadcastReceiver, filter);
// Register the broadcast receiver to handle messages when the screen is turned off
@@ -224,11 +219,6 @@
Console.AnsiRed);
super.onStop();
- // Finish the current recents activity after we have launched a task
- if (mTaskLaunched && Constants.DebugFlags.App.EnableToggleNewRecentsActivity) {
- finish();
- }
-
mVisible = false;
mTaskLaunched = false;
}
@@ -250,8 +240,18 @@
@Override
public void onBackPressed() {
- if (!mRecentsView.unfilterFilteredStacks()) {
- super.onBackPressed();
+ boolean interceptedByInfoPanelClose = false;
+
+ // Try and return from any open info panes
+ if (Constants.DebugFlags.App.EnableInfoPane) {
+ interceptedByInfoPanelClose = mRecentsView.closeOpenInfoPanes();
+ }
+
+ // If we haven't been intercepted already, then unfilter any stacks
+ if (!interceptedByInfoPanelClose) {
+ if (!mRecentsView.unfilterFilteredStacks()) {
+ super.onBackPressed();
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
index 8949663..9fdb5f9 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java
@@ -40,6 +40,8 @@
public int filteringCurrentViewsMinAnimDuration;
public int filteringNewViewsMinAnimDuration;
public int taskBarEnterAnimDuration;
+ public int taskStackScrollDismissInfoPaneDistance;
+ public int taskViewInfoPaneAnimDuration;
public boolean launchedWithThumbnailAnimation;
@@ -81,6 +83,10 @@
res.getInteger(R.integer.recents_filter_animate_new_views_min_duration);
taskBarEnterAnimDuration =
res.getInteger(R.integer.recents_animate_task_bar_enter_duration);
+ taskStackScrollDismissInfoPaneDistance = res.getDimensionPixelSize(
+ R.dimen.recents_task_stack_scroll_dismiss_info_pane_distance);
+ taskViewInfoPaneAnimDuration =
+ res.getInteger(R.integer.recents_animate_task_view_info_pane_duration);
}
/** Updates the system insets */
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsService.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsService.java
index f78a999..06ca9e2 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsService.java
@@ -112,7 +112,6 @@
/* Service */
public class RecentsService extends Service {
- final static String ACTION_FINISH_RECENTS_ACTIVITY = "action_finish_recents_activity";
final static String ACTION_TOGGLE_RECENTS_ACTIVITY = "action_toggle_recents_activity";
Messenger mSystemUIMessenger = new Messenger(new SystemUIMessageHandler(this));
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index ec28379..b054a22 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -17,13 +17,16 @@
package com.android.systemui.recents.views;
import android.app.ActivityOptions;
+import android.app.TaskStackBuilder;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Rect;
+import android.net.Uri;
import android.os.UserHandle;
+import android.provider.Settings;
import android.view.View;
import android.widget.FrameLayout;
import com.android.systemui.recents.Console;
@@ -179,6 +182,21 @@
return true;
}
+ /** Closes any open info panes */
+ public boolean closeOpenInfoPanes() {
+ if (mBSP != null) {
+ // Get the first stack view
+ int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ TaskStackView stackView = (TaskStackView) getChildAt(i);
+ if (stackView.closeOpenInfoPanes()) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
/** Unfilters any filtered stacks */
public boolean unfilterFilteredStacks() {
if (mBSP != null) {
@@ -206,6 +224,9 @@
mCb.onTaskLaunching();
}
+ // Close any open info panes
+ closeOpenInfoPanes();
+
final Runnable launchRunnable = new Runnable() {
@Override
public void run() {
@@ -283,4 +304,15 @@
tv.animateOnLeavingRecents(launchRunnable);
}
}
+
+ @Override
+ public void onTaskAppInfoLaunched(Task t) {
+ // Create a new task stack with the application info details activity
+ Intent baseIntent = t.key.baseIntent;
+ Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
+ Uri.fromParts("package", baseIntent.getComponent().getPackageName(), null));
+ intent.setComponent(intent.resolveActivity(getContext().getPackageManager()));
+ TaskStackBuilder.create(getContext())
+ .addNextIntentWithParentStack(intent).startActivities();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskInfoView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskInfoView.java
new file mode 100644
index 0000000..233e38c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskInfoView.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2014 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.recents.views;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Path;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.widget.Button;
+import android.widget.FrameLayout;
+import com.android.systemui.R;
+import com.android.systemui.recents.BakedBezierInterpolator;
+import com.android.systemui.recents.Utilities;
+
+
+/* The task info view */
+class TaskInfoView extends FrameLayout {
+
+ Button mAppInfoButton;
+
+ // Circular clip animation
+ boolean mCircularClipEnabled;
+ Path mClipPath = new Path();
+ float mClipRadius;
+ float mMaxClipRadius;
+ Point mClipOrigin = new Point();
+ ObjectAnimator mCircularClipAnimator;
+
+ public TaskInfoView(Context context) {
+ this(context, null);
+ }
+
+ public TaskInfoView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public TaskInfoView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public TaskInfoView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ // Initialize the buttons on the info panel
+ mAppInfoButton = (Button) findViewById(R.id.task_view_app_info_button);
+ }
+
+ /** Updates the positions of each of the items to fit in the rect specified */
+ void updateContents(Rect visibleRect) {
+ // Offset the app info button
+ LayoutParams lp = (LayoutParams) mAppInfoButton.getLayoutParams();
+ lp.topMargin = visibleRect.top +
+ (visibleRect.height() - mAppInfoButton.getMeasuredHeight()) / 2;
+ requestLayout();
+ }
+
+ /** Sets the circular clip radius on this panel */
+ public void setCircularClipRadius(float r) {
+ mClipRadius = r;
+ invalidate();
+ }
+
+ /** Gets the circular clip radius on this panel */
+ public float getCircularClipRadius() {
+ return mClipRadius;
+ }
+
+ /** Animates the circular clip radius on the icon */
+ void animateCircularClip(Point o, float fromRadius, float toRadius,
+ final Runnable postRunnable, boolean animateInContent) {
+ if (mCircularClipAnimator != null) {
+ mCircularClipAnimator.cancel();
+ }
+
+ // Calculate the max clip radius to each of the corners
+ int w = getMeasuredWidth() - o.x;
+ int h = getMeasuredHeight() - o.y;
+ // origin to tl, tr, br, bl
+ mMaxClipRadius = (int) Math.ceil(Math.sqrt(o.x * o.x + o.y * o.y));
+ mMaxClipRadius = (int) Math.max(mMaxClipRadius, Math.ceil(Math.sqrt(w * w + o.y * o.y)));
+ mMaxClipRadius = (int) Math.max(mMaxClipRadius, Math.ceil(Math.sqrt(w * w + h * h)));
+ mMaxClipRadius = (int) Math.max(mMaxClipRadius, Math.ceil(Math.sqrt(o.x * o.x + h * h)));
+
+ mClipOrigin.set(o.x, o.y);
+ mClipRadius = fromRadius;
+ int duration = Utilities.calculateTranslationAnimationDuration((int) mMaxClipRadius);
+ mCircularClipAnimator = ObjectAnimator.ofFloat(this, "circularClipRadius", toRadius);
+ mCircularClipAnimator.setDuration(duration);
+ mCircularClipAnimator.setInterpolator(BakedBezierInterpolator.INSTANCE);
+ mCircularClipAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mCircularClipEnabled = false;
+ if (postRunnable != null) {
+ postRunnable.run();
+ }
+ }
+ });
+ mCircularClipAnimator.start();
+ mCircularClipEnabled = true;
+
+ if (animateInContent) {
+ animateAppInfoButtonIn(duration);
+ }
+ }
+
+ /** Cancels the circular clip animation. */
+ void cancelCircularClipAnimation() {
+ if (mCircularClipAnimator != null) {
+ mCircularClipAnimator.cancel();
+ }
+ }
+
+ void animateAppInfoButtonIn(int duration) {
+ mAppInfoButton.setScaleX(0.75f);
+ mAppInfoButton.setScaleY(0.75f);
+ mAppInfoButton.animate()
+ .scaleX(1f)
+ .scaleY(1f)
+ .setDuration(duration)
+ .setInterpolator(BakedBezierInterpolator.INSTANCE)
+ .withLayer()
+ .start();
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ int saveCount = 0;
+ if (mCircularClipEnabled) {
+ saveCount = canvas.save(Canvas.CLIP_SAVE_FLAG);
+ mClipPath.reset();
+ mClipPath.addCircle(mClipOrigin.x, mClipOrigin.y, mClipRadius * mMaxClipRadius,
+ Path.Direction.CW);
+ canvas.clipPath(mClipPath);
+ }
+ super.draw(canvas);
+ if (mCircularClipEnabled) {
+ canvas.restoreToCount(saveCount);
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index 88fb972..033bd67 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -52,11 +52,12 @@
/* The visual representation of a task stack view */
public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCallbacks,
TaskView.TaskViewCallbacks, ViewPool.ViewPoolConsumer<TaskView, Task>,
- View.OnClickListener {
+ View.OnClickListener, View.OnLongClickListener {
/** The TaskView callbacks */
interface TaskStackViewCallbacks {
public void onTaskLaunched(TaskStackView stackView, TaskView tv, TaskStack stack, Task t);
+ public void onTaskAppInfoLaunched(Task t);
}
TaskStack mStack;
@@ -75,6 +76,7 @@
int mMinScroll;
int mMaxScroll;
int mStashedScroll;
+ int mLastInfoPaneStackScroll;
OverScroller mScroller;
ObjectAnimator mScrollAnimator;
@@ -281,6 +283,17 @@
public void setStackScroll(int value) {
mStackScroll = value;
requestSynchronizeStackViewsWithModel();
+
+ // Close any open info panes if the user has scrolled away from them
+ boolean isAnimatingScroll = (mScrollAnimator != null && mScrollAnimator.isRunning());
+ if (mLastInfoPaneStackScroll > -1 && !isAnimatingScroll) {
+ RecentsConfiguration config = RecentsConfiguration.getInstance();
+ if (Math.abs(mStackScroll - mLastInfoPaneStackScroll) >
+ config.taskStackScrollDismissInfoPaneDistance) {
+ // Close any open info panes
+ closeOpenInfoPanes();
+ }
+ }
}
/** Sets the current stack scroll without synchronizing the stack view with the model */
public void setStackScrollRaw(int value) {
@@ -300,19 +313,24 @@
// Enable hw layers on the stack
addHwLayersRefCount("animateBoundScroll");
- // Abort any current animations
- abortScroller();
- abortBoundScrollAnimation();
-
// Start a new scroll animation
- animateScroll(curScroll, newScroll);
- mScrollAnimator.start();
+ animateScroll(curScroll, newScroll, new Runnable() {
+ @Override
+ public void run() {
+ // Disable hw layers on the stack
+ decHwLayersRefCount("animateBoundScroll");
+ }
+ });
}
return mScrollAnimator;
}
/** Animates the stack scroll */
- void animateScroll(int curScroll, int newScroll) {
+ void animateScroll(int curScroll, int newScroll, final Runnable postRunnable) {
+ // Abort any current animations
+ abortScroller();
+ abortBoundScrollAnimation();
+
mScrollAnimator = ObjectAnimator.ofInt(this, "stackScroll", curScroll, newScroll);
mScrollAnimator.setDuration(Utilities.calculateTranslationAnimationDuration(newScroll -
curScroll, 250));
@@ -326,20 +344,23 @@
mScrollAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
- // Disable hw layers on the stack
- decHwLayersRefCount("animateBoundScroll");
+ if (postRunnable != null) {
+ postRunnable.run();
+ }
+ mScrollAnimator.removeAllListeners();
}
});
+ mScrollAnimator.start();
}
/** Aborts any current stack scrolls */
void abortBoundScrollAnimation() {
if (mScrollAnimator != null) {
mScrollAnimator.cancel();
- mScrollAnimator.removeAllListeners();
}
}
+ /** Aborts the scroller and any current fling */
void abortScroller() {
if (!mScroller.isFinished()) {
// Abort the scroller
@@ -407,6 +428,21 @@
}
}
+ /** Closes any open info panes. */
+ boolean closeOpenInfoPanes() {
+ if (!Constants.DebugFlags.App.EnableInfoPane) return false;
+
+ int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ TaskView tv = (TaskView) getChildAt(i);
+ if (tv.isInfoPaneVisible()) {
+ tv.hideInfoPane();
+ return true;
+ }
+ }
+ return false;
+ }
+
/** Enables the hw layers and increments the hw layer requirement ref count */
void addHwLayersRefCount(String reason) {
Console.log(Constants.DebugFlags.UI.HwLayers,
@@ -644,7 +680,6 @@
requestSynchronizeStackViewsWithModel(Utilities.calculateTranslationAnimationDuration(movement));
}
-
/**
* Creates the animations for all the children views that need to be removed or to move views
* to their un/filtered position when we are un/filtering a stack, and returns the duration
@@ -789,6 +824,9 @@
@Override
public void onStackFiltered(TaskStack newStack, final ArrayList<Task> curTasks,
Task filteredTask) {
+ // Close any open info panes
+ closeOpenInfoPanes();
+
// Stash the scroll and filtered task for us to restore to when we unfilter
mStashedScroll = getStackScroll();
@@ -813,6 +851,9 @@
@Override
public void onStackUnfiltered(TaskStack newStack, final ArrayList<Task> curTasks) {
+ // Close any open info panes
+ closeOpenInfoPanes();
+
// Calculate the current task transforms
final ArrayList<TaskViewTransform> curTaskTransforms =
getStackTransforms(curTasks, getStackScroll(), null, true);
@@ -892,6 +933,9 @@
// Set the callbacks and listeners for this new view
tv.setOnClickListener(this);
+ if (Constants.DebugFlags.App.EnableInfoPane) {
+ tv.setOnLongClickListener(this);
+ }
tv.setCallbacks(this);
} else {
attachViewToParent(tv, insertIndex, tv.getLayoutParams());
@@ -926,6 +970,24 @@
}
}
+ @Override
+ public void onTaskInfoPanelShown(TaskView tv) {
+ // Do nothing
+ }
+
+ @Override
+ public void onTaskInfoPanelHidden(TaskView tv) {
+ // Unset the saved scroll
+ mLastInfoPaneStackScroll = -1;
+ }
+
+ @Override
+ public void onTaskAppInfoClicked(TaskView tv) {
+ if (mCb != null) {
+ mCb.onTaskAppInfoLaunched(tv.getTask());
+ }
+ }
+
/**** View.OnClickListener Implementation ****/
@Override
@@ -935,10 +997,51 @@
Console.log(Constants.DebugFlags.UI.ClickEvents, "[TaskStack|Clicked|Thumbnail]",
task + " cb: " + mCb);
+ // Close any open info panes if the user taps on another task
+ if (closeOpenInfoPanes()) {
+ return;
+ }
+
if (mCb != null) {
mCb.onTaskLaunched(this, tv, mStack, task);
}
}
+
+ @Override
+ public boolean onLongClick(View v) {
+ if (!Constants.DebugFlags.App.EnableInfoPane) return false;
+
+ TaskView tv = (TaskView) v;
+
+ // Close any other task info panels if we launch another info pane
+ closeOpenInfoPanes();
+
+ // Scroll the task view so that it is maximally visible
+ float overlapHeight = Constants.Values.TaskStackView.StackOverlapPct * mTaskRect.height();
+ int taskIndex = mStack.indexOfTask(tv.getTask());
+ int curScroll = getStackScroll();
+ int newScroll = (int) Math.max(mMinScroll, Math.min(mMaxScroll, taskIndex * overlapHeight));
+ TaskViewTransform transform = getStackTransform(taskIndex, curScroll);
+ Rect nonOverlapRect = new Rect(transform.rect);
+ if (taskIndex < (mStack.getTaskCount() - 1)) {
+ nonOverlapRect.bottom = nonOverlapRect.top + (int) overlapHeight;
+ }
+
+ // XXX: Use HW Layers
+ if (transform.t < 0f) {
+ animateScroll(curScroll, newScroll, null);
+ } else if (nonOverlapRect.bottom > mStackRectSansPeek.bottom) {
+ // Check if we are out of bounds, if so, just scroll it in such that the bottom of the
+ // task view is visible
+ newScroll = curScroll - (mStackRectSansPeek.bottom - nonOverlapRect.bottom);
+ animateScroll(curScroll, newScroll, null);
+ }
+ mLastInfoPaneStackScroll = newScroll;
+
+ // Show the info pane for this task view
+ tv.showInfoPane(new Rect(0, 0, 0, (int) overlapHeight));
+ return true;
+ }
}
/* Handles touch events */
@@ -1153,9 +1256,10 @@
int activePointerIndex = ev.findPointerIndex(mActivePointerId);
int x = (int) ev.getX(activePointerIndex);
int y = (int) ev.getY(activePointerIndex);
+ int yTotal = Math.abs(y - mInitialMotionY);
int deltaY = mLastMotionY - y;
if (!mIsScrolling) {
- if (Math.abs(y - mInitialMotionY) > mScrollTouchSlop) {
+ if (yTotal > mScrollTouchSlop) {
mIsScrolling = true;
// Initialize the velocity tracker
initOrResetVelocityTracker();
@@ -1277,6 +1381,13 @@
if (parent != null) {
parent.requestDisallowInterceptTouchEvent(true);
}
+ // If the info panel is currently showing on this view, then we need to dismiss it
+ if (Constants.DebugFlags.App.EnableInfoPane) {
+ TaskView tv = (TaskView) v;
+ if (tv.isInfoPaneVisible()) {
+ tv.hideInfoPane();
+ }
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
index b4100127..81805d5 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java
@@ -17,8 +17,10 @@
package com.android.systemui.recents.views;
import android.content.Context;
+import android.graphics.Point;
import android.graphics.Rect;
import android.util.AttributeSet;
+import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;
import com.android.systemui.R;
@@ -29,18 +31,26 @@
/* A task view */
-public class TaskView extends FrameLayout implements View.OnClickListener, Task.TaskCallbacks {
+public class TaskView extends FrameLayout implements View.OnClickListener,
+ Task.TaskCallbacks {
/** The TaskView callbacks */
interface TaskViewCallbacks {
public void onTaskIconClicked(TaskView tv);
+ public void onTaskInfoPanelShown(TaskView tv);
+ public void onTaskInfoPanelHidden(TaskView tv);
+ public void onTaskAppInfoClicked(TaskView tv);
+
// public void onTaskViewReboundToTask(TaskView tv, Task t);
}
Task mTask;
boolean mTaskDataLoaded;
+ boolean mTaskInfoPaneVisible;
+ Point mLastTouchDown = new Point();
TaskThumbnailView mThumbnailView;
TaskBarView mBarView;
+ TaskInfoView mInfoView;
TaskViewCallbacks mCb;
@@ -65,12 +75,24 @@
// Bind the views
mThumbnailView = (TaskThumbnailView) findViewById(R.id.task_view_thumbnail);
mBarView = (TaskBarView) findViewById(R.id.task_view_bar);
- mBarView.mApplicationIcon.setOnClickListener(this);
+ mInfoView = (TaskInfoView) findViewById(R.id.task_view_info_pane);
+
if (mTaskDataLoaded) {
onTaskDataLoaded(false);
}
}
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ switch (ev.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ case MotionEvent.ACTION_MOVE:
+ mLastTouchDown.set((int) ev.getX(), (int) ev.getY());
+ break;
+ }
+ return super.onInterceptTouchEvent(ev);
+ }
+
/** Set callback */
void setCallbacks(TaskViewCallbacks cb) {
mCb = cb;
@@ -189,6 +211,63 @@
return outRect;
}
+ /** Returns whether this task has an info pane visible */
+ boolean isInfoPaneVisible() {
+ return mTaskInfoPaneVisible;
+ }
+
+ /** Shows the info pane if it is not visible. */
+ void showInfoPane(Rect taskVisibleRect) {
+ if (mTaskInfoPaneVisible) return;
+
+ // Remove the bar view from the visible rect and update the info pane contents
+ taskVisibleRect.top += mBarView.getMeasuredHeight();
+ mInfoView.updateContents(taskVisibleRect);
+
+ // Show the info pane and animate it into view
+ mInfoView.setVisibility(View.VISIBLE);
+ mInfoView.animateCircularClip(mLastTouchDown, 0f, 1f, null, true);
+ mInfoView.setOnClickListener(this);
+ mTaskInfoPaneVisible = true;
+
+ // Notify any callbacks
+ if (mCb != null) {
+ mCb.onTaskInfoPanelShown(this);
+ }
+ }
+
+ /** Hides the info pane if it is visible. */
+ void hideInfoPane() {
+ if (!mTaskInfoPaneVisible) return;
+ RecentsConfiguration config = RecentsConfiguration.getInstance();
+
+ // Cancel any circular clip animation
+ mInfoView.cancelCircularClipAnimation();
+
+ // Animate the info pane out
+ mInfoView.animate()
+ .alpha(0f)
+ .setDuration(config.taskViewInfoPaneAnimDuration)
+ .setInterpolator(BakedBezierInterpolator.INSTANCE)
+ .withLayer()
+ .withEndAction(new Runnable() {
+ @Override
+ public void run() {
+ mInfoView.setVisibility(View.INVISIBLE);
+ mInfoView.setOnClickListener(null);
+
+ mInfoView.setAlpha(1f);
+ }
+ })
+ .start();
+ mTaskInfoPaneVisible = false;
+
+ // Notify any callbacks
+ if (mCb != null) {
+ mCb.onTaskInfoPanelHidden(this);
+ }
+ }
+
/** Enable the hw layers on this task view */
void enableHwLayers() {
mThumbnailView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
@@ -209,27 +288,39 @@
@Override
public void onTaskDataLoaded(boolean reloadingTaskData) {
- if (mThumbnailView != null && mBarView != null) {
+ if (mThumbnailView != null && mBarView != null && mInfoView != null) {
// Bind each of the views to the new task data
mThumbnailView.rebindToTask(mTask, reloadingTaskData);
mBarView.rebindToTask(mTask, reloadingTaskData);
+ // Rebind any listeners
+ mBarView.mApplicationIcon.setOnClickListener(this);
+ mInfoView.mAppInfoButton.setOnClickListener(this);
}
mTaskDataLoaded = true;
}
@Override
public void onTaskDataUnloaded() {
- if (mThumbnailView != null && mBarView != null) {
+ if (mThumbnailView != null && mBarView != null && mInfoView != null) {
// Unbind each of the views from the task data and remove the task callback
mTask.setCallbacks(null);
mThumbnailView.unbindFromTask();
mBarView.unbindFromTask();
+ // Unbind any listeners
+ mBarView.mApplicationIcon.setOnClickListener(null);
+ mInfoView.mAppInfoButton.setOnClickListener(null);
}
mTaskDataLoaded = false;
}
@Override
public void onClick(View v) {
- mCb.onTaskIconClicked(this);
+ if (v == mInfoView) {
+ // Do nothing
+ } else if (v == mBarView.mApplicationIcon) {
+ mCb.onTaskIconClicked(this);
+ } else if (v == mInfoView.mAppInfoButton) {
+ mCb.onTaskAppInfoClicked(this);
+ }
}
}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/NsdService.java b/services/core/java/com/android/server/NsdService.java
index 1ed943c..7b3b65b 100644
--- a/services/core/java/com/android/server/NsdService.java
+++ b/services/core/java/com/android/server/NsdService.java
@@ -35,8 +35,12 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
import java.util.concurrent.CountDownLatch;
import com.android.internal.util.AsyncChannel;
@@ -433,14 +437,14 @@
case NativeResponseCode.SERVICE_FOUND:
/* NNN uniqueId serviceName regType domain */
if (DBG) Slog.d(TAG, "SERVICE_FOUND Raw: " + raw);
- servInfo = new NsdServiceInfo(cooked[2], cooked[3], null);
+ servInfo = new NsdServiceInfo(cooked[2], cooked[3]);
clientInfo.mChannel.sendMessage(NsdManager.SERVICE_FOUND, 0,
clientId, servInfo);
break;
case NativeResponseCode.SERVICE_LOST:
/* NNN uniqueId serviceName regType domain */
if (DBG) Slog.d(TAG, "SERVICE_LOST Raw: " + raw);
- servInfo = new NsdServiceInfo(cooked[2], cooked[3], null);
+ servInfo = new NsdServiceInfo(cooked[2], cooked[3]);
clientInfo.mChannel.sendMessage(NsdManager.SERVICE_LOST, 0,
clientId, servInfo);
break;
@@ -453,7 +457,7 @@
case NativeResponseCode.SERVICE_REGISTERED:
/* NNN regId serviceName regType */
if (DBG) Slog.d(TAG, "SERVICE_REGISTERED Raw: " + raw);
- servInfo = new NsdServiceInfo(cooked[2], null, null);
+ servInfo = new NsdServiceInfo(cooked[2], null);
clientInfo.mChannel.sendMessage(NsdManager.REGISTER_SERVICE_SUCCEEDED,
id, clientId, servInfo);
break;
@@ -673,9 +677,22 @@
private boolean registerService(int regId, NsdServiceInfo service) {
if (DBG) Slog.d(TAG, "registerService: " + regId + " " + service);
try {
- //Add txtlen and txtdata
- mNativeConnector.execute("mdnssd", "register", regId, service.getServiceName(),
+ Command cmd = new Command("mdnssd", "register", regId, service.getServiceName(),
service.getServiceType(), service.getPort());
+
+ // Add TXT records as additional arguments.
+ Map<String, byte[]> txtRecords = service.getAttributes();
+ for (String key : txtRecords.keySet()) {
+ try {
+ // TODO: Send encoded TXT record as bytes once NDC/netd supports binary data.
+ cmd.appendArg(String.format(Locale.US, "%s=%s", key,
+ new String(txtRecords.get(key), "UTF_8")));
+ } catch (UnsupportedEncodingException e) {
+ Slog.e(TAG, "Failed to encode txtRecord " + e);
+ }
+ }
+
+ mNativeConnector.execute(cmd);
} catch(NativeDaemonConnectorException e) {
Slog.e(TAG, "Failed to execute registerService " + e);
return false;
diff --git a/tests/CoreTests/android/core/NsdServiceInfoTest.java b/tests/CoreTests/android/core/NsdServiceInfoTest.java
new file mode 100644
index 0000000..5bf0167
--- /dev/null
+++ b/tests/CoreTests/android/core/NsdServiceInfoTest.java
@@ -0,0 +1,163 @@
+package android.core;
+
+import android.test.AndroidTestCase;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.StrictMode;
+import android.net.nsd.NsdServiceInfo;
+import android.util.Log;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+
+public class NsdServiceInfoTest extends AndroidTestCase {
+
+ public final static InetAddress LOCALHOST;
+ static {
+ // Because test.
+ StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
+ StrictMode.setThreadPolicy(policy);
+
+ InetAddress _host = null;
+ try {
+ _host = InetAddress.getLocalHost();
+ } catch (UnknownHostException e) { }
+ LOCALHOST = _host;
+ }
+
+ public void testLimits() throws Exception {
+ NsdServiceInfo info = new NsdServiceInfo();
+
+ // Non-ASCII keys.
+ boolean exceptionThrown = false;
+ try {
+ info.setAttribute("猫", "meow");
+ } catch (IllegalArgumentException e) {
+ exceptionThrown = true;
+ }
+ assertTrue(exceptionThrown);
+ assertEmptyServiceInfo(info);
+
+ // ASCII keys with '=' character.
+ exceptionThrown = false;
+ try {
+ info.setAttribute("kitten=", "meow");
+ } catch (IllegalArgumentException e) {
+ exceptionThrown = true;
+ }
+ assertTrue(exceptionThrown);
+ assertEmptyServiceInfo(info);
+
+ // Single key + value length too long.
+ exceptionThrown = false;
+ try {
+ String longValue = "loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo" +
+ "oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo" +
+ "oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo" +
+ "ooooooooooooooooooooooooooooong"; // 248 characters.
+ info.setAttribute("longcat", longValue); // Key + value == 255 characters.
+ } catch (IllegalArgumentException e) {
+ exceptionThrown = true;
+ }
+ assertTrue(exceptionThrown);
+ assertEmptyServiceInfo(info);
+
+ // Total TXT record length too long.
+ exceptionThrown = false;
+ int recordsAdded = 0;
+ try {
+ for (int i = 100; i < 300; ++i) {
+ // 6 char key + 5 char value + 2 bytes overhead = 13 byte record length.
+ String key = String.format("key%d", i);
+ info.setAttribute(key, "12345");
+ recordsAdded++;
+ }
+ } catch (IllegalArgumentException e) {
+ exceptionThrown = true;
+ }
+ assertTrue(exceptionThrown);
+ assertTrue(100 == recordsAdded);
+ assertTrue(info.getTxtRecord().length == 1300);
+ }
+
+ public void testParcel() throws Exception {
+ NsdServiceInfo emptyInfo = new NsdServiceInfo();
+ checkParcelable(emptyInfo);
+
+ NsdServiceInfo fullInfo = new NsdServiceInfo();
+ fullInfo.setServiceName("kitten");
+ fullInfo.setServiceType("_kitten._tcp");
+ fullInfo.setPort(4242);
+ fullInfo.setHost(LOCALHOST);
+ checkParcelable(fullInfo);
+
+ NsdServiceInfo noHostInfo = new NsdServiceInfo();
+ noHostInfo.setServiceName("kitten");
+ noHostInfo.setServiceType("_kitten._tcp");
+ noHostInfo.setPort(4242);
+ checkParcelable(noHostInfo);
+
+ NsdServiceInfo attributedInfo = new NsdServiceInfo();
+ attributedInfo.setServiceName("kitten");
+ attributedInfo.setServiceType("_kitten._tcp");
+ attributedInfo.setPort(4242);
+ attributedInfo.setHost(LOCALHOST);
+ attributedInfo.setAttribute("color", "pink");
+ attributedInfo.setAttribute("sound", (new String("にゃあ")).getBytes("UTF-8"));
+ attributedInfo.setAttribute("adorable", (String) null);
+ attributedInfo.setAttribute("sticky", "yes");
+ attributedInfo.setAttribute("siblings", new byte[] {});
+ attributedInfo.setAttribute("edge cases", new byte[] {0, -1, 127, -128});
+ attributedInfo.removeAttribute("sticky");
+ checkParcelable(attributedInfo);
+
+ // Sanity check that we actually wrote attributes to attributedInfo.
+ assertTrue(attributedInfo.getAttributes().keySet().contains("adorable"));
+ String sound = new String(attributedInfo.getAttributes().get("sound"), "UTF-8");
+ assertTrue(sound.equals("にゃあ"));
+ byte[] edgeCases = attributedInfo.getAttributes().get("edge cases");
+ assertTrue(Arrays.equals(edgeCases, new byte[] {0, -1, 127, -128}));
+ assertFalse(attributedInfo.getAttributes().keySet().contains("sticky"));
+ }
+
+ public void checkParcelable(NsdServiceInfo original) {
+ // Write to parcel.
+ Parcel p = Parcel.obtain();
+ Bundle writer = new Bundle();
+ writer.putParcelable("test_info", original);
+ writer.writeToParcel(p, 0);
+
+ // Extract from parcel.
+ p.setDataPosition(0);
+ Bundle reader = p.readBundle();
+ reader.setClassLoader(NsdServiceInfo.class.getClassLoader());
+ NsdServiceInfo result = reader.getParcelable("test_info");
+
+ // Assert equality of base fields.
+ assertEquality(original.getServiceName(), result.getServiceName());
+ assertEquality(original.getServiceType(), result.getServiceType());
+ assertEquality(original.getHost(), result.getHost());
+ assertTrue(original.getPort() == result.getPort());
+
+ // Assert equality of attribute map.
+ Map<String, byte[]> originalMap = original.getAttributes();
+ Map<String, byte[]> resultMap = result.getAttributes();
+ assertEquality(originalMap.keySet(), resultMap.keySet());
+ for (String key : originalMap.keySet()) {
+ assertTrue(Arrays.equals(originalMap.get(key), resultMap.get(key)));
+ }
+ }
+
+ public void assertEquality(Object expected, Object result) {
+ assertTrue(expected == result || expected.equals(result));
+ }
+
+ public void assertEmptyServiceInfo(NsdServiceInfo shouldBeEmpty) {
+ assertTrue(null == shouldBeEmpty.getTxtRecord());
+ }
+}
diff --git a/tools/layoutlib/bridge/resources/bars/action_bar.xml b/tools/layoutlib/bridge/resources/bars/action_bar.xml
deleted file mode 100644
index 7adc5af..0000000
--- a/tools/layoutlib/bridge/resources/bars/action_bar.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<merge xmlns:android="http://schemas.android.com/apk/res/android">
- <include layout="@android:layout/action_bar_home" />
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"/>
-</merge>
diff --git a/tools/layoutlib/bridge/src/com/android/internal/widget/ActionBarAccessor.java b/tools/layoutlib/bridge/src/com/android/internal/widget/ActionBarAccessor.java
new file mode 100644
index 0000000..40b6220
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/internal/widget/ActionBarAccessor.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2014 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.internal.widget;
+
+import android.widget.ActionMenuPresenter;
+
+/**
+ * To access non public members of AbsActionBarView
+ */
+public class ActionBarAccessor {
+
+ /**
+ * Returns the {@link ActionMenuPresenter} associated with the {@link AbsActionBarView}
+ */
+ public static ActionMenuPresenter getActionMenuPresenter(AbsActionBarView view) {
+ return view.mActionMenuPresenter;
+ }
+}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
index ab4be71..fa8050f 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
@@ -215,7 +215,8 @@
Capability.ADAPTER_BINDING,
Capability.EXTENDED_VIEWINFO,
Capability.FIXED_SCALABLE_NINE_PATCH,
- Capability.RTL);
+ Capability.RTL,
+ Capability.ACTION_BAR);
BridgeAssetManager.initSystem();
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java
index f9f4b3a..e0f87fd 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java
@@ -64,6 +64,11 @@
}
@Override
+ public List<ViewInfo> getSystemRootViews() {
+ return mSession.getSystemViewInfos();
+ }
+
+ @Override
public Map<String, String> getDefaultProperties(Object viewObject) {
return mSession.getDefaultProperties(viewObject);
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
index 9ee2f60..6595ce1 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
@@ -613,7 +613,8 @@
}
if (value != null) {
- if (value.getFirst() == ResourceType.STYLE) {
+ if ((value.getFirst() == ResourceType.STYLE)
+ || (value.getFirst() == ResourceType.ATTR)) {
// look for the style in the current theme, and its parent:
ResourceValue item = mRenderResources.findItemInTheme(value.getSecond(),
isFrameworkRes);
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/ActionBarLayout.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/ActionBarLayout.java
new file mode 100644
index 0000000..49027c6
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/ActionBarLayout.java
@@ -0,0 +1,354 @@
+/*
+ * Copyright (C) 2014 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.layoutlib.bridge.bars;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+import com.android.ide.common.rendering.api.ActionBarCallback;
+import com.android.ide.common.rendering.api.ActionBarCallback.HomeButtonStyle;
+import com.android.ide.common.rendering.api.RenderResources;
+import com.android.ide.common.rendering.api.ResourceValue;
+import com.android.ide.common.rendering.api.SessionParams;
+import com.android.internal.R;
+import com.android.internal.app.WindowDecorActionBar;
+import com.android.internal.view.menu.MenuBuilder;
+import com.android.internal.view.menu.MenuItemImpl;
+import com.android.internal.widget.ActionBarAccessor;
+import com.android.internal.widget.ActionBarContainer;
+import com.android.internal.widget.ActionBarView;
+import com.android.layoutlib.bridge.android.BridgeContext;
+import com.android.layoutlib.bridge.impl.ResourceHelper;
+import com.android.resources.ResourceType;
+
+import android.app.ActionBar;
+import android.app.ActionBar.Tab;
+import android.app.ActionBar.TabListener;
+import android.app.FragmentTransaction;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
+import android.util.DisplayMetrics;
+import android.util.TypedValue;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.MenuInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+import android.widget.RelativeLayout;
+
+import java.util.ArrayList;
+
+/**
+ * A layout representing the action bar.
+ */
+public class ActionBarLayout extends LinearLayout {
+
+ // Store another reference to the context so that we don't have to cast it repeatedly.
+ @NonNull private final BridgeContext mBridgeContext;
+ @NonNull private final Context mThemedContext;
+
+ @NonNull private final ActionBar mActionBar;
+
+ // Data for Action Bar.
+ @Nullable private final String mIcon;
+ @Nullable private final String mTitle;
+ @Nullable private final String mSubTitle;
+ private final boolean mSplit;
+ private final boolean mShowHomeAsUp;
+ private final int mNavMode;
+
+ // Helper fields.
+ @NonNull private final MenuBuilder mMenuBuilder;
+ private final int mPopupMaxWidth;
+ @NonNull private final RenderResources res;
+ @Nullable private final ActionBarView mActionBarView;
+ @Nullable private FrameLayout mContentRoot;
+ @NonNull private final ActionBarCallback mCallback;
+
+ // A fake parent for measuring views.
+ @Nullable private ViewGroup mMeasureParent;
+
+ public ActionBarLayout(@NonNull BridgeContext context, @NonNull SessionParams params) {
+
+ super(context);
+ setOrientation(LinearLayout.HORIZONTAL);
+ setGravity(Gravity.CENTER_VERTICAL);
+
+ // Inflate action bar layout.
+ LayoutInflater.from(context).inflate(R.layout.screen_action_bar, this,
+ true /*attachToRoot*/);
+ mActionBar = new WindowDecorActionBar(this);
+
+ // Set contexts.
+ mBridgeContext = context;
+ mThemedContext = mActionBar.getThemedContext();
+
+ // Set data for action bar.
+ mCallback = params.getProjectCallback().getActionBarCallback();
+ mIcon = params.getAppIcon();
+ mTitle = params.getAppLabel();
+ // Split Action Bar when the screen size is narrow and the application requests split action
+ // bar when narrow.
+ mSplit = context.getResources().getBoolean(R.bool.split_action_bar_is_narrow) &&
+ mCallback.getSplitActionBarWhenNarrow();
+ mNavMode = mCallback.getNavigationMode();
+ // TODO: Support Navigation Drawer Indicator.
+ mShowHomeAsUp = mCallback.getHomeButtonStyle() == HomeButtonStyle.SHOW_HOME_AS_UP;
+ mSubTitle = mCallback.getSubTitle();
+
+
+ // Set helper fields.
+ mMenuBuilder = new MenuBuilder(mThemedContext);
+ res = mBridgeContext.getRenderResources();
+ mPopupMaxWidth = Math.max(mBridgeContext.getMetrics().widthPixels / 2,
+ mThemedContext.getResources().getDimensionPixelSize(
+ R.dimen.config_prefDialogWidth));
+ mActionBarView = (ActionBarView) findViewById(R.id.action_bar);
+ mContentRoot = (FrameLayout) findViewById(android.R.id.content);
+
+ setupActionBar();
+ }
+
+ /**
+ * Sets up the action bar by filling the appropriate data.
+ */
+ private void setupActionBar() {
+ // Add title and sub title.
+ ResourceValue titleValue = res.findResValue(mTitle, false /*isFramework*/);
+ if (titleValue != null && titleValue.getValue() != null) {
+ mActionBar.setTitle(titleValue.getValue());
+ } else {
+ mActionBar.setTitle(mTitle);
+ }
+ if (mSubTitle != null) {
+ mActionBar.setSubtitle(mSubTitle);
+ }
+
+ // Add show home as up icon.
+ if (mShowHomeAsUp) {
+ mActionBar.setDisplayOptions(0xFF, ActionBar.DISPLAY_HOME_AS_UP);
+ }
+
+ // Set the navigation mode.
+ mActionBar.setNavigationMode(mNavMode);
+ if (mNavMode == ActionBar.NAVIGATION_MODE_TABS) {
+ setupTabs(3);
+ }
+
+ if (mActionBarView != null) {
+ // If the action bar style doesn't specify an icon, set the icon obtained from the session
+ // params.
+ if (!mActionBarView.hasIcon() && mIcon != null) {
+ Drawable iconDrawable = getDrawable(mIcon, false /*isFramework*/);
+ if (iconDrawable != null) {
+ mActionBar.setIcon(iconDrawable);
+ }
+ }
+
+ // Set action bar to be split, if needed.
+ mActionBarView.setSplitView((ActionBarContainer) findViewById(R.id.split_action_bar));
+ mActionBarView.setSplitActionBar(mSplit);
+
+ inflateMenus();
+ }
+ }
+
+ /**
+ * Gets the menus to add to the action bar from the callback, resolves them, inflates them and
+ * adds them to the action bar.
+ */
+ private void inflateMenus() {
+ if (mActionBarView == null) {
+ return;
+ }
+ final MenuInflater inflater = new MenuInflater(mThemedContext);
+ for (String name : mCallback.getMenuIdNames()) {
+ if (mBridgeContext.getRenderResources().getProjectResource(ResourceType.MENU, name)
+ != null) {
+ int id = mBridgeContext.getProjectResourceValue(ResourceType.MENU, name, -1);
+ if (id > -1) {
+ inflater.inflate(id, mMenuBuilder);
+ }
+ }
+ }
+ mActionBarView.setMenu(mMenuBuilder, null /*callback*/);
+ }
+
+ // TODO: Use an adapter, like List View to set up tabs.
+ private void setupTabs(int num) {
+ for (int i = 1; i <= num; i++) {
+ Tab tab = mActionBar.newTab().setText("Tab" + i).setTabListener(new TabListener() {
+ @Override
+ public void onTabUnselected(Tab t, FragmentTransaction ft) {
+ // pass
+ }
+ @Override
+ public void onTabSelected(Tab t, FragmentTransaction ft) {
+ // pass
+ }
+ @Override
+ public void onTabReselected(Tab t, FragmentTransaction ft) {
+ // pass
+ }
+ });
+ mActionBar.addTab(tab);
+ }
+ }
+
+ @Nullable
+ private Drawable getDrawable(@NonNull String name, boolean isFramework) {
+ ResourceValue value = res.findResValue(name, isFramework);
+ value = res.resolveResValue(value);
+ if (value != null) {
+ return ResourceHelper.getDrawable(value, mBridgeContext);
+ }
+ return null;
+ }
+
+ /**
+ * Creates a Popup and adds it to the content frame. It also adds another {@link FrameLayout} to
+ * the content frame which shall serve as the new content root.
+ */
+ public void createMenuPopup() {
+ assert mContentRoot != null && findViewById(android.R.id.content) == mContentRoot
+ : "Action Bar Menus have already been created.";
+
+ if (!isOverflowPopupNeeded()) {
+ return;
+ }
+
+ // Create a layout to hold the menus and the user's content.
+ RelativeLayout layout = new RelativeLayout(mThemedContext);
+ layout.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,
+ LayoutParams.MATCH_PARENT));
+ mContentRoot.addView(layout);
+ // Create a layout for the user's content.
+ FrameLayout contentRoot = new FrameLayout(mBridgeContext);
+ contentRoot.setLayoutParams(new LayoutParams(
+ LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
+ // Add contentRoot and menus to the layout.
+ layout.addView(contentRoot);
+ layout.addView(createMenuView());
+ // ContentRoot is now the view we just created.
+ mContentRoot = contentRoot;
+ }
+
+ /**
+ * Returns a {@link LinearLayout} containing the menu list view to be embedded in a
+ * {@link RelativeLayout}
+ */
+ @NonNull
+ private View createMenuView() {
+ DisplayMetrics metrics = mBridgeContext.getMetrics();
+ OverflowMenuAdapter adapter = new OverflowMenuAdapter(mMenuBuilder, mThemedContext);
+
+ LinearLayout layout = new LinearLayout(mThemedContext);
+ RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(
+ measureContentWidth(adapter), LayoutParams.WRAP_CONTENT);
+ layoutParams.addRule(RelativeLayout.ALIGN_PARENT_END);
+ if (mSplit) {
+ layoutParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
+ // TODO: Find correct value instead of hardcoded 10dp.
+ layoutParams.bottomMargin = getPixelValue("-10dp", metrics);
+ } else {
+ layoutParams.topMargin = getPixelValue("-10dp", metrics);
+ }
+ layout.setLayoutParams(layoutParams);
+ final TypedArray a = mThemedContext.obtainStyledAttributes(null,
+ R.styleable.PopupWindow, R.attr.popupMenuStyle, 0);
+ layout.setBackground(a.getDrawable(R.styleable.PopupWindow_popupBackground));
+ layout.setDividerDrawable(a.getDrawable(R.attr.actionBarDivider));
+ a.recycle();
+ layout.setOrientation(LinearLayout.VERTICAL);
+ layout.setDividerPadding(getPixelValue("12dp", metrics));
+ layout.setShowDividers(LinearLayout.SHOW_DIVIDER_MIDDLE);
+
+ ListView listView = new ListView(mThemedContext, null, R.attr.dropDownListViewStyle);
+ listView.setAdapter(adapter);
+ layout.addView(listView);
+ return layout;
+ }
+
+ private boolean isOverflowPopupNeeded() {
+ boolean needed = mCallback.isOverflowPopupNeeded();
+ if (!needed) {
+ return false;
+ }
+ // Copied from android.widget.ActionMenuPresenter.updateMenuView()
+ ArrayList<MenuItemImpl> menus = mMenuBuilder.getNonActionItems();
+ if (ActionBarAccessor.getActionMenuPresenter(mActionBarView).isOverflowReserved() &&
+ menus != null) {
+ final int count = menus.size();
+ if (count == 1) {
+ needed = !menus.get(0).isActionViewExpanded();
+ } else {
+ needed = count > 0;
+ }
+ }
+ return needed;
+ }
+
+ @Nullable
+ public FrameLayout getContentRoot() {
+ return mContentRoot;
+ }
+
+ // Copied from com.android.internal.view.menu.MenuPopHelper.measureContentWidth()
+ private int measureContentWidth(@NonNull ListAdapter adapter) {
+ // Menus don't tend to be long, so this is more sane than it looks.
+ int maxWidth = 0;
+ View itemView = null;
+ int itemType = 0;
+
+ final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+ final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+ final int count = adapter.getCount();
+ for (int i = 0; i < count; i++) {
+ final int positionType = adapter.getItemViewType(i);
+ if (positionType != itemType) {
+ itemType = positionType;
+ itemView = null;
+ }
+
+ if (mMeasureParent == null) {
+ mMeasureParent = new FrameLayout(mThemedContext);
+ }
+
+ itemView = adapter.getView(i, itemView, mMeasureParent);
+ itemView.measure(widthMeasureSpec, heightMeasureSpec);
+
+ final int itemWidth = itemView.getMeasuredWidth();
+ if (itemWidth >= mPopupMaxWidth) {
+ return mPopupMaxWidth;
+ } else if (itemWidth > maxWidth) {
+ maxWidth = itemWidth;
+ }
+ }
+
+ return maxWidth;
+ }
+
+ private int getPixelValue(@NonNull String value, @NonNull DisplayMetrics metrics) {
+ TypedValue typedValue = ResourceHelper.getValue(null, value, false /*requireUnit*/);
+ return (int) typedValue.getDimension(metrics);
+ }
+
+}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/FakeActionBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/FakeActionBar.java
deleted file mode 100644
index 226649d..0000000
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/FakeActionBar.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2011 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.layoutlib.bridge.bars;
-
-import com.android.resources.Density;
-
-import org.xmlpull.v1.XmlPullParserException;
-
-import android.content.Context;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-public class FakeActionBar extends CustomBar {
-
- private TextView mTextView;
-
- public FakeActionBar(Context context, Density density, String label, String icon)
- throws XmlPullParserException {
- super(context, density, LinearLayout.HORIZONTAL, "/bars/action_bar.xml", "action_bar.xml");
-
- // Cannot access the inside items through id because no R.id values have been
- // created for them.
- // We do know the order though.
- loadIconById(android.R.id.home, icon);
- mTextView = setText(1, label);
-
- setStyle("actionBarStyle");
- }
-
- @Override
- protected TextView getStyleableTextView() {
- return mTextView;
- }
-}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/OverflowMenuAdapter.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/OverflowMenuAdapter.java
new file mode 100644
index 0000000..778305d
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/OverflowMenuAdapter.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2014 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.layoutlib.bridge.bars;
+
+import com.android.internal.view.menu.MenuBuilder;
+import com.android.internal.view.menu.MenuItemImpl;
+import com.android.internal.view.menu.MenuView;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+
+import java.util.ArrayList;
+
+/**
+ * Provides an adapter for Overflow menu popup. This is very similar to
+ * {@code MenuPopupHelper.MenuAdapter}
+ */
+public class OverflowMenuAdapter extends BaseAdapter {
+
+ private final MenuBuilder mMenu;
+ private int mExpandedIndex = -1;
+ private final Context context;
+
+ public OverflowMenuAdapter(MenuBuilder menu, Context context) {
+ mMenu = menu;
+ findExpandedIndex();
+ this.context = context;
+ }
+
+ @Override
+ public int getCount() {
+ ArrayList<MenuItemImpl> items = mMenu.getNonActionItems();
+ if (mExpandedIndex < 0) {
+ return items.size();
+ }
+ return items.size() - 1;
+ }
+
+ @Override
+ public MenuItemImpl getItem(int position) {
+ ArrayList<MenuItemImpl> items = mMenu.getNonActionItems();
+ if (mExpandedIndex >= 0 && position >= mExpandedIndex) {
+ position++;
+ }
+ return items.get(position);
+ }
+
+ @Override
+ public long getItemId(int position) {
+ // Since a menu item's ID is optional, we'll use the position as an
+ // ID for the item in the AdapterView
+ return position;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ if (convertView == null) {
+ LayoutInflater mInflater = LayoutInflater.from(context);
+ convertView = mInflater.inflate(com.android.internal.R.layout.popup_menu_item_layout,
+ parent, false);
+ }
+
+ MenuView.ItemView itemView = (MenuView.ItemView) convertView;
+ itemView.initialize(getItem(position), 0);
+ return convertView;
+ }
+
+ private void findExpandedIndex() {
+ final MenuItemImpl expandedItem = mMenu.getExpandedItem();
+ if (expandedItem != null) {
+ final ArrayList<MenuItemImpl> items = mMenu.getNonActionItems();
+ final int count = items.size();
+ for (int i = 0; i < count; i++) {
+ final MenuItemImpl item = items.get(i);
+ if (item == expandedItem) {
+ mExpandedIndex = i;
+ return;
+ }
+ }
+ }
+ }
+}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
index 377d996..afcadef 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
@@ -43,12 +43,13 @@
import com.android.layoutlib.bridge.android.BridgeContext;
import com.android.layoutlib.bridge.android.BridgeLayoutParamsMapAttributes;
import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
-import com.android.layoutlib.bridge.bars.FakeActionBar;
import com.android.layoutlib.bridge.bars.NavigationBar;
import com.android.layoutlib.bridge.bars.StatusBar;
import com.android.layoutlib.bridge.bars.TitleBar;
+import com.android.layoutlib.bridge.bars.ActionBarLayout;
import com.android.layoutlib.bridge.impl.binding.FakeAdapter;
import com.android.layoutlib.bridge.impl.binding.FakeExpandableAdapter;
+import com.android.resources.Density;
import com.android.resources.ResourceType;
import com.android.resources.ScreenOrientation;
import com.android.util.Pair;
@@ -134,6 +135,7 @@
// information being returned through the API
private BufferedImage mImage;
private List<ViewInfo> mViewInfoList;
+ private List<ViewInfo> mSystemViewInfoList;
private static final class PostInflateException extends Exception {
private static final long serialVersionUID = 1L;
@@ -146,10 +148,11 @@
/**
* Creates a layout scene with all the information coming from the layout bridge API.
* <p>
- * This <b>must</b> be followed by a call to {@link RenderSessionImpl#init()}, which act as a
+ * This <b>must</b> be followed by a call to {@link RenderSessionImpl#init(long)},
+ * which act as a
* call to {@link RenderSessionImpl#acquire(long)}
*
- * @see LayoutBridge#createScene(com.android.layoutlib.api.SceneParams)
+ * @see Bridge#createSession(SessionParams)
*/
public RenderSessionImpl(SessionParams params) {
super(new SessionParams(params));
@@ -225,14 +228,15 @@
HardwareConfig hardwareConfig = params.getHardwareConfig();
BridgeContext context = getContext();
boolean isRtl = Bridge.isLocaleRtl(params.getLocale());
- int direction = isRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR;
+ int layoutDirection = isRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR;
+ ActionBarLayout actionBar = null;
// the view group that receives the window background.
ViewGroup backgroundView = null;
if (mWindowIsFloating || params.isForceNoDecor()) {
backgroundView = mViewRoot = mContentRoot = new FrameLayout(context);
- mViewRoot.setLayoutDirection(direction);
+ mViewRoot.setLayoutDirection(layoutDirection);
} else {
if (hasSoftwareButtons() && mNavigationBarOrientation == LinearLayout.VERTICAL) {
/*
@@ -254,18 +258,13 @@
the bottom
*/
LinearLayout topLayout = new LinearLayout(context);
- topLayout.setLayoutDirection(direction);
+ topLayout.setLayoutDirection(layoutDirection);
mViewRoot = topLayout;
topLayout.setOrientation(LinearLayout.HORIZONTAL);
try {
- NavigationBar navigationBar = new NavigationBar(context,
- hardwareConfig.getDensity(), LinearLayout.VERTICAL, isRtl,
- params.isRtlSupported());
- navigationBar.setLayoutParams(
- new LinearLayout.LayoutParams(
- mNavigationBarSize,
- LayoutParams.MATCH_PARENT));
+ NavigationBar navigationBar = createNavigationBar(context,
+ hardwareConfig.getDensity(), isRtl, params.isRtlSupported());
topLayout.addView(navigationBar);
} catch (XmlPullParserException e) {
@@ -293,14 +292,15 @@
LinearLayout topLayout = new LinearLayout(context);
topLayout.setOrientation(LinearLayout.VERTICAL);
- topLayout.setLayoutDirection(direction);
+ topLayout.setLayoutDirection(layoutDirection);
// if we don't already have a view root this is it
if (mViewRoot == null) {
mViewRoot = topLayout;
} else {
+ int topLayoutWidth =
+ params.getHardwareConfig().getScreenWidth() - mNavigationBarSize;
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(
- LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
- layoutParams.weight = 1;
+ topLayoutWidth, LayoutParams.MATCH_PARENT);
topLayout.setLayoutParams(layoutParams);
// this is the case of soft buttons + vertical bar.
@@ -319,12 +319,9 @@
if (mStatusBarSize > 0) {
// system bar
try {
- StatusBar systemBar = new StatusBar(context, hardwareConfig.getDensity(),
- direction, params.isRtlSupported());
- systemBar.setLayoutParams(
- new LinearLayout.LayoutParams(
- LayoutParams.MATCH_PARENT, mStatusBarSize));
- topLayout.addView(systemBar);
+ StatusBar statusBar = createStatusBar(context, hardwareConfig.getDensity(),
+ layoutDirection, params.isRtlSupported());
+ topLayout.addView(statusBar);
} catch (XmlPullParserException e) {
}
@@ -343,23 +340,17 @@
// if the theme says no title/action bar, then the size will be 0
if (mActionBarSize > 0) {
try {
- FakeActionBar actionBar = new FakeActionBar(context,
- hardwareConfig.getDensity(),
- params.getAppLabel(), params.getAppIcon());
- actionBar.setLayoutParams(
- new LinearLayout.LayoutParams(
- LayoutParams.MATCH_PARENT, mActionBarSize));
+ actionBar = createActionBar(context, params);
backgroundLayout.addView(actionBar);
+ actionBar.createMenuPopup();
+ mContentRoot = actionBar.getContentRoot();
} catch (XmlPullParserException e) {
}
} else if (mTitleBarSize > 0) {
try {
- TitleBar titleBar = new TitleBar(context,
+ TitleBar titleBar = createTitleBar(context,
hardwareConfig.getDensity(), params.getAppLabel());
- titleBar.setLayoutParams(
- new LinearLayout.LayoutParams(
- LayoutParams.MATCH_PARENT, mTitleBarSize));
backgroundLayout.addView(titleBar);
} catch (XmlPullParserException e) {
@@ -367,23 +358,21 @@
}
// content frame
- mContentRoot = new FrameLayout(context);
- layoutParams = new LinearLayout.LayoutParams(
- LayoutParams.MATCH_PARENT, 0);
- layoutParams.weight = 1;
- mContentRoot.setLayoutParams(layoutParams);
- backgroundLayout.addView(mContentRoot);
+ if (mContentRoot == null) {
+ mContentRoot = new FrameLayout(context);
+ layoutParams = new LinearLayout.LayoutParams(
+ LayoutParams.MATCH_PARENT, 0);
+ layoutParams.weight = 1;
+ mContentRoot.setLayoutParams(layoutParams);
+ backgroundLayout.addView(mContentRoot);
+ }
if (mNavigationBarOrientation == LinearLayout.HORIZONTAL &&
mNavigationBarSize > 0) {
// system bar
try {
- NavigationBar navigationBar = new NavigationBar(context,
- hardwareConfig.getDensity(), LinearLayout.HORIZONTAL, isRtl,
- params.isRtlSupported());
- navigationBar.setLayoutParams(
- new LinearLayout.LayoutParams(
- LayoutParams.MATCH_PARENT, mNavigationBarSize));
+ NavigationBar navigationBar = createNavigationBar(context,
+ hardwareConfig.getDensity(), isRtl, params.isRtlSupported());
topLayout.addView(navigationBar);
} catch (XmlPullParserException e) {
@@ -441,7 +430,7 @@
* @throws IllegalStateException if the current context is different than the one owned by
* the scene, or if {@link #acquire(long)} was not called.
*
- * @see RenderParams#getRenderingMode()
+ * @see SessionParams#getRenderingMode()
* @see RenderSession#render(long)
*/
public Result render(boolean freshRender) {
@@ -584,7 +573,7 @@
mViewRoot.draw(mCanvas);
}
- mViewInfoList = startVisitingViews(mViewRoot, 0, params.getExtendedViewInfoMode());
+ mSystemViewInfoList = visitAllChildren(mViewRoot, 0, params.getExtendedViewInfoMode(), false);
// success!
return SUCCESS.createResult();
@@ -1369,50 +1358,126 @@
}
}
- private List<ViewInfo> startVisitingViews(View view, int offset, boolean setExtendedInfo) {
- if (view == null) {
- return null;
- }
-
- // adjust the offset to this view.
- offset += view.getTop();
-
- if (view == mContentRoot) {
- return visitAllChildren(mContentRoot, offset, setExtendedInfo);
- }
-
- // otherwise, look for mContentRoot in the children
- if (view instanceof ViewGroup) {
- ViewGroup group = ((ViewGroup) view);
-
- for (int i = 0; i < group.getChildCount(); i++) {
- List<ViewInfo> list = startVisitingViews(group.getChildAt(i), offset,
- setExtendedInfo);
- if (list != null) {
- return list;
- }
- }
- }
-
- return null;
- }
-
/**
- * Visits a View and its children and generate a {@link ViewInfo} containing the
+ * Visits a {@link View} and its children and generate a {@link ViewInfo} containing the
* bounds of all the views.
+ *
* @param view the root View
* @param offset an offset for the view bounds.
* @param setExtendedInfo whether to set the extended view info in the {@link ViewInfo} object.
+ * @param isContentFrame {@code true} if the {@code ViewInfo} to be created is part of the
+ * content frame.
+ *
+ * @return {@code ViewInfo} containing the bounds of the view and it children otherwise.
*/
- private ViewInfo visit(View view, int offset, boolean setExtendedInfo) {
+ private ViewInfo visit(View view, int offset, boolean setExtendedInfo,
+ boolean isContentFrame) {
+ ViewInfo result = createViewInfo(view, offset, setExtendedInfo, isContentFrame);
+
+ if (view instanceof ViewGroup) {
+ ViewGroup group = ((ViewGroup) view);
+ result.setChildren(visitAllChildren(group, isContentFrame ? 0 : offset,
+ setExtendedInfo, isContentFrame));
+ }
+ return result;
+ }
+
+ /**
+ * Visits all the children of a given ViewGroup and generates a list of {@link ViewInfo}
+ * containing the bounds of all the views. It also initializes the {@link #mViewInfoList} with
+ * the children of the {@code mContentRoot}.
+ *
+ * @param viewGroup the root View
+ * @param offset an offset from the top for the content view frame.
+ * @param setExtendedInfo whether to set the extended view info in the {@link ViewInfo} object.
+ * @param isContentFrame {@code true} if the {@code ViewInfo} to be created is part of the
+ * content frame. {@code false} if the {@code ViewInfo} to be created is
+ * part of the system decor.
+ */
+ private List<ViewInfo> visitAllChildren(ViewGroup viewGroup, int offset,
+ boolean setExtendedInfo, boolean isContentFrame) {
+ if (viewGroup == null) {
+ return null;
+ }
+
+ if (!isContentFrame) {
+ offset += viewGroup.getTop();
+ }
+
+ int childCount = viewGroup.getChildCount();
+ if (viewGroup == mContentRoot) {
+ List<ViewInfo> childrenWithoutOffset = new ArrayList<ViewInfo>(childCount);
+ List<ViewInfo> childrenWithOffset = new ArrayList<ViewInfo>(childCount);
+ for (int i = 0; i < childCount; i++) {
+ ViewInfo[] childViewInfo = visitContentRoot(viewGroup.getChildAt(i), offset,
+ setExtendedInfo);
+ childrenWithoutOffset.add(childViewInfo[0]);
+ childrenWithOffset.add(childViewInfo[1]);
+ }
+ mViewInfoList = childrenWithOffset;
+ return childrenWithoutOffset;
+ } else {
+ List<ViewInfo> children = new ArrayList<ViewInfo>(childCount);
+ for (int i = 0; i < childCount; i++) {
+ children.add(visit(viewGroup.getChildAt(i), offset, setExtendedInfo,
+ isContentFrame));
+ }
+ return children;
+ }
+ }
+
+ /**
+ * Visits the children of {@link #mContentRoot} and generates {@link ViewInfo} containing the
+ * bounds of all the views. It returns two {@code ViewInfo} objects with the same children,
+ * one with the {@code offset} and other without the {@code offset}. The offset is needed to
+ * get the right bounds if the {@code ViewInfo} hierarchy is accessed from
+ * {@code mViewInfoList}. When the hierarchy is accessed via {@code mSystemViewInfoList}, the
+ * offset is not needed.
+ *
+ * @return an array of length two, with ViewInfo at index 0 is without offset and ViewInfo at
+ * index 1 is with the offset.
+ */
+ private ViewInfo[] visitContentRoot(View view, int offset, boolean setExtendedInfo) {
+ ViewInfo[] result = new ViewInfo[2];
+ if (view == null) {
+ return result;
+ }
+
+ result[0] = createViewInfo(view, 0, setExtendedInfo, true);
+ result[1] = createViewInfo(view, offset, setExtendedInfo, true);
+ if (view instanceof ViewGroup) {
+ List<ViewInfo> children = visitAllChildren((ViewGroup) view, 0, setExtendedInfo, true);
+ result[0].setChildren(children);
+ result[1].setChildren(children);
+ }
+ return result;
+ }
+
+ /**
+ * Creates a {@link ViewInfo} for the view. The {@code ViewInfo} corresponding to the children
+ * of the {@code view} are not created. Consequently, the children of {@code ViewInfo} is not
+ * set.
+ * @param offset an offset for the view bounds. Used only if view is part of the content frame.
+ */
+ private ViewInfo createViewInfo(View view, int offset, boolean setExtendedInfo,
+ boolean isContentFrame) {
if (view == null) {
return null;
}
- ViewInfo result = new ViewInfo(view.getClass().getName(),
- getContext().getViewKey(view),
- view.getLeft(), view.getTop() + offset, view.getRight(), view.getBottom() + offset,
- view, view.getLayoutParams());
+ ViewInfo result;
+ if (isContentFrame) {
+ result = new ViewInfo(view.getClass().getName(),
+ getContext().getViewKey(view),
+ view.getLeft(), view.getTop() + offset, view.getRight(),
+ view.getBottom() + offset, view, view.getLayoutParams());
+
+ } else {
+ result = new SystemViewInfo(view.getClass().getName(),
+ getContext().getViewKey(view),
+ view.getLeft(), view.getTop(), view.getRight(),
+ view.getBottom(), view, view.getLayoutParams());
+ }
if (setExtendedInfo) {
MarginLayoutParams marginParams = null;
@@ -1427,39 +1492,67 @@
marginParams != null ? marginParams.bottomMargin : 0);
}
- if (view instanceof ViewGroup) {
- ViewGroup group = ((ViewGroup) view);
- result.setChildren(visitAllChildren(group, 0 /*offset*/, setExtendedInfo));
- }
-
return result;
}
- /**
- * Visits all the children of a given ViewGroup generate a list of {@link ViewInfo}
- * containing the bounds of all the views.
- * @param view the root View
- * @param offset an offset for the view bounds.
- * @param setExtendedInfo whether to set the extended view info in the {@link ViewInfo} object.
- */
- private List<ViewInfo> visitAllChildren(ViewGroup viewGroup, int offset,
- boolean setExtendedInfo) {
- if (viewGroup == null) {
- return null;
- }
-
- List<ViewInfo> children = new ArrayList<ViewInfo>();
- for (int i = 0; i < viewGroup.getChildCount(); i++) {
- children.add(visit(viewGroup.getChildAt(i), offset, setExtendedInfo));
- }
- return children;
- }
-
-
private void invalidateRenderingSize() {
mMeasuredScreenWidth = mMeasuredScreenHeight = -1;
}
+ /**
+ * Creates the status bar with wifi and battery icons.
+ */
+ private StatusBar createStatusBar(BridgeContext context, Density density, int direction,
+ boolean isRtlSupported) throws XmlPullParserException {
+ StatusBar statusBar = new StatusBar(context, density,
+ direction, isRtlSupported);
+ statusBar.setLayoutParams(
+ new LinearLayout.LayoutParams(
+ LayoutParams.MATCH_PARENT, mStatusBarSize));
+ return statusBar;
+ }
+
+ /**
+ * Creates the navigation bar with back, home and recent buttons.
+ *
+ * @param isRtl true if the current locale is right-to-left
+ * @param isRtlSupported true is the project manifest declares that the application
+ * is RTL aware.
+ */
+ private NavigationBar createNavigationBar(BridgeContext context, Density density,
+ boolean isRtl, boolean isRtlSupported) throws XmlPullParserException {
+ NavigationBar navigationBar = new NavigationBar(context,
+ density, mNavigationBarOrientation, isRtl,
+ isRtlSupported);
+ if (mNavigationBarOrientation == LinearLayout.VERTICAL) {
+ navigationBar.setLayoutParams(new LinearLayout.LayoutParams(mNavigationBarSize,
+ LayoutParams.MATCH_PARENT));
+ } else {
+ navigationBar.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT,
+ mNavigationBarSize));
+ }
+ return navigationBar;
+ }
+
+ private TitleBar createTitleBar(BridgeContext context, Density density, String title)
+ throws XmlPullParserException {
+ TitleBar titleBar = new TitleBar(context, density, title);
+ titleBar.setLayoutParams(
+ new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, mTitleBarSize));
+ return titleBar;
+ }
+
+ /**
+ * Creates the action bar. Also queries the project callback for missing information.
+ */
+ private ActionBarLayout createActionBar(BridgeContext context, SessionParams params)
+ throws XmlPullParserException {
+ ActionBarLayout actionBar = new ActionBarLayout(context, params);
+ actionBar.setLayoutParams(new LinearLayout.LayoutParams(
+ LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
+ return actionBar;
+ }
+
public BufferedImage getImage() {
return mImage;
}
@@ -1472,6 +1565,10 @@
return mViewInfoList;
}
+ public List<ViewInfo> getSystemViewInfos() {
+ return mSystemViewInfoList;
+ }
+
public Map<String, String> getDefaultProperties(Object viewObject) {
return getContext().getDefaultPropMap(viewObject);
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java
index 6dcb693..adb0937 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java
@@ -165,6 +165,9 @@
* @param context the current context
*/
public static Drawable getDrawable(ResourceValue value, BridgeContext context) {
+ if (value == null) {
+ return null;
+ }
String stringValue = value.getValue();
if (RenderResources.REFERENCE_NULL.equals(stringValue)) {
return null;
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/SystemViewInfo.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/SystemViewInfo.java
new file mode 100644
index 0000000..5c267df
--- /dev/null
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/SystemViewInfo.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2014 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.layoutlib.bridge.impl;
+
+import com.android.ide.common.rendering.api.ViewInfo;
+
+public class SystemViewInfo extends ViewInfo {
+
+ public SystemViewInfo(String name, Object cookie, int left, int top,
+ int right, int bottom) {
+ super(name, cookie, left, top, right, bottom);
+ }
+
+ public SystemViewInfo(String name, Object cookie, int left, int top,
+ int right, int bottom, Object viewObject, Object layoutParamsObject) {
+ super(name, cookie, left, top, right, bottom, viewObject,
+ layoutParamsObject);
+ }
+
+ @Override
+ public boolean isSystemView() {
+ return true;
+ }
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java
index 7c3ab6f..2e952fc 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java
@@ -116,6 +116,7 @@
"com.android.i18n.phonenumbers.*", // for TextView with autolink attribute
"android.app.DatePickerDialog", // b.android.com/28318
"android.app.TimePickerDialog", // b.android.com/61515
+ "com.android.internal.view.menu.ActionMenu",
},
excludeClasses,
new String[] {