diff options
57 files changed, 2308 insertions, 316 deletions
diff --git a/api/current.txt b/api/current.txt index 2eea8ac012a8..2274ea03a343 100644 --- a/api/current.txt +++ b/api/current.txt @@ -15808,10 +15808,13 @@ package android.net.nsd { 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 @@ package android.provider { 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 34b0f3a70332..9818c333bcec 100644 --- a/core/java/android/app/ActionBar.java +++ b/core/java/android/app/ActionBar.java @@ -1239,6 +1239,8 @@ public abstract class ActionBar { 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 205a21d95b25..6fdb0d0f8462 100644 --- a/core/java/android/net/nsd/NsdServiceInfo.java +++ b/core/java/android/net/nsd/NsdServiceInfo.java @@ -18,8 +18,15 @@ package android.net.nsd; 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 @@ import java.net.InetAddress; */ 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 @@ public final class NsdServiceInfo implements Parcelable { } /** @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 @@ public final class NsdServiceInfo implements Parcelable { 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 @@ public final class NsdServiceInfo implements Parcelable { 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 final class NsdServiceInfo implements Parcelable { 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 @@ public final class NsdServiceInfo implements Parcelable { 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 000000000000..233e0caaec4f --- /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 cbb98e14385f..14298378e4cf 100644 --- a/core/java/android/view/ThreadedRenderer.java +++ b/core/java/android/view/ThreadedRenderer.java @@ -67,7 +67,7 @@ public class ThreadedRenderer extends HardwareRenderer { void destroy(boolean full) { mInitialized = false; updateEnabledState(null); - nDestroyCanvas(mNativeProxy); + nDestroyCanvasAndSurface(mNativeProxy); } private void updateEnabledState(Surface surface) { @@ -300,7 +300,7 @@ public class ThreadedRenderer extends HardwareRenderer { 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 64a1574f00e6..cde808042d64 100644 --- a/core/java/android/widget/ShareActionProvider.java +++ b/core/java/android/widget/ShareActionProvider.java @@ -161,9 +161,11 @@ public class ShareActionProvider extends ActionProvider { @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 5e4c14384cdb..a7278dae4497 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -714,19 +714,19 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener 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 c6afae07ab5a..fb93ddda7c2c 100644 --- a/core/java/com/android/internal/app/WindowDecorActionBar.java +++ b/core/java/com/android/internal/app/WindowDecorActionBar.java @@ -170,6 +170,15 @@ public class WindowDecorActionBar extends ActionBar { 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 @@ public class WindowDecorActionBar extends ActionBar { 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 @@ public class WindowDecorActionBar extends ActionBar { } } - 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 6ae6c8f84356..d0269a339155 100644 --- a/core/jni/android_view_GLRenderer.cpp +++ b/core/jni/android_view_GLRenderer.cpp @@ -146,8 +146,8 @@ static void android_view_GLRenderer_prepareTree(JNIEnv* env, jobject clazz, 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 2eb0d78db263..b2f17de7fa95 100644 --- a/core/jni/android_view_HardwareLayer.cpp +++ b/core/jni/android_view_HardwareLayer.cpp @@ -127,8 +127,8 @@ static void android_view_HardwareLayer_updateRenderLayer(JNIEnv* env, jobject cl 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 30d3e0cc4c18..b5f489d563e9 100644 --- a/core/jni/android_view_ThreadedRenderer.cpp +++ b/core/jni/android_view_ThreadedRenderer.cpp @@ -121,10 +121,10 @@ static void android_view_ThreadedRenderer_drawDisplayList(JNIEnv* env, jobject c 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 @@ static JNINativeMethod gMethods[] = { { "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 000000000000..396842914cab --- /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 000000000000..7789219a67e8 --- /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 39137526ae5c..02e61e299efd 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 2720d61532a5..595dc793afe4 100644 --- a/core/res/res/values/styles_quantum.xml +++ b/core/res/res/values/styles_quantum.xml @@ -713,10 +713,9 @@ please see styles_device_defaults.xml. <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 @@ please see styles_device_defaults.xml. <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 @@ please see styles_device_defaults.xml. </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 51e806a1b415..58d95beaaa12 100644 --- a/core/res/res/values/themes_quantum.xml +++ b/core/res/res/values/themes_quantum.xml @@ -300,10 +300,10 @@ please see themes_device_defaults.xml. <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 @@ please see themes_device_defaults.xml. <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 @@ please see themes_device_defaults.xml. <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 8b239551a1af..285c8c3f7346 100644 --- a/libs/hwui/DeferredLayerUpdater.cpp +++ b/libs/hwui/DeferredLayerUpdater.cpp @@ -63,7 +63,7 @@ void DeferredLayerUpdater::setDisplayList(RenderNode* displayList, } } -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 @@ bool DeferredLayerUpdater::apply(bool* hasFunctors) { 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 2cc92295c5f6..cc62caa6dc86 100644 --- a/libs/hwui/DeferredLayerUpdater.h +++ b/libs/hwui/DeferredLayerUpdater.h @@ -77,7 +77,7 @@ public: 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 06f675efcf23..f19da9d6af3e 100644 --- a/libs/hwui/DisplayListOp.h +++ b/libs/hwui/DisplayListOp.h @@ -286,12 +286,6 @@ public: int getFlags() const { return mFlags; } private: - SaveOp() {} - DisplayListOp* reinit(int flags) { - mFlags = flags; - return this; - } - int mFlags; }; @@ -318,12 +312,6 @@ public: virtual const char* name() { return "RestoreToCount"; } private: - RestoreToCountOp() {} - DisplayListOp* reinit(int count) { - mCount = count; - return this; - } - int mCount; }; @@ -514,7 +502,6 @@ public: } protected: - ClipOp() {} virtual bool isRect() { return false; } SkRegion::Op mOp; @@ -539,13 +526,6 @@ protected: 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 c55ebd6e796b..cf218347ee2a 100644 --- a/libs/hwui/RenderNode.cpp +++ b/libs/hwui/RenderNode.cpp @@ -109,7 +109,7 @@ void RenderNode::pushStagingChanges(TreeInfo& info) { 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::pushStagingChanges(TreeInfo& info) { 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 9e6ee3fc7c10..6688952836bc 100644 --- a/libs/hwui/RenderNode.h +++ b/libs/hwui/RenderNode.h @@ -66,7 +66,13 @@ class RestoreToCountOp; 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 7923ce794e0f..e783905b677f 100644 --- a/libs/hwui/Texture.cpp +++ b/libs/hwui/Texture.cpp @@ -25,14 +25,14 @@ namespace android { 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 d48ec5924d62..d5601f801fcd 100644 --- a/libs/hwui/Texture.h +++ b/libs/hwui/Texture.h @@ -94,6 +94,12 @@ public: */ 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 01d72d1e479c..34e22658c4be 100644 --- a/libs/hwui/TextureCache.cpp +++ b/libs/hwui/TextureCache.cpp @@ -121,29 +121,49 @@ void TextureCache::operator()(const SkBitmap*&, Texture*& texture) { // 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 @@ Texture* TextureCache::get(const SkBitmap* bitmap) { 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 e33c60dede81..48a10c20d639 100644 --- a/libs/hwui/TextureCache.h +++ b/libs/hwui/TextureCache.h @@ -62,6 +62,18 @@ public: 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 @@ public: 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 36381842b21d..16baf7714bf5 100644 --- a/libs/hwui/renderthread/CanvasContext.cpp +++ b/libs/hwui/renderthread/CanvasContext.cpp @@ -318,10 +318,10 @@ CanvasContext::CanvasContext(bool translucent) } CanvasContext::~CanvasContext() { - destroyCanvas(); + destroyCanvasAndSurface(); } -void CanvasContext::destroyCanvas() { +void CanvasContext::destroyCanvasAndSurface() { if (mCanvas) { delete mCanvas; mCanvas = 0; @@ -382,13 +382,18 @@ void CanvasContext::setup(int width, int height) { mCanvas->setViewport(width, height); } +void CanvasContext::makeCurrent() { + mGlobalContext->makeCurrent(mEglSurface); +} + void CanvasContext::processLayerUpdates(const Vector<DeferredLayerUpdater*>* layerUpdaters, - bool* hasFunctors) { + TreeInfo& info) { LOG_ALWAYS_FATAL_IF(!mCanvas, "Cannot process layer updates without a canvas!"); - mGlobalContext->makeCurrent(mEglSurface); + 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 @@ void CanvasContext::invokeFunctor(Functor* functor) { 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 dcb5957a7fef..a3fe59120585 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 android { namespace uirenderer { class DeferredLayerUpdater; -class RenderNode; -class DisplayListData; class OpenGLRenderer; class Rect; class Layer; @@ -54,9 +53,10 @@ public: 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 cf6c8db9889a..f542d4396cfe 100644 --- a/libs/hwui/renderthread/DrawFrameTask.cpp +++ b/libs/hwui/renderthread/DrawFrameTask.cpp @@ -85,7 +85,6 @@ void DrawFrameTask::postAndWait(RenderThread* renderThread) { 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 @@ void DrawFrameTask::run() { } } +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 b233ae91d79e..ce490f1cde6a 100644 --- a/libs/hwui/renderthread/RenderProxy.cpp +++ b/libs/hwui/renderthread/RenderProxy.cpp @@ -140,15 +140,18 @@ void RenderProxy::drawDisplayList(RenderNode* displayList, 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 3eb8ed8c984e..a112493ae39a 100644 --- a/libs/hwui/renderthread/RenderProxy.h +++ b/libs/hwui/renderthread/RenderProxy.h @@ -65,7 +65,7 @@ public: 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 48d972288802..da3780309d39 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 82978784aa10..7f640328cd9e 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 e305d942e0f5..478c54115419 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 5e7db8bc60df..94d354146175 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 d994a5bdec95..73e5e194eb57 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 cde17f5e3a6c..64770a459636 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 class Constants { 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 f61c28c3888f..f61c9f1f5f68 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 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView 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 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView // 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 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView 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 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView @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 894966362b21..9fdb5f9d65a2 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 class RecentsConfiguration { public int filteringCurrentViewsMinAnimDuration; public int filteringNewViewsMinAnimDuration; public int taskBarEnterAnimDuration; + public int taskStackScrollDismissInfoPaneDistance; + public int taskViewInfoPaneAnimDuration; public boolean launchedWithThumbnailAnimation; @@ -81,6 +83,10 @@ public class RecentsConfiguration { 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 f78a999f3e48..06ca9e2ba228 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 @@ class SystemUIMessageHandler extends Handler { /* 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 ec28379f628e..b054a22e52d1 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 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV 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 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV mCb.onTaskLaunching(); } + // Close any open info panes + closeOpenInfoPanes(); + final Runnable launchRunnable = new Runnable() { @Override public void run() { @@ -283,4 +304,15 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV 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 000000000000..233e38c03325 --- /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 88fb9721bded..033bd67681d9 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 @@ import java.util.HashMap; /* 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 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal int mMinScroll; int mMaxScroll; int mStashedScroll; + int mLastInfoPaneStackScroll; OverScroller mScroller; ObjectAnimator mScrollAnimator; @@ -281,6 +283,17 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal 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 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal // 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 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal 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 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } } + /** 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 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal 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 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal @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 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal @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 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal // 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 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal } } + @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 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal 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 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { 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 @@ class TaskStackViewTouchHandler implements SwipeHelper.Callback { 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 b4100127bcad..81805d59b116 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 @@ import com.android.systemui.recents.model.Task; /* 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 @@ public class TaskView extends FrameLayout implements View.OnClickListener, Task. // 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 @@ public class TaskView extends FrameLayout implements View.OnClickListener, Task. 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 @@ public class TaskView extends FrameLayout implements View.OnClickListener, Task. @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 1ed943c56159..7b3b65bd5807 100644 --- a/services/core/java/com/android/server/NsdService.java +++ b/services/core/java/com/android/server/NsdService.java @@ -35,8 +35,12 @@ import android.util.SparseArray; 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 @@ public class NsdService extends INsdManager.Stub { 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 @@ public class NsdService extends INsdManager.Stub { 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 @@ public class NsdService extends INsdManager.Stub { 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 000000000000..5bf0167f1cf0 --- /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 7adc5af44b75..000000000000 --- 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 000000000000..40b6220cf8a0 --- /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 ab4be7132967..fa8050f1315c 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 @@ public final class Bridge extends com.android.ide.common.rendering.api.Bridge { 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 f9f4b3a86b3e..e0f87fd63b11 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 @@ public class BridgeRenderSession extends RenderSession { } @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 9ee2f60add7e..6595ce1918f7 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 @@ public final class BridgeContext extends Context { } 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 000000000000..49027c65114d --- /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 226649dbc406..000000000000 --- 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 000000000000..778305da6d70 --- /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 377d99664fb7..afcadef33adf 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.Bridge; 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 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { // 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 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { /** * 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 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { 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 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { 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 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { 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 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { 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 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { // 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 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { } // 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 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { * @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 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { mViewRoot.draw(mCanvas); } - mViewInfoList = startVisitingViews(mViewRoot, 0, params.getExtendedViewInfoMode()); + mSystemViewInfoList = visitAllChildren(mViewRoot, 0, params.getExtendedViewInfoMode(), false); // success! return SUCCESS.createResult(); @@ -1369,50 +1358,126 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { } } - private List<ViewInfo> startVisitingViews(View view, int offset, boolean setExtendedInfo) { - if (view == null) { - return null; - } + /** + * 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, + boolean isContentFrame) { + ViewInfo result = createViewInfo(view, offset, setExtendedInfo, isContentFrame); - // adjust the offset to this view. - offset += view.getTop(); + if (view instanceof ViewGroup) { + ViewGroup group = ((ViewGroup) view); + result.setChildren(visitAllChildren(group, isContentFrame ? 0 : offset, + setExtendedInfo, isContentFrame)); + } + return result; + } - if (view == mContentRoot) { - return visitAllChildren(mContentRoot, offset, setExtendedInfo); + /** + * 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; } - // otherwise, look for mContentRoot in the children - if (view instanceof ViewGroup) { - ViewGroup group = ((ViewGroup) view); + if (!isContentFrame) { + offset += viewGroup.getTop(); + } - for (int i = 0; i < group.getChildCount(); i++) { - List<ViewInfo> list = startVisitingViews(group.getChildAt(i), offset, + 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); - if (list != null) { - return list; - } + 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; } + } - return null; + /** + * 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; } /** - * Visits a 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. + * 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 visit(View view, int offset, boolean setExtendedInfo) { + 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,37 +1492,65 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { marginParams != null ? marginParams.bottomMargin : 0); } - if (view instanceof ViewGroup) { - ViewGroup group = ((ViewGroup) view); - result.setChildren(visitAllChildren(group, 0 /*offset*/, setExtendedInfo)); - } - return result; } + private void invalidateRenderingSize() { + mMeasuredScreenWidth = mMeasuredScreenHeight = -1; + } + /** - * 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. + * Creates the status bar with wifi and battery icons. */ - private List<ViewInfo> visitAllChildren(ViewGroup viewGroup, int offset, - boolean setExtendedInfo) { - if (viewGroup == null) { - return null; - } + 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; + } - List<ViewInfo> children = new ArrayList<ViewInfo>(); - for (int i = 0; i < viewGroup.getChildCount(); i++) { - children.add(visit(viewGroup.getChildAt(i), offset, setExtendedInfo)); + /** + * 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 children; + 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; + } - private void invalidateRenderingSize() { - mMeasuredScreenWidth = mMeasuredScreenHeight = -1; + /** + * 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() { @@ -1472,6 +1565,10 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { 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 6dcb69393bd4..adb0937a6c70 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 @@ public final class ResourceHelper { * @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 000000000000..5c267df56a3a --- /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 7c3ab6fe4244..2e952fcff4c2 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 @@ public class Main { "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[] { |