diff options
189 files changed, 9503 insertions, 1366 deletions
@@ -117,6 +117,7 @@ LOCAL_SRC_FILES += \ core/java/android/net/INetworkPolicyListener.aidl \ core/java/android/net/INetworkPolicyManager.aidl \ core/java/android/net/INetworkStatsService.aidl \ + core/java/android/net/nsd/INsdManager.aidl \ core/java/android/nfc/INdefPushCallback.aidl \ core/java/android/nfc/INfcAdapter.aidl \ core/java/android/nfc/INfcAdapterExtras.aidl \ @@ -56,6 +56,17 @@ the Apache2 License. ========================================================================= == NOTICE file corresponding to the section 4 d of == == the Apache License, Version 2.0, == + == in this case for the mDnsResponder code. == + ========================================================================= + +mDnsResponder TXTRecord +This file is Copyright 2004 Apple Computer, Inc. but released under +the Apache2 License. + + + ========================================================================= + == NOTICE file corresponding to the section 4 d of == + == the Apache License, Version 2.0, == == in this case for the TagSoup code. == ========================================================================= diff --git a/api/current.txt b/api/current.txt index 05fca90..d8461e7 100644 --- a/api/current.txt +++ b/api/current.txt @@ -596,7 +596,7 @@ package android { field public static final int layerType = 16843604; // 0x1010354 field public static final int layout = 16842994; // 0x10100f2 field public static final int layoutAnimation = 16842988; // 0x10100ec - field public static final int layoutDirection = 16843690; // 0x10103aa + field public static final int layoutDirection = 16843691; // 0x10103ab field public static final int layout_above = 16843140; // 0x1010184 field public static final int layout_alignBaseline = 16843142; // 0x1010186 field public static final int layout_alignBottom = 16843146; // 0x101018a @@ -618,10 +618,10 @@ package android { field public static final int layout_height = 16842997; // 0x10100f5 field public static final int layout_margin = 16842998; // 0x10100f6 field public static final int layout_marginBottom = 16843002; // 0x10100fa - field public static final int layout_marginEnd = 16843694; // 0x10103ae + field public static final int layout_marginEnd = 16843695; // 0x10103af field public static final int layout_marginLeft = 16842999; // 0x10100f7 field public static final int layout_marginRight = 16843001; // 0x10100f9 - field public static final int layout_marginStart = 16843693; // 0x10103ad + field public static final int layout_marginStart = 16843694; // 0x10103ae field public static final int layout_marginTop = 16843000; // 0x10100f8 field public static final int layout_row = 16843643; // 0x101037b field public static final int layout_rowSpan = 16843644; // 0x101037c @@ -717,10 +717,10 @@ package android { field public static final int packageNames = 16843649; // 0x1010381 field public static final int padding = 16842965; // 0x10100d5 field public static final int paddingBottom = 16842969; // 0x10100d9 - field public static final int paddingEnd = 16843692; // 0x10103ac + field public static final int paddingEnd = 16843693; // 0x10103ad field public static final int paddingLeft = 16842966; // 0x10100d6 field public static final int paddingRight = 16842968; // 0x10100d8 - field public static final int paddingStart = 16843691; // 0x10103ab + field public static final int paddingStart = 16843692; // 0x10103ac field public static final int paddingTop = 16842967; // 0x10100d7 field public static final int panelBackground = 16842846; // 0x101005e field public static final int panelColorBackground = 16842849; // 0x1010061 @@ -962,6 +962,7 @@ package android { field public static final int tension = 16843370; // 0x101026a field public static final int testOnly = 16843378; // 0x1010272 field public static final int text = 16843087; // 0x101014f + field public static final int textAlignment = 16843690; // 0x10103aa field public static final int textAllCaps = 16843660; // 0x101038c field public static final int textAppearance = 16842804; // 0x1010034 field public static final int textAppearanceButton = 16843271; // 0x1010207 @@ -6132,7 +6133,7 @@ package android.content.pm { field public static final int FLAG_STOPPED = 2097152; // 0x200000 field public static final int FLAG_SUPPORTS_LARGE_SCREENS = 2048; // 0x800 field public static final int FLAG_SUPPORTS_NORMAL_SCREENS = 1024; // 0x400 - field public static final int FLAG_SUPPORTS_RTL = 1073741824; // 0x40000000 + field public static final int FLAG_SUPPORTS_RTL = 4194304; // 0x400000 field public static final int FLAG_SUPPORTS_SCREEN_DENSITIES = 8192; // 0x2000 field public static final int FLAG_SUPPORTS_SMALL_SCREENS = 512; // 0x200 field public static final int FLAG_SUPPORTS_XLARGE_SCREENS = 524288; // 0x80000 @@ -12899,6 +12900,7 @@ package android.nfc.tech { method public byte[] getHistoricalBytes(); method public int getMaxTransceiveLength(); method public int getTimeout(); + method public boolean isExtendedLengthApduSupported(); method public void setTimeout(int); method public byte[] transceive(byte[]) throws java.io.IOException; } @@ -23209,6 +23211,7 @@ package android.view { method public void buildLayer(); method public boolean callOnClick(); method public boolean canResolveLayoutDirection(); + method public boolean canResolveTextAlignment(); method public boolean canResolveTextDirection(); method public boolean canScrollHorizontally(int); method public boolean canScrollVertically(int); @@ -23311,6 +23314,8 @@ package android.view { method public final int getMeasuredState(); method public final int getMeasuredWidth(); method public final int getMeasuredWidthAndState(); + method public int getMinimumHeight(); + method public int getMinimumWidth(); method public int getNextFocusDownId(); method public int getNextFocusForwardId(); method public int getNextFocusLeftId(); @@ -23329,6 +23334,7 @@ package android.view { method public float getPivotY(); method public int getResolvedLayoutDirection(); method public int getResolvedLayoutDirection(android.graphics.drawable.Drawable); + method public int getResolvedTextAlignment(); method public int getResolvedTextDirection(); method public android.content.res.Resources getResources(); method public final int getRight(); @@ -23340,6 +23346,9 @@ package android.view { method public float getRotationY(); method public float getScaleX(); method public float getScaleY(); + method public int getScrollBarDefaultDelayBeforeFade(); + method public int getScrollBarFadeDuration(); + method public int getScrollBarSize(); method public int getScrollBarStyle(); method public final int getScrollX(); method public final int getScrollY(); @@ -23349,6 +23358,7 @@ package android.view { method public int getSystemUiVisibility(); method public java.lang.Object getTag(); method public java.lang.Object getTag(int); + method public int getTextAlignment(); method public int getTextDirection(); method public final int getTop(); method protected float getTopFadingEdgeStrength(); @@ -23373,6 +23383,7 @@ package android.view { method public boolean hasFocus(); method public boolean hasFocusable(); method public boolean hasOnClickListeners(); + method public boolean hasOverlappingRendering(); method public boolean hasTransientState(); method public boolean hasWindowFocus(); method public static android.view.View inflate(android.content.Context, int, android.view.ViewGroup); @@ -23408,6 +23419,7 @@ package android.view { method public boolean isPressed(); method public boolean isSaveEnabled(); method public boolean isSaveFromParentEnabled(); + method public boolean isScrollContainer(); method public boolean isScrollbarFadingEnabled(); method public boolean isSelected(); method public boolean isShown(); @@ -23455,6 +23467,8 @@ package android.view { method public void onPopulateAccessibilityEvent(android.view.accessibility.AccessibilityEvent); method public void onResolvedLayoutDirectionChanged(); method public void onResolvedLayoutDirectionReset(); + method public void onResolvedTextAlignmentChanged(); + method public void onResolvedTextAlignmentReset(); method public void onResolvedTextDirectionChanged(); method public void onResolvedTextDirectionReset(); method protected void onRestoreInstanceState(android.os.Parcelable); @@ -23495,11 +23509,13 @@ package android.view { method public boolean requestRectangleOnScreen(android.graphics.Rect); method public boolean requestRectangleOnScreen(android.graphics.Rect, boolean); method public void resetResolvedLayoutDirection(); + method public void resetResolvedTextAlignment(); method public void resetResolvedTextDirection(); method public void resolveLayoutDirection(); method public void resolvePadding(); method public static int resolveSize(int, int); method public static int resolveSizeAndState(int, int, int); + method public void resolveTextAlignment(); method public void resolveTextDirection(); method public void restoreHierarchyState(android.util.SparseArray<android.os.Parcelable>); method public void saveHierarchyState(android.util.SparseArray<android.os.Parcelable>); @@ -23512,8 +23528,9 @@ package android.view { method public void setActivated(boolean); method public void setAlpha(float); method public void setAnimation(android.view.animation.Animation); + method public void setBackground(android.graphics.drawable.Drawable); method public void setBackgroundColor(int); - method public void setBackgroundDrawable(android.graphics.drawable.Drawable); + method public deprecated void setBackgroundDrawable(android.graphics.drawable.Drawable); method public void setBackgroundResource(int); method public final void setBottom(int); method public void setCameraDistance(float); @@ -23573,6 +23590,9 @@ package android.view { method public void setSaveFromParentEnabled(boolean); method public void setScaleX(float); method public void setScaleY(float); + method public void setScrollBarDefaultDelayBeforeFade(int); + method public void setScrollBarFadeDuration(int); + method public void setScrollBarSize(int); method public void setScrollBarStyle(int); method public void setScrollContainer(boolean); method public void setScrollX(int); @@ -23583,6 +23603,7 @@ package android.view { method public void setSystemUiVisibility(int); method public void setTag(java.lang.Object); method public void setTag(int, java.lang.Object); + method public void setTextAlignment(int); method public void setTextDirection(int); method public final void setTop(int); method public void setTouchDelegate(android.view.TouchDelegate); @@ -23693,6 +23714,15 @@ package android.view { field public static final int SYSTEM_UI_FLAG_LOW_PROFILE = 1; // 0x1 field public static final int SYSTEM_UI_FLAG_VISIBLE = 0; // 0x0 field public static final int SYSTEM_UI_LAYOUT_FLAGS = 1536; // 0x600 + field public static final int TEXT_ALIGNMENT_CENTER = 4; // 0x4 + field protected static int TEXT_ALIGNMENT_DEFAULT; + field public static final int TEXT_ALIGNMENT_GRAVITY = 1; // 0x1 + field public static final int TEXT_ALIGNMENT_INHERIT = 0; // 0x0 + field public static final int TEXT_ALIGNMENT_RESOLVED_DEFAULT = 131072; // 0x20000 + field public static final int TEXT_ALIGNMENT_TEXT_END = 3; // 0x3 + field public static final int TEXT_ALIGNMENT_TEXT_START = 2; // 0x2 + field public static final int TEXT_ALIGNMENT_VIEW_END = 6; // 0x6 + field public static final int TEXT_ALIGNMENT_VIEW_START = 5; // 0x5 field public static final int TEXT_DIRECTION_ANY_RTL = 2; // 0x2 field protected static int TEXT_DIRECTION_DEFAULT; field public static final int TEXT_DIRECTION_FIRST_STRONG = 1; // 0x1 @@ -24118,15 +24148,18 @@ package android.view { } public final class ViewTreeObserver { + method public void addOnDrawListener(android.view.ViewTreeObserver.OnDrawListener); method public void addOnGlobalFocusChangeListener(android.view.ViewTreeObserver.OnGlobalFocusChangeListener); method public void addOnGlobalLayoutListener(android.view.ViewTreeObserver.OnGlobalLayoutListener); method public void addOnPreDrawListener(android.view.ViewTreeObserver.OnPreDrawListener); method public void addOnScrollChangedListener(android.view.ViewTreeObserver.OnScrollChangedListener); method public void addOnTouchModeChangeListener(android.view.ViewTreeObserver.OnTouchModeChangeListener); + method public final void dispatchOnDraw(); method public final void dispatchOnGlobalLayout(); method public final boolean dispatchOnPreDraw(); method public boolean isAlive(); method public deprecated void removeGlobalOnLayoutListener(android.view.ViewTreeObserver.OnGlobalLayoutListener); + method public void removeOnDrawListener(android.view.ViewTreeObserver.OnDrawListener); method public void removeOnGlobalFocusChangeListener(android.view.ViewTreeObserver.OnGlobalFocusChangeListener); method public void removeOnGlobalLayoutListener(android.view.ViewTreeObserver.OnGlobalLayoutListener); method public void removeOnPreDrawListener(android.view.ViewTreeObserver.OnPreDrawListener); @@ -24134,6 +24167,10 @@ package android.view { method public void removeOnTouchModeChangeListener(android.view.ViewTreeObserver.OnTouchModeChangeListener); } + public static abstract interface ViewTreeObserver.OnDrawListener { + method public abstract void onDraw(); + } + public static abstract interface ViewTreeObserver.OnGlobalFocusChangeListener { method public abstract void onGlobalFocusChanged(android.view.View, android.view.View); } diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index d758eca..5ffceb3 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -59,6 +59,8 @@ import android.net.NetworkPolicyManager; import android.net.ThrottleManager; import android.net.IThrottleManager; import android.net.Uri; +import android.net.nsd.INsdManager; +import android.net.nsd.NsdManager; import android.net.wifi.IWifiManager; import android.net.wifi.WifiManager; import android.net.wifi.p2p.IWifiP2pManager; @@ -372,6 +374,14 @@ class ContextImpl extends Context { ctx.mMainThread.getHandler()); }}); + registerService(NSD_SERVICE, new ServiceFetcher() { + @Override + public Object createService(ContextImpl ctx) { + IBinder b = ServiceManager.getService(NSD_SERVICE); + INsdManager service = INsdManager.Stub.asInterface(b); + return new NsdManager(service); + }}); + // Note: this was previously cached in a static variable, but // constructed using mMainThread.getHandler(), so converting // it to be a regular Context-cached service... diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 096af93..04c64a0 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -26,6 +26,7 @@ import android.os.Bundle; import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; +import android.os.SystemClock; import android.text.TextUtils; import android.util.IntProperty; import android.util.Log; @@ -808,6 +809,7 @@ public class Notification implements Parcelable @Deprecated public void setLatestEventInfo(Context context, CharSequence contentTitle, CharSequence contentText, PendingIntent contentIntent) { + // TODO: rewrite this to use Builder RemoteViews contentView = new RemoteViews(context.getPackageName(), R.layout.notification_template_base); if (this.icon != 0) { @@ -820,6 +822,7 @@ public class Notification implements Parcelable contentView.setTextViewText(R.id.text, contentText); } if (this.when != 0) { + contentView.setViewVisibility(R.id.time, View.VISIBLE); contentView.setLong(R.id.time, "setTime", when); } @@ -942,6 +945,7 @@ public class Notification implements Parcelable private ArrayList<Action> mActions = new ArrayList<Action>(3); private boolean mCanHasIntruder; private boolean mIntruderActionsShowText; + private boolean mUseChronometer; /** * Constructs a new Builder with the defaults: @@ -983,6 +987,18 @@ public class Notification implements Parcelable } /** + * @hide + * + * Show the {@link Notification#when} field as a countdown (or count-up) timer instead of a timestamp. + * + * @see Notification#when + */ + public Builder setUsesChronometer(boolean b) { + mUseChronometer = b; + return this; + } + + /** * Set the small icon resource, which will be used to represent the notification in the * status bar. * @@ -1434,7 +1450,15 @@ public class Notification implements Parcelable } } if (mWhen != 0) { - contentView.setLong(R.id.time, "setTime", mWhen); + if (mUseChronometer) { + contentView.setViewVisibility(R.id.chronometer, View.VISIBLE); + contentView.setLong(R.id.chronometer, "setBase", + mWhen + (SystemClock.elapsedRealtime() - System.currentTimeMillis())); + contentView.setBoolean(R.id.chronometer, "setStarted", true); + } else { + contentView.setViewVisibility(R.id.time, View.VISIBLE); + contentView.setLong(R.id.time, "setTime", mWhen); + } } contentView.setViewVisibility(R.id.line3, hasLine3 ? View.VISIBLE : View.GONE); return contentView; diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 2902504..98ed117 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -1769,6 +1769,18 @@ public abstract class Context { public static final String WIFI_P2P_SERVICE = "wifip2p"; /** + * Use with {@link #getSystemService} to retrieve a {@link + * android.net.NsdManager} for handling management of network service + * discovery + * + * @hide + * @see #getSystemService + * @see android.net.NsdManager + */ + public static final String NSD_SERVICE = "servicediscovery"; + + + /** * Use with {@link #getSystemService} to retrieve a * {@link android.media.AudioManager} for handling management of volume, * ringer modes and audio routing. diff --git a/core/java/android/net/nsd/DnsSdServiceInfo.java b/core/java/android/net/nsd/DnsSdServiceInfo.java new file mode 100644 index 0000000..47d6ec6 --- /dev/null +++ b/core/java/android/net/nsd/DnsSdServiceInfo.java @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2012 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.net.nsd; + +import android.os.Parcelable; +import android.os.Parcel; + +/** + * Defines a service based on DNS service discovery + * {@hide} + */ +public class DnsSdServiceInfo implements NetworkServiceInfo, Parcelable { + + private String mServiceName; + + private String mRegistrationType; + + private DnsSdTxtRecord mTxtRecord; + + private String mHostname; + + private int mPort; + + DnsSdServiceInfo() { + } + + DnsSdServiceInfo(String sn, String rt, DnsSdTxtRecord tr) { + mServiceName = sn; + mRegistrationType = rt; + mTxtRecord = tr; + } + + @Override + /** @hide */ + public String getServiceName() { + return mServiceName; + } + + @Override + /** @hide */ + public void setServiceName(String s) { + mServiceName = s; + } + + @Override + /** @hide */ + public String getServiceType() { + return mRegistrationType; + } + + @Override + /** @hide */ + public void setServiceType(String s) { + mRegistrationType = s; + } + + public DnsSdTxtRecord getTxtRecord() { + return mTxtRecord; + } + + public void setTxtRecord(DnsSdTxtRecord t) { + mTxtRecord = new DnsSdTxtRecord(t); + } + + public String getHostName() { + return mHostname; + } + + public void setHostName(String s) { + mHostname = s; + } + + public int getPort() { + return mPort; + } + + public void setPort(int p) { + mPort = p; + } + + public String toString() { + StringBuffer sb = new StringBuffer(); + + sb.append("name: ").append(mServiceName). + append("type: ").append(mRegistrationType). + append("txtRecord: ").append(mTxtRecord); + return sb.toString(); + } + + /** Implement the Parcelable interface */ + public int describeContents() { + return 0; + } + + /** Implement the Parcelable interface */ + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mServiceName); + dest.writeString(mRegistrationType); + dest.writeParcelable(mTxtRecord, flags); + dest.writeString(mHostname); + dest.writeInt(mPort); + } + + /** Implement the Parcelable interface */ + public static final Creator<DnsSdServiceInfo> CREATOR = + new Creator<DnsSdServiceInfo>() { + public DnsSdServiceInfo createFromParcel(Parcel in) { + DnsSdServiceInfo info = new DnsSdServiceInfo(); + info.mServiceName = in.readString(); + info.mRegistrationType = in.readString(); + info.mTxtRecord = in.readParcelable(null); + info.mHostname = in.readString(); + info.mPort = in.readInt(); + return info; + } + + public DnsSdServiceInfo[] newArray(int size) { + return new DnsSdServiceInfo[size]; + } + }; + +} diff --git a/core/java/android/net/nsd/DnsSdTxtRecord.java b/core/java/android/net/nsd/DnsSdTxtRecord.java new file mode 100644 index 0000000..6d4342c --- /dev/null +++ b/core/java/android/net/nsd/DnsSdTxtRecord.java @@ -0,0 +1,305 @@ +/* -*- Mode: Java; tab-width: 4 -*- + * + * Copyright (c) 2004 Apple Computer, Inc. All rights reserved. + * + * 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. + + To do: + - implement remove() + - fix set() to replace existing values + */ + +package android.net.nsd; + +import android.os.Parcelable; +import android.os.Parcel; + +/** + * This class handles TXT record data for DNS based service discovery as specified at + * http://tools.ietf.org/html/draft-cheshire-dnsext-dns-sd-11 + * + * DNS-SD specifies that a TXT record corresponding to an SRV record consist of + * a packed array of bytes, each preceded by a length byte. Each string + * is an attribute-value pair. + * + * The DnsSdTxtRecord object stores the entire TXT data as a single byte array, traversing it + * as need be to implement its various methods. + * + * @hide + */ +public class DnsSdTxtRecord implements Parcelable { + private static final byte mSeperator = '='; + + private byte[] mData; + + /** Constructs a new, empty TXT record. */ + public DnsSdTxtRecord() { + mData = new byte[0]; + } + + /** Constructs a new TXT record from a byte array in the standard format. */ + public DnsSdTxtRecord(byte[] data) { + mData = (byte[]) data.clone(); + } + + /** Copy constructor */ + public DnsSdTxtRecord(DnsSdTxtRecord src) { + if (src != null && src.mData != null) { + mData = (byte[]) src.mData.clone(); + } + } + + /** + * Set a key/value pair. Setting an existing key will replace its value. + * @param key Must be ascii with no '=' + * @param value matching value to key + */ + public void set(String key, String value) { + byte[] keyBytes; + byte[] valBytes; + int valLen; + + if (value != null) { + valBytes = value.getBytes(); + valLen = valBytes.length; + } else { + valBytes = null; + valLen = 0; + } + + try { + keyBytes = key.getBytes("US-ASCII"); + } + catch (java.io.UnsupportedEncodingException e) { + throw new IllegalArgumentException("key should be US-ASCII"); + } + + for (int i = 0; i < keyBytes.length; i++) { + if (keyBytes[i] == '=') { + throw new IllegalArgumentException("= is not a valid character in key"); + } + } + + if (keyBytes.length + valLen >= 255) { + throw new IllegalArgumentException("Key and Value length cannot exceed 255 bytes"); + } + + int currentLoc = remove(key); + if (currentLoc == -1) + currentLoc = keyCount(); + + insert(keyBytes, valBytes, currentLoc); + } + + /** + * Get a value for a key + * + * @param key + * @return The value associated with the key + */ + public String get(String key) { + byte[] val = this.getValue(key); + return val != null ? new String(val) : null; + } + + /** Remove a key/value pair. If found, returns the index or -1 if not found */ + public int remove(String key) { + int avStart = 0; + + for (int i=0; avStart < mData.length; i++) { + int avLen = mData[avStart]; + if (key.length() <= avLen && + (key.length() == avLen || mData[avStart + key.length() + 1] == mSeperator)) { + String s = new String(mData, avStart + 1, key.length()); + if (0 == key.compareToIgnoreCase(s)) { + byte[] oldBytes = mData; + mData = new byte[oldBytes.length - avLen - 1]; + System.arraycopy(oldBytes, 0, mData, 0, avStart); + System.arraycopy(oldBytes, avStart + avLen + 1, mData, avStart, + oldBytes.length - avStart - avLen - 1); + return i; + } + } + avStart += (0xFF & (avLen + 1)); + } + return -1; + } + + /** Return the count of keys */ + public int keyCount() { + int count = 0, nextKey; + for (nextKey = 0; nextKey < mData.length; count++) { + nextKey += (0xFF & (mData[nextKey] + 1)); + } + return count; + } + + /** Return true if key is present, false if not. */ + public boolean contains(String key) { + String s = null; + for (int i = 0; null != (s = this.getKey(i)); i++) { + if (0 == key.compareToIgnoreCase(s)) return true; + } + return false; + } + + /* Gets the size in bytes */ + public int size() { + return mData.length; + } + + /* Gets the raw data in bytes */ + public byte[] getRawData() { + return mData; + } + + private void insert(byte[] keyBytes, byte[] value, int index) { + byte[] oldBytes = mData; + int valLen = (value != null) ? value.length : 0; + int insertion = 0; + int newLen, avLen; + + for (int i = 0; i < index && insertion < mData.length; i++) { + insertion += (0xFF & (mData[insertion] + 1)); + } + + avLen = keyBytes.length + valLen + (value != null ? 1 : 0); + newLen = avLen + oldBytes.length + 1; + + mData = new byte[newLen]; + System.arraycopy(oldBytes, 0, mData, 0, insertion); + int secondHalfLen = oldBytes.length - insertion; + System.arraycopy(oldBytes, insertion, mData, newLen - secondHalfLen, secondHalfLen); + mData[insertion] = (byte) avLen; + System.arraycopy(keyBytes, 0, mData, insertion + 1, keyBytes.length); + if (value != null) { + mData[insertion + 1 + keyBytes.length] = mSeperator; + System.arraycopy(value, 0, mData, insertion + keyBytes.length + 2, valLen); + } + } + + /** Return a key in the TXT record by zero-based index. Returns null if index exceeds the total number of keys. */ + private String getKey(int index) { + int avStart = 0; + + for (int i=0; i < index && avStart < mData.length; i++) { + avStart += mData[avStart] + 1; + } + + if (avStart < mData.length) { + int avLen = mData[avStart]; + int aLen = 0; + + for (aLen=0; aLen < avLen; aLen++) { + if (mData[avStart + aLen + 1] == mSeperator) break; + } + return new String(mData, avStart + 1, aLen); + } + return null; + } + + /** + * Look up a key in the TXT record by zero-based index and return its value. + * Returns null if index exceeds the total number of keys. + * Returns null if the key is present with no value. + */ + private byte[] getValue(int index) { + int avStart = 0; + byte[] value = null; + + for (int i=0; i < index && avStart < mData.length; i++) { + avStart += mData[avStart] + 1; + } + + if (avStart < mData.length) { + int avLen = mData[avStart]; + int aLen = 0; + + for (aLen=0; aLen < avLen; aLen++) { + if (mData[avStart + aLen + 1] == mSeperator) { + value = new byte[avLen - aLen - 1]; + System.arraycopy(mData, avStart + aLen + 2, value, 0, avLen - aLen - 1); + break; + } + } + } + return value; + } + + private String getValueAsString(int index) { + byte[] value = this.getValue(index); + return value != null ? new String(value) : null; + } + + private byte[] getValue(String forKey) { + String s = null; + int i; + + for (i = 0; null != (s = this.getKey(i)); i++) { + if (0 == forKey.compareToIgnoreCase(s)) { + return this.getValue(i); + } + } + + return null; + } + + /** + * Return a string representation. + * Example : {key1=value1},{key2=value2}.. + * + * For a key say like "key3" with null value + * {key1=value1},{key2=value2}{key3} + */ + public String toString() { + String a, result = null; + + for (int i = 0; null != (a = this.getKey(i)); i++) { + String av = "{" + a; + String val = this.getValueAsString(i); + if (val != null) + av += "=" + val + "}"; + else + av += "}"; + if (result == null) + result = av; + else + result = result + ", " + av; + } + return result != null ? result : ""; + } + + /** Implement the Parcelable interface */ + public int describeContents() { + return 0; + } + + /** Implement the Parcelable interface */ + public void writeToParcel(Parcel dest, int flags) { + dest.writeByteArray(mData); + } + + /** Implement the Parcelable interface */ + public static final Creator<DnsSdTxtRecord> CREATOR = + new Creator<DnsSdTxtRecord>() { + public DnsSdTxtRecord createFromParcel(Parcel in) { + DnsSdTxtRecord info = new DnsSdTxtRecord(); + in.readByteArray(info.mData); + return info; + } + + public DnsSdTxtRecord[] newArray(int size) { + return new DnsSdTxtRecord[size]; + } + }; +} diff --git a/core/java/android/net/nsd/INsdManager.aidl b/core/java/android/net/nsd/INsdManager.aidl new file mode 100644 index 0000000..077a675 --- /dev/null +++ b/core/java/android/net/nsd/INsdManager.aidl @@ -0,0 +1,29 @@ +/** + * Copyright (c) 2012, 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.net.nsd; + +import android.os.Messenger; + +/** + * Interface that NsdService implements + * + * {@hide} + */ +interface INsdManager +{ + Messenger getMessenger(); +} diff --git a/core/java/android/net/nsd/NetworkServiceInfo.java b/core/java/android/net/nsd/NetworkServiceInfo.java new file mode 100644 index 0000000..34d83d1 --- /dev/null +++ b/core/java/android/net/nsd/NetworkServiceInfo.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2012 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.net.nsd; + +/** + * Interface for a network service. + * + * {@hide} + */ +public interface NetworkServiceInfo { + + String getServiceName(); + void setServiceName(String s); + + String getServiceType(); + void setServiceType(String s); + +} diff --git a/core/java/android/net/nsd/NsdManager.java b/core/java/android/net/nsd/NsdManager.java new file mode 100644 index 0000000..a109a98 --- /dev/null +++ b/core/java/android/net/nsd/NsdManager.java @@ -0,0 +1,394 @@ +/* + * Copyright (C) 2012 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.net.nsd; + +import android.content.Context; +import android.os.Binder; +import android.os.IBinder; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.RemoteException; +import android.os.Messenger; +import android.util.Log; + +import com.android.internal.util.AsyncChannel; +import com.android.internal.util.Protocol; + +/** + * The Network Service Discovery Manager class provides the API for service + * discovery. Service discovery enables applications to discover and connect with services + * on a network. Example applications include a game application discovering another instance + * of the game application or a printer application discovering other printers on a network. + * + * <p> The API is asynchronous and responses to requests from an application are on listener + * callbacks provided by the application. The application needs to do an initialization with + * {@link #initialize} before doing any operation. + * + * <p> Android currently supports DNS based service discovery and it is limited to a local + * network with the use of multicast DNS. In future, this class will be + * extended to support other service discovery mechanisms. + * + * Get an instance of this class by calling {@link android.content.Context#getSystemService(String) + * Context.getSystemService(Context.NSD_SERVICE)}. + * @hide + * + */ +public class NsdManager { + private static final String TAG = "NsdManager"; + INsdManager mService; + + private static final int BASE = Protocol.BASE_NSD_MANAGER; + + /** @hide */ + public static final int DISCOVER_SERVICES = BASE + 1; + /** @hide */ + public static final int DISCOVER_SERVICES_STARTED = BASE + 2; + /** @hide */ + public static final int DISCOVER_SERVICES_FAILED = BASE + 3; + /** @hide */ + public static final int SERVICE_FOUND = BASE + 4; + /** @hide */ + public static final int SERVICE_LOST = BASE + 5; + + /** @hide */ + public static final int STOP_DISCOVERY = BASE + 6; + /** @hide */ + public static final int STOP_DISCOVERY_FAILED = BASE + 7; + /** @hide */ + public static final int STOP_DISCOVERY_SUCCEEDED = BASE + 8; + + /** @hide */ + public static final int REGISTER_SERVICE = BASE + 9; + /** @hide */ + public static final int REGISTER_SERVICE_FAILED = BASE + 10; + /** @hide */ + public static final int REGISTER_SERVICE_SUCCEEDED = BASE + 11; + + /** @hide */ + public static final int UPDATE_SERVICE = BASE + 12; + /** @hide */ + public static final int UPDATE_SERVICE_FAILED = BASE + 13; + /** @hide */ + public static final int UPDATE_SERVICE_SUCCEEDED = BASE + 14; + + /** @hide */ + public static final int RESOLVE_SERVICE = BASE + 15; + /** @hide */ + public static final int RESOLVE_SERVICE_FAILED = BASE + 16; + /** @hide */ + public static final int RESOLVE_SERVICE_SUCCEEDED = BASE + 17; + + /** + * Create a new Nsd instance. Applications use + * {@link android.content.Context#getSystemService Context.getSystemService()} to retrieve + * {@link android.content.Context#NSD_SERVICE Context.NSD_SERVICE}. + * @param service the Binder interface + * @hide - hide this because it takes in a parameter of type INsdManager, which + * is a system private class. + */ + public NsdManager(INsdManager service) { + mService = service; + } + + /** + * Indicates that the operation failed due to an internal error. + */ + public static final int ERROR = 0; + + /** + * Indicates that the operation failed because service discovery is unsupported on the device. + */ + public static final int UNSUPPORTED = 1; + + /** + * Indicates that the operation failed because the framework is busy and + * unable to service the request + */ + public static final int BUSY = 2; + + /** Interface for callback invocation when framework channel is connected or lost */ + public interface ChannelListener { + public void onChannelConnected(Channel c); + /** + * The channel to the framework has been disconnected. + * Application could try re-initializing using {@link #initialize} + */ + public void onChannelDisconnected(); + } + + public interface ActionListener { + + public void onFailure(int errorCode); + + public void onSuccess(); + } + + public interface DnsSdDiscoveryListener { + + public void onFailure(int errorCode); + + public void onStarted(String registrationType); + + public void onServiceFound(DnsSdServiceInfo serviceInfo); + + public void onServiceLost(DnsSdServiceInfo serviceInfo); + + } + + public interface DnsSdRegisterListener { + + public void onFailure(int errorCode); + + public void onServiceRegistered(int registeredId, DnsSdServiceInfo serviceInfo); + } + + public interface DnsSdUpdateRegistrationListener { + + public void onFailure(int errorCode); + + public void onServiceUpdated(int registeredId, DnsSdTxtRecord txtRecord); + } + + public interface DnsSdResolveListener { + + public void onFailure(int errorCode); + + public void onServiceResolved(DnsSdServiceInfo serviceInfo); + } + + /** + * A channel that connects the application to the NetworkService framework. + * Most service operations require a Channel as an argument. An instance of Channel is obtained + * by doing a call on {@link #initialize} + */ + public static class Channel { + Channel(Looper looper, ChannelListener l) { + mAsyncChannel = new AsyncChannel(); + mHandler = new ServiceHandler(looper); + mChannelListener = l; + } + private ChannelListener mChannelListener; + private DnsSdDiscoveryListener mDnsSdDiscoveryListener; + private ActionListener mDnsSdStopDiscoveryListener; + private DnsSdRegisterListener mDnsSdRegisterListener; + private DnsSdUpdateRegistrationListener mDnsSdUpdateListener; + private DnsSdResolveListener mDnsSdResolveListener; + + AsyncChannel mAsyncChannel; + ServiceHandler mHandler; + class ServiceHandler extends Handler { + ServiceHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message message) { + switch (message.what) { + case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: + mAsyncChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION); + break; + case AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED: + if (mChannelListener != null) { + mChannelListener.onChannelConnected(Channel.this); + } + break; + case AsyncChannel.CMD_CHANNEL_DISCONNECTED: + if (mChannelListener != null) { + mChannelListener.onChannelDisconnected(); + mChannelListener = null; + } + break; + case DISCOVER_SERVICES_STARTED: + if (mDnsSdDiscoveryListener != null) { + mDnsSdDiscoveryListener.onStarted((String) message.obj); + } + break; + case DISCOVER_SERVICES_FAILED: + if (mDnsSdDiscoveryListener != null) { + mDnsSdDiscoveryListener.onFailure(message.arg1); + } + break; + case SERVICE_FOUND: + if (mDnsSdDiscoveryListener != null) { + mDnsSdDiscoveryListener.onServiceFound( + (DnsSdServiceInfo) message.obj); + } + break; + case SERVICE_LOST: + if (mDnsSdDiscoveryListener != null) { + mDnsSdDiscoveryListener.onServiceLost( + (DnsSdServiceInfo) message.obj); + } + break; + case STOP_DISCOVERY_FAILED: + if (mDnsSdStopDiscoveryListener != null) { + mDnsSdStopDiscoveryListener.onFailure(message.arg1); + } + break; + case STOP_DISCOVERY_SUCCEEDED: + if (mDnsSdStopDiscoveryListener != null) { + mDnsSdStopDiscoveryListener.onSuccess(); + } + break; + case REGISTER_SERVICE_FAILED: + if (mDnsSdRegisterListener != null) { + mDnsSdRegisterListener.onFailure(message.arg1); + } + break; + case REGISTER_SERVICE_SUCCEEDED: + if (mDnsSdRegisterListener != null) { + mDnsSdRegisterListener.onServiceRegistered(message.arg1, + (DnsSdServiceInfo) message.obj); + } + break; + case UPDATE_SERVICE_FAILED: + if (mDnsSdUpdateListener != null) { + mDnsSdUpdateListener.onFailure(message.arg1); + } + break; + case UPDATE_SERVICE_SUCCEEDED: + if (mDnsSdUpdateListener != null) { + mDnsSdUpdateListener.onServiceUpdated(message.arg1, + (DnsSdTxtRecord) message.obj); + } + break; + case RESOLVE_SERVICE_FAILED: + if (mDnsSdResolveListener != null) { + mDnsSdResolveListener.onFailure(message.arg1); + } + break; + case RESOLVE_SERVICE_SUCCEEDED: + if (mDnsSdResolveListener != null) { + mDnsSdResolveListener.onServiceResolved( + (DnsSdServiceInfo) message.obj); + } + break; + default: + Log.d(TAG, "Ignored " + message); + break; + } + } + } + } + + /** + * Registers the application with the service discovery framework. This function + * must be the first to be called before any other operations are performed. No service + * discovery operations must be performed until the ChannelListener callback notifies + * that the channel is connected + * + * @param srcContext is the context of the source + * @param srcLooper is the Looper on which the callbacks are receivied + * @param listener for callback at loss of framework communication. + */ + public void initialize(Context srcContext, Looper srcLooper, ChannelListener listener) { + Messenger messenger = getMessenger(); + if (messenger == null) throw new RuntimeException("Failed to initialize"); + if (listener == null) throw new IllegalArgumentException("ChannelListener cannot be null"); + + Channel c = new Channel(srcLooper, listener); + c.mAsyncChannel.connect(srcContext, c.mHandler, messenger); + } + + /** + * Set the listener for service discovery. Can be null. + */ + public void setDiscoveryListener(Channel c, DnsSdDiscoveryListener b) { + if (c == null) throw new IllegalArgumentException("Channel needs to be initialized"); + c.mDnsSdDiscoveryListener = b; + } + + /** + * Set the listener for stop service discovery. Can be null. + */ + public void setStopDiscoveryListener(Channel c, ActionListener a) { + if (c == null) throw new IllegalArgumentException("Channel needs to be initialized"); + c.mDnsSdStopDiscoveryListener = a; + } + + /** + * Set the listener for service registration. Can be null. + */ + public void setRegisterListener(Channel c, DnsSdRegisterListener b) { + if (c == null) throw new IllegalArgumentException("Channel needs to be initialized"); + c.mDnsSdRegisterListener = b; + } + + /** + * Set the listener for service registration. Can be null. + */ + public void setUpdateRegistrationListener(Channel c, DnsSdUpdateRegistrationListener b) { + if (c == null) throw new IllegalArgumentException("Channel needs to be initialized"); + c.mDnsSdUpdateListener = b; + } + + /** + * Set the listener for service resolution. Can be null. + */ + public void setResolveListener(Channel c, DnsSdResolveListener b) { + if (c == null) throw new IllegalArgumentException("Channel needs to be initialized"); + c.mDnsSdResolveListener = b; + } + + public void registerService(Channel c, DnsSdServiceInfo serviceInfo) { + if (c == null) throw new IllegalArgumentException("Channel needs to be initialized"); + if (serviceInfo == null) throw new IllegalArgumentException("Null serviceInfo"); + c.mAsyncChannel.sendMessage(REGISTER_SERVICE, serviceInfo); + } + + public void updateService(Channel c, int registeredId, DnsSdTxtRecord txtRecord) { + if (c == null) throw new IllegalArgumentException("Channel needs to be initialized"); + c.mAsyncChannel.sendMessage(UPDATE_SERVICE, registeredId, 0, txtRecord); + } + + public void discoverServices(Channel c, String serviceType) { + if (c == null) throw new IllegalArgumentException("Channel needs to be initialized"); + if (c.mDnsSdDiscoveryListener == null) throw new + IllegalStateException("Discovery listener needs to be set first"); + DnsSdServiceInfo s = new DnsSdServiceInfo(); + s.setServiceType(serviceType); + c.mAsyncChannel.sendMessage(DISCOVER_SERVICES, s); + } + + public void stopServiceDiscovery(Channel c) { + if (c == null) throw new IllegalArgumentException("Channel needs to be initialized"); + c.mAsyncChannel.sendMessage(STOP_DISCOVERY); + } + + public void resolveService(Channel c, DnsSdServiceInfo serviceInfo) { + if (c == null) throw new IllegalArgumentException("Channel needs to be initialized"); + if (serviceInfo == null) throw new IllegalArgumentException("Null serviceInfo"); + if (c.mDnsSdResolveListener == null) throw new + IllegalStateException("Resolve listener needs to be set first"); + c.mAsyncChannel.sendMessage(RESOLVE_SERVICE, serviceInfo); + } + + /** + * Get a reference to NetworkService handler. This is used to establish + * an AsyncChannel communication with the service + * + * @return Messenger pointing to the NetworkService handler + */ + private Messenger getMessenger() { + try { + return mService.getMessenger(); + } catch (RemoteException e) { + return null; + } + } +} diff --git a/core/java/android/nfc/INdefPushCallback.aidl b/core/java/android/nfc/INdefPushCallback.aidl index e60a5b0..4e79822 100644 --- a/core/java/android/nfc/INdefPushCallback.aidl +++ b/core/java/android/nfc/INdefPushCallback.aidl @@ -17,6 +17,7 @@ package android.nfc; import android.nfc.NdefMessage; +import android.net.Uri; /** * @hide @@ -24,5 +25,7 @@ import android.nfc.NdefMessage; interface INdefPushCallback { NdefMessage createMessage(); + Uri getUri(); + String getMimeType(); void onNdefPushComplete(); } diff --git a/core/java/android/nfc/INfcTag.aidl b/core/java/android/nfc/INfcTag.aidl index 2223255..3ac1dcc 100644 --- a/core/java/android/nfc/INfcTag.aidl +++ b/core/java/android/nfc/INfcTag.aidl @@ -45,4 +45,5 @@ interface INfcTag void resetTimeouts(); boolean canMakeReadOnly(int ndefType); int getMaxTransceiveLength(int technology); + boolean getExtendedLengthApdusSupported(); } diff --git a/core/java/android/nfc/NfcActivityManager.java b/core/java/android/nfc/NfcActivityManager.java index 2c73056..8335459 100644 --- a/core/java/android/nfc/NfcActivityManager.java +++ b/core/java/android/nfc/NfcActivityManager.java @@ -18,6 +18,7 @@ package android.nfc; import android.app.Activity; import android.app.Application; +import android.net.Uri; import android.os.Bundle; import android.os.RemoteException; import android.util.Log; @@ -107,6 +108,8 @@ public final class NfcActivityManager extends INdefPushCallback.Stub NdefMessage ndefMessage = null; // static NDEF message NfcAdapter.CreateNdefMessageCallback ndefMessageCallback = null; NfcAdapter.OnNdefPushCompleteCallback onNdefPushCompleteCallback = null; + Uri uri = null; + String mimeType = null; public NfcActivityState(Activity activity) { if (activity.getWindow().isDestroyed()) { throw new IllegalStateException("activity is already destroyed"); @@ -121,12 +124,14 @@ public final class NfcActivityManager extends INdefPushCallback.Stub ndefMessage = null; ndefMessageCallback = null; onNdefPushCompleteCallback = null; + uri = null; + mimeType = null; } @Override public String toString() { StringBuilder s = new StringBuilder("[").append(" "); s.append(ndefMessage).append(" ").append(ndefMessageCallback).append(" "); - s.append(onNdefPushCompleteCallback).append("]"); + s.append(onNdefPushCompleteCallback).append(" ").append(uri).append("]"); return s.toString(); } } @@ -175,6 +180,19 @@ public final class NfcActivityManager extends INdefPushCallback.Stub mDefaultEvent = new NfcEvent(mAdapter); } + public void setNdefPushContentUri(Activity activity, String mimeType, Uri uri) { + boolean isResumed; + synchronized (NfcActivityManager.this) { + NfcActivityState state = getActivityState(activity); + state.uri = uri; + state.mimeType = mimeType; + isResumed = state.resumed; + } + if (isResumed) { + requestNfcServiceCallback(true); + } + } + public void setNdefPushMessage(Activity activity, NdefMessage message) { boolean isResumed; synchronized (NfcActivityManager.this) { @@ -249,6 +267,26 @@ public final class NfcActivityManager extends INdefPushCallback.Stub /** Callback from NFC service, usually on binder thread */ @Override + public Uri getUri() { + synchronized (NfcActivityManager.this) { + NfcActivityState state = findResumedActivityState(); + if (state == null) return null; + + return state.uri; + } + } + /** Callback from NFC service, usually on binder thread */ + @Override + public String getMimeType() { + synchronized (NfcActivityManager.this) { + NfcActivityState state = findResumedActivityState(); + if (state == null) return null; + + return state.mimeType; + } + } + /** Callback from NFC service, usually on binder thread */ + @Override public void onNdefPushComplete() { NfcAdapter.OnNdefPushCompleteCallback callback; synchronized (NfcActivityManager.this) { diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java index b7a7bd5..917751c 100644 --- a/core/java/android/nfc/NfcAdapter.java +++ b/core/java/android/nfc/NfcAdapter.java @@ -28,6 +28,7 @@ import android.content.Context; import android.content.IntentFilter; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; +import android.net.Uri; import android.nfc.tech.MifareClassic; import android.nfc.tech.Ndef; import android.nfc.tech.NfcA; @@ -555,6 +556,18 @@ public final class NfcAdapter { } } + //TODO: Consider a callback alternative + //TOOD: See if we get rid of mimeType + //TODO: make sure NFC service has permission for URI + //TODO: javadoc + /** @hide */ + public void setBeamPushUri(String mimeType, Uri uri, Activity activity) { + if (activity == null) { + throw new NullPointerException("activity cannot be null"); + } + mNfcActivityManager.setNdefPushContentUri(activity, mimeType, uri); + } + /** * Set a static {@link NdefMessage} to send using Android Beam (TM). * @@ -580,7 +593,18 @@ public final class NfcAdapter { * and/or {@link #setNdefPushMessageCallback} is called with a null callback, * then NDEF push will be completely disabled for the specified activity(s). * This also disables any default NDEF message the Android OS would have - * otherwise sent on your behalf. + * otherwise sent on your behalf for those activity(s). + * + * <p>If you want to prevent the Android OS from sending default NDEF + * messages completely (for all activities), you can include a + * <code><meta-data></code> element inside the <code><application></code> + * element of your AndroidManifest.xml file, like this: + * <pre>{@code + * <application ...> + * <meta-data android:name="android.nfc.disable_beam_default" + * android:value="true" /> + * </application> + * }</pre> * * <p>The API allows for multiple activities to be specified at a time, * but it is strongly recommended to just register one at a time, @@ -664,7 +688,18 @@ public final class NfcAdapter { * and/or {@link #setNdefPushMessageCallback} is called with a null callback, * then NDEF push will be completely disabled for the specified activity(s). * This also disables any default NDEF message the Android OS would have - * otherwise sent on your behalf. + * otherwise sent on your behalf for those activity(s). + * + * <p>If you want to prevent the Android OS from sending default NDEF + * messages completely (for all activities), you can include a + * <code><meta-data></code> element inside the <code><application></code> + * element of your AndroidManifest.xml file, like this: + * <pre>{@code + * <application ...> + * <meta-data android:name="android.nfc.disable_beam_default" + * android:value="true" /> + * </application> + * }</pre> * * <p>The API allows for multiple activities to be specified at a time, * but it is strongly recommended to just register one at a time, diff --git a/core/java/android/nfc/tech/IsoDep.java b/core/java/android/nfc/tech/IsoDep.java index 1859877..089b159 100644 --- a/core/java/android/nfc/tech/IsoDep.java +++ b/core/java/android/nfc/tech/IsoDep.java @@ -179,4 +179,27 @@ public final class IsoDep extends BasicTagTechnology { public int getMaxTransceiveLength() { return getMaxTransceiveLengthInternal(); } + + /** + * <p>Standard APDUs have a 1-byte length field, allowing a maximum of + * 255 payload bytes, which results in a maximum APDU length of 261 bytes. + * + * <p>Extended length APDUs have a 3-byte length field, allowing 65535 + * payload bytes. + * + * <p>Some NFC adapters, like the one used in the Nexus S and the Galaxy Nexus + * do not support extended length APDUs. They are expected to be well-supported + * in the future though. Use this method to check for extended length APDU + * support. + * + * @return whether the NFC adapter on this device supports extended length APDUs. + */ + public boolean isExtendedLengthApduSupported() { + try { + return mTag.getTagService().getExtendedLengthApdusSupported(); + } catch (RemoteException e) { + Log.e(TAG, "NFC service dead", e); + return false; + } + } } diff --git a/core/java/android/preference/Preference.java b/core/java/android/preference/Preference.java index 74a376d..8df4339 100644 --- a/core/java/android/preference/Preference.java +++ b/core/java/android/preference/Preference.java @@ -497,27 +497,30 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis * @see #onCreateView(ViewGroup) */ protected void onBindView(View view) { - TextView textView = (TextView) view.findViewById(com.android.internal.R.id.title); - if (textView != null) { - textView.setText(getTitle()); + final TextView titleView = (TextView) view.findViewById( + com.android.internal.R.id.title); + if (titleView != null) { + final CharSequence title = getTitle(); + if (!TextUtils.isEmpty(title)) { + titleView.setText(title); + titleView.setVisibility(View.VISIBLE); + } else { + titleView.setVisibility(View.GONE); + } } - - textView = (TextView) view.findViewById(com.android.internal.R.id.summary); - if (textView != null) { + + final TextView summaryView = (TextView) view.findViewById( + com.android.internal.R.id.summary); + if (summaryView != null) { final CharSequence summary = getSummary(); if (!TextUtils.isEmpty(summary)) { - if (textView.getVisibility() != View.VISIBLE) { - textView.setVisibility(View.VISIBLE); - } - - textView.setText(getSummary()); + summaryView.setText(summary); + summaryView.setVisibility(View.VISIBLE); } else { - if (textView.getVisibility() != View.GONE) { - textView.setVisibility(View.GONE); - } + summaryView.setVisibility(View.GONE); } } - + ImageView imageView = (ImageView) view.findViewById(com.android.internal.R.id.icon); if (imageView != null) { if (mIconResId != 0 || mIcon != null) { diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index d74ccb8..830a85f 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -1506,13 +1506,22 @@ public final class Settings { public static final String VOLUME_MASTER = "volume_master"; /** - * Whether the notifications should use the ring volume (value of 1) or a separate - * notification volume (value of 0). In most cases, users will have this enabled so the - * notification and ringer volumes will be the same. However, power users can disable this - * and use the separate notification volume control. + * Master volume mute (int 1 = mute, 0 = not muted). + * + * @hide + */ + public static final String VOLUME_MASTER_MUTE = "volume_master_mute"; + + /** + * Whether the notifications should use the ring volume (value of 1) or + * a separate notification volume (value of 0). In most cases, users + * will have this enabled so the notification and ringer volumes will be + * the same. However, power users can disable this and use the separate + * notification volume control. * <p> - * Note: This is a one-off setting that will be removed in the future when there is profile - * support. For this reason, it is kept hidden from the public APIs. + * Note: This is a one-off setting that will be removed in the future + * when there is profile support. For this reason, it is kept hidden + * from the public APIs. * * @hide * @deprecated diff --git a/core/java/android/view/DisplayList.java b/core/java/android/view/DisplayList.java index 33631b7..fba73fbd 100644 --- a/core/java/android/view/DisplayList.java +++ b/core/java/android/view/DisplayList.java @@ -149,6 +149,15 @@ public abstract class DisplayList { public abstract void setAlpha(float alpha); /** + * Sets whether the DisplayList renders content which overlaps. Non-overlapping rendering + * can use a fast path for alpha that avoids rendering to an offscreen buffer. + * + * @param hasOverlappingRendering + * @see android.view.View#hasOverlappingRendering() + */ + public abstract void setHasOverlappingRendering(boolean hasOverlappingRendering); + + /** * Sets the translationX value for the DisplayList * * @param translationX The translationX value of the DisplayList diff --git a/core/java/android/view/GLES20DisplayList.java b/core/java/android/view/GLES20DisplayList.java index bc3bce0..f3618eb 100644 --- a/core/java/android/view/GLES20DisplayList.java +++ b/core/java/android/view/GLES20DisplayList.java @@ -147,6 +147,15 @@ class GLES20DisplayList extends DisplayList { } @Override + public void setHasOverlappingRendering(boolean hasOverlappingRendering) { + try { + nSetHasOverlappingRendering(getNativeDisplayList(), hasOverlappingRendering); + } catch (IllegalStateException e) { + // invalid DisplayList okay: we'll set current values the next time we render to it + } + } + + @Override public void setTranslationX(float translationX) { try { nSetTranslationX(getNativeDisplayList(), translationX); @@ -335,6 +344,8 @@ class GLES20DisplayList extends DisplayList { private static native void nSetClipChildren(int displayList, boolean clipChildren); private static native void nSetApplicationScale(int displayList, float scale); private static native void nSetAlpha(int displayList, float alpha); + private static native void nSetHasOverlappingRendering(int displayList, + boolean hasOverlappingRendering); private static native void nSetTranslationX(int displayList, float translationX); private static native void nSetTranslationY(int displayList, float translationY); private static native void nSetRotation(int displayList, float rotation); diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java index b100a0c..b8c692a 100644 --- a/core/java/android/view/HardwareRenderer.java +++ b/core/java/android/view/HardwareRenderer.java @@ -87,7 +87,7 @@ public abstract class HardwareRenderer { /** * System property used to enable or disable hardware rendering profiling. * The default value of this property is assumed to be false. - * + * * When profiling is enabled, the adb shell dumpsys gfxinfo command will * output extra information about the time taken to execute by the last * frames. @@ -99,6 +99,20 @@ public abstract class HardwareRenderer { static final String PROFILE_PROPERTY = "hwui.profile"; /** + * System property used to specify the number of frames to be used + * when doing hardware rendering profiling. + * The default value of this property is #PROFILE_MAX_FRAMES. + * + * When profiling is enabled, the adb shell dumpsys gfxinfo command will + * output extra information about the time taken to execute by the last + * frames. + * + * Possible values: + * "60", to set the limit of frames to 60 + */ + static final String PROFILE_MAXFRAMES_PROPERTY = "hwui.profile.maxframes"; + + /** * System property used to debug EGL configuration choice. * * Possible values: @@ -579,7 +593,13 @@ public abstract class HardwareRenderer { } if (mProfileEnabled) { - mProfileData = new float[PROFILE_MAX_FRAMES * PROFILE_FRAME_DATA_COUNT]; + property = SystemProperties.get(PROFILE_MAXFRAMES_PROPERTY, + Integer.toString(PROFILE_MAX_FRAMES)); + int maxProfileFrames = Integer.valueOf(property); + mProfileData = new float[maxProfileFrames * PROFILE_FRAME_DATA_COUNT]; + for (int i = 0; i < mProfileData.length; i += PROFILE_FRAME_DATA_COUNT) { + mProfileData[i] = mProfileData[i + 1] = mProfileData[i + 2] = -1; + } } else { mProfileData = null; } @@ -596,9 +616,14 @@ public abstract class HardwareRenderer { if (mProfileEnabled) { pw.printf("\n\tDraw\tProcess\tExecute\n"); for (int i = 0; i < mProfileData.length; i += PROFILE_FRAME_DATA_COUNT) { + if (mProfileData[i] < 0) { + break; + } pw.printf("\t%3.2f\t%3.2f\t%3.2f\n", mProfileData[i], mProfileData[i + 1], mProfileData[i + 2]); + mProfileData[i] = mProfileData[i + 1] = mProfileData[i + 2] = -1; } + mProfileCurrentFrame = mProfileData.length; } } @@ -1046,10 +1071,6 @@ public abstract class HardwareRenderer { Log.d(ViewDebug.DEBUG_LATENCY_TAG, "- getDisplayList() took " + total + "ms"); } - if (View.USE_DISPLAY_LIST_PROPERTIES) { - Log.d("DLProperties", "getDisplayList():\t" + - mProfileData[mProfileCurrentFrame]); - } } if (displayList != null) { diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index a9421f0..5d7c8cd 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -71,6 +71,7 @@ import android.view.inputmethod.InputMethodManager; import android.widget.ScrollBarDrawable; import static android.os.Build.VERSION_CODES.*; +import static java.lang.Math.max; import com.android.internal.R; import com.android.internal.util.Predicate; @@ -126,7 +127,7 @@ import java.util.concurrent.CopyOnWriteArrayList; * example, all views will let you set a listener to be notified when the view * gains or loses focus. You can register such a listener using * {@link #setOnFocusChangeListener(android.view.View.OnFocusChangeListener)}. - * Other view subclasses offer more specialized listeners. For example, a Button + * Other view subclasses offer more specialized listeners. For example, a Button * exposes a listener to notify clients when the button is clicked.</li> * <li><strong>Set visibility:</strong> You can hide or show views using * {@link #setVisibility(int)}.</li> @@ -579,6 +580,7 @@ import java.util.concurrent.CopyOnWriteArrayList; * @attr ref android.R.styleable#View_duplicateParentState * @attr ref android.R.styleable#View_id * @attr ref android.R.styleable#View_requiresFadingEdge + * @attr ref android.R.styleable#View_fadeScrollbars * @attr ref android.R.styleable#View_fadingEdgeLength * @attr ref android.R.styleable#View_filterTouchesWhenObscured * @attr ref android.R.styleable#View_fitsSystemWindows @@ -624,6 +626,7 @@ import java.util.concurrent.CopyOnWriteArrayList; * @attr ref android.R.styleable#View_scrollbarAlwaysDrawVerticalTrack * @attr ref android.R.styleable#View_soundEffectsEnabled * @attr ref android.R.styleable#View_tag + * @attr ref android.R.styleable#View_textAlignment * @attr ref android.R.styleable#View_transformPivotX * @attr ref android.R.styleable#View_transformPivotY * @attr ref android.R.styleable#View_translationX @@ -1827,15 +1830,15 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal public static final int TEXT_DIRECTION_LOCALE = 5; /** - * Bit shift to get the horizontal layout direction. (bits after LAYOUT_DIRECTION_RESOLVED) - * @hide + * Default text direction is inherited */ - static final int TEXT_DIRECTION_MASK_SHIFT = 6; + protected static int TEXT_DIRECTION_DEFAULT = TEXT_DIRECTION_INHERIT; /** - * Default text direction is inherited + * Bit shift to get the horizontal layout direction. (bits after LAYOUT_DIRECTION_RESOLVED) + * @hide */ - protected static int TEXT_DIRECTION_DEFAULT = TEXT_DIRECTION_INHERIT; + static final int TEXT_DIRECTION_MASK_SHIFT = 6; /** * Mask for use with private flags indicating bits used for text direction. @@ -1882,6 +1885,113 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal static final int TEXT_DIRECTION_RESOLVED_DEFAULT = TEXT_DIRECTION_FIRST_STRONG << TEXT_DIRECTION_RESOLVED_MASK_SHIFT; + /* + * Default text alignment. The text alignment of this View is inherited from its parent. + * Use with {@link #setTextAlignment(int)} + */ + public static final int TEXT_ALIGNMENT_INHERIT = 0; + + /** + * Default for the root view. The gravity determines the text alignment, ALIGN_NORMAL, + * ALIGN_CENTER, or ALIGN_OPPOSITE, which are relative to each paragraph’s text direction. + * + * Use with {@link #setTextAlignment(int)} + */ + public static final int TEXT_ALIGNMENT_GRAVITY = 1; + + /** + * Align to the start of the paragraph, e.g. ALIGN_NORMAL. + * + * Use with {@link #setTextAlignment(int)} + */ + public static final int TEXT_ALIGNMENT_TEXT_START = 2; + + /** + * Align to the end of the paragraph, e.g. ALIGN_OPPOSITE. + * + * Use with {@link #setTextAlignment(int)} + */ + public static final int TEXT_ALIGNMENT_TEXT_END = 3; + + /** + * Center the paragraph, e.g. ALIGN_CENTER. + * + * Use with {@link #setTextAlignment(int)} + */ + public static final int TEXT_ALIGNMENT_CENTER = 4; + + /** + * Align to the start of the view, which is ALIGN_LEFT if the view’s resolved + * layoutDirection is LTR, and ALIGN_RIGHT otherwise. + * + * Use with {@link #setTextAlignment(int)} + */ + public static final int TEXT_ALIGNMENT_VIEW_START = 5; + + /** + * Align to the end of the view, which is ALIGN_RIGHT if the view’s resolved + * layoutDirection is LTR, and ALIGN_LEFT otherwise. + * + * Use with {@link #setTextAlignment(int)} + */ + public static final int TEXT_ALIGNMENT_VIEW_END = 6; + + /** + * Default text alignment is inherited + */ + protected static int TEXT_ALIGNMENT_DEFAULT = TEXT_ALIGNMENT_GRAVITY; + + /** + * Bit shift to get the horizontal layout direction. (bits after DRAG_HOVERED) + * @hide + */ + static final int TEXT_ALIGNMENT_MASK_SHIFT = 13; + + /** + * Mask for use with private flags indicating bits used for text alignment. + * @hide + */ + static final int TEXT_ALIGNMENT_MASK = 0x00000007 << TEXT_ALIGNMENT_MASK_SHIFT; + + /** + * Array of text direction flags for mapping attribute "textAlignment" to correct + * flag value. + * @hide + */ + private static final int[] TEXT_ALIGNMENT_FLAGS = { + TEXT_ALIGNMENT_INHERIT << TEXT_ALIGNMENT_MASK_SHIFT, + TEXT_ALIGNMENT_GRAVITY << TEXT_ALIGNMENT_MASK_SHIFT, + TEXT_ALIGNMENT_TEXT_START << TEXT_ALIGNMENT_MASK_SHIFT, + TEXT_ALIGNMENT_TEXT_END << TEXT_ALIGNMENT_MASK_SHIFT, + TEXT_ALIGNMENT_CENTER << TEXT_ALIGNMENT_MASK_SHIFT, + TEXT_ALIGNMENT_VIEW_START << TEXT_ALIGNMENT_MASK_SHIFT, + TEXT_ALIGNMENT_VIEW_END << TEXT_ALIGNMENT_MASK_SHIFT + }; + + /** + * Indicates whether the view text alignment has been resolved. + * @hide + */ + static final int TEXT_ALIGNMENT_RESOLVED = 0x00000008 << TEXT_ALIGNMENT_MASK_SHIFT; + + /** + * Bit shift to get the resolved text alignment. + * @hide + */ + static final int TEXT_ALIGNMENT_RESOLVED_MASK_SHIFT = 17; + + /** + * Mask for use with private flags indicating bits used for text alignment. + * @hide + */ + static final int TEXT_ALIGNMENT_RESOLVED_MASK = 0x00000007 << TEXT_ALIGNMENT_RESOLVED_MASK_SHIFT; + + /** + * Indicates whether if the view text alignment has been resolved to gravity + */ + public static final int TEXT_ALIGNMENT_RESOLVED_DEFAULT = + TEXT_ALIGNMENT_GRAVITY << TEXT_ALIGNMENT_RESOLVED_MASK_SHIFT; + /* End of masks for mPrivateFlags2 */ @@ -1926,7 +2036,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * system UI to enter an unobtrusive "low profile" mode. * * <p>This is for use in games, book readers, video players, or any other - * "immersive" application where the usual system chrome is deemed too distracting. + * "immersive" application where the usual system chrome is deemed too distracting. * * <p>In low profile mode, the status bar and/or navigation icons may dim. * @@ -1942,7 +2052,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * {@link #SYSTEM_UI_FLAG_LOW_PROFILE}; on devices that draw essential navigation controls * (Home, Back, and the like) on screen, <code>SYSTEM_UI_FLAG_HIDE_NAVIGATION</code> will cause * those to disappear. This is useful (in conjunction with the - * {@link android.view.WindowManager.LayoutParams#FLAG_FULLSCREEN FLAG_FULLSCREEN} and + * {@link android.view.WindowManager.LayoutParams#FLAG_FULLSCREEN FLAG_FULLSCREEN} and * {@link android.view.WindowManager.LayoutParams#FLAG_LAYOUT_IN_SCREEN FLAG_LAYOUT_IN_SCREEN} * window flags) for displaying content using every last pixel on the display. * @@ -2339,7 +2449,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal */ private int mPrevWidth = -1; private int mPrevHeight = -1; - + /** * The degrees rotation around the vertical axis through the pivot point. */ @@ -2546,7 +2656,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal */ int mOldHeightMeasureSpec = Integer.MIN_VALUE; - private Drawable mBGDrawable; + private Drawable mBackground; private int mBackgroundResource; private boolean mBackgroundSizeChanged; @@ -2620,7 +2730,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal /** * Set to true when drawing cache is enabled and cannot be created. - * + * * @hide */ public boolean mCachingFailed; @@ -2830,19 +2940,6 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal private boolean mSendingHoverAccessibilityEvents; /** - * Delegate for injecting accessiblity functionality. - */ - AccessibilityDelegate mAccessibilityDelegate; - - /** - * Consistency verifier for debugging purposes. - * @hide - */ - protected final InputEventConsistencyVerifier mInputEventConsistencyVerifier = - InputEventConsistencyVerifier.isInstrumentationEnabled() ? - new InputEventConsistencyVerifier(this, 0) : null; - - /** * Simple constructor to use when creating a view from code. * * @param context The Context the view is running in, through which it can @@ -2854,7 +2951,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal mViewFlags = SOUND_EFFECTS_ENABLED | HAPTIC_FEEDBACK_ENABLED; // Set layout and text direction defaults mPrivateFlags2 = (LAYOUT_DIRECTION_DEFAULT << LAYOUT_DIRECTION_MASK_SHIFT) | - (TEXT_DIRECTION_DEFAULT << TEXT_DIRECTION_MASK_SHIFT); + (TEXT_DIRECTION_DEFAULT << TEXT_DIRECTION_MASK_SHIFT) | + (TEXT_ALIGNMENT_DEFAULT << TEXT_ALIGNMENT_MASK_SHIFT); mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); setOverScrollMode(OVER_SCROLL_IF_CONTENT_SCROLLS); mUserPaddingStart = -1; @@ -2863,6 +2961,19 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } /** + * Delegate for injecting accessiblity functionality. + */ + AccessibilityDelegate mAccessibilityDelegate; + + /** + * Consistency verifier for debugging purposes. + * @hide + */ + protected final InputEventConsistencyVerifier mInputEventConsistencyVerifier = + InputEventConsistencyVerifier.isInstrumentationEnabled() ? + new InputEventConsistencyVerifier(this, 0) : null; + + /** * Constructor that is called when inflating a view from XML. This is called * when a view is being constructed from an XML file, supplying attributes * that were specified in the XML file. This version uses a default style of @@ -3222,6 +3333,13 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal mPrivateFlags2 |= TEXT_DIRECTION_FLAGS[textDirection]; } break; + case R.styleable.View_textAlignment: + // Clear any text alignment flag already set + mPrivateFlags2 &= ~TEXT_ALIGNMENT_MASK; + // Set the text alignment flag depending on the value of the attribute + final int textAlignment = a.getInt(attr, TEXT_ALIGNMENT_DEFAULT); + mPrivateFlags2 |= TEXT_ALIGNMENT_FLAGS[textAlignment]; + break; } } @@ -3230,7 +3348,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal setOverScrollMode(overScrollMode); if (background != null) { - setBackgroundDrawable(background); + setBackground(background); } // Cache user padding as we cannot fully resolve padding here (we dont have yet the resolved @@ -3494,6 +3612,11 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } } + private ScrollabilityCache getScrollCache() { + initScrollCache(); + return mScrollCache; + } + /** * Set the position of the vertical scroll bar. Should be one of * {@link #SCROLLBAR_POSITION_DEFAULT}, {@link #SCROLLBAR_POSITION_LEFT} or @@ -3909,8 +4032,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * <p> * <strong>Note:</strong> When a View clears focus the framework is trying * to give focus to the first focusable View from the top. Hence, if this - * View is the first from the top that can take focus, then its focus will - * not be cleared nor will the focus change callback be invoked. + * View is the first from the top that can take focus, then all callbacks + * related to clearing focus will be invoked after wich the framework will + * give focus to this view. * </p> */ public void clearFocus() { @@ -3927,25 +4051,22 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal onFocusChanged(false, 0, null); refreshDrawableState(); + + ensureInputFocusOnFirstFocusable(); } } - /** - * Called to clear the focus of a view that is about to be removed. - * Doesn't call clearChildFocus, which prevents this view from taking - * focus again before it has been removed from the parent - */ - void clearFocusForRemoval() { - if ((mPrivateFlags & FOCUSED) != 0) { - mPrivateFlags &= ~FOCUSED; - - onFocusChanged(false, 0, null); - refreshDrawableState(); - - // The view cleared focus and invoked the callbacks, so now is the - // time to give focus to the the first focusable from the top to - // ensure that the gain focus is announced after clear focus. - getRootView().requestFocus(FOCUS_FORWARD); + void ensureInputFocusOnFirstFocusable() { + View root = getRootView(); + if (root != null) { + // Find the first focusble from the top. + View next = root.focusSearch(FOCUS_FORWARD); + if (next != null) { + // Giving focus to the found focusable will not + // perform a search since we found a view that is + // guaranteed to be able to take focus. + next.requestFocus(FOCUS_FORWARD); + } } } @@ -4562,11 +4683,26 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } /** + * Indicates whether this view is one of the set of scrollable containers in + * its window. + * + * @return whether this view is one of the set of scrollable containers in + * its window + * + * @attr ref android.R.styleable#View_isScrollContainer + */ + public boolean isScrollContainer() { + return (mPrivateFlags & SCROLL_CONTAINER_ADDED) != 0; + } + + /** * Change whether this view is one of the set of scrollable containers in * its window. This will be used to determine whether the window can * resize or must pan when a soft input area is open -- scrollable * containers allow the window to use resize mode since the container * will appropriately shrink. + * + * @attr ref android.R.styleable#View_isScrollContainer */ public void setScrollContainer(boolean isScrollContainer) { if (isScrollContainer) { @@ -4898,7 +5034,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal @RemotableViewMethod public void setVisibility(int visibility) { setFlags(visibility, VISIBILITY_MASK); - if (mBGDrawable != null) mBGDrawable.setVisible(visibility == VISIBLE, false); + if (mBackground != null) mBackground.setVisible(visibility == VISIBLE, false); } /** @@ -5279,7 +5415,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * {@link #setPressed(boolean)} is explicitly called, only clickable views can enter * the pressed state. * - * @see #setPressed(boolean) + * @see #setPressed(boolean) * @see #isClickable() * @see #setClickable(boolean) * @@ -5965,7 +6101,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal /** * Dispatch a hover event. * <p> - * Do not call this method directly. + * Do not call this method directly. * Call {@link #dispatchGenericMotionEvent(MotionEvent)} instead. * </p> * @@ -6152,7 +6288,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * * @param visibility The new visibility of the window. * - * @see #onWindowVisibilityChanged(int) + * @see #onWindowVisibilityChanged(int) */ public void dispatchWindowVisibilityChanged(int visibility) { onWindowVisibilityChanged(visibility); @@ -6228,7 +6364,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * * @param newConfig The new resource configuration. * - * @see #onConfigurationChanged(android.content.res.Configuration) + * @see #onConfigurationChanged(android.content.res.Configuration) */ public void dispatchConfigurationChanged(Configuration newConfig) { onConfigurationChanged(newConfig); @@ -7096,7 +7232,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal if ((changed & DRAW_MASK) != 0) { if ((mViewFlags & WILL_NOT_DRAW) != 0) { - if (mBGDrawable != null) { + if (mBackground != null) { mPrivateFlags &= ~SKIP_DRAW; mPrivateFlags |= ONLY_DRAWS_BACKGROUND; } else { @@ -7484,39 +7620,39 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * views are drawn) from the camera to this view. The camera's distance * affects 3D transformations, for instance rotations around the X and Y * axis. If the rotationX or rotationY properties are changed and this view is - * large (more than half the size of the screen), it is recommended to always + * large (more than half the size of the screen), it is recommended to always * use a camera distance that's greater than the height (X axis rotation) or * the width (Y axis rotation) of this view.</p> - * + * * <p>The distance of the camera from the view plane can have an affect on the * perspective distortion of the view when it is rotated around the x or y axis. * For example, a large distance will result in a large viewing angle, and there * will not be much perspective distortion of the view as it rotates. A short - * distance may cause much more perspective distortion upon rotation, and can + * distance may cause much more perspective distortion upon rotation, and can * also result in some drawing artifacts if the rotated view ends up partially * behind the camera (which is why the recommendation is to use a distance at * least as far as the size of the view, if the view is to be rotated.)</p> - * + * * <p>The distance is expressed in "depth pixels." The default distance depends * on the screen density. For instance, on a medium density display, the * default distance is 1280. On a high density display, the default distance * is 1920.</p> - * + * * <p>If you want to specify a distance that leads to visually consistent * results across various densities, use the following formula:</p> * <pre> * float scale = context.getResources().getDisplayMetrics().density; * view.setCameraDistance(distance * scale); * </pre> - * + * * <p>The density scale factor of a high density display is 1.5, * and 1920 = 1280 * 1.5.</p> - * + * * @param distance The distance in "depth pixels", if negative the opposite * value is used - * - * @see #setRotationX(float) - * @see #setRotationY(float) + * + * @see #setRotationX(float) + * @see #setRotationY(float) */ public void setCameraDistance(float distance) { invalidateViewProperty(true, false); @@ -7541,10 +7677,10 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal /** * The degrees that the view is rotated around the pivot point. * - * @see #setRotation(float) + * @see #setRotation(float) * @see #getPivotX() * @see #getPivotY() - * + * * @return The degrees of rotation. */ @ViewDebug.ExportedProperty(category = "drawing") @@ -7557,12 +7693,12 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * result in clockwise rotation. * * @param rotation The degrees of rotation. - * - * @see #getRotation() + * + * @see #getRotation() * @see #getPivotX() * @see #getPivotY() - * @see #setRotationX(float) - * @see #setRotationY(float) + * @see #setRotationX(float) + * @see #setRotationY(float) * * @attr ref android.R.styleable#View_rotation */ @@ -7586,8 +7722,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * * @see #getPivotX() * @see #getPivotY() - * @see #setRotationY(float) - * + * @see #setRotationY(float) + * * @return The degrees of Y rotation. */ @ViewDebug.ExportedProperty(category = "drawing") @@ -7599,18 +7735,18 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * Sets the degrees that the view is rotated around the vertical axis through the pivot point. * Increasing values result in counter-clockwise rotation from the viewpoint of looking * down the y axis. - * + * * When rotating large views, it is recommended to adjust the camera distance * accordingly. Refer to {@link #setCameraDistance(float)} for more information. * * @param rotationY The degrees of Y rotation. - * - * @see #getRotationY() + * + * @see #getRotationY() * @see #getPivotX() * @see #getPivotY() * @see #setRotation(float) - * @see #setRotationX(float) - * @see #setCameraDistance(float) + * @see #setRotationX(float) + * @see #setCameraDistance(float) * * @attr ref android.R.styleable#View_rotationY */ @@ -7633,8 +7769,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * * @see #getPivotX() * @see #getPivotY() - * @see #setRotationX(float) - * + * @see #setRotationX(float) + * * @return The degrees of X rotation. */ @ViewDebug.ExportedProperty(category = "drawing") @@ -7646,18 +7782,18 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * Sets the degrees that the view is rotated around the horizontal axis through the pivot point. * Increasing values result in clockwise rotation from the viewpoint of looking down the * x axis. - * + * * When rotating large views, it is recommended to adjust the camera distance * accordingly. Refer to {@link #setCameraDistance(float)} for more information. * * @param rotationX The degrees of X rotation. - * - * @see #getRotationX() + * + * @see #getRotationX() * @see #getPivotX() * @see #getPivotY() * @see #setRotation(float) - * @see #setRotationY(float) - * @see #setCameraDistance(float) + * @see #setRotationY(float) + * @see #setCameraDistance(float) * * @attr ref android.R.styleable#View_rotationX */ @@ -7762,6 +7898,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @see #getScaleY() * @see #getPivotY() * @return The x location of the pivot point. + * + * @attr ref android.R.styleable#View_transformPivotX */ @ViewDebug.ExportedProperty(category = "drawing") public float getPivotX() { @@ -7807,6 +7945,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @see #getScaleY() * @see #getPivotY() * @return The y location of the pivot point. + * + * @attr ref android.R.styleable#View_transformPivotY */ @ViewDebug.ExportedProperty(category = "drawing") public float getPivotY() { @@ -7855,6 +7995,23 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } /** + * Returns whether this View has content which overlaps. This function, intended to be + * overridden by specific View types, is an optimization when alpha is set on a view. If + * rendering overlaps in a view with alpha < 1, that view is drawn to an offscreen buffer + * and then composited it into place, which can be expensive. If the view has no overlapping + * rendering, the view can draw each primitive with the appropriate alpha value directly. + * An example of overlapping rendering is a TextView with a background image, such as a + * Button. An example of non-overlapping rendering is a TextView with no background, or + * an ImageView with only the foreground image. The default implementation returns true; + * subclasses should override if they have cases which can be optimized. + * + * @return true if the content in this view might overlap, false otherwise. + */ + public boolean hasOverlappingRendering() { + return true; + } + + /** * <p>Sets the opacity of the view. This is a value from 0 to 1, where 0 means the view is * completely transparent and 1 means the view is completely opaque.</p> * @@ -9005,7 +9162,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal // - Background is opaque // - Doesn't have scrollbars or scrollbars are inside overlay - if (mBGDrawable != null && mBGDrawable.getOpacity() == PixelFormat.OPAQUE) { + if (mBackground != null && mBackground.getOpacity() == PixelFormat.OPAQUE) { mPrivateFlags |= OPAQUE_BACKGROUND; } else { mPrivateFlags &= ~OPAQUE_BACKGROUND; @@ -9053,7 +9210,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal /** * <p>Causes the Runnable to be added to the message queue. * The runnable will be run on the user interface thread.</p> - * + * * <p>This method can be invoked from outside of the UI thread * only when this View is attached to a window.</p> * @@ -9077,7 +9234,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * <p>Causes the Runnable to be added to the message queue, to be run * after the specified amount of time elapses. * The runnable will be run on the user interface thread.</p> - * + * * <p>This method can be invoked from outside of the UI thread * only when this View is attached to a window.</p> * @@ -9151,7 +9308,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal /** * <p>Removes the specified Runnable from the message queue.</p> - * + * * <p>This method can be invoked from outside of the UI thread * only when this View is attached to a window.</p> * @@ -9183,7 +9340,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * * <p>This method can be invoked from outside of the UI thread * only when this View is attached to a window.</p> - * + * * @see #invalidate() */ public void postInvalidate() { @@ -9193,7 +9350,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal /** * <p>Cause an invalidate of the specified area to happen on a subsequent cycle * through the event loop. Use this to invalidate the View from a non-UI thread.</p> - * + * * <p>This method can be invoked from outside of the UI thread * only when this View is attached to a window.</p> * @@ -9212,7 +9369,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal /** * <p>Cause an invalidate to happen on a subsequent cycle through the event * loop. Waits for the specified amount of time.</p> - * + * * <p>This method can be invoked from outside of the UI thread * only when this View is attached to a window.</p> * @@ -9231,7 +9388,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal /** * <p>Cause an invalidate of the specified area to happen on a subsequent cycle * through the event loop. Waits for the specified amount of time.</p> - * + * * <p>This method can be invoked from outside of the UI thread * only when this View is attached to a window.</p> * @@ -9341,6 +9498,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * otherwise * * @see #setHorizontalFadingEdgeEnabled(boolean) + * * @attr ref android.R.styleable#View_requiresFadingEdge */ public boolean isHorizontalFadingEdgeEnabled() { @@ -9356,6 +9514,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * horizontally * * @see #isHorizontalFadingEdgeEnabled() + * * @attr ref android.R.styleable#View_requiresFadingEdge */ public void setHorizontalFadingEdgeEnabled(boolean horizontalFadingEdgeEnabled) { @@ -9376,6 +9535,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * otherwise * * @see #setVerticalFadingEdgeEnabled(boolean) + * * @attr ref android.R.styleable#View_requiresFadingEdge */ public boolean isVerticalFadingEdgeEnabled() { @@ -9391,6 +9551,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * vertically * * @see #isVerticalFadingEdgeEnabled() + * * @attr ref android.R.styleable#View_requiresFadingEdge */ public void setVerticalFadingEdgeEnabled(boolean verticalFadingEdgeEnabled) { @@ -9533,6 +9694,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * * @param fadeScrollbars wheter to enable fading * + * @attr ref android.R.styleable#View_fadeScrollbars */ public void setScrollbarFadingEnabled(boolean fadeScrollbars) { initScrollCache(); @@ -9550,12 +9712,86 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * Returns true if scrollbars will fade when this view is not scrolling * * @return true if scrollbar fading is enabled + * + * @attr ref android.R.styleable#View_fadeScrollbars */ public boolean isScrollbarFadingEnabled() { return mScrollCache != null && mScrollCache.fadeScrollBars; } /** + * + * Returns the delay before scrollbars fade. + * + * @return the delay before scrollbars fade + * + * @attr ref android.R.styleable#View_scrollbarDefaultDelayBeforeFade + */ + public int getScrollBarDefaultDelayBeforeFade() { + return mScrollCache == null ? ViewConfiguration.getScrollDefaultDelay() : + mScrollCache.scrollBarDefaultDelayBeforeFade; + } + + /** + * Define the delay before scrollbars fade. + * + * @param scrollBarDefaultDelayBeforeFade - the delay before scrollbars fade + * + * @attr ref android.R.styleable#View_scrollbarDefaultDelayBeforeFade + */ + public void setScrollBarDefaultDelayBeforeFade(int scrollBarDefaultDelayBeforeFade) { + getScrollCache().scrollBarDefaultDelayBeforeFade = scrollBarDefaultDelayBeforeFade; + } + + /** + * + * Returns the scrollbar fade duration. + * + * @return the scrollbar fade duration + * + * @attr ref android.R.styleable#View_scrollbarFadeDuration + */ + public int getScrollBarFadeDuration() { + return mScrollCache == null ? ViewConfiguration.getScrollBarFadeDuration() : + mScrollCache.scrollBarFadeDuration; + } + + /** + * Define the scrollbar fade duration. + * + * @param scrollBarFadeDuration - the scrollbar fade duration + * + * @attr ref android.R.styleable#View_scrollbarFadeDuration + */ + public void setScrollBarFadeDuration(int scrollBarFadeDuration) { + getScrollCache().scrollBarFadeDuration = scrollBarFadeDuration; + } + + /** + * + * Returns the scrollbar size. + * + * @return the scrollbar size + * + * @attr ref android.R.styleable#View_scrollbarSize + */ + public int getScrollBarSize() { + return mScrollCache == null ? ViewConfiguration.getScrollBarSize() : + mScrollCache.scrollBarSize; + } + + /** + * Define the scrollbar size. + * + * @param scrollBarSize - the scrollbar size + * + * @attr ref android.R.styleable#View_scrollbarSize + */ + public void setScrollBarSize(int scrollBarSize) { + getScrollCache().scrollBarSize = scrollBarSize; + } + + /** * <p>Specify the style of the scrollbars. The scrollbars can be overlaid or * inset. When inset, they add to the padding of the view. And the scrollbars * can be drawn inside the padding area or on the edge of the view. For example, @@ -9571,6 +9807,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @see #SCROLLBARS_INSIDE_INSET * @see #SCROLLBARS_OUTSIDE_OVERLAY * @see #SCROLLBARS_OUTSIDE_INSET + * + * @attr ref android.R.styleable#View_scrollbarStyle */ public void setScrollBarStyle(int style) { if (style != (mViewFlags & SCROLLBARS_STYLE_MASK)) { @@ -9587,6 +9825,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @see #SCROLLBARS_INSIDE_INSET * @see #SCROLLBARS_OUTSIDE_OVERLAY * @see #SCROLLBARS_OUTSIDE_INSET + * + * @attr ref android.R.styleable#View_scrollbarStyle */ @ViewDebug.ExportedProperty(mapping = { @ViewDebug.IntToString(from = SCROLLBARS_INSIDE_OVERLAY, to = "INSIDE_OVERLAY"), @@ -9972,6 +10212,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal resolveLayoutDirection(); resolvePadding(); resolveTextDirection(); + resolveTextAlignment(); if (isFocused()) { InputMethodManager imm = InputMethodManager.peekInstance(); imm.focusIn(this); @@ -10201,6 +10442,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal mCurrentAnimation = null; resetResolvedLayoutDirection(); + resetResolvedTextAlignment(); } /** @@ -10331,9 +10573,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * * @param container The SparseArray in which to save the view's state. * - * @see #restoreHierarchyState(android.util.SparseArray) - * @see #dispatchSaveInstanceState(android.util.SparseArray) - * @see #onSaveInstanceState() + * @see #restoreHierarchyState(android.util.SparseArray) + * @see #dispatchSaveInstanceState(android.util.SparseArray) + * @see #onSaveInstanceState() */ public void saveHierarchyState(SparseArray<Parcelable> container) { dispatchSaveInstanceState(container); @@ -10346,9 +10588,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * * @param container The SparseArray in which to save the view's state. * - * @see #dispatchRestoreInstanceState(android.util.SparseArray) - * @see #saveHierarchyState(android.util.SparseArray) - * @see #onSaveInstanceState() + * @see #dispatchRestoreInstanceState(android.util.SparseArray) + * @see #saveHierarchyState(android.util.SparseArray) + * @see #onSaveInstanceState() */ protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) { if (mID != NO_ID && (mViewFlags & SAVE_DISABLED_MASK) == 0) { @@ -10382,9 +10624,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @return Returns a Parcelable object containing the view's current dynamic * state, or null if there is nothing interesting to save. The * default implementation returns null. - * @see #onRestoreInstanceState(android.os.Parcelable) - * @see #saveHierarchyState(android.util.SparseArray) - * @see #dispatchSaveInstanceState(android.util.SparseArray) + * @see #onRestoreInstanceState(android.os.Parcelable) + * @see #saveHierarchyState(android.util.SparseArray) + * @see #dispatchSaveInstanceState(android.util.SparseArray) * @see #setSaveEnabled(boolean) */ protected Parcelable onSaveInstanceState() { @@ -10397,9 +10639,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * * @param container The SparseArray which holds previously frozen states. * - * @see #saveHierarchyState(android.util.SparseArray) - * @see #dispatchRestoreInstanceState(android.util.SparseArray) - * @see #onRestoreInstanceState(android.os.Parcelable) + * @see #saveHierarchyState(android.util.SparseArray) + * @see #dispatchRestoreInstanceState(android.util.SparseArray) + * @see #onRestoreInstanceState(android.os.Parcelable) */ public void restoreHierarchyState(SparseArray<Parcelable> container) { dispatchRestoreInstanceState(container); @@ -10413,9 +10655,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * * @param container The SparseArray which holds previously saved state. * - * @see #dispatchSaveInstanceState(android.util.SparseArray) - * @see #restoreHierarchyState(android.util.SparseArray) - * @see #onRestoreInstanceState(android.os.Parcelable) + * @see #dispatchSaveInstanceState(android.util.SparseArray) + * @see #restoreHierarchyState(android.util.SparseArray) + * @see #onRestoreInstanceState(android.os.Parcelable) */ protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) { if (mID != NO_ID) { @@ -10441,9 +10683,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @param state The frozen state that had previously been returned by * {@link #onSaveInstanceState}. * - * @see #onSaveInstanceState() - * @see #restoreHierarchyState(android.util.SparseArray) - * @see #dispatchRestoreInstanceState(android.util.SparseArray) + * @see #onSaveInstanceState() + * @see #restoreHierarchyState(android.util.SparseArray) + * @see #dispatchRestoreInstanceState(android.util.SparseArray) */ protected void onRestoreInstanceState(Parcelable state) { mPrivateFlags |= SAVE_STATE_CALLED; @@ -10597,7 +10839,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * {@link #LAYER_TYPE_HARDWARE} * * @see #setLayerType(int, android.graphics.Paint) - * @see #buildLayer() + * @see #buildLayer() * @see #LAYER_TYPE_NONE * @see #LAYER_TYPE_SOFTWARE * @see #LAYER_TYPE_HARDWARE @@ -10610,14 +10852,14 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * Forces this view's layer to be created and this view to be rendered * into its layer. If this view's layer type is set to {@link #LAYER_TYPE_NONE}, * invoking this method will have no effect. - * + * * This method can for instance be used to render a view into its layer before * starting an animation. If this view is complex, rendering into the layer * before starting the animation will avoid skipping frames. - * + * * @throws IllegalStateException If this view is not attached to a window - * - * @see #setLayerType(int, android.graphics.Paint) + * + * @see #setLayerType(int, android.graphics.Paint) */ public void buildLayer() { if (mLayerType == LAYER_TYPE_NONE) return; @@ -10639,7 +10881,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal break; } } - + // Make sure the HardwareRenderer.validate() was invoked before calling this method void flushLayer() { if (mLayerType == LAYER_TYPE_HARDWARE && mHardwareLayer != null) { @@ -10658,7 +10900,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal !mAttachInfo.mHardwareRenderer.isEnabled()) { return null; } - + if (!mAttachInfo.mHardwareRenderer.validate()) return null; final int width = mRight - mLeft; @@ -10692,10 +10934,10 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal /** * Destroys this View's hardware layer if possible. - * + * * @return True if the layer was destroyed, false otherwise. - * - * @see #setLayerType(int, android.graphics.Paint) + * + * @see #setLayerType(int, android.graphics.Paint) * @see #LAYER_TYPE_HARDWARE */ boolean destroyLayer(boolean valid) { @@ -10719,11 +10961,11 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * Destroys all hardware rendering resources. This method is invoked * when the system needs to reclaim resources. Upon execution of this * method, you should free any OpenGL resources created by the view. - * + * * Note: you <strong>must</strong> call * <code>super.destroyHardwareResources()</code> when overriding * this method. - * + * * @hide */ protected void destroyHardwareResources() { @@ -11434,17 +11676,17 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal if (offsetRequired) top += getTopPaddingOffset(); return top; } - + /** * @hide * @param offsetRequired */ protected int getFadeHeight(boolean offsetRequired) { int padding = mPaddingTop; - if (offsetRequired) padding += getTopPaddingOffset(); + if (offsetRequired) padding += getTopPaddingOffset(); return mBottom - mTop - mPaddingBottom - padding; } - + /** * <p>Indicates whether this view is attached to a hardware accelerated * window or not.</p> @@ -11534,6 +11776,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal void setDisplayListProperties(DisplayList displayList) { if (USE_DISPLAY_LIST_PROPERTIES && displayList != null) { displayList.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom); + displayList.setHasOverlappingRendering(hasOverlappingRendering()); if (mParent instanceof ViewGroup) { displayList.setClipChildren( (((ViewGroup)mParent).mGroupFlags & ViewGroup.FLAG_CLIP_CHILDREN) != 0); @@ -11944,7 +12187,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal int saveCount; if (!dirtyOpaque) { - final Drawable background = mBGDrawable; + final Drawable background = mBackground; if (background != null) { final int scrollX = mScrollX; final int scrollY = mScrollY; @@ -12018,7 +12261,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } final ScrollabilityCache scrollabilityCache = mScrollCache; - final float fadeHeight = scrollabilityCache.fadingEdgeLength; + final float fadeHeight = scrollabilityCache.fadingEdgeLength; int length = (int) fadeHeight; // clip the fade length if top and bottom fades overlap @@ -12125,8 +12368,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * optimize the drawing of the fading edges. If you do return a non-zero color, the alpha * should be set to 0xFF. * - * @see #setVerticalFadingEdgeEnabled(boolean) - * @see #setHorizontalFadingEdgeEnabled(boolean) + * @see #setVerticalFadingEdgeEnabled(boolean) + * @see #setHorizontalFadingEdgeEnabled(boolean) * * @return The known solid color background for this view, or 0 if the color may vary */ @@ -12475,7 +12718,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @param who the Drawable to query */ public int getResolvedLayoutDirection(Drawable who) { - return (who == mBGDrawable) ? getResolvedLayoutDirection() : LAYOUT_DIRECTION_DEFAULT; + return (who == mBackground) ? getResolvedLayoutDirection() : LAYOUT_DIRECTION_DEFAULT; } /** @@ -12494,11 +12737,11 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @return boolean If true than the Drawable is being displayed in the * view; else false and it is not allowed to animate. * - * @see #unscheduleDrawable(android.graphics.drawable.Drawable) - * @see #drawableStateChanged() + * @see #unscheduleDrawable(android.graphics.drawable.Drawable) + * @see #drawableStateChanged() */ protected boolean verifyDrawable(Drawable who) { - return who == mBGDrawable; + return who == mBackground; } /** @@ -12508,10 +12751,10 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * <p>Be sure to call through to the superclass when overriding this * function. * - * @see Drawable#setState(int[]) + * @see Drawable#setState(int[]) */ protected void drawableStateChanged() { - Drawable d = mBGDrawable; + Drawable d = mBackground; if (d != null && d.isStateful()) { d.setState(getDrawableState()); } @@ -12541,9 +12784,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * * @return The current drawable state * - * @see Drawable#setState(int[]) - * @see #drawableStateChanged() - * @see #onCreateDrawableState(int) + * @see Drawable#setState(int[]) + * @see #drawableStateChanged() + * @see #onCreateDrawableState(int) */ public final int[] getDrawableState() { if ((mDrawableState != null) && ((mPrivateFlags & DRAWABLE_STATE_DIRTY) == 0)) { @@ -12568,7 +12811,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @return Returns an array holding the current {@link Drawable} state of * the view. * - * @see #mergeDrawableStates(int[], int[]) + * @see #mergeDrawableStates(int[], int[]) */ protected int[] onCreateDrawableState(int extraSpace) { if ((mViewFlags & DUPLICATE_PARENT_STATE) == DUPLICATE_PARENT_STATE && @@ -12644,7 +12887,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @return As a convenience, the <var>baseState</var> array you originally * passed into the function is returned. * - * @see #onCreateDrawableState(int) + * @see #onCreateDrawableState(int) */ protected static int[] mergeDrawableStates(int[] baseState, int[] additionalState) { final int N = baseState.length; @@ -12661,8 +12904,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * on all Drawable objects associated with this view. */ public void jumpDrawablesToCurrentState() { - if (mBGDrawable != null) { - mBGDrawable.jumpToCurrentState(); + if (mBackground != null) { + mBackground.jumpToCurrentState(); } } @@ -12672,10 +12915,10 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal */ @RemotableViewMethod public void setBackgroundColor(int color) { - if (mBGDrawable instanceof ColorDrawable) { - ((ColorDrawable) mBGDrawable).setColor(color); + if (mBackground instanceof ColorDrawable) { + ((ColorDrawable) mBackground).setColor(color); } else { - setBackgroundDrawable(new ColorDrawable(color)); + setBackground(new ColorDrawable(color)); } } @@ -12683,6 +12926,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * Set the background to a given resource. The resource should refer to * a Drawable object or 0 to remove the background. * @param resid The identifier of the resource. + * * @attr ref android.R.styleable#View_background */ @RemotableViewMethod @@ -12695,7 +12939,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal if (resid != 0) { d = mResources.getDrawable(resid); } - setBackgroundDrawable(d); + setBackground(d); mBackgroundResource = resid; } @@ -12707,11 +12951,19 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * touched. If setting the padding is desired, please use * {@link #setPadding(int, int, int, int)}. * - * @param d The Drawable to use as the background, or null to remove the + * @param background The Drawable to use as the background, or null to remove the * background */ - public void setBackgroundDrawable(Drawable d) { - if (d == mBGDrawable) { + public void setBackground(Drawable background) { + setBackgroundDrawable(background); + } + + /** + * @deprecated use {@link #setBackground(Drawable)} instead + */ + @Deprecated + public void setBackgroundDrawable(Drawable background) { + if (background == mBackground) { return; } @@ -12723,19 +12975,19 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * Regardless of whether we're setting a new background or not, we want * to clear the previous drawable. */ - if (mBGDrawable != null) { - mBGDrawable.setCallback(null); - unscheduleDrawable(mBGDrawable); + if (mBackground != null) { + mBackground.setCallback(null); + unscheduleDrawable(mBackground); } - if (d != null) { + if (background != null) { Rect padding = sThreadLocal.get(); if (padding == null) { padding = new Rect(); sThreadLocal.set(padding); } - if (d.getPadding(padding)) { - switch (d.getResolvedLayoutDirectionSelf()) { + if (background.getPadding(padding)) { + switch (background.getResolvedLayoutDirectionSelf()) { case LAYOUT_DIRECTION_RTL: setPadding(padding.right, padding.top, padding.left, padding.bottom); break; @@ -12747,17 +12999,17 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal // Compare the minimum sizes of the old Drawable and the new. If there isn't an old or // if it has a different minimum size, we should layout again - if (mBGDrawable == null || mBGDrawable.getMinimumHeight() != d.getMinimumHeight() || - mBGDrawable.getMinimumWidth() != d.getMinimumWidth()) { + if (mBackground == null || mBackground.getMinimumHeight() != background.getMinimumHeight() || + mBackground.getMinimumWidth() != background.getMinimumWidth()) { requestLayout = true; } - d.setCallback(this); - if (d.isStateful()) { - d.setState(getDrawableState()); + background.setCallback(this); + if (background.isStateful()) { + background.setState(getDrawableState()); } - d.setVisible(getVisibility() == VISIBLE, false); - mBGDrawable = d; + background.setVisible(getVisibility() == VISIBLE, false); + mBackground = background; if ((mPrivateFlags & SKIP_DRAW) != 0) { mPrivateFlags &= ~SKIP_DRAW; @@ -12766,7 +13018,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } } else { /* Remove the background */ - mBGDrawable = null; + mBackground = null; if ((mPrivateFlags & ONLY_DRAWS_BACKGROUND) != 0) { /* @@ -12802,10 +13054,15 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal /** * Gets the background drawable + * * @return The drawable used as the background for this view, if any. + * + * @see #setBackground(Drawable) + * + * @attr ref android.R.styleable#View_background */ public Drawable getBackground() { - return mBGDrawable; + return mBackground; } /** @@ -13346,8 +13603,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * number. * * @see #NO_ID - * @see #getId() - * @see #findViewById(int) + * @see #getId() + * @see #findViewById(int) * * @param id a number used to identify the view * @@ -13386,8 +13643,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @return a positive integer used to identify the view or {@link #NO_ID} * if the view has no ID * - * @see #setId(int) - * @see #findViewById(int) + * @see #setId(int) + * @see #findViewById(int) * @attr ref android.R.styleable#View_id */ @ViewDebug.CapturedViewProperty @@ -13899,16 +14156,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @return The suggested minimum height of the view. */ protected int getSuggestedMinimumHeight() { - int suggestedMinHeight = mMinHeight; + return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight()); - if (mBGDrawable != null) { - final int bgMinHeight = mBGDrawable.getMinimumHeight(); - if (suggestedMinHeight < bgMinHeight) { - suggestedMinHeight = bgMinHeight; - } - } - - return suggestedMinHeight; } /** @@ -13923,16 +14172,20 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @return The suggested minimum width of the view. */ protected int getSuggestedMinimumWidth() { - int suggestedMinWidth = mMinWidth; - - if (mBGDrawable != null) { - final int bgMinWidth = mBGDrawable.getMinimumWidth(); - if (suggestedMinWidth < bgMinWidth) { - suggestedMinWidth = bgMinWidth; - } - } + return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth()); + } - return suggestedMinWidth; + /** + * Returns the minimum height of the view. + * + * @return the minimum height the view will try to be. + * + * @see #setMinimumHeight(int) + * + * @attr ref android.R.styleable#View_minHeight + */ + public int getMinimumHeight() { + return mMinHeight; } /** @@ -13941,9 +14194,27 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * constrains it with less available height). * * @param minHeight The minimum height the view will try to be. + * + * @see #getMinimumHeight() + * + * @attr ref android.R.styleable#View_minHeight */ public void setMinimumHeight(int minHeight) { mMinHeight = minHeight; + requestLayout(); + } + + /** + * Returns the minimum width of the view. + * + * @return the minimum width the view will try to be. + * + * @see #setMinimumWidth(int) + * + * @attr ref android.R.styleable#View_minWidth + */ + public int getMinimumWidth() { + return mMinWidth; } /** @@ -13952,9 +14223,15 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * constrains it with less available width). * * @param minWidth The minimum width the view will try to be. + * + * @see #getMinimumWidth() + * + * @attr ref android.R.styleable#View_minWidth */ public void setMinimumWidth(int minWidth) { mMinWidth = minWidth; + requestLayout(); + } /** @@ -14073,11 +14350,11 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal getLocationInWindow(location); region.op(location[0], location[1], location[0] + mRight - mLeft, location[1] + mBottom - mTop, Region.Op.DIFFERENCE); - } else if ((pflags & ONLY_DRAWS_BACKGROUND) != 0 && mBGDrawable != null) { + } else if ((pflags & ONLY_DRAWS_BACKGROUND) != 0 && mBackground != null) { // The ONLY_DRAWS_BACKGROUND flag IS set and the background drawable // exists, so we remove the background drawable's non-transparent // parts from this transparent region. - applyDrawableToTransparentRegion(mBGDrawable, region); + applyDrawableToTransparentRegion(mBackground, region); } } return true; @@ -14748,7 +15025,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * {@link #TEXT_DIRECTION_ANY_RTL}, * {@link #TEXT_DIRECTION_LTR}, * {@link #TEXT_DIRECTION_RTL}, - * {@link #TEXT_DIRECTION_LOCALE}, + * {@link #TEXT_DIRECTION_LOCALE} */ @ViewDebug.ExportedProperty(category = "text", mapping = { @ViewDebug.IntToString(from = TEXT_DIRECTION_INHERIT, to = "INHERIT"), @@ -14772,7 +15049,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * {@link #TEXT_DIRECTION_ANY_RTL}, * {@link #TEXT_DIRECTION_LTR}, * {@link #TEXT_DIRECTION_RTL}, - * {@link #TEXT_DIRECTION_LOCALE}, + * {@link #TEXT_DIRECTION_LOCALE} */ public void setTextDirection(int textDirection) { if (getTextDirection() != textDirection) { @@ -14781,6 +15058,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal resetResolvedTextDirection(); // Set the new text direction mPrivateFlags2 |= ((textDirection << TEXT_DIRECTION_MASK_SHIFT) & TEXT_DIRECTION_MASK); + // Refresh requestLayout(); invalidate(true); } @@ -14800,7 +15078,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * {@link #TEXT_DIRECTION_ANY_RTL}, * {@link #TEXT_DIRECTION_LTR}, * {@link #TEXT_DIRECTION_RTL}, - * {@link #TEXT_DIRECTION_LOCALE}, + * {@link #TEXT_DIRECTION_LOCALE} */ public int getResolvedTextDirection() { // The text direction will be resolved only if needed @@ -14909,6 +15187,199 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal public void onResolvedTextDirectionReset() { } + /** + * Return the value specifying the text alignment or policy that was set with + * {@link #setTextAlignment(int)}. + * + * @return the defined text alignment. It can be one of: + * + * {@link #TEXT_ALIGNMENT_INHERIT}, + * {@link #TEXT_ALIGNMENT_GRAVITY}, + * {@link #TEXT_ALIGNMENT_CENTER}, + * {@link #TEXT_ALIGNMENT_TEXT_START}, + * {@link #TEXT_ALIGNMENT_TEXT_END}, + * {@link #TEXT_ALIGNMENT_VIEW_START}, + * {@link #TEXT_ALIGNMENT_VIEW_END} + */ + @ViewDebug.ExportedProperty(category = "text", mapping = { + @ViewDebug.IntToString(from = TEXT_ALIGNMENT_INHERIT, to = "INHERIT"), + @ViewDebug.IntToString(from = TEXT_ALIGNMENT_GRAVITY, to = "GRAVITY"), + @ViewDebug.IntToString(from = TEXT_ALIGNMENT_TEXT_START, to = "TEXT_START"), + @ViewDebug.IntToString(from = TEXT_ALIGNMENT_TEXT_END, to = "TEXT_END"), + @ViewDebug.IntToString(from = TEXT_ALIGNMENT_CENTER, to = "CENTER"), + @ViewDebug.IntToString(from = TEXT_ALIGNMENT_VIEW_START, to = "VIEW_START"), + @ViewDebug.IntToString(from = TEXT_ALIGNMENT_VIEW_END, to = "VIEW_END") + }) + public int getTextAlignment() { + return (mPrivateFlags2 & TEXT_ALIGNMENT_MASK) >> TEXT_ALIGNMENT_MASK_SHIFT; + } + + /** + * Set the text alignment. + * + * @param textAlignment The text alignment to set. Should be one of + * + * {@link #TEXT_ALIGNMENT_INHERIT}, + * {@link #TEXT_ALIGNMENT_GRAVITY}, + * {@link #TEXT_ALIGNMENT_CENTER}, + * {@link #TEXT_ALIGNMENT_TEXT_START}, + * {@link #TEXT_ALIGNMENT_TEXT_END}, + * {@link #TEXT_ALIGNMENT_VIEW_START}, + * {@link #TEXT_ALIGNMENT_VIEW_END} + * + * @attr ref android.R.styleable#View_textAlignment + */ + public void setTextAlignment(int textAlignment) { + if (textAlignment != getTextAlignment()) { + // Reset the current and resolved text alignment + mPrivateFlags2 &= ~TEXT_ALIGNMENT_MASK; + resetResolvedTextAlignment(); + // Set the new text alignment + mPrivateFlags2 |= ((textAlignment << TEXT_ALIGNMENT_MASK_SHIFT) & TEXT_ALIGNMENT_MASK); + // Refresh + requestLayout(); + invalidate(true); + } + } + + /** + * Return the resolved text alignment. + * + * The resolved text alignment. This needs resolution if the value is + * TEXT_ALIGNMENT_INHERIT. The resolution matches {@link #setTextAlignment(int)} if it is + * not TEXT_ALIGNMENT_INHERIT, otherwise resolution proceeds up the parent chain of the view. + * + * @return the resolved text alignment. Returns one of: + * + * {@link #TEXT_ALIGNMENT_GRAVITY}, + * {@link #TEXT_ALIGNMENT_CENTER}, + * {@link #TEXT_ALIGNMENT_TEXT_START}, + * {@link #TEXT_ALIGNMENT_TEXT_END}, + * {@link #TEXT_ALIGNMENT_VIEW_START}, + * {@link #TEXT_ALIGNMENT_VIEW_END} + */ + @ViewDebug.ExportedProperty(category = "text", mapping = { + @ViewDebug.IntToString(from = TEXT_ALIGNMENT_INHERIT, to = "INHERIT"), + @ViewDebug.IntToString(from = TEXT_ALIGNMENT_GRAVITY, to = "GRAVITY"), + @ViewDebug.IntToString(from = TEXT_ALIGNMENT_TEXT_START, to = "TEXT_START"), + @ViewDebug.IntToString(from = TEXT_ALIGNMENT_TEXT_END, to = "TEXT_END"), + @ViewDebug.IntToString(from = TEXT_ALIGNMENT_CENTER, to = "CENTER"), + @ViewDebug.IntToString(from = TEXT_ALIGNMENT_VIEW_START, to = "VIEW_START"), + @ViewDebug.IntToString(from = TEXT_ALIGNMENT_VIEW_END, to = "VIEW_END") + }) + public int getResolvedTextAlignment() { + // If text alignment is not resolved, then resolve it + if ((mPrivateFlags2 & TEXT_ALIGNMENT_RESOLVED) != TEXT_ALIGNMENT_RESOLVED) { + resolveTextAlignment(); + } + return (mPrivateFlags2 & TEXT_ALIGNMENT_RESOLVED_MASK) >> TEXT_ALIGNMENT_RESOLVED_MASK_SHIFT; + } + + /** + * Resolve the text alignment. Will call {@link View#onResolvedTextAlignmentChanged} when + * resolution is done. + */ + public void resolveTextAlignment() { + // Reset any previous text alignment resolution + mPrivateFlags2 &= ~(TEXT_ALIGNMENT_RESOLVED | TEXT_ALIGNMENT_RESOLVED_MASK); + + if (hasRtlSupport()) { + // Set resolved text alignment flag depending on text alignment flag + final int textAlignment = getTextAlignment(); + switch (textAlignment) { + case TEXT_ALIGNMENT_INHERIT: + // Check if we can resolve the text alignment + if (canResolveLayoutDirection() && mParent instanceof View) { + View view = (View) mParent; + + final int parentResolvedTextAlignment = view.getResolvedTextAlignment(); + switch (parentResolvedTextAlignment) { + case TEXT_ALIGNMENT_GRAVITY: + case TEXT_ALIGNMENT_TEXT_START: + case TEXT_ALIGNMENT_TEXT_END: + case TEXT_ALIGNMENT_CENTER: + case TEXT_ALIGNMENT_VIEW_START: + case TEXT_ALIGNMENT_VIEW_END: + // Resolved text alignment is the same as the parent resolved + // text alignment + mPrivateFlags2 |= + (parentResolvedTextAlignment << TEXT_ALIGNMENT_RESOLVED_MASK_SHIFT); + break; + default: + // Use default resolved text alignment + mPrivateFlags2 |= TEXT_ALIGNMENT_RESOLVED_DEFAULT; + } + } + else { + // We cannot do the resolution if there is no parent so use the default + mPrivateFlags2 |= TEXT_ALIGNMENT_RESOLVED_DEFAULT; + } + break; + case TEXT_ALIGNMENT_GRAVITY: + case TEXT_ALIGNMENT_TEXT_START: + case TEXT_ALIGNMENT_TEXT_END: + case TEXT_ALIGNMENT_CENTER: + case TEXT_ALIGNMENT_VIEW_START: + case TEXT_ALIGNMENT_VIEW_END: + // Resolved text alignment is the same as text alignment + mPrivateFlags2 |= (textAlignment << TEXT_ALIGNMENT_RESOLVED_MASK_SHIFT); + break; + default: + // Use default resolved text alignment + mPrivateFlags2 |= TEXT_ALIGNMENT_RESOLVED_DEFAULT; + } + } else { + // Use default resolved text alignment + mPrivateFlags2 |= TEXT_ALIGNMENT_RESOLVED_DEFAULT; + } + + // Set the resolved + mPrivateFlags2 |= TEXT_ALIGNMENT_RESOLVED; + onResolvedTextAlignmentChanged(); + } + + /** + * Check if text alignment resolution can be done. + * + * @return true if text alignment resolution can be done otherwise return false. + */ + public boolean canResolveTextAlignment() { + switch (getTextAlignment()) { + case TEXT_DIRECTION_INHERIT: + return (mParent != null); + default: + return true; + } + } + + /** + * Called when text alignment has been resolved. Subclasses that care about text alignment + * resolution should override this method. + * + * The default implementation does nothing. + */ + public void onResolvedTextAlignmentChanged() { + } + + /** + * Reset resolved text alignment. Text alignment can be resolved with a call to + * getResolvedTextAlignment(). Will call {@link View#onResolvedTextAlignmentReset} when + * reset is done. + */ + public void resetResolvedTextAlignment() { + // Reset any previous text alignment resolution + mPrivateFlags2 &= ~(TEXT_ALIGNMENT_RESOLVED | TEXT_ALIGNMENT_RESOLVED_MASK); + onResolvedTextAlignmentReset(); + } + + /** + * Called when text alignment is reset. Subclasses that care about text alignment reset should + * override this method and do a reset of the text alignment of their children. The default + * implementation does nothing. + */ + public void onResolvedTextAlignmentReset() { + } + // // Properties // @@ -15401,7 +15872,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * visibility. This reports <strong>global</strong> changes to the system UI * state, not just what the application is requesting. * - * @see View#setOnSystemUiVisibilityChangeListener(android.view.View.OnSystemUiVisibilityChangeListener) + * @see View#setOnSystemUiVisibilityChangeListener(android.view.View.OnSystemUiVisibilityChangeListener) */ public interface OnSystemUiVisibilityChangeListener { /** diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index d5c783f..121b544 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -3375,7 +3375,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager boolean clearChildFocus = false; if (view == mFocused) { - view.clearFocusForRemoval(); + view.unFocus(); clearChildFocus = true; } @@ -3398,6 +3398,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager if (clearChildFocus) { clearChildFocus(view); + ensureInputFocusOnFirstFocusable(); } } @@ -3450,7 +3451,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } if (view == focused) { - view.clearFocusForRemoval(); + view.unFocus(); clearChildFocus = view; } @@ -3474,6 +3475,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager if (clearChildFocus != null) { clearChildFocus(clearChildFocus); + ensureInputFocusOnFirstFocusable(); } } @@ -3519,7 +3521,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } if (view == focused) { - view.clearFocusForRemoval(); + view.unFocus(); clearChildFocus = view; } @@ -3542,6 +3544,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager if (clearChildFocus != null) { clearChildFocus(clearChildFocus); + ensureInputFocusOnFirstFocusable(); } } @@ -5056,6 +5059,18 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } } + @Override + public void onResolvedTextAlignmentReset() { + // Take care of resetting the children resolution too + final int count = getChildCount(); + for (int i = 0; i < count; i++) { + final View child = getChildAt(i); + if (child.getTextAlignment() == TEXT_ALIGNMENT_INHERIT) { + child.resetResolvedTextAlignment(); + } + } + } + /** * Return true if the pressed state should be delayed for children or descendants of this * ViewGroup. Generally, this should be done for containers that can scroll, such as a List. diff --git a/core/java/android/view/ViewPropertyAnimator.java b/core/java/android/view/ViewPropertyAnimator.java index 623b567..3626aba 100644 --- a/core/java/android/view/ViewPropertyAnimator.java +++ b/core/java/android/view/ViewPropertyAnimator.java @@ -834,40 +834,49 @@ public class ViewPropertyAnimator { */ private void setValue(int propertyConstant, float value) { final View.TransformationInfo info = mView.mTransformationInfo; + DisplayList displayList = View.USE_DISPLAY_LIST_PROPERTIES ? mView.mDisplayList : null; switch (propertyConstant) { case TRANSLATION_X: info.mTranslationX = value; + if (displayList != null) displayList.setTranslationX(value); break; case TRANSLATION_Y: info.mTranslationY = value; + if (displayList != null) displayList.setTranslationY(value); break; case ROTATION: info.mRotation = value; + if (displayList != null) displayList.setRotation(value); break; case ROTATION_X: info.mRotationX = value; + if (displayList != null) displayList.setRotationX(value); break; case ROTATION_Y: info.mRotationY = value; + if (displayList != null) displayList.setRotationY(value); break; case SCALE_X: info.mScaleX = value; + if (displayList != null) displayList.setScaleX(value); break; case SCALE_Y: info.mScaleY = value; + if (displayList != null) displayList.setScaleY(value); break; case X: info.mTranslationX = value - mView.mLeft; + if (displayList != null) displayList.setTranslationX(value - mView.mLeft); break; case Y: info.mTranslationY = value - mView.mTop; + if (displayList != null) displayList.setTranslationY(value - mView.mTop); break; case ALPHA: info.mAlpha = value; + if (displayList != null) displayList.setAlpha(value); break; } - // TODO: optimize to set only the properties that have changed - mView.setDisplayListProperties(); } /** diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index d72f3b7..899fb32 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -2039,9 +2039,10 @@ public final class ViewRootImpl implements ViewParent, scrollToRectOrFocus(null, false); - if (mAttachInfo.mViewScrollChanged) { - mAttachInfo.mViewScrollChanged = false; - mAttachInfo.mTreeObserver.dispatchOnScrollChanged(); + final AttachInfo attachInfo = mAttachInfo; + if (attachInfo.mViewScrollChanged) { + attachInfo.mViewScrollChanged = false; + attachInfo.mTreeObserver.dispatchOnScrollChanged(); } int yoff; @@ -2056,8 +2057,8 @@ public final class ViewRootImpl implements ViewParent, fullRedrawNeeded = true; } - final float appScale = mAttachInfo.mApplicationScale; - final boolean scalingRequired = mAttachInfo.mScalingRequired; + final float appScale = attachInfo.mApplicationScale; + final boolean scalingRequired = attachInfo.mScalingRequired; int resizeAlpha = 0; if (mResizeBuffer != null) { @@ -2086,7 +2087,7 @@ public final class ViewRootImpl implements ViewParent, } if (fullRedrawNeeded) { - mAttachInfo.mIgnoreDirtyState = true; + attachInfo.mIgnoreDirtyState = true; dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f)); } @@ -2099,8 +2100,10 @@ public final class ViewRootImpl implements ViewParent, appScale + ", width=" + mWidth + ", height=" + mHeight); } + attachInfo.mTreeObserver.dispatchOnDraw(); + if (!dirty.isEmpty() || mIsAnimating) { - if (mAttachInfo.mHardwareRenderer != null && mAttachInfo.mHardwareRenderer.isEnabled()) { + if (attachInfo.mHardwareRenderer != null && attachInfo.mHardwareRenderer.isEnabled()) { // Draw with hardware renderer. mIsAnimating = false; mHardwareYOffset = yoff; @@ -2111,154 +2114,164 @@ public final class ViewRootImpl implements ViewParent, mPreviousDirty.set(dirty); dirty.setEmpty(); - if (mAttachInfo.mHardwareRenderer.draw(mView, mAttachInfo, this, + if (attachInfo.mHardwareRenderer.draw(mView, attachInfo, this, animating ? null : mCurrentDirty)) { mPreviousDirty.set(0, 0, mWidth, mHeight); } - } else { - // Draw with software renderer. - Canvas canvas; - try { - int left = dirty.left; - int top = dirty.top; - int right = dirty.right; - int bottom = dirty.bottom; - - final long lockCanvasStartTime; - if (ViewDebug.DEBUG_LATENCY) { - lockCanvasStartTime = System.nanoTime(); - } - - canvas = mSurface.lockCanvas(dirty); + } else if (!drawSoftware(surface, attachInfo, yoff, scalingRequired, dirty)) { + return; + } + } - if (ViewDebug.DEBUG_LATENCY) { - long now = System.nanoTime(); - Log.d(ViewDebug.DEBUG_LATENCY_TAG, "- lockCanvas() took " - + ((now - lockCanvasStartTime) * 0.000001f) + "ms"); - } + if (animating) { + mFullRedrawNeeded = true; + scheduleTraversals(); + } + } - if (left != dirty.left || top != dirty.top || right != dirty.right || - bottom != dirty.bottom) { - mAttachInfo.mIgnoreDirtyState = true; - } + /** + * @return true if drawing was succesfull, false if an error occurred + */ + private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int yoff, + boolean scalingRequired, Rect dirty) { - // TODO: Do this in native - canvas.setDensity(mDensity); - } catch (Surface.OutOfResourcesException e) { - Log.e(TAG, "OutOfResourcesException locking surface", e); - try { - if (!sWindowSession.outOfMemory(mWindow)) { - Slog.w(TAG, "No processes killed for memory; killing self"); - Process.killProcess(Process.myPid()); - } - } catch (RemoteException ex) { - } - mLayoutRequested = true; // ask wm for a new surface next time. - return; - } catch (IllegalArgumentException e) { - Log.e(TAG, "IllegalArgumentException locking surface", e); - // Don't assume this is due to out of memory, it could be - // something else, and if it is something else then we could - // kill stuff (or ourself) for no reason. - mLayoutRequested = true; // ask wm for a new surface next time. - return; - } + // Draw with software renderer. + Canvas canvas; + try { + int left = dirty.left; + int top = dirty.top; + int right = dirty.right; + int bottom = dirty.bottom; - try { - if (DEBUG_ORIENTATION || DEBUG_DRAW) { - Log.v(TAG, "Surface " + surface + " drawing to bitmap w=" - + canvas.getWidth() + ", h=" + canvas.getHeight()); - //canvas.drawARGB(255, 255, 0, 0); - } + final long lockCanvasStartTime; + if (ViewDebug.DEBUG_LATENCY) { + lockCanvasStartTime = System.nanoTime(); + } - long startTime = 0L; - if (ViewDebug.DEBUG_PROFILE_DRAWING) { - startTime = SystemClock.elapsedRealtime(); - } + canvas = mSurface.lockCanvas(dirty); - // If this bitmap's format includes an alpha channel, we - // need to clear it before drawing so that the child will - // properly re-composite its drawing on a transparent - // background. This automatically respects the clip/dirty region - // or - // If we are applying an offset, we need to clear the area - // where the offset doesn't appear to avoid having garbage - // left in the blank areas. - if (!canvas.isOpaque() || yoff != 0) { - canvas.drawColor(0, PorterDuff.Mode.CLEAR); - } + if (ViewDebug.DEBUG_LATENCY) { + long now = System.nanoTime(); + Log.d(ViewDebug.DEBUG_LATENCY_TAG, "- lockCanvas() took " + + ((now - lockCanvasStartTime) * 0.000001f) + "ms"); + } - dirty.setEmpty(); - mIsAnimating = false; - mAttachInfo.mDrawingTime = SystemClock.uptimeMillis(); - mView.mPrivateFlags |= View.DRAWN; + if (left != dirty.left || top != dirty.top || right != dirty.right || + bottom != dirty.bottom) { + attachInfo.mIgnoreDirtyState = true; + } - if (DEBUG_DRAW) { - Context cxt = mView.getContext(); - Log.i(TAG, "Drawing: package:" + cxt.getPackageName() + - ", metrics=" + cxt.getResources().getDisplayMetrics() + - ", compatibilityInfo=" + cxt.getResources().getCompatibilityInfo()); - } - try { - canvas.translate(0, -yoff); - if (mTranslator != null) { - mTranslator.translateCanvas(canvas); - } - canvas.setScreenDensity(scalingRequired - ? DisplayMetrics.DENSITY_DEVICE : 0); - mAttachInfo.mSetIgnoreDirtyState = false; + // TODO: Do this in native + canvas.setDensity(mDensity); + } catch (Surface.OutOfResourcesException e) { + Log.e(TAG, "OutOfResourcesException locking surface", e); + try { + if (!sWindowSession.outOfMemory(mWindow)) { + Slog.w(TAG, "No processes killed for memory; killing self"); + Process.killProcess(Process.myPid()); + } + } catch (RemoteException ex) { + } + mLayoutRequested = true; // ask wm for a new surface next time. + return false; + } catch (IllegalArgumentException e) { + Log.e(TAG, "IllegalArgumentException locking surface", e); + // Don't assume this is due to out of memory, it could be + // something else, and if it is something else then we could + // kill stuff (or ourself) for no reason. + mLayoutRequested = true; // ask wm for a new surface next time. + return false; + } - final long drawStartTime; - if (ViewDebug.DEBUG_LATENCY) { - drawStartTime = System.nanoTime(); - } + try { + if (DEBUG_ORIENTATION || DEBUG_DRAW) { + Log.v(TAG, "Surface " + surface + " drawing to bitmap w=" + + canvas.getWidth() + ", h=" + canvas.getHeight()); + //canvas.drawARGB(255, 255, 0, 0); + } - mView.draw(canvas); + long startTime = 0L; + if (ViewDebug.DEBUG_PROFILE_DRAWING) { + startTime = SystemClock.elapsedRealtime(); + } - if (ViewDebug.DEBUG_LATENCY) { - long now = System.nanoTime(); - Log.d(ViewDebug.DEBUG_LATENCY_TAG, "- draw() took " - + ((now - drawStartTime) * 0.000001f) + "ms"); - } - } finally { - if (!mAttachInfo.mSetIgnoreDirtyState) { - // Only clear the flag if it was not set during the mView.draw() call - mAttachInfo.mIgnoreDirtyState = false; - } - } + // If this bitmap's format includes an alpha channel, we + // need to clear it before drawing so that the child will + // properly re-composite its drawing on a transparent + // background. This automatically respects the clip/dirty region + // or + // If we are applying an offset, we need to clear the area + // where the offset doesn't appear to avoid having garbage + // left in the blank areas. + if (!canvas.isOpaque() || yoff != 0) { + canvas.drawColor(0, PorterDuff.Mode.CLEAR); + } - if (false && ViewDebug.consistencyCheckEnabled) { - mView.dispatchConsistencyCheck(ViewDebug.CONSISTENCY_DRAWING); - } + dirty.setEmpty(); + mIsAnimating = false; + attachInfo.mDrawingTime = SystemClock.uptimeMillis(); + mView.mPrivateFlags |= View.DRAWN; - if (ViewDebug.DEBUG_PROFILE_DRAWING) { - EventLog.writeEvent(60000, SystemClock.elapsedRealtime() - startTime); - } - } finally { - final long unlockCanvasAndPostStartTime; - if (ViewDebug.DEBUG_LATENCY) { - unlockCanvasAndPostStartTime = System.nanoTime(); - } + if (DEBUG_DRAW) { + Context cxt = mView.getContext(); + Log.i(TAG, "Drawing: package:" + cxt.getPackageName() + + ", metrics=" + cxt.getResources().getDisplayMetrics() + + ", compatibilityInfo=" + cxt.getResources().getCompatibilityInfo()); + } + try { + canvas.translate(0, -yoff); + if (mTranslator != null) { + mTranslator.translateCanvas(canvas); + } + canvas.setScreenDensity(scalingRequired + ? DisplayMetrics.DENSITY_DEVICE : 0); + attachInfo.mSetIgnoreDirtyState = false; - surface.unlockCanvasAndPost(canvas); + final long drawStartTime; + if (ViewDebug.DEBUG_LATENCY) { + drawStartTime = System.nanoTime(); + } - if (ViewDebug.DEBUG_LATENCY) { - long now = System.nanoTime(); - Log.d(ViewDebug.DEBUG_LATENCY_TAG, "- unlockCanvasAndPost() took " - + ((now - unlockCanvasAndPostStartTime) * 0.000001f) + "ms"); - } + mView.draw(canvas); - if (LOCAL_LOGV) { - Log.v(TAG, "Surface " + surface + " unlockCanvasAndPost"); - } + if (ViewDebug.DEBUG_LATENCY) { + long now = System.nanoTime(); + Log.d(ViewDebug.DEBUG_LATENCY_TAG, "- draw() took " + + ((now - drawStartTime) * 0.000001f) + "ms"); + } + } finally { + if (!attachInfo.mSetIgnoreDirtyState) { + // Only clear the flag if it was not set during the mView.draw() call + attachInfo.mIgnoreDirtyState = false; } } - } - if (animating) { - mFullRedrawNeeded = true; - scheduleTraversals(); + if (false && ViewDebug.consistencyCheckEnabled) { + mView.dispatchConsistencyCheck(ViewDebug.CONSISTENCY_DRAWING); + } + + if (ViewDebug.DEBUG_PROFILE_DRAWING) { + EventLog.writeEvent(60000, SystemClock.elapsedRealtime() - startTime); + } + } finally { + final long unlockCanvasAndPostStartTime; + if (ViewDebug.DEBUG_LATENCY) { + unlockCanvasAndPostStartTime = System.nanoTime(); + } + + surface.unlockCanvasAndPost(canvas); + + if (ViewDebug.DEBUG_LATENCY) { + long now = System.nanoTime(); + Log.d(ViewDebug.DEBUG_LATENCY_TAG, "- unlockCanvasAndPost() took " + + ((now - unlockCanvasAndPostStartTime) * 0.000001f) + "ms"); + } + + if (LOCAL_LOGV) { + Log.v(TAG, "Surface " + surface + " unlockCanvasAndPost"); + } } + return true; } void invalidateDisplayLists() { @@ -3830,30 +3843,33 @@ public final class ViewRootImpl implements ViewParent, if (LOCAL_LOGV) Log.v(TAG, "DIE in " + this + " of " + mSurface); synchronized (this) { if (mAdded) { - mAdded = false; dispatchDetachedFromWindow(); } if (mAdded && !mFirst) { destroyHardwareRenderer(); - int viewVisibility = mView.getVisibility(); - boolean viewVisibilityChanged = mViewVisibility != viewVisibility; - if (mWindowAttributesChanged || viewVisibilityChanged) { - // If layout params have been changed, first give them - // to the window manager to make sure it has the correct - // animation info. - try { - if ((relayoutWindow(mWindowAttributes, viewVisibility, false) - & WindowManagerImpl.RELAYOUT_RES_FIRST_TIME) != 0) { - sWindowSession.finishDrawing(mWindow); + if (mView != null) { + int viewVisibility = mView.getVisibility(); + boolean viewVisibilityChanged = mViewVisibility != viewVisibility; + if (mWindowAttributesChanged || viewVisibilityChanged) { + // If layout params have been changed, first give them + // to the window manager to make sure it has the correct + // animation info. + try { + if ((relayoutWindow(mWindowAttributes, viewVisibility, false) + & WindowManagerImpl.RELAYOUT_RES_FIRST_TIME) != 0) { + sWindowSession.finishDrawing(mWindow); + } + } catch (RemoteException e) { } - } catch (RemoteException e) { } + + mSurface.release(); } - - mSurface.release(); } + + mAdded = false; } } diff --git a/core/java/android/view/ViewTreeObserver.java b/core/java/android/view/ViewTreeObserver.java index 7fd3389..1c5d436 100644 --- a/core/java/android/view/ViewTreeObserver.java +++ b/core/java/android/view/ViewTreeObserver.java @@ -38,6 +38,7 @@ public final class ViewTreeObserver { private CopyOnWriteArrayList<OnComputeInternalInsetsListener> mOnComputeInternalInsetsListeners; private CopyOnWriteArrayList<OnScrollChangedListener> mOnScrollChangedListeners; private ArrayList<OnPreDrawListener> mOnPreDrawListeners; + private ArrayList<OnDrawListener> mOnDrawListeners; private boolean mAlive = true; @@ -90,6 +91,27 @@ public final class ViewTreeObserver { } /** + * Interface definition for a callback to be invoked when the view tree is about to be drawn. + */ + public interface OnDrawListener { + /** + * <p>Callback method to be invoked when the view tree is about to be drawn. At this point, + * views cannot be modified in any way.</p> + * + * <p>Unlike with {@link OnPreDrawListener}, this method cannot be used to cancel the + * current drawing pass.</p> + * + * <p>An {@link OnDrawListener} listener <strong>cannot be added or removed</strong> + * from this method.</p> + * + * @see android.view.View#onMeasure + * @see android.view.View#onLayout + * @see android.view.View#onDraw + */ + public void onDraw(); + } + + /** * Interface definition for a callback to be invoked when the touch mode changes. */ public interface OnTouchModeChangeListener { @@ -171,11 +193,7 @@ public final class ViewTreeObserver { public void setTouchableInsets(int val) { mTouchableInsets = val; } - - public int getTouchableInsets() { - return mTouchableInsets; - } - + int mTouchableInsets; void reset() { @@ -184,29 +202,28 @@ public final class ViewTreeObserver { touchableRegion.setEmpty(); mTouchableInsets = TOUCHABLE_INSETS_FRAME; } - + + @Override + public int hashCode() { + int result = contentInsets != null ? contentInsets.hashCode() : 0; + result = 31 * result + (visibleInsets != null ? visibleInsets.hashCode() : 0); + result = 31 * result + (touchableRegion != null ? touchableRegion.hashCode() : 0); + result = 31 * result + mTouchableInsets; + return result; + } + @Override public boolean equals(Object o) { - try { - if (o == null) { - return false; - } - InternalInsetsInfo other = (InternalInsetsInfo)o; - if (mTouchableInsets != other.mTouchableInsets) { - return false; - } - if (!contentInsets.equals(other.contentInsets)) { - return false; - } - if (!visibleInsets.equals(other.visibleInsets)) { - return false; - } - return touchableRegion.equals(other.touchableRegion); - } catch (ClassCastException e) { - return false; - } + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + InternalInsetsInfo other = (InternalInsetsInfo)o; + return mTouchableInsets == other.mTouchableInsets && + contentInsets.equals(other.contentInsets) && + visibleInsets.equals(other.visibleInsets) && + touchableRegion.equals(other.touchableRegion); } - + void set(InternalInsetsInfo other) { contentInsets.set(other.contentInsets); visibleInsets.set(other.visibleInsets); @@ -420,6 +437,44 @@ public final class ViewTreeObserver { } /** + * <p>Register a callback to be invoked when the view tree is about to be drawn.</p> + * <p><strong>Note:</strong> this method <strong>cannot</strong> be invoked from + * {@link android.view.ViewTreeObserver.OnDrawListener#onDraw()}.</p> + * + * @param listener The callback to add + * + * @throws IllegalStateException If {@link #isAlive()} returns false + */ + public void addOnDrawListener(OnDrawListener listener) { + checkIsAlive(); + + if (mOnDrawListeners == null) { + mOnDrawListeners = new ArrayList<OnDrawListener>(); + } + + mOnDrawListeners.add(listener); + } + + /** + * <p>Remove a previously installed pre-draw callback.</p> + * <p><strong>Note:</strong> this method <strong>cannot</strong> be invoked from + * {@link android.view.ViewTreeObserver.OnDrawListener#onDraw()}.</p> + * + * @param victim The callback to remove + * + * @throws IllegalStateException If {@link #isAlive()} returns false + * + * @see #addOnDrawListener(OnDrawListener) + */ + public void removeOnDrawListener(OnDrawListener victim) { + checkIsAlive(); + if (mOnDrawListeners == null) { + return; + } + mOnDrawListeners.remove(victim); + } + + /** * Register a callback to be invoked when a view has been scrolled. * * @param listener The callback to add @@ -601,6 +656,7 @@ public final class ViewTreeObserver { * * @return True if the current draw should be canceled and resceduled, false otherwise. */ + @SuppressWarnings("unchecked") public final boolean dispatchOnPreDraw() { // NOTE: we *must* clone the listener list to perform the dispatching. // The clone is a safe guard against listeners that @@ -619,6 +675,19 @@ public final class ViewTreeObserver { } /** + * Notifies registered listeners that the drawing pass is about to start. + */ + public final void dispatchOnDraw() { + if (mOnDrawListeners != null) { + final ArrayList<OnDrawListener> listeners = mOnDrawListeners; + int numListeners = listeners.size(); + for (int i = 0; i < numListeners; ++i) { + listeners.get(i).onDraw(); + } + } + } + + /** * Notifies registered listeners that the touch mode has changed. * * @param inTouchMode True if the touch mode is now enabled, false otherwise. diff --git a/core/java/android/webkit/WebSettingsClassic.java b/core/java/android/webkit/WebSettingsClassic.java index 94b46fc..aa3d8d3 100644 --- a/core/java/android/webkit/WebSettingsClassic.java +++ b/core/java/android/webkit/WebSettingsClassic.java @@ -33,7 +33,7 @@ import java.util.Locale; */ public class WebSettingsClassic extends WebSettings { // TODO: Keep this up to date - private static final String PREVIOUS_VERSION = "4.0.3"; + private static final String PREVIOUS_VERSION = "4.0.4"; // WebView associated with this WebSettings. private WebViewClassic mWebView; diff --git a/core/java/android/widget/Chronometer.java b/core/java/android/widget/Chronometer.java index 0370049..9c9eb4b 100644 --- a/core/java/android/widget/Chronometer.java +++ b/core/java/android/widget/Chronometer.java @@ -25,6 +25,7 @@ import android.os.SystemClock; import android.text.format.DateUtils; import android.util.AttributeSet; import android.util.Log; +import android.util.Slog; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; import android.widget.RemoteViews.RemoteView; @@ -247,6 +248,7 @@ public class Chronometer extends TextView { } } setText(text); + Slog.v("Chronometer", "updateText: sec=" + seconds + " mFormat=" + mFormat + " text=" + text); } private void updateRunning() { diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index 880dc34..cbff58c 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -1609,7 +1609,6 @@ public class Editor { private boolean mCancelled; public void run() { - Log.d("GILLES", "blinking !!!"); if (mCancelled) { return; } diff --git a/core/java/android/widget/ImageView.java b/core/java/android/widget/ImageView.java index b1a75e1..91e2e49 100644 --- a/core/java/android/widget/ImageView.java +++ b/core/java/android/widget/ImageView.java @@ -201,7 +201,7 @@ public class ImageView extends View { @Override protected boolean onSetAlpha(int alpha) { - if (getBackground() == null) { + if (!USE_DISPLAY_LIST_PROPERTIES && getBackground() == null) { int scale = alpha + (alpha >> 7); if (mViewAlphaScale != scale) { mViewAlphaScale = scale; @@ -214,6 +214,15 @@ public class ImageView extends View { } @Override + public boolean hasOverlappingRendering() { + if (!USE_DISPLAY_LIST_PROPERTIES) { + return super.hasOverlappingRendering(); + } else { + return (getBackground() != null); + } + } + + @Override public void onPopulateAccessibilityEvent(AccessibilityEvent event) { super.onPopulateAccessibilityEvent(event); CharSequence contentDescription = getContentDescription(); diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 2a81f08..9867e47 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -4268,7 +4268,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener protected boolean onSetAlpha(int alpha) { // Alpha is supported if and only if the drawing can be done in one pass. // TODO text with spans with a background color currently do not respect this alpha. - if (getBackground() == null) { + if (!USE_DISPLAY_LIST_PROPERTIES && + (getBackground() != null || mText instanceof Spannable || hasSelection())) { if (mCurrentAlpha != alpha) { mCurrentAlpha = alpha; final Drawables dr = mDrawables; @@ -4292,6 +4293,15 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return false; } + @Override + public boolean hasOverlappingRendering() { + if (!USE_DISPLAY_LIST_PROPERTIES) { + return super.hasOverlappingRendering(); + } else { + return (getBackground() != null || mText instanceof Spannable || hasSelection()); + } + } + /** * When a TextView is used to display a useful piece of information to the user (such as a * contact's address), it should be made selectable, so that the user can select and copy this @@ -5330,24 +5340,63 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener physicalWidth, false); } + @Override + public void onResolvedLayoutDirectionReset() { + if (mLayoutAlignment != null) { + int resolvedTextAlignment = getResolvedTextAlignment(); + if (resolvedTextAlignment == TEXT_ALIGNMENT_VIEW_START || + resolvedTextAlignment == TEXT_ALIGNMENT_VIEW_END) { + mLayoutAlignment = null; + } + } + } + private Layout.Alignment getLayoutAlignment() { if (mLayoutAlignment == null) { - switch (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) { - case Gravity.START: + int textAlign = getResolvedTextAlignment(); + switch (textAlign) { + case TEXT_ALIGNMENT_GRAVITY: + switch (mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) { + case Gravity.START: + mLayoutAlignment = Layout.Alignment.ALIGN_NORMAL; + break; + case Gravity.END: + mLayoutAlignment = Layout.Alignment.ALIGN_OPPOSITE; + break; + case Gravity.LEFT: + mLayoutAlignment = Layout.Alignment.ALIGN_LEFT; + break; + case Gravity.RIGHT: + mLayoutAlignment = Layout.Alignment.ALIGN_RIGHT; + break; + case Gravity.CENTER_HORIZONTAL: + mLayoutAlignment = Layout.Alignment.ALIGN_CENTER; + break; + default: + mLayoutAlignment = Layout.Alignment.ALIGN_NORMAL; + break; + } + break; + case TEXT_ALIGNMENT_TEXT_START: mLayoutAlignment = Layout.Alignment.ALIGN_NORMAL; break; - case Gravity.END: + case TEXT_ALIGNMENT_TEXT_END: mLayoutAlignment = Layout.Alignment.ALIGN_OPPOSITE; break; - case Gravity.LEFT: - mLayoutAlignment = Layout.Alignment.ALIGN_LEFT; + case TEXT_ALIGNMENT_CENTER: + mLayoutAlignment = Layout.Alignment.ALIGN_CENTER; break; - case Gravity.RIGHT: - mLayoutAlignment = Layout.Alignment.ALIGN_RIGHT; + case TEXT_ALIGNMENT_VIEW_START: + mLayoutAlignment = (getResolvedLayoutDirection() == LAYOUT_DIRECTION_RTL) ? + Layout.Alignment.ALIGN_RIGHT : Layout.Alignment.ALIGN_LEFT; break; - case Gravity.CENTER_HORIZONTAL: - mLayoutAlignment = Layout.Alignment.ALIGN_CENTER; + case TEXT_ALIGNMENT_VIEW_END: + mLayoutAlignment = (getResolvedLayoutDirection() == LAYOUT_DIRECTION_RTL) ? + Layout.Alignment.ALIGN_LEFT : Layout.Alignment.ALIGN_RIGHT; break; + case TEXT_ALIGNMENT_INHERIT: + // This should never happen as we have already resolved the text alignment + // but better safe than sorry so we just fall through default: mLayoutAlignment = Layout.Alignment.ALIGN_NORMAL; break; diff --git a/core/java/com/android/internal/util/Protocol.java b/core/java/com/android/internal/util/Protocol.java index d462d7f..7c2b1b5 100644 --- a/core/java/com/android/internal/util/Protocol.java +++ b/core/java/com/android/internal/util/Protocol.java @@ -49,7 +49,7 @@ public class Protocol { public static final int BASE_DATA_CONNECTION = 0x00040000; public static final int BASE_DATA_CONNECTION_AC = 0x00041000; public static final int BASE_DATA_CONNECTION_TRACKER = 0x00042000; - public static final int BASE_DNS_PINGER = 0x00050000; + public static final int BASE_NSD_MANAGER = 0x00060000; //TODO: define all used protocols } diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp index 5e73a5f..3c27caf 100644 --- a/core/jni/android/graphics/Bitmap.cpp +++ b/core/jni/android/graphics/Bitmap.cpp @@ -236,7 +236,7 @@ static jobject Bitmap_creator(JNIEnv* env, jobject, jintArray jColors, 0, 0, width, height, bitmap);
}
- return GraphicsJNI::createBitmap(env, new SkBitmap(bitmap), buff, isMutable, NULL);
+ return GraphicsJNI::createBitmap(env, new SkBitmap(bitmap), buff, isMutable, NULL, NULL);
}
static jobject Bitmap_copy(JNIEnv* env, jobject, const SkBitmap* src,
@@ -248,7 +248,7 @@ static jobject Bitmap_copy(JNIEnv* env, jobject, const SkBitmap* src, return NULL;
}
- return GraphicsJNI::createBitmap(env, new SkBitmap(result), allocator.getStorageObj(), isMutable, NULL);
+ return GraphicsJNI::createBitmap(env, new SkBitmap(result), allocator.getStorageObj(), isMutable, NULL, NULL);
}
static void Bitmap_destructor(JNIEnv* env, jobject, SkBitmap* bitmap) {
@@ -407,7 +407,7 @@ static jobject Bitmap_createFromParcel(JNIEnv* env, jobject, jobject parcel) { bitmap->unlockPixels();
blob.release();
- return GraphicsJNI::createBitmap(env, bitmap, buffer, isMutable, NULL, density);
+ return GraphicsJNI::createBitmap(env, bitmap, buffer, isMutable, NULL, NULL, density);
}
static jboolean Bitmap_writeToParcel(JNIEnv* env, jobject,
@@ -485,7 +485,7 @@ static jobject Bitmap_extractAlpha(JNIEnv* env, jobject clazz, env->ReleaseIntArrayElements(offsetXY, array, 0);
}
- return GraphicsJNI::createBitmap(env, dst, allocator.getStorageObj(), true, NULL);
+ return GraphicsJNI::createBitmap(env, dst, allocator.getStorageObj(), true, NULL, NULL);
}
///////////////////////////////////////////////////////////////////////////////
diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp index dcd1d28..dd59444 100644 --- a/core/jni/android/graphics/BitmapFactory.cpp +++ b/core/jni/android/graphics/BitmapFactory.cpp @@ -35,6 +35,7 @@ jfieldID gOptions_mimeFieldID; jfieldID gOptions_mCancelID; jfieldID gOptions_bitmapFieldID; jfieldID gBitmap_nativeBitmapFieldID; +jfieldID gBitmap_layoutBoundsFieldID; #if 0 #define TRACE_BITMAP(code) code @@ -276,7 +277,7 @@ static jobject doDecode(JNIEnv* env, SkStream* stream, jobject padding, } jbyteArray ninePatchChunk = NULL; - if (peeker.fPatchIsValid) { + if (peeker.fPatch != NULL) { if (willScale) { scaleNinePatchChunk(peeker.fPatch, scale); } @@ -296,6 +297,18 @@ static jobject doDecode(JNIEnv* env, SkStream* stream, jobject padding, env->ReleasePrimitiveArrayCritical(ninePatchChunk, array, 0); } + jintArray layoutBounds = NULL; + if (peeker.fLayoutBounds != NULL) { + layoutBounds = env->NewIntArray(4); + if (layoutBounds == NULL) { + return nullObjectReturn("layoutBounds == null"); + } + + env->SetIntArrayRegion(layoutBounds, 0, 4, (jint*) peeker.fLayoutBounds); + if (javaBitmap != NULL) { + env->SetObjectField(javaBitmap, gBitmap_layoutBoundsFieldID, layoutBounds); + } + } // detach bitmap from its autodeleter, since we want to own it now adb.detach(); @@ -321,7 +334,7 @@ static jobject doDecode(JNIEnv* env, SkStream* stream, jobject padding, } if (padding) { - if (peeker.fPatchIsValid) { + if (peeker.fPatch != NULL) { GraphicsJNI::set_jrect(env, padding, peeker.fPatch->paddingLeft, peeker.fPatch->paddingTop, peeker.fPatch->paddingRight, peeker.fPatch->paddingBottom); @@ -350,7 +363,7 @@ static jobject doDecode(JNIEnv* env, SkStream* stream, jobject padding, } // now create the java bitmap return GraphicsJNI::createBitmap(env, bitmap, javaAllocator.getStorageObj(), - isMutable, ninePatchChunk); + isMutable, ninePatchChunk, layoutBounds, -1); } static jobject nativeDecodeStreamScaled(JNIEnv* env, jobject clazz, jobject is, jbyteArray storage, @@ -576,7 +589,7 @@ int register_android_graphics_BitmapFactory(JNIEnv* env) { jclass bitmap_class = env->FindClass("android/graphics/Bitmap"); SkASSERT(bitmap_class); gBitmap_nativeBitmapFieldID = getFieldIDCheck(env, bitmap_class, "mNativeBitmap", "I"); - + gBitmap_layoutBoundsFieldID = getFieldIDCheck(env, bitmap_class, "mLayoutBounds", "[I"); int ret = AndroidRuntime::registerNativeMethods(env, "android/graphics/BitmapFactory$Options", gOptionsMethods, diff --git a/core/jni/android/graphics/BitmapRegionDecoder.cpp b/core/jni/android/graphics/BitmapRegionDecoder.cpp index 682877a..dd8e84f 100644 --- a/core/jni/android/graphics/BitmapRegionDecoder.cpp +++ b/core/jni/android/graphics/BitmapRegionDecoder.cpp @@ -244,7 +244,7 @@ static jobject nativeDecodeRegion(JNIEnv* env, jobject, SkBitmapRegionDecoder *b JavaPixelAllocator* allocator = (JavaPixelAllocator*) decoder->getAllocator(); jbyteArray buff = allocator->getStorageObjAndReset(); - return GraphicsJNI::createBitmap(env, bitmap, buff, false, NULL, -1); + return GraphicsJNI::createBitmap(env, bitmap, buff, false, NULL, NULL, -1); } static int nativeGetHeight(JNIEnv* env, jobject, SkBitmapRegionDecoder *brd) { diff --git a/core/jni/android/graphics/Graphics.cpp b/core/jni/android/graphics/Graphics.cpp index a1d41ee..d4c7600 100644 --- a/core/jni/android/graphics/Graphics.cpp +++ b/core/jni/android/graphics/Graphics.cpp @@ -345,14 +345,14 @@ SkRegion* GraphicsJNI::getNativeRegion(JNIEnv* env, jobject region) /////////////////////////////////////////////////////////////////////////////////////////// jobject GraphicsJNI::createBitmap(JNIEnv* env, SkBitmap* bitmap, jbyteArray buffer, - bool isMutable, jbyteArray ninepatch, int density) + bool isMutable, jbyteArray ninepatch, jintArray layoutbounds, + int density) { SkASSERT(bitmap); SkASSERT(bitmap->pixelRef()); - jobject obj = env->NewObject(gBitmap_class, gBitmap_constructorMethodID, static_cast<jint>(reinterpret_cast<uintptr_t>(bitmap)), - buffer, isMutable, ninepatch, density); + buffer, isMutable, ninepatch, layoutbounds, density); hasException(env); // For the side effect of logging. return obj; } @@ -360,7 +360,7 @@ jobject GraphicsJNI::createBitmap(JNIEnv* env, SkBitmap* bitmap, jbyteArray buff jobject GraphicsJNI::createBitmap(JNIEnv* env, SkBitmap* bitmap, bool isMutable, jbyteArray ninepatch, int density) { - return createBitmap(env, bitmap, NULL, isMutable, ninepatch, density); + return createBitmap(env, bitmap, NULL, isMutable, ninepatch, NULL, density); } @@ -587,7 +587,7 @@ int register_android_graphics_Graphics(JNIEnv* env) gBitmap_class = make_globalref(env, "android/graphics/Bitmap"); gBitmap_nativeInstanceID = getFieldIDCheck(env, gBitmap_class, "mNativeBitmap", "I"); gBitmap_constructorMethodID = env->GetMethodID(gBitmap_class, "<init>", - "(I[BZ[BI)V"); + "(I[BZ[B[II)V"); gBitmapRegionDecoder_class = make_globalref(env, "android/graphics/BitmapRegionDecoder"); gBitmapRegionDecoder_constructorMethodID = env->GetMethodID(gBitmapRegionDecoder_class, "<init>", "(I)V"); diff --git a/core/jni/android/graphics/GraphicsJNI.h b/core/jni/android/graphics/GraphicsJNI.h index cc32f44..c5b06f5 100644 --- a/core/jni/android/graphics/GraphicsJNI.h +++ b/core/jni/android/graphics/GraphicsJNI.h @@ -53,7 +53,8 @@ public: storage array (may be null). */ static jobject createBitmap(JNIEnv* env, SkBitmap* bitmap, jbyteArray buffer, - bool isMutable, jbyteArray ninepatch, int density = -1); + bool isMutable, jbyteArray ninepatch, jintArray layoutbounds, + int density = -1); static jobject createBitmap(JNIEnv* env, SkBitmap* bitmap, bool isMutable, jbyteArray ninepatch, int density = -1); diff --git a/core/jni/android/graphics/NinePatchPeeker.cpp b/core/jni/android/graphics/NinePatchPeeker.cpp index 365d985..df996af 100644 --- a/core/jni/android/graphics/NinePatchPeeker.cpp +++ b/core/jni/android/graphics/NinePatchPeeker.cpp @@ -31,14 +31,11 @@ bool NinePatchPeeker::peek(const char tag[], const void* data, size_t length) { // this relies on deserialization being done in place Res_png_9patch::deserialize(patchNew); patchNew->fileToDevice(); - if (fPatchIsValid) { - free(fPatch); - } + free(fPatch); fPatch = patchNew; //printf("9patch: (%d,%d)-(%d,%d)\n", // fPatch.sizeLeft, fPatch.sizeTop, // fPatch.sizeRight, fPatch.sizeBottom); - fPatchIsValid = true; // now update our host to force index or 32bit config // 'cause we don't want 565 predithered, since as a 9patch, we know @@ -52,8 +49,9 @@ bool NinePatchPeeker::peek(const char tag[], const void* data, size_t length) { SkBitmap::kARGB_8888_Config, }; fHost->setPrefConfigTable(gNo565Pref); - } else { - fPatch = NULL; + } else if (strcmp("npLb", tag) == 0 && length == sizeof(int) * 4) { + fLayoutBounds = new int[4]; + memcpy(fLayoutBounds, data, sizeof(int) * 4); } return true; // keep on decoding } diff --git a/core/jni/android/graphics/NinePatchPeeker.h b/core/jni/android/graphics/NinePatchPeeker.h index 207536c..10d268a 100644 --- a/core/jni/android/graphics/NinePatchPeeker.h +++ b/core/jni/android/graphics/NinePatchPeeker.h @@ -28,17 +28,17 @@ public: NinePatchPeeker(SkImageDecoder* host) { // the host lives longer than we do, so a raw ptr is safe fHost = host; - fPatchIsValid = false; + fPatch = NULL; + fLayoutBounds = NULL; } ~NinePatchPeeker() { - if (fPatchIsValid) { - free(fPatch); - } + free(fPatch); + delete fLayoutBounds; } - bool fPatchIsValid; Res_png_9patch* fPatch; + int *fLayoutBounds; virtual bool peek(const char tag[], const void* data, size_t length); }; diff --git a/core/jni/android_view_GLES20DisplayList.cpp b/core/jni/android_view_GLES20DisplayList.cpp index 60fb6d4..b307a2f 100644 --- a/core/jni/android_view_GLES20DisplayList.cpp +++ b/core/jni/android_view_GLES20DisplayList.cpp @@ -65,6 +65,11 @@ static void android_view_GLES20DisplayList_setAlpha(JNIEnv* env, displayList->setAlpha(alpha); } +static void android_view_GLES20DisplayList_setHasOverlappingRendering(JNIEnv* env, + jobject clazz, DisplayList* displayList, bool hasOverlappingRendering) { + displayList->setHasOverlappingRendering(hasOverlappingRendering); +} + static void android_view_GLES20DisplayList_setTranslationX(JNIEnv* env, jobject clazz, DisplayList* displayList, float tx) { displayList->setTranslationX(tx); @@ -185,6 +190,8 @@ static JNINativeMethod gMethods[] = { { "nSetAnimationMatrix", "(II)V", (void*) android_view_GLES20DisplayList_setAnimationMatrix }, { "nSetClipChildren", "(IZ)V", (void*) android_view_GLES20DisplayList_setClipChildren }, { "nSetAlpha", "(IF)V", (void*) android_view_GLES20DisplayList_setAlpha }, + { "nSetHasOverlappingRendering", "(IZ)V", + (void*) android_view_GLES20DisplayList_setHasOverlappingRendering }, { "nSetTranslationX", "(IF)V", (void*) android_view_GLES20DisplayList_setTranslationX }, { "nSetTranslationY", "(IF)V", (void*) android_view_GLES20DisplayList_setTranslationY }, { "nSetRotation", "(IF)V", (void*) android_view_GLES20DisplayList_setRotation }, diff --git a/core/res/res/layout/notification_template_base.xml b/core/res/res/layout/notification_template_base.xml index 5b06460..b9710d6 100644 --- a/core/res/res/layout/notification_template_base.xml +++ b/core/res/res/layout/notification_template_base.xml @@ -28,7 +28,7 @@ <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_gravity="center_vertical" + android:layout_gravity="fill_vertical" android:layout_marginLeft="@dimen/notification_large_icon_width" android:minHeight="@dimen/notification_large_icon_height" android:orientation="vertical" @@ -36,6 +36,7 @@ android:paddingRight="12dp" android:paddingTop="4dp" android:paddingBottom="4dp" + android:gravity="center_vertical" > <LinearLayout android:id="@+id/line1" @@ -52,15 +53,21 @@ android:fadingEdge="horizontal" android:layout_weight="1" /> - <DateTimeView android:id="@+id/time" - android:textAppearance="@style/TextAppearance.StatusBar.EventContent.Time" + <ViewStub android:id="@+id/time" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:layout_weight="0" - android:singleLine="true" - android:gravity="center" - android:paddingLeft="8dp" + android:visibility="gone" + android:layout="@layout/notification_template_part_time" + /> + <ViewStub android:id="@+id/chronometer" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:layout_weight="0" + android:visibility="gone" + android:layout="@layout/notification_template_part_chronometer" /> </LinearLayout> <TextView android:id="@+id/text2" diff --git a/core/res/res/layout/notification_template_part_chronometer.xml b/core/res/res/layout/notification_template_part_chronometer.xml new file mode 100644 index 0000000..382b0e4 --- /dev/null +++ b/core/res/res/layout/notification_template_part_chronometer.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2012 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. +--> + +<Chronometer android:id="@+id/chronometer" xmlns:android="http://schemas.android.com/apk/res/android" + android:textAppearance="@style/TextAppearance.StatusBar.EventContent.Time" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:layout_weight="0" + android:singleLine="true" + android:gravity="center" + android:paddingLeft="8dp" + /> diff --git a/core/res/res/layout/notification_template_part_time.xml b/core/res/res/layout/notification_template_part_time.xml new file mode 100644 index 0000000..410fcaf --- /dev/null +++ b/core/res/res/layout/notification_template_part_time.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2012 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. +--> + +<DateTimeView android:id="@+id/time" xmlns:android="http://schemas.android.com/apk/res/android" + android:textAppearance="@style/TextAppearance.StatusBar.EventContent.Time" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:layout_weight="0" + android:singleLine="true" + android:gravity="center" + android:paddingLeft="8dp" + /> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 438c141..2b27585 100755 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -2079,6 +2079,7 @@ <!-- Locale --> <enum name="locale" value="3" /> </attr> + <!-- Direction of the text. A heuristic is used to determine the resolved text direction of paragraphs. --> <attr name="textDirection" format="integer"> @@ -2099,6 +2100,29 @@ <!-- The paragraph direction is coming from the system Locale. --> <enum name="locale" value="5" /> </attr> + + <!-- Alignment of the text. A heuristic is used to determine the resolved + text alignment. --> + <attr name="textAlignment" format="integer"> + <!-- Default --> + <enum name="inherit" value="0" /> + <!-- Default for the root view. The gravity determines the alignment, ALIGN_NORMAL, + ALIGN_CENTER, or ALIGN_OPPOSITE, which are relative to each paragraph’s + text direction --> + <enum name="gravity" value="1" /> + <!-- Align to the start of the paragraph, e.g. ALIGN_NORMAL. --> + <enum name="textStart" value="2" /> + <!-- Align to the end of the paragraph, e.g. ALIGN_OPPOSITE. --> + <enum name="textEnd" value="3" /> + <!-- Center the paragraph, e.g. ALIGN_CENTER. --> + <enum name="center" value="4" /> + <!-- Align to the start of the view, which is ALIGN_LEFT if the view’s resolved + layoutDirection is LTR, and ALIGN_RIGHT otherwise. --> + <enum name="viewStart" value="5" /> + <!-- Align to the end of the view, which is ALIGN_RIGHT if the view’s resolved + layoutDirection is LTR, and ALIGN_LEFT otherwise --> + <enum name="viewEnd" value="6" /> + </attr> </declare-styleable> <!-- Attributes that can be used with a {@link android.view.ViewGroup} or any diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index e5f049d..31b444a 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -202,6 +202,7 @@ <java-symbol type="id" name="action2" /> <java-symbol type="id" name="big_picture" /> <java-symbol type="id" name="big_text" /> + <java-symbol type="id" name="chronometer" /> <java-symbol type="attr" name="actionModeShareDrawable" /> <java-symbol type="attr" name="alertDialogCenterButtons" /> @@ -1079,6 +1080,8 @@ <java-symbol type="layout" name="notification_intruder_content" /> <java-symbol type="layout" name="notification_template_base" /> <java-symbol type="layout" name="notification_template_big_picture" /> + <java-symbol type="layout" name="notification_template_part_time" /> + <java-symbol type="layout" name="notification_template_part_chronometer" /> <java-symbol type="anim" name="slide_in_child_bottom" /> <java-symbol type="anim" name="slide_in_right" /> @@ -3554,6 +3557,7 @@ <public type="attr" name="supportsRtl" id="0x010103a8" /> <public type="attr" name="textDirection"/> + <public type="attr" name="textAlignment"/> <public type="attr" name="layoutDirection" /> diff --git a/docs/html/design/building-blocks/progress.jd b/docs/html/design/building-blocks/progress.jd index dc3ded1..b188538 100644 --- a/docs/html/design/building-blocks/progress.jd +++ b/docs/html/design/building-blocks/progress.jd @@ -42,9 +42,9 @@ available space.</p> <li class="value-1"><h4>Activity bar (shown with the Holo Dark theme)</h4> <p> -An indeterminate activity bar is used at the start of an application download because Google Play hasn't -been able to contact the server yet, and it's not possible to determine how long it will take for -the download to begin. +An indeterminate activity bar is used at the start of an application download because the Play Store +app hasn't been able to contact the server yet, and it's not possible to determine how long it will +take for the download to begin. </p> </li> diff --git a/docs/html/design/building-blocks/tabs.jd b/docs/html/design/building-blocks/tabs.jd index 2c854d3..19ed1c3 100644 --- a/docs/html/design/building-blocks/tabs.jd +++ b/docs/html/design/building-blocks/tabs.jd @@ -25,7 +25,7 @@ to the next/previous view, swipe left or right.</p> <source src="{@docRoot}design/media/tabs_scrolly.ogv" type="video/ogg"> </video> <div class="figure-caption"> - Scrolling tabs in Google Play. + Scrolling tabs in the Play Store app. <div class="video-instructions"> </div> </div> diff --git a/docs/html/design/design_toc.cs b/docs/html/design/design_toc.cs index 19b58d9..6dd8d61 100644 --- a/docs/html/design/design_toc.cs +++ b/docs/html/design/design_toc.cs @@ -35,6 +35,7 @@ <li><a href="<?cs var:toroot ?>design/patterns/swipe-views.html">Swipe Views</a></li> <li><a href="<?cs var:toroot ?>design/patterns/selection.html">Selection</a></li> <li><a href="<?cs var:toroot ?>design/patterns/notifications.html">Notifications</a></li> + <li><a href="<?cs var:toroot ?>design/patterns/settings.html">Settings</a></li> <li><a href="<?cs var:toroot ?>design/patterns/compatibility.html">Compatibility</a></li> <li><a href="<?cs var:toroot ?>design/patterns/pure-android.html">Pure Android</a></li> </ul> diff --git a/docs/html/design/downloads/index.jd b/docs/html/design/downloads/index.jd index 618c44b..67dfd79 100644 --- a/docs/html/design/downloads/index.jd +++ b/docs/html/design/downloads/index.jd @@ -39,7 +39,7 @@ available.</p> <p> <a class="download-button" href="https://dl-ssl.google.com/android/design/Android_Design_Fireworks_Stencil_20120229.png">Adobe® Fireworks® PNG Stencil</a> <a class="download-button" href="https://dl-ssl.google.com/android/design/Android_Design_OmniGraffle_Stencil_20120229.graffle">Omni® OmniGraffle® Stencil</a> - <a class="download-button" href="https://dl-ssl.google.com/android/design/Android_Design_Holo_Widgets_20120229.zip">Adobe® Photoshop® Sources</a> + <a class="download-button" href="https://dl-ssl.google.com/android/design/Android_Design_Holo_Widgets_20120302.zip">Adobe® Photoshop® Sources</a> </p> </div> diff --git a/docs/html/design/get-started/principles.jd b/docs/html/design/get-started/principles.jd index 8f5b446..0b7147b 100644 --- a/docs/html/design/get-started/principles.jd +++ b/docs/html/design/get-started/principles.jd @@ -10,7 +10,7 @@ with purpose.</p> <div class="layout-content-row"> <div class="layout-content-col span-7"> -<h4>Delight me in surprising ways</h4> +<h4 id="delight-me">Delight me in surprising ways</h4> <p>A beautiful surface, a carefully-placed animation, or a well-timed sound effect is a joy to experience. Subtle effects contribute to a feeling of effortlessness and a sense that a powerful force is at hand.</p> @@ -28,7 +28,7 @@ force is at hand.</p> <div class="layout-content-row"> <div class="layout-content-col span-7"> -<h4>Real objects are more fun than buttons and menus</h4> +<h4 id="real-objects-more-fun">Real objects are more fun than buttons and menus</h4> <p>Allow people to directly touch and manipulate objects in your app. It reduces the cognitive effort needed to perform a task while making it more emotionally satisfying.</p> @@ -45,7 +45,7 @@ needed to perform a task while making it more emotionally satisfying.</p> <div class="layout-content-row"> <div class="layout-content-col span-7"> -<h4>Let me make it mine</h4> +<h4 id="make-it-mine">Let me make it mine</h4> <p>People love to add personal touches because it helps them feel at home and in control. Provide sensible, beautiful defaults, but also consider fun, optional customizations that don't hinder primary tasks.</p> @@ -63,7 +63,7 @@ primary tasks.</p> <div class="layout-content-row"> <div class="layout-content-col span-7"> -<h4>Get to know me</h4> +<h4 id="get-to-know-me">Get to know me</h4> <p>Learn peoples' preferences over time. Rather than asking them to make the same choices over and over, place previous choices within easy reach.</p> @@ -80,7 +80,7 @@ over, place previous choices within easy reach.</p> <div class="layout-content-row"> <div class="layout-content-col span-7"> -<h4>Keep it brief</h4> +<h4 id="keep-it-brief">Keep it brief</h4> <p>Use short phrases with simple words. People are likely to skip sentences if they're long.</p> </div> @@ -96,7 +96,7 @@ over, place previous choices within easy reach.</p> <div class="layout-content-row"> <div class="layout-content-col span-7"> -<h4>Pictures are faster than words</h4> +<h4 id="pictures-faster-than-words">Pictures are faster than words</h4> <p>Consider using pictures to explain ideas. They get people's attention and can be much more efficient than words.</p> @@ -113,7 +113,7 @@ than words.</p> <div class="layout-content-row"> <div class="layout-content-col span-7"> -<h4>Decide for me but let me have the final say</h4> +<h4 id="decide-for-me">Decide for me but let me have the final say</h4> <p>Take your best guess and act rather than asking first. Too many choices and decisions make people unhappy. Just in case you get it wrong, allow for 'undo'.</p> @@ -130,7 +130,7 @@ unhappy. Just in case you get it wrong, allow for 'undo'.</p> <div class="layout-content-row"> <div class="layout-content-col span-7"> -<h4>Only show what I need when I need it</h4> +<h4 id="only-show-when-i-need-it">Only show what I need when I need it</h4> <p>People get overwhelmed when they see too much at once. Break tasks and information into small, digestible chunks. Hide options that aren't essential at the moment, and teach people as they go.</p> @@ -147,7 +147,7 @@ digestible chunks. Hide options that aren't essential at the moment, and teach p <div class="layout-content-row"> <div class="layout-content-col span-7"> -<h4>I should always know where I am</h4> +<h4 id="always-know-where-i-am">I should always know where I am</h4> <p>Give people confidence that they know their way around. Make places in your app look distinct and use transitions to show relationships among screens. Provide feedback on tasks in progress.</p> @@ -164,7 +164,7 @@ use transitions to show relationships among screens. Provide feedback on tasks i <div class="layout-content-row"> <div class="layout-content-col span-7"> -<h4>Never lose my stuff</h4> +<h4 id="never-lose-my-stuff">Never lose my stuff</h4> <p>Save what people took time to create and let them access it from anywhere. Remember settings, personal touches, and creations across phones, tablets, and computers. It makes upgrading the easiest thing in the world.</p> @@ -182,7 +182,7 @@ easiest thing in the world.</p> <div class="layout-content-row"> <div class="layout-content-col span-7"> -<h4>If it looks the same, it should act the same</h4> +<h4 id="looks-same-should-act-same">If it looks the same, it should act the same</h4> <p>Help people discern functional differences by making them visually distinct rather than subtle. Avoid modes, which are places that look similar but act differently on the same input.</p> @@ -199,7 +199,7 @@ Avoid modes, which are places that look similar but act differently on the same <div class="layout-content-row"> <div class="layout-content-col span-7"> -<h4>Only interrupt me if it's important</h4> +<h4 id="interrupt-only-if-important">Only interrupt me if it's important</h4> <p>Like a good personal assistant, shield people from unimportant minutiae. People want to stay focused, and unless it's critical and time-sensitive, an interruption can be taxing and frustrating.</p> @@ -216,7 +216,7 @@ focused, and unless it's critical and time-sensitive, an interruption can be tax <div class="layout-content-row"> <div class="layout-content-col span-7"> -<h4>Give me tricks that work everywhere</h4> +<h4 id="give-me-tricks">Give me tricks that work everywhere</h4> <p>People feel great when they figure things out for themselves. Make your app easier to learn by leveraging visual patterns and muscle memory from other Android apps. For example, the swipe gesture may be a good navigational shortcut.</p> @@ -234,7 +234,7 @@ may be a good navigational shortcut.</p> <div class="layout-content-row"> <div class="layout-content-col span-7"> -<h4>It's not my fault</h4> +<h4 id="its-not-my-fault">It's not my fault</h4> <p>Be gentle in how you prompt people to make corrections. They want to feel smart when they use your app. If something goes wrong, give clear recovery instructions but spare them the technical details. If you can fix it behind the scenes, even better.</p> @@ -252,7 +252,7 @@ If you can fix it behind the scenes, even better.</p> <div class="layout-content-row"> <div class="layout-content-col span-7"> -<h4>Sprinkle encouragement</h4> +<h4 id="sprinkle-encouragement">Sprinkle encouragement</h4> <p>Break complex tasks into smaller steps that can be easily accomplished. Give feedback on actions, even if it's just a subtle glow.</p> @@ -269,7 +269,7 @@ even if it's just a subtle glow.</p> <div class="layout-content-row"> <div class="layout-content-col span-7"> -<h4>Do the heavy lifting for me</h4> +<h4 id="do-heavy-lifting-for-me">Do the heavy lifting for me</h4> <p>Make novices feel like experts by enabling them to do things they never thought they could. For example, shortcuts that combine multiple photo effects can make amateur photographs look amazing in only a few steps.</p> @@ -287,7 +287,7 @@ only a few steps.</p> <div class="layout-content-row"> <div class="layout-content-col span-7"> -<h4>Make important things fast</h4> +<h4 id="make-important-things-fast">Make important things fast</h4> <p>Not all actions are equal. Decide what's most important in your app and make it easy to find and fast to use, like the shutter button in a camera, or the pause button in a music player.</p> diff --git a/docs/html/design/media/app_structure_market.png b/docs/html/design/media/app_structure_market.png Binary files differindex 3b0b786..5aa595e 100644 --- a/docs/html/design/media/app_structure_market.png +++ b/docs/html/design/media/app_structure_market.png diff --git a/docs/html/design/media/app_structure_music_lndscp.png b/docs/html/design/media/app_structure_music_lndscp.png Binary files differindex 0dd400c..67354de 100644 --- a/docs/html/design/media/app_structure_music_lndscp.png +++ b/docs/html/design/media/app_structure_music_lndscp.png diff --git a/docs/html/design/media/app_structure_scrolltabs.png b/docs/html/design/media/app_structure_scrolltabs.png Binary files differindex ef4fca4..ea742c2 100644 --- a/docs/html/design/media/app_structure_scrolltabs.png +++ b/docs/html/design/media/app_structure_scrolltabs.png diff --git a/docs/html/design/media/app_structure_shortcut_on_item.png b/docs/html/design/media/app_structure_shortcut_on_item.png Binary files differindex 3874e1d4..1341f1f 100644 --- a/docs/html/design/media/app_structure_shortcut_on_item.png +++ b/docs/html/design/media/app_structure_shortcut_on_item.png diff --git a/docs/html/design/media/iconography_launcher_example.png b/docs/html/design/media/iconography_launcher_example.png Binary files differindex a5db53e..0cce7ef 100644 --- a/docs/html/design/media/iconography_launcher_example.png +++ b/docs/html/design/media/iconography_launcher_example.png diff --git a/docs/html/design/media/iconography_overview.png b/docs/html/design/media/iconography_overview.png Binary files differindex 688c1b5..56cd409 100644 --- a/docs/html/design/media/iconography_overview.png +++ b/docs/html/design/media/iconography_overview.png diff --git a/docs/html/design/media/migrating_intents.png b/docs/html/design/media/migrating_intents.png Binary files differnew file mode 100644 index 0000000..65fc1a5 --- /dev/null +++ b/docs/html/design/media/migrating_intents.png diff --git a/docs/html/design/media/misc_full_galaxynexus_blank_land_span13.png b/docs/html/design/media/misc_full_galaxynexus_blank_land_span13.png Binary files differdeleted file mode 100644 index bab6aca..0000000 --- a/docs/html/design/media/misc_full_galaxynexus_blank_land_span13.png +++ /dev/null diff --git a/docs/html/design/media/misc_full_galaxynexus_blank_port_span5.png b/docs/html/design/media/misc_full_galaxynexus_blank_port_span5.png Binary files differdeleted file mode 100644 index bdccc2f..0000000 --- a/docs/html/design/media/misc_full_galaxynexus_blank_port_span5.png +++ /dev/null diff --git a/docs/html/design/media/misc_full_galaxynexus_blank_port_span9.png b/docs/html/design/media/misc_full_galaxynexus_blank_port_span9.png Binary files differdeleted file mode 100644 index 5e0135b..0000000 --- a/docs/html/design/media/misc_full_galaxynexus_blank_port_span9.png +++ /dev/null diff --git a/docs/html/design/media/navigation_between_apps_back.png b/docs/html/design/media/navigation_between_apps_back.png Binary files differnew file mode 100755 index 0000000..ded5d0a --- /dev/null +++ b/docs/html/design/media/navigation_between_apps_back.png diff --git a/docs/html/design/media/navigation_between_apps_inward.png b/docs/html/design/media/navigation_between_apps_inward.png Binary files differnew file mode 100755 index 0000000..1f5e401 --- /dev/null +++ b/docs/html/design/media/navigation_between_apps_inward.png diff --git a/docs/html/design/media/navigation_between_apps_up.png b/docs/html/design/media/navigation_between_apps_up.png Binary files differnew file mode 100755 index 0000000..f192c88 --- /dev/null +++ b/docs/html/design/media/navigation_between_apps_up.png diff --git a/docs/html/design/media/navigation_between_siblings_market1.png b/docs/html/design/media/navigation_between_siblings_market1.png Binary files differindex c3148f8..8f2b3dc 100644..100755 --- a/docs/html/design/media/navigation_between_siblings_market1.png +++ b/docs/html/design/media/navigation_between_siblings_market1.png diff --git a/docs/html/design/media/navigation_between_siblings_market2.png b/docs/html/design/media/navigation_between_siblings_market2.png Binary files differindex 208be47..33b654c 100644..100755 --- a/docs/html/design/media/navigation_between_siblings_market2.png +++ b/docs/html/design/media/navigation_between_siblings_market2.png diff --git a/docs/html/design/media/navigation_from_outside_up.png b/docs/html/design/media/navigation_from_outside_up.png Binary files differdeleted file mode 100644 index eaa3cdb..0000000 --- a/docs/html/design/media/navigation_from_outside_up.png +++ /dev/null diff --git a/docs/html/design/media/navigation_indirect_notification.png b/docs/html/design/media/navigation_indirect_notification.png Binary files differnew file mode 100644 index 0000000..6f99267 --- /dev/null +++ b/docs/html/design/media/navigation_indirect_notification.png diff --git a/docs/html/design/media/navigation_popup_notification.png b/docs/html/design/media/navigation_popup_notification.png Binary files differnew file mode 100644 index 0000000..a0a3ee7 --- /dev/null +++ b/docs/html/design/media/navigation_popup_notification.png diff --git a/docs/html/design/media/navigation_up_vs_back_gmail.png b/docs/html/design/media/navigation_up_vs_back_gmail.png Binary files differindex 71e6484..ff7adfe 100644 --- a/docs/html/design/media/navigation_up_vs_back_gmail.png +++ b/docs/html/design/media/navigation_up_vs_back_gmail.png diff --git a/docs/html/design/media/navigation_with_back_and_up.png b/docs/html/design/media/navigation_with_back_and_up.png Binary files differindex 4fb6dce..5440220 100644 --- a/docs/html/design/media/navigation_with_back_and_up.png +++ b/docs/html/design/media/navigation_with_back_and_up.png diff --git a/docs/html/design/media/progress_activity.png b/docs/html/design/media/progress_activity.png Binary files differindex 51210b4..32cf1f5 100644 --- a/docs/html/design/media/progress_activity.png +++ b/docs/html/design/media/progress_activity.png diff --git a/docs/html/design/media/progress_download.png b/docs/html/design/media/progress_download.png Binary files differindex f567f74..ab6bf58 100644 --- a/docs/html/design/media/progress_download.png +++ b/docs/html/design/media/progress_download.png diff --git a/docs/html/design/media/settings_checkbox.png b/docs/html/design/media/settings_checkbox.png Binary files differnew file mode 100644 index 0000000..6615bfb --- /dev/null +++ b/docs/html/design/media/settings_checkbox.png diff --git a/docs/html/design/media/settings_date_time.png b/docs/html/design/media/settings_date_time.png Binary files differnew file mode 100644 index 0000000..8df92d4 --- /dev/null +++ b/docs/html/design/media/settings_date_time.png diff --git a/docs/html/design/media/settings_dependency.png b/docs/html/design/media/settings_dependency.png Binary files differnew file mode 100644 index 0000000..4821c61 --- /dev/null +++ b/docs/html/design/media/settings_dependency.png diff --git a/docs/html/design/media/settings_flowchart.png b/docs/html/design/media/settings_flowchart.png Binary files differnew file mode 100644 index 0000000..7e8623c --- /dev/null +++ b/docs/html/design/media/settings_flowchart.png diff --git a/docs/html/design/media/settings_grouping.png b/docs/html/design/media/settings_grouping.png Binary files differnew file mode 100644 index 0000000..d271ea8 --- /dev/null +++ b/docs/html/design/media/settings_grouping.png diff --git a/docs/html/design/media/settings_individual_on_off.png b/docs/html/design/media/settings_individual_on_off.png Binary files differnew file mode 100644 index 0000000..03bea0b --- /dev/null +++ b/docs/html/design/media/settings_individual_on_off.png diff --git a/docs/html/design/media/settings_list_subscreen.png b/docs/html/design/media/settings_list_subscreen.png Binary files differnew file mode 100644 index 0000000..385aa6a --- /dev/null +++ b/docs/html/design/media/settings_list_subscreen.png diff --git a/docs/html/design/media/settings_main.png b/docs/html/design/media/settings_main.png Binary files differnew file mode 100644 index 0000000..f42a358 --- /dev/null +++ b/docs/html/design/media/settings_main.png diff --git a/docs/html/design/media/settings_master_on_off.png b/docs/html/design/media/settings_master_on_off.png Binary files differnew file mode 100644 index 0000000..e46bb97 --- /dev/null +++ b/docs/html/design/media/settings_master_on_off.png diff --git a/docs/html/design/media/settings_master_on_off_2.png b/docs/html/design/media/settings_master_on_off_2.png Binary files differnew file mode 100644 index 0000000..ab4e992 --- /dev/null +++ b/docs/html/design/media/settings_master_on_off_2.png diff --git a/docs/html/design/media/settings_multiple_choice.png b/docs/html/design/media/settings_multiple_choice.png Binary files differnew file mode 100644 index 0000000..9b28566 --- /dev/null +++ b/docs/html/design/media/settings_multiple_choice.png diff --git a/docs/html/design/media/settings_overflow.png b/docs/html/design/media/settings_overflow.png Binary files differnew file mode 100644 index 0000000..9000bec --- /dev/null +++ b/docs/html/design/media/settings_overflow.png diff --git a/docs/html/design/media/settings_slider.png b/docs/html/design/media/settings_slider.png Binary files differnew file mode 100644 index 0000000..51545c8 --- /dev/null +++ b/docs/html/design/media/settings_slider.png diff --git a/docs/html/design/media/settings_subscreen_navigation.png b/docs/html/design/media/settings_subscreen_navigation.png Binary files differnew file mode 100644 index 0000000..2ab0b96 --- /dev/null +++ b/docs/html/design/media/settings_subscreen_navigation.png diff --git a/docs/html/design/media/tabs_scrolly.mp4 b/docs/html/design/media/tabs_scrolly.mp4 Binary files differindex 4329243..dc9e9c6 100644 --- a/docs/html/design/media/tabs_scrolly.mp4 +++ b/docs/html/design/media/tabs_scrolly.mp4 diff --git a/docs/html/design/media/tabs_scrolly.ogv b/docs/html/design/media/tabs_scrolly.ogv Binary files differindex 345e57a..3e484f9 100644 --- a/docs/html/design/media/tabs_scrolly.ogv +++ b/docs/html/design/media/tabs_scrolly.ogv diff --git a/docs/html/design/media/tabs_scrolly.webm b/docs/html/design/media/tabs_scrolly.webm Binary files differindex 17e368e..e9d371d 100644 --- a/docs/html/design/media/tabs_scrolly.webm +++ b/docs/html/design/media/tabs_scrolly.webm diff --git a/docs/html/design/media/ui_overview_all_apps.png b/docs/html/design/media/ui_overview_all_apps.png Binary files differindex 467f5ad..17e7ece 100644 --- a/docs/html/design/media/ui_overview_all_apps.png +++ b/docs/html/design/media/ui_overview_all_apps.png diff --git a/docs/html/design/patterns/actionbar.jd b/docs/html/design/patterns/actionbar.jd index 9e3f48c..2226fec 100644 --- a/docs/html/design/patterns/actionbar.jd +++ b/docs/html/design/patterns/actionbar.jd @@ -176,7 +176,7 @@ themselves.</p> <source src="{@docRoot}design/media/tabs_scrolly.ogv" type="video/ogg"> </video> <div class="figure-caption"> - Scrolling tabs in Google Play. + Scrolling tabs in the Play Store app. <div class="video-instructions"> </div> </div> diff --git a/docs/html/design/patterns/app-structure.jd b/docs/html/design/patterns/app-structure.jd index b54b12f..e2398ed 100644 --- a/docs/html/design/patterns/app-structure.jd +++ b/docs/html/design/patterns/app-structure.jd @@ -7,7 +7,7 @@ page.title=Application Structure single screen</li> <li>Apps such as Phone whose main purpose is to switch between different activities without deeper navigation</li> -<li>Apps such as Gmail or Google Play that combine a broad set of data views with deep navigation</li> +<li>Apps such as Gmail or the Play Store that combine a broad set of data views with deep navigation</li> </ul> <p>Your app's structure depends largely on the content and tasks you want to surface for your users.</p> <h2 id="general-structure">General Structure</h2> @@ -60,7 +60,7 @@ layouts that are visually engaging and appropriate for the data type and screen <img src="{@docRoot}design/media/app_structure_market.png"> <div class="figure-caption"> - The Google Play app's start screen primarily allows navigation into the stores for Apps, Music, Books, + The Play Store app's start screen primarily allows navigation into the stores for Apps, Music, Books, Movies and Games. It is also enriched with tailored recommendations and promotions that surface content of interest to the user. Search is readily available from the action bar. </div> @@ -145,8 +145,8 @@ minimize navigational effort. Rule of thumb: no more than 5–7 tabs.</p> <img src="{@docRoot}design/media/app_structure_scrolltabs.png"> <div class="figure-caption"> - Google Play uses tabs to simultaneously show category choice and content. To navigate between - categories, users can swipe left/right on the content. + The Play Store app uses tabs to simultaneously show category choice and content. To navigate + between categories, users can swipe left/right on the content. </div> </div> diff --git a/docs/html/design/patterns/navigation.jd b/docs/html/design/patterns/navigation.jd index d35cd82..7e288ae 100644 --- a/docs/html/design/patterns/navigation.jd +++ b/docs/html/design/patterns/navigation.jd @@ -13,32 +13,36 @@ the <em>Up</em> button, consisting of the app icon and a left-point caret.</p> <h2 id="up-vs-back">Up vs. Back</h2> -<p>The Up button is used to navigate within an application based on the hierarchical relationships +<p>The Up button is used to navigate within an app based on the hierarchical relationships between screens. For instance, if screen A displays a list of items, and selecting an item leads to screen B (which presents that item in more detail), then screen B should offer an Up button that returns to screen A.</p> -<p>If a screen is the topmost one in an app (i.e. the home of the app), it should not present an Up +<p>If a screen is the topmost one in an app (that is, the app's home), it should not present an Up button.</p> -<p>The system Back key is used to navigate based on the history of screens the user has recently seen, -in reverse chronological order—in effect, the temporal relationships between screens.</p> + +<p>The system Back button is used to navigate, in reverse chronological order, through the history +of screens the user has recently worked with. It is generally based on the temporal relationships +between screens, rather than the app's hierarchy.</p> + <p>When the previously viewed screen is also the hierarchical parent of the current screen, pressing -the Back key will have the same result as pressing an Up button -- this is a common occurrence. -However, unlike the Up button, which ensures the user remains within your app, the Back key can -return the user to the Home screen, or even to a different application.</p> +the Back button has the same result as pressing an Up button—this is a common +occurrence. However, unlike the Up button, which ensures the user remains within your app, the Back +button can return the user to the Home screen, or even to a different app.</p> <img src="{@docRoot}design/media/navigation_up_vs_back_gmail.png"> -<p>The Back key also supports a few behaviors not directly tied to screen-to-screen navigation:</p> +<p>The Back button also supports a few behaviors not directly tied to screen-to-screen navigation: +</p> <ul> -<li>Back dismisses floating windows (dialogs, popups)</li> -<li>Back dismisses contextual action bars, and removes the highlight from the selected items</li> -<li>Back hides the onscreen keyboard (IME)</li> +<li>Dismisses floating windows (dialogs, popups)</li> +<li>Dismisses contextual action bars, and removes the highlight from the selected items</li> +<li>Hides the onscreen keyboard (IME)</li> </ul> <h2 id="within-app">Navigation Within Your App</h2> <h4>Navigating to screens with multiple entry points</h4> <p>Sometimes a screen doesn't have a strict position within the app's hierarchy, and can be reached -from multiple entry points—e.g., a settings screen which can be navigated to from any screen +from multiple entry points—such as a settings screen that can be reached from any other screen in your app. In this case, the Up button should choose to return to the referring screen, behaving identically to Back.</p> <h4>Changing view within a screen</h4> @@ -50,7 +54,7 @@ in the same place within the app's hierarchy, and no new navigation history is c <li>Switching views using a dropdown (aka collapsed tabs)</li> <li>Filtering a list</li> <li>Sorting a list</li> -<li>Changing display characteristics (e.g. zooming)</li> +<li>Changing display characteristics (such as zooming)</li> </ul> <h4>Navigating between sibling screens</h4> <p>When your app supports navigation from a list of items to a detail view of one of those items, it's @@ -61,56 +65,140 @@ navigation does not change the behavior of Up or Back.</p> <img src="{@docRoot}design/media/navigation_between_siblings_gmail.png"> -<p>However, a notable exception to this occurs when browsing between "related" detail views not tied -together by the referring list—for example, when browsing on Google Play between apps from +<p>However, a notable exception to this occurs when browsing between related detail views not tied +together by the referring list—for example, when browsing in the Play Store between apps from the same developer, or albums by the same artist. In these cases, following each link does create -history, causing the Back button to step through each screen of related content which has been -viewed. Up should continue to bypass these related screens and navigate to the most recently viewed -container screen.</p> +history, causing the Back button to step through each previously viewed screen. Up should continue +to bypass these related screens and navigate to the most recently viewed container screen.</p> <img src="{@docRoot}design/media/navigation_between_siblings_market1.png"> <p>You have the ability to make the Up behavior even smarter based on your knowledge of detail -view. If we extend our Google Play sample from above, imagine the user has navigated from the last Book -viewed to the details for the Movie adaptation. In that case, Up can return to a container (Movies) -which the user had not previously navigated through.</p> +view. Extending the Play Store example from above, imagine the user has navigated from the last +Book viewed to the details for the Movie adaptation. In that case, Up can return to a container +(Movies) which the user hasn't previously navigated through.</p> <img src="{@docRoot}design/media/navigation_between_siblings_market2.png"> -<h2 id="from-outside">Navigation From Outside Your App</h2> +<h2 id="into-your-app">Navigation into Your App via Home Screen Widgets and Notifications</h2> + +<p>You can use Home screen widgets or notifications to help your users navigate directly to screens +deep within your app's hierarchy. For example, Gmail's Inbox widget and new message notification can +both bypass the Inbox screen, taking the user directly to a conversation view.</p> + +<p>For both of these cases, handle the Up button as follows:</p> -<p>There are two categories of navigation from outside your app to screens deep within the app's -hierarchy:</p> <ul> -<li>App-to-app navigation, such as via intent completion.</li> -<li>System-to-app navigation, such as via notifications and home screen widgets.</li> +<li><em>If the destination screen is typically reached from one particular screen within your +app</em>, Up should navigate to that screen.</li> +<li><em>Otherwise</em>, Up should navigate to the topmost ("Home") screen of your app.</li> </ul> -<p>Gmail provides examples of each of these. For app-to-app navigation, a "Share" intent goes directly -to the compose screen. For system-to-app navigation, both a new message notification and a home -screen widget can bypass the Inbox screen, taking the user directly to a conversation view.</p> -<h4>App-to-app navigation</h4> -<p>When navigating deep into your app's hierarchy directly from another app via an intent, Back will -return to the referring app.</p> -<p>The Up button is handled as follows: -- If the destination screen is typically reached from one particular screen within your app, Up - should navigate to that screen. -- Otherwise, Up should navigate to the topmost ("Home") screen of your app.</p> -<p>For example, after choosing to share a book being viewed on Google Play, the user navigates directly to -Gmail's compose screen. From there, Up returns to the Inbox (which happens to be both the -typical referrer to compose, as well as the topmost screen of the app), while Back returns to -Google Play.</p> - -<img src="{@docRoot}design/media/navigation_from_outside_up.png"> - -<h4>System-to-app navigation</h4> -<p>If your app was reached via the system mechanisms of notifications or home screen widgets, Up -behaves as described for app-to-app navigation, above.</p> -<p>For the Back key, you should make navigation more predictably by inserting into the task's back -stack the complete upward navigation path to the app's topmost screen. This way, a user who has -forgotten how they entered your app can safely navigate to the app's topmost screen before exiting -it.</p> -<p>For example, Gmail's Home screen widget has a button for diving directly to its compose screen. -After following that path, the Back key first returns to the Inbox, and from there continues to -Home.</p> + +<p>In the case of the Back button, you should make navigation more predictable by inserting into the +task's back stack the complete upward navigation path to the app's topmost screen. This allows users +who've forgotten how they entered your app to navigate to the app's topmost screen before +exiting.</p> + +<p>As an example, Gmail's Home screen widget has a button for diving directly to its compose +screen. Up or Back from the compose screen would take the user to the Inbox, and from there the +Back button continues to Home.</p> <img src="{@docRoot}design/media/navigation_from_outside_back.png"> + +<h4>Indirect notifications</h4> + +<p>When your app needs to present information about multiple events simultaneously, it can use a +single notification that directs the user to an interstitial screen. This screen summarizes these +events, and provides paths for the user to dive deeply into the app. Notifications of this style are +called <em>indirect notifications</em>.</p> + +<p>Unlike standard (direct) notifications, pressing Back from an indirect notification's +interstitial screen returns the user to the point the notification was triggered from—no +additional screens are inserted into the back stack. Once the user proceeds into the app from its +interstitial screen, Up and Back behave as for standard notifications, as described above: +navigating within the app rather than returning to the interstitial.</p> + +<p>For example, suppose a user in Gmail receives an indirect notification from Calendar. Touching +this notification opens the interstitial screen, which displays reminders for several different +events. Touching Back from the interstitial returns the user to Gmail. Touching on a particular +event takes the user away from the interstitial and into the full Calendar app to display details of +the event. From the event details, Up and Back navigate to the top-level view of Calendar.</p> + +<img src="{@docRoot}design/media/navigation_indirect_notification.png"> + +<h4>Pop-up notifications</h4> + +<p><em>Pop-up notifications</em> bypass the notification drawer, instead appearing directly in +front of the user. They are rarely used, and <strong>should be reserved for occasions where a timely +response is required and the interruption of the user's context is necessary</strong>. For example, +Talk uses this style to alert the user of an invitation from a friend to join a video chat, as this +invitation will automatically expire after a few seconds.</p> + +<p>In terms of navigation behavior, pop-up notifications closely follow the behavior of an indirect +notification's interstitial screen. Back dismisses the pop-up notification. If the user navigates +from the pop-up into the notifying app, Up and Back follow the rules for standard notifications, +navigating within the app.</p> + +<img src="{@docRoot}design/media/navigation_popup_notification.png"> + +<h2 id="between-apps">Navigation Between Apps</h2> + +<p>One of the fundamental strengths of the Android system is the ability for apps to activate each +other, giving the user the ability to navigate directly from one app into another. For example, an +app that needs to capture a photo can activate the Camera app, which will return the photo +to the referring app. This is a tremendous benefit to both the developer, who can easily leverage +code from other apps, and the user, who enjoys a consistent experience for commonly performed +actions.</p> + +<p>To understand app-to-app navigation, it's important to understand the Android framework behavior +discussed below.</p> + +<h4>Activities, tasks, and intents</h4> + +<p>In Android, an <strong>activity</strong> is an application component that defines a screen of +information and all of the associated actions the user can perform. Your app is a collection of +activities, consisting of both the activities you create and those you re-use from other apps.</p> + +<p>A <strong>task</strong> is the sequence of activities a user follows to accomplish a goal. A +single task can make use of activities from just one app, or may draw on activities from a number +of different apps.</p> + +<p>An <strong>intent</strong> is a mechanism for one app to signal it would like another +app's assistance in performing an action. An app's activities can indicate which intents +they can respond to. For common intents such as "Share", the user may have many apps installed +that can fulfill that request.</p> + +<h4>Example: navigating between apps to support sharing</h4> + +<p>To understand how activities, tasks, and intents work together, consider how one app allows users +to share content by using another app. For example, launching the Play Store app from Home begins +new Task A (see figure below). After navigating through the Play Store and touching a promoted book +to see its details, the user remains in the same task, extending it by adding activities. Triggering +the Share action prompts the user with a dialog listing each of the activities (from different apps) +which have registered to handle the Share intent.</p> + +<img src="{@docRoot}design/media/navigation_between_apps_inward.png"> + +<p>When the user elects to share via Gmail, Gmail's compose activity is added as a continuation of +Task A—no new task is created. If Gmail had its own task running in the background, it would +be unaffected.</p> + +<p>From the compose activity, sending the message or touching the Back button returns the user to +the book details activity. Subsequent touches of Back continue to navigate back through the Play +Store, ultimately arriving at Home.</p> + +<img src="{@docRoot}design/media/navigation_between_apps_back.png"> + +<p>However, by touching Up from the compose activity, the user indicates a desire to remain within +Gmail. Gmail's conversation list activity appears, and a new Task B is created for it. New tasks are +always rooted to Home, so touching Back from the conversation list returns there.</p> + +<img src="{@docRoot}design/media/navigation_between_apps_up.png"> + +<p>Task A persists in the background, and the user may return to it later (for example, via the +Recents screen). If Gmail already had its own task running in the background, it would be replaced +with Task B—the prior context is abandoned in favor of the user's new goal.</p> + +<p>When your app registers to handle intents with an activity deep within the app's hierarchy, +refer to <a href="#into-your-app">Navigation into Your App via Home Screen Widgets and +Notifications</a> for guidance on how to specify Up navigation.</p> diff --git a/docs/html/design/patterns/pure-android.jd b/docs/html/design/patterns/pure-android.jd index 8ed1347..77813c0 100644 --- a/docs/html/design/patterns/pure-android.jd +++ b/docs/html/design/patterns/pure-android.jd @@ -48,7 +48,8 @@ conventions of a different platform.</p> document or deleting.</p> <p>As you are migrating your app to Android, please swap out platform-specific icons with their Android counterparts.</p> -<p>You can find a wide variety of icons for use in your app in the Android SDK.</p> +<p>You can find a wide variety of icons for use in your app on the +<a href="{@docRoot}design/downloads/index.html">Downloads</a> page.</p> </div> <div class="layout-content-col span-8"> @@ -89,6 +90,33 @@ platform and to avoid confusion between actions and view switching on Android.</ <div class="layout-content-row"> <div class="layout-content-col span-5"> +<h4>Don't hardcode links to other apps</h4> +<p>In some cases you might want your app to take advantage of another app's feature set. For +example, you may want to share the content that your app created via a social network or messaging +app, or view the content of a weblink in a browser. Don't use hard-coded, explicit links to +particular apps to achieve this. Instead, use Android's intent API to launch an activity chooser +which lists all applications that are set up to handle the particular request. This lets the user +complete the task with their preferred app. For sharing in particular, consider using the <em>Share +Action Provider</em> in your action bar to provide faster access to the user's most recently used +sharing target.</p> + + </div> + <div class="layout-content-col span-8"> + + <img src="{@docRoot}design/media/migrating_intents.png"> + <div class="figure-caption"> + Link to other apps with the activity chooser or use the <em>Share Action Provider</em> in the + action bar. + </div> + + </div> +</div> + +<div class="vspace size-2"> </div> + +<div class="layout-content-row"> + <div class="layout-content-col span-5"> + <h4>Don't use labeled back buttons on action bars</h4> <p>Other platforms use an explicit back button with label to allow the user to navigate up the application's hierarchy. Instead, Android uses the main action bar's app icon for hierarchical diff --git a/docs/html/design/patterns/settings.jd b/docs/html/design/patterns/settings.jd new file mode 100644 index 0000000..3b28b84 --- /dev/null +++ b/docs/html/design/patterns/settings.jd @@ -0,0 +1,689 @@ +page.title=Settings +@jd:body + +<p>Settings is a place in your app where users indicate their preferences for how your app should +behave. This benefits users because:</p> + +<ul> +<li>You don't need to interrupt them with the same questions over and over when certain situations +arise. The settings predetermine what will always happen in those situations (see design +principle: <a href="{@docRoot}design/get-started/principles.html#decide-for-me">Decide for me but +let me have the final say</a>).</li> +<li>You help them feel at home and in control (see design principle: +<a href="{@docRoot}design/get-started/principles.html#make-it-mine">Let me make it mine</a>).</li> +</ul> + +<h2 id="flow-structure">Flow and Structure</h2> + +<h4 id="settings-access">Provide access to Settings in the action overflow</h4> + +<p>Settings is given low prominence in the UI because it's not frequently needed. Even if there's +room in the <a href="{@docRoot}design/patterns/actionbar.html">action bar</a>, never make Settings +an action button. Always keep it in the action overflow and label it "Settings". Place it below +all other items except "Help".</p> + +<img src="{@docRoot}design/media/settings_overflow.png"> + +<div class="vspace size-2"> </div> + +<h4 id="what-to-make-a-setting">Avoid the temptation to make everything a setting</h4> + +<p>Because Settings is a few navigational steps away, no matter how many items you have, they'll +never clutter up the core part of your UI. This may seem like good news, but it also poses a +challenge.</p> + +<p>Settings can be a tempting place to keep a lot of stuff—like a hall closet where things +get stashed when you tidy up before company comes over. It's not a place where you spend lots of +time, so it's easy to rationalize and ignore its cluttered condition. But when users visit +Settings—however infrequently—they'll have the same expectations for the experience as +they do everywhere else in your app. More settings means more choices to make, and too many are +overwhelming.</p> + +<p>So don't punt on the difficult product decisions and debates that can bring on the urge to +"just make it a setting". For each control you're considering adding to Settings, make sure it +meets the bar:</p> + +<img src="{@docRoot}design/media/settings_flowchart.png"> + +<div class="vspace size-3"> </div> + +<div class="layout-content-row"> + <div class="layout-content-col span-5 with-callouts"> + +<h4 id="group-settings">If you still have lots of settings, group related settings together</h4> + +<p>The number of items an average human can hold in short-term memory is 7±2. If you +present a list of 10 or more settings (even after applying the criteria above), users will have +more difficulty scanning, comprehending, and processing them.</p> + +<p>You can remedy this by dividing some or all of the settings into groups, effectively turning +one long list into multiple shorter lists. A group of related settings can be presented in one of +two ways:</p> + +<ol> +<li><h4>Under a section divider</h4></li> +<li><h4>In a separate subscreen</h4></li> +</ol> + +<p>You can use one or both these grouping techniques to organize your app's settings.</p> + +<p>For example, in the main screen of the Android Settings app, each item in the list navigates +to a subscreen of related settings. In addition, the items themselves are grouped under section +dividers.</p> + + </div> + <div class="layout-content-col span-8"> + + <img src="{@docRoot}design/media/settings_grouping.png"> + + </div> +</div> + +<p>Grouping settings is not an exact science, but here's some advice for how to approach it, based +on the total number of settings in your app.</p> + +<div class="vspace size-1"> </div> + +<div class="layout-content-row"> + <div class="layout-content-col span-2"> + +<h4>7 or fewer</h4> + + </div> + <div class="layout-content-col span-11"> + +<p>Don't group them at all. It won't benefit users and will seem like overkill.</p> + + </div> +</div> + +<div class="layout-content-row"> + <div class="layout-content-col span-2"> + +<h4>8 to 10</h4> + + </div> + <div class="layout-content-col span-11"> + +<p>Try grouping related settings under 1 or 2 section dividers. If you have any "singletons" +(settings that don't relate to any other settings and can't be grouped under your section +dividers), treat them as follows:</p> + +<ul> +<li>If they include some of your most important settings, list them at the top without a section +divider.</li> +<li>Otherwise, list them at the bottom with a section divider called "OTHER", in order of +importance.</li> +</ul> + + </div> +</div> + +<div class="layout-content-row"> + <div class="layout-content-col span-2"> + +<h4>11 to 15</h4> + + </div> + <div class="layout-content-col span-11"> + +<p>Same advice as above, but try 2 to 4 section dividers.</p> + +<p>Also, try the following to reduce the list:</p> + +<ul> +<li>If 2 or more of the settings are mainly for power users, move them out of your main Settings +screen and into an "Advanced" subscreen. Place an item in the action overflow called "Advanced" to +navigate to it.</li> +<li>Look for "doubles": two settings that relate to one another, but not to any other settings. +Try to combine them into one setting, using the design patterns described later in this section. +For example, you might be able to redesign two related checkbox settings into one multiple choice +setting.</li> +</ul> + + </div> +</div> + +<div class="layout-content-row"> + <div class="layout-content-col span-2"> + +<h4>16 or more</h4> + + </div> + <div class="layout-content-col span-11"> + +<p>If you have any instances of 4 or more related settings, group them under a subscreen. Then use +the advice suggested above for the reduced list size.</p> + + </div> +</div> + + +<h2 id="patterns">Design Patterns</h2> + +<div class="layout-content-row"> + <div class="layout-content-col span-3"> + +<h4>Checkbox</h4> +<p>Use this pattern for a setting that is either selected or not selected.</p> + + </div> + <div class="layout-content-col span-10"> + +<img src="{@docRoot}design/media/settings_checkbox.png"> + + </div> +</div> + +<div class="layout-content-row"> + <div class="layout-content-col span-3"> + +<h4>Multiple choice</h4> +<p>Use this pattern for a setting that needs to present a discrete set of options, from which the +user can choose only one.</p> + + </div> + <div class="layout-content-col span-10"> + +<img src="{@docRoot}design/media/settings_multiple_choice.png"> + + </div> +</div> + +<div class="layout-content-row"> + <div class="layout-content-col span-3"> + +<h4>Slider</h4> +<p>Use this pattern for a setting where the range of values are not discrete and fall along a +continuum.</p> + + </div> + <div class="layout-content-col span-10"> + +<img src="{@docRoot}design/media/settings_slider.png"> + + </div> +</div> + +<div class="layout-content-row"> + <div class="layout-content-col span-3"> + +<h4>Date/time</h4> +<p>Use this pattern for a setting that needs to collect a date and/or time from the user.</p> + + </div> + <div class="layout-content-col span-10"> + +<img src="{@docRoot}design/media/settings_date_time.png"> + + </div> +</div> + +<div class="layout-content-row"> + <div class="layout-content-col span-3"> + +<h4>Subscreen navigation</h4> +<p>Use this pattern for navigating to a subscreen or sequence of subscreens that guide the user +through a more complex setup process.</p> +<ul> +<li>If navigating to a single subscreen, use the same title in both the subscreen and the label +navigating to it.</li> +<li>If navigating to a sequence of subscreens (as in this example), use a title that describes the +first step in the sequence.</li> +</ul> + + </div> + <div class="layout-content-col span-10"> + +<img src="{@docRoot}design/media/settings_subscreen_navigation.png"> + + </div> +</div> + +<div class="layout-content-row"> + <div class="layout-content-col span-3"> + +<h4>List subscreen</h4> +<p>Use this pattern for a setting or category of settings that contains a list of equivalent items. +</p> +<p>The label provides the name of the item, and secondary text may be used for status. (In this +example, status is reinforced with an icon to the right of the label.) Any actions associated with +the list appear in the action bar rather than the list itself.</p> + + </div> + <div class="layout-content-col span-10"> + +<img src="{@docRoot}design/media/settings_list_subscreen.png"> + + </div> +</div> + +<div class="layout-content-row"> + <div class="layout-content-col span-3"> + +<h4>Master on/off switch</h4> +<p>Use this pattern for a category of settings that need a mechanism for turning on or off as a +whole.</p> +<p>An on/off switch is placed as the first item in the action bar of a subscreen. When the switch +is turned off, the items in the list disappear, replaced by text that describes why the list is +empty. If any actions require the switch to be on, they become disabled.</p> + + </div> + <div class="layout-content-col span-10"> + +<img src="{@docRoot}design/media/settings_master_on_off.png"> + + </div> +</div> + +<div class="layout-content-row"> + <div class="layout-content-col span-3"> + +<div class="vspace size-2"> </div> + +<p>You can also echo the master on/off switch in the menu item that leads to the subscreen. +However, you should only do this in cases where users rarely need to access the subscreen once +it's initially set up and more often just want to toggle the switch.</p> + + </div> + <div class="layout-content-col span-10"> + +<img src="{@docRoot}design/media/settings_master_on_off_2.png"> + + </div> +</div> + +<div class="layout-content-row"> + <div class="layout-content-col span-3"> + +<h4>Individual on/off switch</h4> +<p>Use this pattern for an individual setting that requires a more elaborate description than can +be provided in checkbox form.</p> +<p>The on/off switch only appears in the subscreen so that users aren't able to toggle it without +also being exposed to the descriptive text. Secondary text appears below the setting label to +reflect the current selection.</p> +<p>In this example, Android Beam is on by default. Since users might not know what this setting +does, we made the status more descriptive than just "On".</p> + + </div> + <div class="layout-content-col span-10"> + +<img src="{@docRoot}design/media/settings_individual_on_off.png"> + + </div> +</div> + +<div class="layout-content-row"> + <div class="layout-content-col span-3"> + +<h4>Dependency</h4> +<p>Use this pattern for a setting that changes availability based on the value of another setting. +</p> +<p>The disabled setting appears below its dependency, without any indentation. If the setting +includes a status line, it says "Unavailable", and if the reason isn't obvious, a brief +explanation is included in the status.</p> +<p>If a given setting is a dependency to 3 or more settings, consider using a subscreen with a +master on/off switch so that your main settings screen isn't cluttered by lots of disabled items. +</p> + + </div> + <div class="layout-content-col span-10"> + +<img src="{@docRoot}design/media/settings_dependency.png"> + + </div> +</div> + +<h2 id="defaults">Defaults</h2> + +<p>Take great care in choosing default values for each of your settings. Because settings +determine app behavior, your choices will contribute to users' first impressions of your app. Even +though users can change settings, they'll expect the initial states to be sensible. The following +questions (when applicable) may help inform your decisions:</p> + +<ul> +<li>Which choice would most users be likely to choose on their own if there were no default?</li> +<li>Which choice is the most neutral or middle-of-the-road?</li> +<li>Which choice is the least risky, controversial, or over-the-top?</li> +<li>Which choice uses the least amount of battery or mobile data?</li> +<li>Which choice best supports the design principle +<a href="{@docRoot}design/get-started/principles.html#never-lose-my-stuff">Never lose my stuff</a>?</li> +<li>Which choice best supports the design principle +<a href="{@docRoot}design/get-started/principles.html#interrupt-only-if-important">Only interrupt +me if it's important</a>? +</li> +</ul> + +<h2 id="writing">Writing Guidelines</h2> + +<h4>Label clearly and concisely</h4> + +<p>Writing a good label for a setting can be challenging because space is very limited. You only +get one line, and it's incredibly short on the smallest of devices. Follow these guidelines to +make your labels brief, meaningful, and scannable:</p> + +<ul> +<li>Write each label in sentence case (i.e. only the first word and proper nouns are capitalized). +</li> +<li>Don't start a label with an instructional verb like "Set", "Change", "Edit", "Modify", +"Manage", "Use", "Select", or "Choose". Users already understand that they can do these things to +settings.</li> +<li>Likewise, don't end a label with a word like "setting" or "settings". It's already implied. +</li> +<li>If the setting is part of a grouping, don't repeat the word(s) used in the section divider or +subscreen title.</li> +<li>Avoid starting a label with a negative word like "Don't" or "Never". For example, "Don't +allow" could be rephrased to "Block".</li> +<li>Steer clear of technical jargon as much as possible, unless it's a term widely understood by +your target users. Use common verbs and nouns to convey the setting's purpose rather than its +underlying technology.</li> +<li>Don't refer to the user. For example, for a setting allowing the user to turn notifications on +or off, label it "Notifications" instead of "Notify me".</li> +</ul> + +<p>Once you've decided on labels for your settings, be sure to preview them on an +<a href="{@docRoot}design/style/metrics-grids.html">LDPI handset</a> in portrait to make sure +they'll fit everywhere.</p> + +<h4>Secondary text below is for status, not description…</h4> + +<p>Before Ice Cream Sandwich, we often displayed secondary text below a label to further describe +it or provide instructions. Starting in Ice Cream Sandwich, we're using secondary text for status. +</p> + +<div class="layout-content-row"> + <div class="layout-content-col span-4"> + + <div class="do-dont-label bad emulate-content-left-padding">Before</div> + + <table class="ui-table bad emulate-content-left-padding"> + <thead> + <tr> + <th class="label"> + Screen timeout + </th> + </tr> + </thead> + <tbody> + <tr> + <td class="secondary-text"> + Adjust the delay before the screen automatically turns off + </td> + </tr> + </tbody> + </table> + + </div> + <div class="layout-content-col span-4"> + + <div class="do-dont-label good">After</div> + + <table class="ui-table good"> + <thead> + <tr> + <th class="label"> + Sleep + </th> + </tr> + </thead> + <tbody> + <tr> + <td class="secondary-text"> + After 10 minutes of activity + </td> + </tr> + </tbody> + </table> + + </div> +</div> + +<p>Status in secondary text has the following benefits:</p> +<ul> +<li>Users can see at a glance what the current value of a setting is without having to navigate +any further.</li> +<li>It applies the design principle +<a href="{@docRoot}design/get-started/principles.html#keep-it-brief">Keep it brief</a>, which +users greatly appreciate.</li> +</ul> + +<h4>…unless it's a checkbox setting</h4> +<p>There's one important exception to the using secondary text for status: checkbox settings. +Here, use secondary text for description, not status. Status below a checkbox is unnecessary +because the checkbox already indicates it. The reason why it's appropriate to have a description +below a checkbox setting is because—unlike other controls—it doesn't display a dialog +or navigate to another screen where additional information can be provided.</p> + +<p>That said, if a checkbox setting's label is clear enough on its own, there's no need to also +provide a description. Only include one if necessary.</p> + +<p>Follow these guidelines to write checkbox setting descriptions:</p> +<ul> +<li>Keep it to one sentence and don't use ending punctuation.</li> +<li>Convey what happens when the setting is checked, phrased in the form of a command. Example: +"Allow data exchange", not "Allows data exchange".</li> +<li>Avoid repetition by choosing words that don't already appear in the label.</li> +<li>Don't refer to the user unless it's necessary for understanding the setting.</li> +<li>If you must refer to the user, do so in the second person ("you") rather than the first person +("I"). Android speaks to users, not on behalf of them.</li> +</ul> + +<h4>Writing examples</h4> + +<p>The following are examples of changes we made to labels and secondary text in the Settings app +in Ice Cream Sandwich.</p> + +<div class="layout-content-row"> + <div class="layout-content-col span-4"> + + <div class="do-dont-label bad emulate-content-left-padding">Before</div> + + <table class="ui-table bad emulate-content-left-padding"> + <thead> + <tr> + <th class="label"> + Use tactile feedback + </th> + </tr> + </thead> + </table> + + </div> + <div class="layout-content-col span-4"> + + <div class="do-dont-label good">After</div> + + <table class="ui-table good"> + <thead> + <tr> + <th class="label"> + Vibrate on touch + </th> + </tr> + </thead> + </table> + + </div> + <div class="layout-content-col span-5"> + +<p>In this checkbox setting, we eliminated the throwaway word "Use" and rephrased the label to be +more direct and understandable.</p> + + </div> + +</div> + +<div class="layout-content-row"> + <div class="layout-content-col span-4"> + + <div class="do-dont-label bad emulate-content-left-padding">Before</div> + + <table class="ui-table bad emulate-content-left-padding"> + <thead> + <tr> + <th class="label"> + Screen timeout + </th> + </tr> + </thead> + <tbody> + <tr> + <td class="secondary-text"> + Adjust the delay before the screen automatically turns off + </td> + </tr> + </tbody> + </table> + + </div> + <div class="layout-content-col span-4"> + + <div class="do-dont-label good">After</div> + + <table class="ui-table good"> + <thead> + <tr> + <th class="label"> + Sleep + </th> + </tr> + </thead> + <tbody> + <tr> + <td class="secondary-text"> + After 10 minutes of activity + </td> + </tr> + </tbody> + </table> + + </div> + <div class="layout-content-col span-5"> + +<p>In this multiple choice setting, we changed the label to a friendlier term and also replaced +the description with status. We put some descriptive words around the selected value, "10 +minutes", because on its own, the meaning could be misinterpreted as "sleep for 10 minutes".</p> + + </div> +</div> + +<div class="layout-content-row"> + <div class="layout-content-col span-4"> + + <div class="do-dont-label bad emulate-content-left-padding">Before</div> + + <table class="ui-table bad emulate-content-left-padding"> + <thead> + <tr> + <th class="label"> + Change screen lock + </th> + </tr> + </thead> + <tbody> + <tr> + <td class="secondary-text"> + Change or disable pattern, PIN, or password security + </td> + </tr> + </tbody> + </table> + + </div> + <div class="layout-content-col span-4"> + + <div class="do-dont-label good">After</div> + + <table class="ui-table good"> + <thead> + <tr> + <th class="label"> + Screen lock + </th> + </tr> + </thead> + <tbody> + <tr> + <td class="secondary-text"> + Pattern + </td> + </tr> + </tbody> + </table> + + </div> + <div class="layout-content-col span-5"> + +<p>This setting navigates to a a sequence of subscreens that allow users to choose a type of +screen lock and then set it up. We eliminated the throwaway word "Change" in the label, and +replaced the description with the current type of screen lock set up by the user. If the user +hasn't set up a screen lock, the secondary text says "None".</p> + + </div> +</div> + +<div class="layout-content-row"> + <div class="layout-content-col span-4"> + + <div class="do-dont-label bad emulate-content-left-padding">Before</div> + + <table class="ui-table bad emulate-content-left-padding"> + <thead> + <tr> + <th class="label"> + NFC + </th> + </tr> + </thead> + <tbody> + <tr> + <td class="secondary-text"> + Use Near Field Communication to read and exchange tags + </td> + </tr> + </tbody> + </table> + + </div> + <div class="layout-content-col span-4"> + + <div class="do-dont-label good">After</div> + + <table class="ui-table good"> + <thead> + <tr> + <th class="label"> + NFC + </th> + </tr> + </thead> + <tbody> + <tr> + <td class="secondary-text"> + Allow data exchange when the phone touches another device + </td> + </tr> + </tbody> + </table> + + </div> + <div class="layout-content-col span-5"> + +<p>In this checkbox setting—although it's technical jargon—we kept the "NFC" label +because: (1) we couldn't find a clear, concise alternative, and (2) user familiarity with the +acronym is expected to increase dramatically in the next couple of years.</p> +<p>We did, however, rewrite the description. It's far less technical than before and does a better +job of conveying how and why you'd use NFC. We didn't include what the acronym stands for because +it doesn't mean anything to most users and would have taken up a lot of space.</p> + + </div> +</div> + +<h2 id="checklist">Checklist</h2> +<ul> +<li><p>Make sure each item in Settings meets the criteria for belonging there.</p></li> +<li><p>If you have more than 7 items, explore ways to group related settings.</p></li> +<li><p>Use design patterns wherever applicable so users don't face a learning curve.</p></li> +<li><p>Choose defaults that are safe, neutral, and fit the majority of users.</p></li> +<li><p>Give each setting a clear, concise label and use secondary text appropriately.</p></li> +</ul>
\ No newline at end of file diff --git a/docs/html/design/style/writing.jd b/docs/html/design/style/writing.jd index 80fd03e..919ea7a 100644 --- a/docs/html/design/style/writing.jd +++ b/docs/html/design/style/writing.jd @@ -1,58 +1,6 @@ page.title=Writing Style @jd:body -<style> - -/* UI tables */ - -.ui_table { - width: 100%; - background: #282828; - color: #fff; - border-radius: 2px; - box-shadow: 0 2px 4px rgba(0,0,0,0.25); - border-collapse: separate; -} - -.ui_table th, -.ui_table td { - padding: 5px 10px; -} - -.ui_table thead th { - font-weight: 600; -} - -.ui_table tfoot td { - border-top: 1px solid #494949; - border-right: 1px solid #494949; - text-align: center; -} - -.ui_table tfoot td:last-child { - border-right: 0; -} - -.list_item_margins { - margin-left: 30px !important; -} - -.example_label { - margin-bottom: 10px; - padding-left: 20px; - background: transparent none no-repeat scroll 0px 3px; -} - -.example_label.bad { - background-image: url({@docRoot}assets/design/ico_wrong.png); -} - -.example_label.good { - background-image: url({@docRoot}assets/design/ico_good.png); -} - -</style> - <p>When choosing words for your app:</p> <ol> <li> @@ -90,20 +38,20 @@ page.title=Writing Style <ol><li class="value-1"><strong>Keep it brief.</strong> From the setup wizard:</ol> <div class="layout-content-row"> - <div class="layout-content-col span-6 list_item_margins"> + <div class="layout-content-col span-6 layout-with-list-item-margins"> - <div class="example_label bad">Too formal</div> + <div class="do-dont-label bad">Too formal</div> - <table class="ui_table good"><tbody><tr><td> + <table class="ui-table good"><tbody><tr><td> Consult the documentation that came with your phone for further instructions. </td></tr></tbody></table> </div> <div class="layout-content-col span-6"> - <div class="example_label good">Better</div> + <div class="do-dont-label good">Better</div> - <table class="ui_table good"><tbody><tr><td> + <table class="ui-table good"><tbody><tr><td> Read the instructions that came with your phone. </td></tr></tbody></table> @@ -115,11 +63,11 @@ page.title=Writing Style <ol><li class="value-2"><strong>Keep it simple.</strong> From the Location settings screen:</ol> <div class="layout-content-row"> - <div class="layout-content-col span-6 list_item_margins"> + <div class="layout-content-col span-6 layout-with-list-item-margins"> - <div class="example_label bad">Confusing</div> + <div class="do-dont-label bad">Confusing</div> - <table class="ui_table bad"> + <table class="ui-table bad"> <thead> <tr> <th> @@ -139,9 +87,9 @@ page.title=Writing Style </div> <div class="layout-content-col span-6"> - <div class="example_label good">Better</div> + <div class="do-dont-label good">Better</div> - <table class="ui_table good"> + <table class="ui-table good"> <thead> <tr> <th> @@ -167,12 +115,12 @@ page.title=Writing Style crashes:</ol> <div class="layout-content-row"> - <div class="layout-content-col span-6 list_item_margins"> + <div class="layout-content-col span-6 layout-with-list-item-margins"> - <div class="example_label bad">Confusing and annoying—"Sorry" just rubs salt in the + <div class="do-dont-label bad">Confusing and annoying—"Sorry" just rubs salt in the wound.</div> - <table class="ui_table bad"> + <table class="ui-table bad"> <thead> <tr> <th colspan="3"> @@ -200,9 +148,9 @@ crashes:</ol> </div> <div class="layout-content-col span-6"> - <div class="example_label good">Shorter, more direct, no faux-apologetic title:<br><br></div> + <div class="do-dont-label good">Shorter, more direct, no faux-apologetic title:<br><br></div> - <table class="ui_table good"> + <table class="ui-table good"> <thead> <tr> <th colspan="3"> @@ -234,20 +182,20 @@ crashes:</ol> <ol><li class="value-4"><strong>Put the most important thing first.</strong></ol> <div class="layout-content-row"> - <div class="layout-content-col span-6 list_item_margins"> + <div class="layout-content-col span-6 layout-with-list-item-margins"> - <div class="example_label bad">Top news last</div> + <div class="do-dont-label bad">Top news last</div> - <table class="ui_table bad"><tbody><tr><td> + <table class="ui-table bad"><tbody><tr><td> 77 other people +1'd this, including Larry Page. </td></tr></tbody></table> </div> <div class="layout-content-col span-6"> - <div class="example_label good">Top news first</div> + <div class="do-dont-label good">Top news first</div> - <table class="ui_table good"><tbody><tr><td> + <table class="ui-table good"><tbody><tr><td> Larry Page and 77 others +1'd this. </td></tr></tbody></table> @@ -255,20 +203,20 @@ crashes:</ol> </div> <div class="layout-content-row"> - <div class="layout-content-col span-6 list_item_margins"> + <div class="layout-content-col span-6 layout-with-list-item-margins"> - <div class="example_label bad">Task last</div> + <div class="do-dont-label bad">Task last</div> - <table class="ui_table bad"><tbody><tr><td> + <table class="ui-table bad"><tbody><tr><td> Touch Next to complete setup using a Wi-Fi connection. </td></tr></tbody></table> </div> <div class="layout-content-col span-6"> - <div class="example_label good">Task first</div> + <div class="do-dont-label good">Task first</div> - <table class="ui_table good"><tbody><tr><td> + <table class="ui-table good"><tbody><tr><td> To finish setup using Wi-Fi, touch Next. </td></tr></tbody></table> @@ -280,11 +228,11 @@ crashes:</ol> <ol><li class="value-5"><strong>Describe only what's necessary, and no more.</strong></ol> <div class="layout-content-row"> - <div class="layout-content-col span-6 list_item_margins"> + <div class="layout-content-col span-6 layout-with-list-item-margins"> - <div class="example_label bad">From a Setup Wizard screen</div> + <div class="do-dont-label bad">From a Setup Wizard screen</div> - <table class="ui_table bad"> + <table class="ui-table bad"> <thead> <tr> <th> @@ -306,9 +254,9 @@ crashes:</ol> </div> <div class="layout-content-col span-6"> - <div class="example_label good">From a Setup Wizard screen</div> + <div class="do-dont-label good">From a Setup Wizard screen</div> - <table class="ui_table good"> + <table class="ui-table good"> <thead> <tr> <th> diff --git a/docs/html/guide/developing/testing/testing_otheride.jd b/docs/html/guide/developing/testing/testing_otheride.jd index 93af979..7745ae7 100644 --- a/docs/html/guide/developing/testing/testing_otheride.jd +++ b/docs/html/guide/developing/testing/testing_otheride.jd @@ -209,7 +209,7 @@ $ android create test-project -m ../HelloAndroid -n HelloAndroidTest -p HelloAnd <p> To update a test project with the <code>android</code> tool, enter: </p> -<pre>android update-test-project -m <main_path> -p <test_path></pre> +<pre>android update test-project -m <main_path> -p <test_path></pre> <table> <tr> diff --git a/docs/html/images/training/cool-places.png b/docs/html/images/training/cool-places.png Binary files differnew file mode 100755 index 0000000..769b5b7 --- /dev/null +++ b/docs/html/images/training/cool-places.png diff --git a/docs/html/images/training/panoramio-grid.png b/docs/html/images/training/panoramio-grid.png Binary files differnew file mode 100755 index 0000000..45c0eb5 --- /dev/null +++ b/docs/html/images/training/panoramio-grid.png diff --git a/docs/html/resources/resources_toc.cs b/docs/html/resources/resources_toc.cs index e4ab16f..5297c23 100644 --- a/docs/html/resources/resources_toc.cs +++ b/docs/html/resources/resources_toc.cs @@ -278,6 +278,51 @@ class="new"> new!</span></span> </a> </li> </ul> + </li> + <li class="toggle-list"> + <div><a href="<?cs var:toroot ?>training/tv/index.html"> + <span class="en">Designing for TV<span class="new"> new!</span></span> + </a> + </div> + <ul> + <li><a href="<?cs var:toroot ?>training/tv/optimizing-layouts-tv.html"> + <span class="en">Optimizing Layouts for TV</span> + </a> + </li> + <li><a href="<?cs var:toroot ?>training/tv/optimizing-navigation-tv.html"> + <span class="en">Optimizing Navigation for TV</span> + </a> + </li> + <li><a href="<?cs var:toroot ?>training/tv/unsupported-features-tv.html"> + <span class="en">Handling Features Not Supported on TV</span> + </a> + </li> + </ul> + </li> + + <li class="toggle-list"> + <div><a href="<?cs var:toroot ?>training/displaying-bitmaps/index.html"> + <span class="en">Displaying Bitmaps Efficiently<span class="new"> new!</span></span> + </a> + </div> + <ul> + <li><a href="<?cs var:toroot ?>training/displaying-bitmaps/load-bitmap.html"> + <span class="en">Loading Large Bitmaps Efficiently</span> + </a> + </li> + <li><a href="<?cs var:toroot ?>training/displaying-bitmaps/process-bitmap.html"> + <span class="en">Processing Bitmaps Off the UI Thread</span> + </a> + </li> + <li><a href="<?cs var:toroot ?>training/displaying-bitmaps/cache-bitmap.html"> + <span class="en">Caching Bitmaps</span> + </a> + </li> + <li><a href="<?cs var:toroot ?>training/displaying-bitmaps/display-bitmap.html"> + <span class="en">Displaying Bitmaps in Your UI</span> + </a> + </li> + </ul> </li> <li class="toggle-list"> diff --git a/docs/html/shareables/training/BitmapFun.zip b/docs/html/shareables/training/BitmapFun.zip Binary files differnew file mode 100644 index 0000000..e7e71f9 --- /dev/null +++ b/docs/html/shareables/training/BitmapFun.zip diff --git a/docs/html/shareables/training/LocationAware.zip b/docs/html/shareables/training/LocationAware.zip Binary files differindex e1926fa..46970cd 100644 --- a/docs/html/shareables/training/LocationAware.zip +++ b/docs/html/shareables/training/LocationAware.zip diff --git a/docs/html/training/displaying-bitmaps/cache-bitmap.jd b/docs/html/training/displaying-bitmaps/cache-bitmap.jd new file mode 100644 index 0000000..94abe21 --- /dev/null +++ b/docs/html/training/displaying-bitmaps/cache-bitmap.jd @@ -0,0 +1,337 @@ +page.title=Caching Bitmaps +parent.title=Displaying Bitmaps Efficiently +parent.link=index.html + +trainingnavtop=true +next.title=Displaying Bitmaps in Your UI +next.link=display-bitmap.html +previous.title=Processing Bitmaps Off the UI Thread +previous.link=process-bitmap.html + +@jd:body + +<div id="tb-wrapper"> +<div id="tb"> + +<h2>This lesson teaches you to</h2> +<ol> + <li><a href="#memory-cache">Use a Memory Cache</a></li> + <li><a href="#disk-cache">Use a Disk Cache</a></li> + <li><a href="#config-changes">Handle Configuration Changes</a></li> +</ol> + +<h2>You should also read</h2> +<ul> + <li><a href="{@docRoot}guide/topics/resources/runtime-changes.html">Handling Runtime Changes</a></li> +</ul> + +<h2>Try it out</h2> + +<div class="download-box"> + <a href="{@docRoot}shareables/training/BitmapFun.zip" class="button">Download the sample</a> + <p class="filename">BitmapFun.zip</p> +</div> + +</div> +</div> + +<p>Loading a single bitmap into your user interface (UI) is straightforward, however things get more +complicated if you need to load a larger set of images at once. In many cases (such as with +components like {@link android.widget.ListView}, {@link android.widget.GridView} or {@link +android.support.v4.view.ViewPager }), the total number of images on-screen combined with images that +might soon scroll onto the screen are essentially unlimited.</p> + +<p>Memory usage is kept down with components like this by recycling the child views as they move +off-screen. The garbage collector also frees up your loaded bitmaps, assuming you don't keep any +long lived references. This is all good and well, but in order to keep a fluid and fast-loading UI +you want to avoid continually processing these images each time they come back on-screen. A memory +and disk cache can often help here, allowing components to quickly reload processed images.</p> + +<p>This lesson walks you through using a memory and disk bitmap cache to improve the responsiveness +and fluidity of your UI when loading multiple bitmaps.</p> + +<h2 id="memory-cache">Use a Memory Cache</h2> + +<p>A memory cache offers fast access to bitmaps at the cost of taking up valuable application +memory. The {@link android.util.LruCache} class (also available in the <a +href="{@docRoot}reference/android/support/v4/util/LruCache.html">Support Library</a> for use back +to API Level 4) is particularly well suited to the task of caching bitmaps, keeping recently +referenced objects in a strong referenced {@link java.util.LinkedHashMap} and evicting the least +recently used member before the cache exceeds its designated size.</p> + +<p class="note"><strong>Note:</strong> In the past, a popular memory cache implementation was a +{@link java.lang.ref.SoftReference} or {@link java.lang.ref.WeakReference} bitmap cache, however +this is not recommended. Starting from Android 2.3 (API Level 9) the garbage collector is more +aggressive with collecting soft/weak references which makes them fairly ineffective. In addition, +prior to Android 3.0 (API Level 11), the backing data of a bitmap was stored in native memory which +is not released in a predictable manner, potentially causing an application to briefly exceed its +memory limits and crash.</p> + +<p>In order to choose a suitable size for a {@link android.util.LruCache}, a number of factors +should be taken into consideration, for example:</p> + +<ul> + <li>How memory intensive is the rest of your activity and/or application?</li> + <li>How many images will be on-screen at once? How many need to be available ready to come + on-screen?</li> + <li>What is the screen size and density of the device? An extra high density screen (xhdpi) device + like <a href="http://www.android.com/devices/detail/galaxy-nexus">Galaxy Nexus</a> will need a + larger cache to hold the same number of images in memory compared to a device like <a + href="http://www.android.com/devices/detail/nexus-s">Nexus S</a> (hdpi).</li> + <li>What dimensions and configuration are the bitmaps and therefore how much memory will each take + up?</li> + <li>How frequently will the images be accessed? Will some be accessed more frequently than others? + If so, perhaps you may want to keep certain items always in memory or even have multiple {@link + android.util.LruCache} objects for different groups of bitmaps.</li> + <li>Can you balance quality against quantity? Sometimes it can be more useful to store a larger + number of lower quality bitmaps, potentially loading a higher quality version in another + background task.</li> +</ul> + +<p>There is no specific size or formula that suits all applications, it's up to you to analyze your +usage and come up with a suitable solution. A cache that is too small causes additional overhead with +no benefit, a cache that is too large can once again cause {@code java.lang.OutOfMemory} exceptions +and leave the rest of your app little memory to work with.</p> + +<p>Here’s an example of setting up a {@link android.util.LruCache} for bitmaps:</p> + +<pre> +private LruCache<String, Bitmap> mMemoryCache; + +@Override +protected void onCreate(Bundle savedInstanceState) { + ... + // Get memory class of this device, exceeding this amount will throw an + // OutOfMemory exception. + final int memClass = ((ActivityManager) context.getSystemService( + Context.ACTIVITY_SERVICE)).getMemoryClass(); + + // Use 1/8th of the available memory for this memory cache. + final int cacheSize = 1024 * 1024 * memClass / 8; + + mMemoryCache = new LruCache<String, Bitmap>(cacheSize) { + @Override + protected int sizeOf(String key, Bitmap bitmap) { + // The cache size will be measured in bytes rather than number of items. + return bitmap.getByteCount(); + } + }; + ... +} + +public void addBitmapToMemoryCache(String key, Bitmap bitmap) { + if (getBitmapFromMemCache(key) == null) { + mMemoryCache.put(key, bitmap); + } +} + +public Bitmap getBitmapFromMemCache(String key) { + return mMemoryCache.get(key); +} +</pre> + +<p class="note"><strong>Note:</strong> In this example, one eighth of the application memory is +allocated for our cache. On a normal/hdpi device this is a minimum of around 4MB (32/8). A full +screen {@link android.widget.GridView} filled with images on a device with 800x480 resolution would +use around 1.5MB (800*480*4 bytes), so this would cache a minimum of around 2.5 pages of images in +memory.</p> + +<p>When loading a bitmap into an {@link android.widget.ImageView}, the {@link android.util.LruCache} +is checked first. If an entry is found, it is used immediately to update the {@link +android.widget.ImageView}, otherwise a background thread is spawned to process the image:</p> + +<pre> +public void loadBitmap(int resId, ImageView imageView) { + final String imageKey = String.valueOf(resId); + + final Bitmap bitmap = getBitmapFromMemCache(imageKey); + if (bitmap != null) { + mImageView.setImageBitmap(bitmap); + } else { + mImageView.setImageResource(R.drawable.image_placeholder); + BitmapWorkerTask task = new BitmapWorkerTask(mImageView); + task.execute(resId); + } +} +</pre> + +<p>The <a href="process-bitmap.html#BitmapWorkerTask">{@code BitmapWorkerTask}</a> also needs to be +updated to add entries to the memory cache:</p> + +<pre> +class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> { + ... + // Decode image in background. + @Override + protected Bitmap doInBackground(Integer... params) { + final Bitmap bitmap = decodeSampledBitmapFromResource( + getResources(), params[0], 100, 100)); + addBitmapToMemoryCache(String.valueOf(params[0]), bitmap); + return bitmap; + } + ... +} +</pre> + +<h2 id="disk-cache">Use a Disk Cache</h2> + +<p>A memory cache is useful in speeding up access to recently viewed bitmaps, however you cannot +rely on images being available in this cache. Components like {@link android.widget.GridView} with +larger datasets can easily fill up a memory cache. Your application could be interrupted by another +task like a phone call, and while in the background it might be killed and the memory cache +destroyed. Once the user resumes, your application it has to process each image again.</p> + +<p>A disk cache can be used in these cases to persist processed bitmaps and help decrease loading +times where images are no longer available in a memory cache. Of course, fetching images from disk +is slower than loading from memory and should be done in a background thread, as disk read times can +be unpredictable.</p> + +<p class="note"><strong>Note:</strong> A {@link android.content.ContentProvider} might be a more +appropriate place to store cached images if they are accessed more frequently, for example in an +image gallery application.</p> + +<p>Included in the sample code of this class is a basic {@code DiskLruCache} implementation. +However, a more robust and recommended {@code DiskLruCache} solution is included in the Android 4.0 +source code ({@code libcore/luni/src/main/java/libcore/io/DiskLruCache.java}). Back-porting this +class for use on previous Android releases should be fairly straightforward (a <a +href="http://www.google.com/search?q=disklrucache">quick search</a> shows others who have already +implemented this solution).</p> + +<p>Here’s updated example code that uses the simple {@code DiskLruCache} included in the sample +application of this class:</p> + +<pre> +private DiskLruCache mDiskCache; +private static final int DISK_CACHE_SIZE = 1024 * 1024 * 10; // 10MB +private static final String DISK_CACHE_SUBDIR = "thumbnails"; + +@Override +protected void onCreate(Bundle savedInstanceState) { + ... + // Initialize memory cache + ... + File cacheDir = getCacheDir(this, DISK_CACHE_SUBDIR); + mDiskCache = DiskLruCache.openCache(this, cacheDir, DISK_CACHE_SIZE); + ... +} + +class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> { + ... + // Decode image in background. + @Override + protected Bitmap doInBackground(Integer... params) { + final String imageKey = String.valueOf(params[0]); + + // Check disk cache in background thread + Bitmap bitmap = getBitmapFromDiskCache(imageKey); + + if (bitmap == null) { // Not found in disk cache + // Process as normal + final Bitmap bitmap = decodeSampledBitmapFromResource( + getResources(), params[0], 100, 100)); + } + + // Add final bitmap to caches + addBitmapToCache(String.valueOf(imageKey, bitmap); + + return bitmap; + } + ... +} + +public void addBitmapToCache(String key, Bitmap bitmap) { + // Add to memory cache as before + if (getBitmapFromMemCache(key) == null) { + mMemoryCache.put(key, bitmap); + } + + // Also add to disk cache + if (!mDiskCache.containsKey(key)) { + mDiskCache.put(key, bitmap); + } +} + +public Bitmap getBitmapFromDiskCache(String key) { + return mDiskCache.get(key); +} + +// Creates a unique subdirectory of the designated app cache directory. Tries to use external +// but if not mounted, falls back on internal storage. +public static File getCacheDir(Context context, String uniqueName) { + // Check if media is mounted or storage is built-in, if so, try and use external cache dir + // otherwise use internal cache dir + final String cachePath = Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED + || !Environment.isExternalStorageRemovable() ? + context.getExternalCacheDir().getPath() : context.getCacheDir().getPath(); + + return new File(cachePath + File.separator + uniqueName); +} +</pre> + +<p>While the memory cache is checked in the UI thread, the disk cache is checked in the background +thread. Disk operations should never take place on the UI thread. When image processing is +complete, the final bitmap is added to both the memory and disk cache for future use.</p> + +<h2 id="config-changes">Handle Configuration Changes</h2> + +<p>Runtime configuration changes, such as a screen orientation change, cause Android to destroy and +restart the running activity with the new configuration (For more information about this behavior, +see <a href="{@docRoot}guide/topics/resources/runtime-changes.html">Handling Runtime Changes</a>). +You want to avoid having to process all your images again so the user has a smooth and fast +experience when a configuration change occurs.</p> + +<p>Luckily, you have a nice memory cache of bitmaps that you built in the <a +href="#memory-cache">Use a Memory Cache</a> section. This cache can be passed through to the new +activity instance using a {@link android.app.Fragment} which is preserved by calling {@link +android.app.Fragment#setRetainInstance setRetainInstance(true)}). After the activity has been +recreated, this retained {@link android.app.Fragment} is reattached and you gain access to the +existing cache object, allowing images to be quickly fetched and re-populated into the {@link +android.widget.ImageView} objects.</p> + +<p>Here’s an example of retaining a {@link android.util.LruCache} object across configuration +changes using a {@link android.app.Fragment}:</p> + +<pre> +private LruCache<String, Bitmap> mMemoryCache; + +@Override +protected void onCreate(Bundle savedInstanceState) { + ... + RetainFragment mRetainFragment = + RetainFragment.findOrCreateRetainFragment(getFragmentManager()); + mMemoryCache = RetainFragment.mRetainedCache; + if (mMemoryCache == null) { + mMemoryCache = new LruCache<String, Bitmap>(cacheSize) { + ... // Initialize cache here as usual + } + mRetainFragment.mRetainedCache = mMemoryCache; + } + ... +} + +class RetainFragment extends Fragment { + private static final String TAG = "RetainFragment"; + public LruCache<String, Bitmap> mRetainedCache; + + public RetainFragment() {} + + public static RetainFragment findOrCreateRetainFragment(FragmentManager fm) { + RetainFragment fragment = (RetainFragment) fm.findFragmentByTag(TAG); + if (fragment == null) { + fragment = new RetainFragment(); + } + return fragment; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + <strong>setRetainInstance(true);</strong> + } +} +</pre> + +<p>To test this out, try rotating a device both with and without retaining the {@link +android.app.Fragment}. You should notice little to no lag as the images populate the activity almost +instantly from memory when you retain the cache. Any images not found in the memory cache are +hopefully available in the disk cache, if not, they are processed as usual.</p> diff --git a/docs/html/training/displaying-bitmaps/display-bitmap.jd b/docs/html/training/displaying-bitmaps/display-bitmap.jd new file mode 100644 index 0000000..7a93313 --- /dev/null +++ b/docs/html/training/displaying-bitmaps/display-bitmap.jd @@ -0,0 +1,400 @@ +page.title=Displaying Bitmaps in Your UI +parent.title=Displaying Bitmaps Efficiently +parent.link=index.html + +trainingnavtop=true +previous.title=Caching Bitmaps +previous.link=cache-bitmap.html + +@jd:body + +<div id="tb-wrapper"> +<div id="tb"> + +<h2>This lesson teaches you to</h2> +<ol> + <li><a href="#viewpager">Load Bitmaps into a ViewPager Implementation</a></li> + <li><a href="#gridview">Load Bitmaps into a GridView Implementation</a></li> +</ol> + +<h2>You should also read</h2> +<ul> + <li><a href="{@docRoot}design/patterns/swipe-views.html">Android Design: Swipe Views</a></li> + <li><a href="{@docRoot}design/building-blocks/grid-lists.html">Android Design: Grid Lists</a></li> +</ul> + +<h2>Try it out</h2> + +<div class="download-box"> + <a href="{@docRoot}shareables/training/BitmapFun.zip" class="button">Download the sample</a> + <p class="filename">BitmapFun.zip</p> +</div> + +</div> +</div> + +<p></p> + +<p>This lesson brings together everything from previous lessons, showing you how to load multiple +bitmaps into {@link android.support.v4.view.ViewPager} and {@link android.widget.GridView} +components using a background thread and bitmap cache, while dealing with concurrency and +configuration changes.</p> + +<h2 id="viewpager">Load Bitmaps into a ViewPager Implementation</h2> + +<p>The <a href="{@docRoot}design/patterns/swipe-views.html">swipe view pattern</a> is an excellent +way to navigate the detail view of an image gallery. You can implement this pattern using a {@link +android.support.v4.view.ViewPager} component backed by a {@link +android.support.v4.view.PagerAdapter}. However, a more suitable backing adapter is the subclass +{@link android.support.v4.app.FragmentStatePagerAdapter} which automatically destroys and saves +state of the {@link android.app.Fragment Fragments} in the {@link android.support.v4.view.ViewPager} +as they disappear off-screen, keeping memory usage down.</p> + +<p class="note"><strong>Note:</strong> If you have a smaller number of images and are confident they +all fit within the application memory limit, then using a regular {@link +android.support.v4.view.PagerAdapter} or {@link android.support.v4.app.FragmentPagerAdapter} might +be more appropriate.</p> + +<p>Here’s an implementation of a {@link android.support.v4.view.ViewPager} with {@link +android.widget.ImageView} children. The main activity holds the {@link +android.support.v4.view.ViewPager} and the adapter:</p> + +<pre> +public class ImageDetailActivity extends FragmentActivity { + public static final String EXTRA_IMAGE = "extra_image"; + + private ImagePagerAdapter mAdapter; + private ViewPager mPager; + + // A static dataset to back the ViewPager adapter + public final static Integer[] imageResIds = new Integer[] { + R.drawable.sample_image_1, R.drawable.sample_image_2, R.drawable.sample_image_3, + R.drawable.sample_image_4, R.drawable.sample_image_5, R.drawable.sample_image_6, + R.drawable.sample_image_7, R.drawable.sample_image_8, R.drawable.sample_image_9}; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.image_detail_pager); // Contains just a ViewPager + + mAdapter = new ImagePagerAdapter(getSupportFragmentManager(), imageResIds.length); + mPager = (ViewPager) findViewById(R.id.pager); + mPager.setAdapter(mAdapter); + } + + public static class ImagePagerAdapter extends FragmentStatePagerAdapter { + private final int mSize; + + public ImagePagerAdapter(FragmentManager fm, int size) { + super(fm); + mSize = size; + } + + @Override + public int getCount() { + return mSize; + } + + @Override + public Fragment getItem(int position) { + return ImageDetailFragment.newInstance(position); + } + } +} +</pre> + +<p>The details {@link android.app.Fragment} holds the {@link android.widget.ImageView} children:</p> + +<pre> +public class ImageDetailFragment extends Fragment { + private static final String IMAGE_DATA_EXTRA = "resId"; + private int mImageNum; + private ImageView mImageView; + + static ImageDetailFragment newInstance(int imageNum) { + final ImageDetailFragment f = new ImageDetailFragment(); + final Bundle args = new Bundle(); + args.putInt(IMAGE_DATA_EXTRA, imageNum); + f.setArguments(args); + return f; + } + + // Empty constructor, required as per Fragment docs + public ImageDetailFragment() {} + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mImageNum = getArguments() != null ? getArguments().getInt(IMAGE_DATA_EXTRA) : -1; + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + // image_detail_fragment.xml contains just an ImageView + final View v = inflater.inflate(R.layout.image_detail_fragment, container, false); + mImageView = (ImageView) v.findViewById(R.id.imageView); + return v; + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + final int resId = ImageDetailActivity.imageResIds[mImageNum]; + <strong>mImageView.setImageResource(resId);</strong> // Load image into ImageView + } +} +</pre> + +<p>Hopefully you noticed the issue with this implementation; The images are being read from +resources on the UI thread which can lead to an application hanging and being force closed. Using an +{@link android.os.AsyncTask} as described in the <a href="process-bitmap.html">Processing Bitmaps Off +the UI Thread</a> lesson, it’s straightforward to move image loading and processing to a background +thread:</p> + +<pre> +public class ImageDetailActivity extends FragmentActivity { + ... + + public void loadBitmap(int resId, ImageView imageView) { + mImageView.setImageResource(R.drawable.image_placeholder); + BitmapWorkerTask task = new BitmapWorkerTask(mImageView); + task.execute(resId); + } + + ... // include <a href="process-bitmap.html#BitmapWorkerTask">{@code BitmapWorkerTask}</a> class +} + +public class ImageDetailFragment extends Fragment { + ... + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + if (ImageDetailActivity.class.isInstance(getActivity())) { + final int resId = ImageDetailActivity.imageResIds[mImageNum]; + // Call out to ImageDetailActivity to load the bitmap in a background thread + ((ImageDetailActivity) getActivity()).loadBitmap(resId, mImageView); + } + } +} +</pre> + +<p>Any additional processing (such as resizing or fetching images from the network) can take place +in the <a href="process-bitmap.html#BitmapWorkerTask">{@code BitmapWorkerTask}</a> without affecting +responsiveness of the main UI. If the background thread is doing more than just loading an image +directly from disk, it can also be beneficial to add a memory and/or disk cache as described in the +lesson <a href="cache-bitmap.html#memory-cache">Caching Bitmaps</a>. Here's the additional +modifications for a memory cache:</p> + +<pre> +public class ImageDetailActivity extends FragmentActivity { + ... + private LruCache<String, Bitmap> mMemoryCache; + + @Override + public void onCreate(Bundle savedInstanceState) { + ... + // initialize LruCache as per <a href="cache-bitmap.html#memory-cache">Use a Memory Cache</a> section + } + + public void loadBitmap(int resId, ImageView imageView) { + final String imageKey = String.valueOf(resId); + + final Bitmap bitmap = mMemoryCache.get(imageKey); + if (bitmap != null) { + mImageView.setImageBitmap(bitmap); + } else { + mImageView.setImageResource(R.drawable.image_placeholder); + BitmapWorkerTask task = new BitmapWorkerTask(mImageView); + task.execute(resId); + } + } + + ... // include updated BitmapWorkerTask from <a href="cache-bitmap.html#memory-cache">Use a Memory Cache</a> section +} +</pre> + +<p>Putting all these pieces together gives you a responsive {@link +android.support.v4.view.ViewPager} implementation with minimal image loading latency and the ability +to do as much or as little background processing on your images as needed.</p> + +<h2 id="gridview">Load Bitmaps into a GridView Implementation</h2> + +<p>The <a href="{@docRoot}design/building-blocks/grid-lists.html">grid list building block</a> is +useful for showing image data sets and can be implemented using a {@link android.widget.GridView} +component in which many images can be on-screen at any one time and many more need to be ready to +appear if the user scrolls up or down. When implementing this type of control, you must ensure the +UI remains fluid, memory usage remains under control and concurrency is handled correctly (due to +the way {@link android.widget.GridView} recycles its children views).</p> + +<p>To start with, here is a standard {@link android.widget.GridView} implementation with {@link +android.widget.ImageView} children placed inside a {@link android.app.Fragment}:</p> + +<pre> +public class ImageGridFragment extends Fragment implements AdapterView.OnItemClickListener { + private ImageAdapter mAdapter; + + // A static dataset to back the GridView adapter + public final static Integer[] imageResIds = new Integer[] { + R.drawable.sample_image_1, R.drawable.sample_image_2, R.drawable.sample_image_3, + R.drawable.sample_image_4, R.drawable.sample_image_5, R.drawable.sample_image_6, + R.drawable.sample_image_7, R.drawable.sample_image_8, R.drawable.sample_image_9}; + + // Empty constructor as per Fragment docs + public ImageGridFragment() {} + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mAdapter = new ImageAdapter(getActivity()); + } + + @Override + public View onCreateView( + LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + final View v = inflater.inflate(R.layout.image_grid_fragment, container, false); + final GridView mGridView = (GridView) v.findViewById(R.id.gridView); + mGridView.setAdapter(mAdapter); + mGridView.setOnItemClickListener(this); + return v; + } + + @Override + public void onItemClick(AdapterView<?> parent, View v, int position, long id) { + final Intent i = new Intent(getActivity(), ImageDetailActivity.class); + i.putExtra(ImageDetailActivity.EXTRA_IMAGE, position); + startActivity(i); + } + + private class ImageAdapter extends BaseAdapter { + private final Context mContext; + + public ImageAdapter(Context context) { + super(); + mContext = context; + } + + @Override + public int getCount() { + return imageResIds.length; + } + + @Override + public Object getItem(int position) { + return imageResIds[position]; + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public View getView(int position, View convertView, ViewGroup container) { + ImageView imageView; + if (convertView == null) { // if it's not recycled, initialize some attributes + imageView = new ImageView(mContext); + imageView.setScaleType(ImageView.ScaleType.CENTER_CROP); + imageView.setLayoutParams(new GridView.LayoutParams( + LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); + } else { + imageView = (ImageView) convertView; + } + <strong>imageView.setImageResource(imageResIds[position]);</strong> // Load image into ImageView + return imageView; + } + } +} +</pre> + +<p>Once again, the problem with this implementation is that the image is being set in the UI thread. +While this may work for small, simple images (due to system resource loading and caching), if any +additional processing needs to be done, your UI grinds to a halt.</p> + +<p>The same asynchronous processing and caching methods from the previous section can be implemented +here. However, you also need to wary of concurrency issues as the {@link android.widget.GridView} +recycles its children views. To handle this, use the techniques discussed in the <a +href="process-bitmap#concurrency">Processing Bitmaps Off the UI Thread</a> lesson. Here is the updated +solution:</p> + +<pre> +public class ImageGridFragment extends Fragment implements AdapterView.OnItemClickListener { + ... + + private class ImageAdapter extends BaseAdapter { + ... + + @Override + public View getView(int position, View convertView, ViewGroup container) { + ... + <strong>loadBitmap(imageResIds[position], imageView)</strong> + return imageView; + } + } + + public void loadBitmap(int resId, ImageView imageView) { + if (cancelPotentialWork(resId, imageView)) { + final BitmapWorkerTask task = new BitmapWorkerTask(imageView); + final AsyncDrawable asyncDrawable = + new AsyncDrawable(getResources(), mPlaceHolderBitmap, task); + imageView.setImageDrawable(asyncDrawable); + task.execute(resId); + } + } + + static class AsyncDrawable extends BitmapDrawable { + private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference; + + public AsyncDrawable(Resources res, Bitmap bitmap, + BitmapWorkerTask bitmapWorkerTask) { + super(res, bitmap); + bitmapWorkerTaskReference = + new WeakReference<BitmapWorkerTask>(bitmapWorkerTask); + } + + public BitmapWorkerTask getBitmapWorkerTask() { + return bitmapWorkerTaskReference.get(); + } + } + + public static boolean cancelPotentialWork(int data, ImageView imageView) { + final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView); + + if (bitmapWorkerTask != null) { + final int bitmapData = bitmapWorkerTask.data; + if (bitmapData != data) { + // Cancel previous task + bitmapWorkerTask.cancel(true); + } else { + // The same work is already in progress + return false; + } + } + // No task associated with the ImageView, or an existing task was cancelled + return true; + } + + private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) { + if (imageView != null) { + final Drawable drawable = imageView.getDrawable(); + if (drawable instanceof AsyncDrawable) { + final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable; + return asyncDrawable.getBitmapWorkerTask(); + } + } + return null; + } + + ... // include updated <a href="process-bitmap.html#BitmapWorkerTaskUpdated">{@code BitmapWorkerTask}</a> class +</pre> + +<p class="note"><strong>Note:</strong> The same code can easily be adapted to work with {@link +android.widget.ListView} as well.</p> + +<p>This implementation allows for flexibility in how the images are processed and loaded without +impeding the smoothness of the UI. In the background task you can load images from the network or +resize large digital camera photos and the images appear as the tasks finish processing.</p> + +<p>For a full example of this and other concepts discussed in this lesson, please see the included +sample application.</p> diff --git a/docs/html/training/displaying-bitmaps/index.jd b/docs/html/training/displaying-bitmaps/index.jd new file mode 100644 index 0000000..6755c24 --- /dev/null +++ b/docs/html/training/displaying-bitmaps/index.jd @@ -0,0 +1,78 @@ +page.title=Displaying Bitmaps Efficiently + +trainingnavtop=true +startpage=true +next.title=Loading Large Bitmaps Efficiently +next.link=load-bitmap.html + +@jd:body + +<div id="tb-wrapper"> +<div id="tb"> + +<h2>Dependencies and prerequisites</h2> +<ul> + <li>Android 2.1 (API Level 7) or higher</li> + <li><a href="{@docRoot}sdk/compatibility-library.html">Support Library</a></li> +</ul> + +<h2>Try it out</h2> + +<div class="download-box"> + <a href="{@docRoot}shareables/training/BitmapFun.zip" class="button">Download the sample</a> + <p class="filename">BitmapFun.zip</p> +</div> + +</div> +</div> + +<p>This class covers some common techniques for processing and loading {@link +android.graphics.Bitmap} objects in a way that keeps your user interface (UI) components responsive +and avoids exceeding your application memory limit. If you're not careful, bitmaps can quickly +consume your available memory budget leading to an application crash due to the dreaded +exception:<br />{@code java.lang.OutofMemoryError: bitmap size exceeds VM budget}.</p> + +<p>There are a number of reasons why loading bitmaps in your Android application is tricky:</p> + +<ul> + <li>Mobile devices typically have constrained system resources. Android devices can have as little + as 16MB of memory available to a single application. The <a + href="http://source.android.com/compatibility/downloads.html">Android Compatibility Definition + Document</a> (CDD), <i>Section 3.7. Virtual Machine Compatibility</i> gives the required minimum + application memory for various screen sizes and densities. Applications should be optimized to + perform under this minimum memory limit. However, keep in mind many devices are configured with + higher limits.</li> + <li>Bitmaps take up a lot of memory, especially for rich images like photographs. For example, the + camera on the <a href="http://www.google.com/nexus/">Galaxy Nexus</a> takes photos up to 2592x1936 + pixels (5 megapixels). If the bitmap configuration used is {@link + android.graphics.Bitmap.Config ARGB_8888} (the default from the Android 2.3 onward) then loading + this image into memory takes about 19MB of memory (2592*1936*4 bytes), immediately exhausting the + per-app limit on some devices.</li> + <li>Android app UI’s frequently require several bitmaps to be loaded at once. Components such as + {@link android.widget.ListView}, {@link android.widget.GridView} and {@link + android.support.v4.view.ViewPager} commonly include multiple bitmaps on-screen at once with many + more potentially off-screen ready to show at the flick of a finger.</li> +</ul> + +<h2>Lessons</h2> + +<dl> + <dt><b><a href="load-bitmap.html">Loading Large Bitmaps Efficiently</a></b></dt> + <dd>This lesson walks you through decoding large bitmaps without exceeding the per application + memory limit.</dd> + + <dt><b><a href="process-bitmap.html">Processing Bitmaps Off the UI Thread</a></b></dt> + <dd>Bitmap processing (resizing, downloading from a remote source, etc.) should never take place + on the main UI thread. This lesson walks you through processing bitmaps in a background thread + using {@link android.os.AsyncTask} and explains how to handle concurrency issues.</dd> + + <dt><b><a href="cache-bitmap.html">Caching Bitmaps</a></b></dt> + <dd>This lesson walks you through using a memory and disk bitmap cache to improve the + responsiveness and fluidity of your UI when loading multiple bitmaps.</dd> + + <dt><b><a href="display-bitmap.html">Displaying Bitmaps in Your UI</a></b></dt> + <dd>This lesson brings everything together, showing you how to load multiple bitmaps into + components like {@link android.support.v4.view.ViewPager} and {@link android.widget.GridView} + using a background thread and bitmap cache.</dd> + +</dl>
\ No newline at end of file diff --git a/docs/html/training/displaying-bitmaps/load-bitmap.jd b/docs/html/training/displaying-bitmaps/load-bitmap.jd new file mode 100644 index 0000000..c0a5709 --- /dev/null +++ b/docs/html/training/displaying-bitmaps/load-bitmap.jd @@ -0,0 +1,165 @@ +page.title=Loading Large Bitmaps Efficiently +parent.title=Displaying Bitmaps Efficiently +parent.link=index.html + +trainingnavtop=true +next.title=Processing Bitmaps Off the UI Thread +next.link=process-bitmap.html + +@jd:body + +<div id="tb-wrapper"> +<div id="tb"> + +<h2>This lesson teaches you to</h2> +<ol> + <li><a href="#read-bitmap">Read Bitmap Dimensions and Type</a></li> + <li><a href="#load-bitmap">Load a Scaled Down Version into Memory</a></li> +</ol> + +<h2>Try it out</h2> + +<div class="download-box"> + <a href="{@docRoot}shareables/training/BitmapFun.zip" class="button">Download the sample</a> + <p class="filename">BitmapFun.zip</p> +</div> + +</div> +</div> + +<p>Images come in all shapes and sizes. In many cases they are larger than required for a typical +application user interface (UI). For example, the system Gallery application displays photos taken +using your Android devices's camera which are typically much higher resolution than the screen +density of your device.</p> + +<p>Given that you are working with limited memory, ideally you only want to load a lower resolution +version in memory. The lower resolution version should match the size of the UI component that +displays it. An image with a higher resolution does not provide any visible benefit, but still takes +up precious memory and incurs additional performance overhead due to additional on the fly +scaling.</p> + +<p>This lesson walks you through decoding large bitmaps without exceeding the per application +memory limit by loading a smaller subsampled version in memory.</p> + +<h2 id="read-bitmap">Read Bitmap Dimensions and Type</h2> + +<p>The {@link android.graphics.BitmapFactory} class provides several decoding methods ({@link +android.graphics.BitmapFactory#decodeByteArray(byte[],int,int,android.graphics.BitmapFactory.Options) +decodeByteArray()}, {@link +android.graphics.BitmapFactory#decodeFile(java.lang.String,android.graphics.BitmapFactory.Options) +decodeFile()}, {@link +android.graphics.BitmapFactory#decodeResource(android.content.res.Resources,int,android.graphics.BitmapFactory.Options) +decodeResource()}, etc.) for creating a {@link android.graphics.Bitmap} from various sources. Choose +the most appropriate decode method based on your image data source. These methods attempt to +allocate memory for the constructed bitmap and therefore can easily result in an {@code OutOfMemory} +exception. Each type of decode method has additional signatures that let you specify decoding +options via the {@link android.graphics.BitmapFactory.Options} class. Setting the {@link +android.graphics.BitmapFactory.Options#inJustDecodeBounds} property to {@code true} while decoding +avoids memory allocation, returning {@code null} for the bitmap object but setting {@link +android.graphics.BitmapFactory.Options#outWidth}, {@link +android.graphics.BitmapFactory.Options#outHeight} and {@link +android.graphics.BitmapFactory.Options#outMimeType}. This technique allows you to read the +dimensions and type of the image data prior to construction (and memory allocation) of the +bitmap.</p> + +<pre> +BitmapFactory.Options options = new BitmapFactory.Options(); +options.inJustDecodeBounds = true; +BitmapFactory.decodeResource(getResources(), R.id.myimage, options); +int imageHeight = options.outHeight; +int imageWidth = options.outWidth; +String imageType = options.outMimeType; +</pre> + +<p>To avoid {@code java.lang.OutOfMemory} exceptions, check the dimensions of a bitmap before +decoding it, unless you absolutely trust the source to provide you with predictably sized image data +that comfortably fits within the available memory.</p> + +<h2 id="load-bitmap">Load a Scaled Down Version into Memory</h2> + +<p>Now that the image dimensions are known, they can be used to decide if the full image should be +loaded into memory or if a subsampled version should be loaded instead. Here are some factors to +consider:</p> + +<ul> + <li>Estimated memory usage of loading the full image in memory.</li> + <li>Amount of memory you are willing to commit to loading this image given any other memory + requirements of your application.</li> + <li>Dimensions of the target {@link android.widget.ImageView} or UI component that the image + is to be loaded into.</li> + <li>Screen size and density of the current device.</li> +</ul> + +<p>For example, it’s not worth loading a 1024x768 pixel image into memory if it will eventually be +displayed in a 128x96 pixel thumbnail in an {@link android.widget.ImageView}.</p> + +<p>To tell the decoder to subsample the image, loading a smaller version into memory, set {@link +android.graphics.BitmapFactory.Options#inSampleSize} to {@code true} in your {@link +android.graphics.BitmapFactory.Options} object. For example, an image with resolution 2048x1536 that +is decoded with an {@link android.graphics.BitmapFactory.Options#inSampleSize} of 4 produces a +bitmap of approximately 512x384. Loading this into memory uses 0.75MB rather than 12MB for the full +image (assuming a bitmap configuration of {@link android.graphics.Bitmap.Config ARGB_8888}). Here’s +a method to calculate a the sample size value based on a target width and height:</p> + +<pre> +public static int calculateInSampleSize( + BitmapFactory.Options options, int reqWidth, int reqHeight) { + // Raw height and width of image + final int height = options.outHeight; + final int width = options.outWidth; + int inSampleSize = 1; + + if (height > reqHeight || width > reqWidth) { + if (width > height) { + inSampleSize = Math.round((float)height / (float)reqHeight); + } else { + inSampleSize = Math.round((float)width / (float)reqWidth); + } + } + return inSampleSize; +} +</pre> + +<p class="note"><strong>Note:</strong> Using powers of 2 for {@link +android.graphics.BitmapFactory.Options#inSampleSize} values is faster and more efficient for the +decoder. However, if you plan to cache the resized versions in memory or on disk, it’s usually still +worth decoding to the most appropriate image dimensions to save space.</p> + +<p>To use this method, first decode with {@link +android.graphics.BitmapFactory.Options#inJustDecodeBounds} set to {@code true}, pass the options +through and then decode again using the new {@link +android.graphics.BitmapFactory.Options#inSampleSize} value and {@link +android.graphics.BitmapFactory.Options#inJustDecodeBounds} set to {@code false}:</p> + +<a name="decodeSampledBitmapFromResource"></a> +<pre> +public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, + int reqWidth, int reqHeight) { + + // First decode with inJustDecodeBounds=true to check dimensions + final BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeResource(res, resId, options); + + // Calculate inSampleSize + options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); + + // Decode bitmap with inSampleSize set + options.inJustDecodeBounds = false; + return BitmapFactory.decodeResource(res, resId, options); +} +</pre> + +<p>This method makes it easy to load a bitmap of arbitrarily large size into an {@link +android.widget.ImageView} that displays a 100x100 pixel thumbnail, as shown in the following example +code:</p> + +<pre> +mImageView.setImageBitmap( + decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100)); +</pre> + +<p>You can follow a similar process to decode bitmaps from other sources, by substituting the +appropriate {@link +android.graphics.BitmapFactory#decodeByteArray(byte[],int,int,android.graphics.BitmapFactory.Options) +BitmapFactory.decode*} method as needed.</p>
\ No newline at end of file diff --git a/docs/html/training/displaying-bitmaps/process-bitmap.jd b/docs/html/training/displaying-bitmaps/process-bitmap.jd new file mode 100644 index 0000000..c1450b4 --- /dev/null +++ b/docs/html/training/displaying-bitmaps/process-bitmap.jd @@ -0,0 +1,239 @@ +page.title=Processing Bitmaps Off the UI Thread +parent.title=Displaying Bitmaps Efficiently +parent.link=index.html + +trainingnavtop=true +next.title=Caching Bitmaps +next.link=cache-bitmap.html +previous.title=Loading Large Bitmaps Efficiently +previous.link=load-bitmap.html + +@jd:body + +<div id="tb-wrapper"> +<div id="tb"> + +<h2>This lesson teaches you to</h2> +<ol> + <li><a href="#async-task">Use an AsyncTask</a></li> + <li><a href="#concurrency">Handle Concurrency</a></li> +</ol> + +<h2>You should also read</h2> +<ul> + <li><a href="{@docRoot}guide/practices/design/responsiveness.html">Designing for Responsiveness</a></li> + <li><a + href="http://android-developers.blogspot.com/2010/07/multithreading-for-performance.html">Multithreading + for Performance</a></li> +</ul> + +<h2>Try it out</h2> + +<div class="download-box"> + <a href="{@docRoot}shareables/training/BitmapFun.zip" class="button">Download the sample</a> + <p class="filename">BitmapFun.zip</p> +</div> + +</div> +</div> + +<p>The {@link +android.graphics.BitmapFactory#decodeByteArray(byte[],int,int,android.graphics.BitmapFactory.Options) +BitmapFactory.decode*} methods, discussed in the <a href="load-bitmap.html">Load Large Bitmaps +Efficiently</a> lesson, should not be executed on the main UI thread if the source data is read from +disk or a network location (or really any source other than memory). The time this data takes to +load is unpredictable and depends on a variety of factors (speed of reading from disk or network, +size of image, power of CPU, etc.). If one of these tasks blocks the UI thread, the system flags +your application as non-responsive and the user has the option of closing it (see <a +href="{@docRoot}guide/practices/design/responsiveness.html">Designing for Responsiveness</a> for +more information).</p> + +<p>This lesson walks you through processing bitmaps in a background thread using +{@link android.os.AsyncTask} and shows you how to handle concurrency issues.</p> + +<h2 id="async-task">Use an AsyncTask</h2> + +<p>The {@link android.os.AsyncTask} class provides an easy way to execute some work in a background +thread and publish the results back on the UI thread. To use it, create a subclass and override the +provided methods. Here’s an example of loading a large image into an {@link +android.widget.ImageView} using {@link android.os.AsyncTask} and <a +href="load-bitmap.html#decodeSampledBitmapFromResource">{@code +decodeSampledBitmapFromResource()}</a>: </p> + +<a name="BitmapWorkerTask"></a> +<pre> +class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> { + private final WeakReference<ImageView> imageViewReference; + private int data = 0; + + public BitmapWorkerTask(ImageView imageView) { + // Use a WeakReference to ensure the ImageView can be garbage collected + imageViewReference = new WeakReference<ImageView>(imageView); + } + + // Decode image in background. + @Override + protected Bitmap doInBackground(Integer... params) { + data = params[0]; + return decodeSampledBitmapFromResource(getResources(), data, 100, 100)); + } + + // Once complete, see if ImageView is still around and set bitmap. + @Override + protected void onPostExecute(Bitmap bitmap) { + if (imageViewReference != null && bitmap != null) { + final ImageView imageView = imageViewReference.get(); + if (imageView != null) { + imageView.setImageBitmap(bitmap); + } + } + } +} +</pre> + +<p>The {@link java.lang.ref.WeakReference} to the {@link android.widget.ImageView} ensures that the +{@link android.os.AsyncTask} does not prevent the {@link android.widget.ImageView} and anything it +references from being garbage collected. There’s no guarantee the {@link android.widget.ImageView} +is still around when the task finishes, so you must also check the reference in {@link +android.os.AsyncTask#onPostExecute(Result) onPostExecute()}. The {@link android.widget.ImageView} +may no longer exist, if for example, the user navigates away from the activity or if a +configuration change happens before the task finishes.</p> + +<p>To start loading the bitmap asynchronously, simply create a new task and execute it:</p> + +<pre> +public void loadBitmap(int resId, ImageView imageView) { + BitmapWorkerTask task = new BitmapWorkerTask(imageView); + task.execute(resId); +} +</pre> + +<h2 id="concurrency">Handle Concurrency</h2> + +<p>Common view components such as {@link android.widget.ListView} and {@link +android.widget.GridView} introduce another issue when used in conjunction with the {@link +android.os.AsyncTask} as demonstrated in the previous section. In order to be efficient with memory, +these components recycle child views as the user scrolls. If each child view triggers an {@link +android.os.AsyncTask}, there is no guarantee that when it completes, the associated view has not +already been recycled for use in another child view. Furthermore, there is no guarantee that the +order in which asynchronous tasks are started is the order that they complete.</p> + +<p>The blog post <a +href="http://android-developers.blogspot.com/2010/07/multithreading-for-performance.html">Multithreading +for Performance</a> further discusses dealing with concurrency, and offers a solution where the +{@link android.widget.ImageView} stores a reference to the most recent {@link android.os.AsyncTask} +which can later be checked when the task completes. Using a similar method, the {@link +android.os.AsyncTask} from the previous section can be extended to follow a similar pattern.</p> + +<p>Create a dedicated {@link android.graphics.drawable.Drawable} subclass to store a reference +back to the worker task. In this case, a {@link android.graphics.drawable.BitmapDrawable} is used so +that a placeholder image can be displayed in the {@link android.widget.ImageView} while the task +completes:</p> + +<a name="AsyncDrawable"></a> +<pre> +static class AsyncDrawable extends BitmapDrawable { + private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference; + + public AsyncDrawable(Resources res, Bitmap bitmap, + BitmapWorkerTask bitmapWorkerTask) { + super(res, bitmap); + bitmapWorkerTaskReference = + new WeakReference<BitmapWorkerTask>(bitmapWorkerTask); + } + + public BitmapWorkerTask getBitmapWorkerTask() { + return bitmapWorkerTaskReference.get(); + } +} +</pre> + +<p>Before executing the <a href="#BitmapWorkerTask">{@code BitmapWorkerTask}</a>, you create an <a +href="#AsyncDrawable">{@code AsyncDrawable}</a> and bind it to the target {@link +android.widget.ImageView}:</p> + +<pre> +public void loadBitmap(int resId, ImageView imageView) { + if (cancelPotentialWork(resId, imageView)) { + final BitmapWorkerTask task = new BitmapWorkerTask(imageView); + final AsyncDrawable asyncDrawable = + new AsyncDrawable(getResources(), mPlaceHolderBitmap, task); + imageView.setImageDrawable(asyncDrawable); + task.execute(resId); + } +} +</pre> + +<p>The {@code cancelPotentialWork} method referenced in the code sample above checks if another +running task is already associated with the {@link android.widget.ImageView}. If so, it attempts to +cancel the previous task by calling {@link android.os.AsyncTask#cancel cancel()}. In a small number +of cases, the new task data matches the existing task and nothing further needs to happen. Here is +the implementation of {@code cancelPotentialWork}:</p> + +<pre> +public static boolean cancelPotentialWork(int data, ImageView imageView) { + final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView); + + if (bitmapWorkerTask != null) { + final int bitmapData = bitmapWorkerTask.data; + if (bitmapData != data) { + // Cancel previous task + bitmapWorkerTask.cancel(true); + } else { + // The same work is already in progress + return false; + } + } + // No task associated with the ImageView, or an existing task was cancelled + return true; +} +</pre> + +<p>A helper method, {@code getBitmapWorkerTask()}, is used above to retrieve the task associated +with a particular {@link android.widget.ImageView}:</p> + +<pre> +private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) { + if (imageView != null) { + final Drawable drawable = imageView.getDrawable(); + if (drawable instanceof AsyncDrawable) { + final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable; + return asyncDrawable.getBitmapWorkerTask(); + } + } + return null; +} +</pre> + +<p>The last step is updating {@code onPostExecute()} in <a href="#BitmapWorkerTask">{@code +BitmapWorkerTask}</a> so that it checks if the task is cancelled and if the current task matches the +one associated with the {@link android.widget.ImageView}:</p> + +<a name="BitmapWorkerTaskUpdated"></a> +<pre> +class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> { + ... + + @Override + protected void onPostExecute(Bitmap bitmap) { + <strong>if (isCancelled()) { + bitmap = null; + }</strong> + + if (imageViewReference != null && bitmap != null) { + final ImageView imageView = imageViewReference.get(); + <strong>final BitmapWorkerTask bitmapWorkerTask = + getBitmapWorkerTask(imageView);</strong> + if (<strong>this == bitmapWorkerTask &&</strong> imageView != null) { + imageView.setImageBitmap(bitmap); + } + } + } +} +</pre> + +<p>This implementation is now suitable for use in {@link android.widget.ListView} and {@link +android.widget.GridView} components as well as any other components that recycle their child +views. Simply call {@code loadBitmap} where you normally set an image to your {@link +android.widget.ImageView}. For example, in a {@link android.widget.GridView} implementation this +would be in the {@link android.widget.Adapter#getView getView()} method of the backing adapter.</p>
\ No newline at end of file diff --git a/docs/html/training/tv/index.jd b/docs/html/training/tv/index.jd new file mode 100644 index 0000000..ae13c4a --- /dev/null +++ b/docs/html/training/tv/index.jd @@ -0,0 +1,52 @@ +page.title=Designing for TV + +trainingnavtop=true +startpage=true +next.title=Optimizing layouts for TV +next.link=optimizing-layouts-tv.html + +@jd:body + +<div id="tb-wrapper"> +<div id="tb"> + +<!-- Required platform, tools, add-ons, devices, knowledge, etc. --> +<h2>Dependencies and prerequisites</h2> +<ul> + <li>Android 2.0 (API Level 5) or higher</li> +</ul> + +</div> +</div> +<p> + Smart TVs powered by Android bring your favorite Android apps to the best screen in your house. + Thousands of apps in the Google Play Store are already optimized for TVs. This class shows how + you can optimize your Android app for TVs, including how to build a layout that + works great when the user is ten feet away and navigating with a remote control. +</p> + +<h2>Lessons</h2> + +<dl> + <dt><b><a href="optimizing-layouts-tv.html">Optimizing Layouts for TV</a></b></dt> + <dd>Shows you how to optimize app layouts for TV screens, which have some unique characteristics such as: + <ul> + <li>permanent "landscape" mode</li> + <li>high-resolution displays</li> + <li>"10 foot UI" environment.</li> + </ul> + </dd> + + <dt><b><a href="optimizing-navigation-tv.html">Optimizing Navigation for TV</a></b></dt> + <dd>Shows you how to design navigation for TVs, including: + <ul> + <li>handling D-pad navigation</li> + <li>providing navigational feedback</li> + <li>providing easily-accessible controls on the screen.</li> + </ul> + </dd> + + <dt><b><a href="unsupported-features-tv.html">Handling features not supported on TV</a></b></dt> + <dd>Lists the hardware features that are usually not available on TVs. This lesson also shows you how to + provide alternatives for missing features or check for missing features and disable code at run time.</dd> +</dl>
\ No newline at end of file diff --git a/docs/html/training/tv/optimizing-layouts-tv.jd b/docs/html/training/tv/optimizing-layouts-tv.jd new file mode 100644 index 0000000..6eac6d3 --- /dev/null +++ b/docs/html/training/tv/optimizing-layouts-tv.jd @@ -0,0 +1,246 @@ +page.title=Optimizing Layouts for TV +parent.title=Designing for TV +parent.link=index.html + +trainingnavtop=true +next.title=Optimizing Navigation for TV +next.link=optimizing-navigation-tv.html + +@jd:body + +<div id="tb-wrapper"> +<div id="tb"> + +<h2>This lesson teaches you to</h2> +<ol> + <li><a href="#DesignLandscapeLayouts">Design Landscape Layouts</a></li> + <li><a href="#MakeTextControlsEasyToSee">Make Text and Controls Easy to See</a></li> + <li><a href="#DesignForLargeScreens">Design for High-Density Large Screens</a></li> + <li><a href="#HandleLargeBitmaps">Handle Large Bitmaps in Your Application</a></li> +</ol> + +<h2>You should also read</h2> +<ul> + <li><a href="{@docRoot}guide/practices/screens_support.html">Supporting Multiple Screens</a></li> +</ul> + +</div> +</div> + +<p> +When your application is running on a television set, you should assume that the user is sitting about +ten feet away from the screen. This user environment is referred to as the +<a href="http://en.wikipedia.org/wiki/10-foot_user_interface">10-foot UI</a>. To provide your +users with a usable and enjoyable experience, you should style and lay out your UI accordingly.. +</p> +<p> +This lesson shows you how to optimize layouts for TV by: +</p> +<ul> + <li>Providing appropriate layout resources for landscape mode.</li> + <li>Ensuring that text and controls are large enough to be visible from a distance.</li> + <li>Providing high resolution bitmaps and icons for HD TV screens.</li> +</ul> + +<h2 id="DesignLandscapeLayouts">Design Landscape Layouts</h2> + +<p> +TV screens are always in landscape orientation. Follow these tips to build landscape layouts optimized for TV screens: +</p> +<ul> + <li>Put on-screen navigational controls on the left or right side of the screen and save the + vertical space for content.</li> + <li>Create UIs that are divided into sections, by using <a href="{@docRoot}guide/topics/fundamentals/fragments.html">Fragments</a> + and use view groups like {@link android.widget.GridView} instead + of {@link android.widget.ListView} to make better use of the + horizontal screen space.</li> + <li>Use view groups such as {@link android.widget.RelativeLayout} + or {@link android.widget.LinearLayout} to arrange views. + This allows the Android system to adjust the position of the views to the size, alignment, + aspect ratio, and pixel density of the TV screen.</li> + <li>Add sufficient margins between layout controls to avoid a cluttered UI.</li> +</ul> + +<p> +For example, the following layout is optimized for TV: +</p> + +<img src="{@docRoot}images/training/panoramio-grid.png" /> + +<p> +In this layout, the controls are on the lefthand side. The UI is displayed within a +{@link android.widget.GridView}, which is well-suited to landscape orientation. +In this layout both GridView and Fragment have the width and height set +dynamically, so they can adjust to the screen resolution. Controls are added to the left side Fragment programatically at runtime. +The layout file for this UI is {@code res/layout-land-large/photogrid_tv.xml}. +(This layout file is placed in {@code layout-land-large} because TVs have large screens with landscape orientation. For details refer to +<a href="{@docRoot}guide/practices/screens_support.html">Supporting Multiple Screens</a>.)</p> + +res/layout-land-large/photogrid_tv.xml +<pre> +<RelativeLayout + android:layout_width="fill_parent" + android:layout_height="fill_parent" > + + <fragment + android:id="@+id/leftsidecontrols" + android:layout_width="0dip" + android:layout_marginLeft="5dip" + android:layout_height="match_parent" /> + + <GridView + android:id="@+id/gridview" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> + +</RelativeLayout> +</pre> + +<p> +To set up action bar items on the left side of the screen, you can also include the <a +href="http://code.google.com/p/googletv-android-samples/source/browse/#git%2FLeftNavBarLibrary"> +Left navigation bar library</a> in your application to set up action items on the left side +of the screen, instead of creating a custom Fragment to add controls: +</p> + +<pre> +LeftNavBar bar = (LeftNavBarService.instance()).getLeftNavBar(this); +</pre> + +<p> +When you have an activity in which the content scrolls vertically, always use a left navigation bar; +otherwise, your users have to scroll to the top of the content to switch between the content view and +the ActionBar. Look at the +<a href="http://code.google.com/p/googletv-android-samples/source/browse/#git%2FLeftNavBarDemo"> +Left navigation bar sample app</a> to see how to simple it is to include the left navigation bar in your app. +</p> + +<h2 id="MakeTextControlsEasyToSee">Make Text and Controls Easy to See</h2> +<p> +The text and controls in a TV application's UI should be easily visible and navigable from a distance. +Follow these tips to make them easier to see from a distance : +</p> + +<ul> + <li>Break text into small chunks that users can quickly scan.</li> + <li>Use light text on a dark background. This style is easier to read on a TV.</li> + <li>Avoid lightweight fonts or fonts that have both very narrow and very broad strokes. Use simple sans-serif + fonts and use anti-aliasing to increase readability.</li> + <li>Use Android's standard font sizes: + <pre> + <TextView + android:id="@+id/atext" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:gravity="center_vertical" + android:singleLine="true" + android:textAppearance="?android:attr/textAppearanceMedium"/> + </pre></li> + <li>Ensure that all your view widgets are large enough to be clearly visible to someone sitting 10 feet away + from the screen (this distance is greater for very large screens). The best way to do this is to use + layout-relative sizing rather than absolute sizing, and density-independent pixel units instead of absolute + pixel units. For example, to set the width of a widget, use wrap_content instead of a pixel measurement, + and to set the margin for a widget, use dip instead of px values. + </li> +</ul> +<p> + +</p> + +<h2 id="DesignForLargeScreens">Design for High-Density Large Screens</h2> + +<p> +The common HDTV display resolutions are 720p, 1080i, and 1080p. Design your UI for 1080p, and then +allow the Android system to downscale your UI to 720p if necessary. In general, downscaling (removing pixels) +does not degrade the UI (Notice that the converse is not true; you should avoid upscaling because it degrades +UI quality). +</p> + +<p> +To get the best scaling results for images, provide them as <a href="{@docRoot}guide/developing/tools/draw9patch.html"> +9-patch image</a> elements if possible. +If you provide low quality or small images in your layouts, they will appear pixelated, fuzzy, or grainy. This +is not a good experience for the user. Instead, use high-quality images. +</p> + +<p> +For more information on optimizing apps for large screens see <a href="{@docRoot}training/multiscreen/index.html"> +Designing for multiple screens</a>. +</p> + +<h2 id="HandleLargeBitmaps">Design to Handle Large Bitmaps</h2> + +<p> +The Android system has a limited amount of memory, so downloading and storing high-resolution images can often +cause out-of-memory errors in your app. To avoid this, follow these tips: +</p> + +<ul> + <li>Load images only when they're displayed on the screen. For example, when displaying multiple images in + a {@link android.widget.GridView} or + {@link android.widget.Gallery}, only load an image when + {@link android.widget.Adapter#getView(int, View, ViewGroup) getView()} + is called on the View's {@link android.widget.Adapter}. + </li> + <li>Call {@link android.graphics.Bitmap#recycle()} on + {@link android.graphics.Bitmap} views that are no longer needed. + </li> + <li>Use {@link java.lang.ref.WeakReference} for storing references + to {@link android.graphics.Bitmap} objects in a in-memory + <a href="{@link java.util.Collection}.</li> + <li>If you fetch images from the network, use {@link android.os.AsyncTask} + to fetch them and store them on the SD card for faster access. + Never do network transactions on the application's UI thread. + </li> + <li>Scale down really large images to a more appropriate size as you download them; otherwise, downloading the image + itself may cause an "Out of Memory" exception. Here is sample code that scales down images while downloading: + + <pre> + // Get the source image's dimensions + BitmapFactory.Options options = new BitmapFactory.Options(); + // This does not download the actual image, just downloads headers. + options.inJustDecodeBounds = true; + BitmapFactory.decodeFile(IMAGE_FILE_URL, options); + // The actual width of the image. + int srcWidth = options.outWidth; + // The actual height of the image. + int srcHeight = options.outHeight; + + // Only scale if the source is bigger than the width of the destination view. + if(desiredWidth > srcWidth) + desiredWidth = srcWidth; + + // Calculate the correct inSampleSize/scale value. This helps reduce memory use. It should be a power of 2. + int inSampleSize = 1; + while(srcWidth / 2 > desiredWidth){ + srcWidth /= 2; + srcHeight /= 2; + inSampleSize *= 2; + } + + float desiredScale = (float) desiredWidth / srcWidth; + + // Decode with inSampleSize + options.inJustDecodeBounds = false; + options.inDither = false; + options.inSampleSize = inSampleSize; + options.inScaled = false; + // Ensures the image stays as a 32-bit ARGB_8888 image. + // This preserves image quality. + options.inPreferredConfig = Bitmap.Config.ARGB_8888; + + Bitmap sampledSrcBitmap = BitmapFactory.decodeFile(IMAGE_FILE_URL, options); + + // Resize + Matrix matrix = new Matrix(); + matrix.postScale(desiredScale, desiredScale); + Bitmap scaledBitmap = Bitmap.createBitmap(sampledSrcBitmap, 0, 0, + sampledSrcBitmap.getWidth(), sampledSrcBitmap.getHeight(), matrix, true); + sampledSrcBitmap = null; + + // Save + FileOutputStream out = new FileOutputStream(LOCAL_PATH_TO_STORE_IMAGE); + scaledBitmap.compress(Bitmap.CompressFormat.JPEG, 100, out); + scaledBitmap = null; + </pre> + </li> </ul>
\ No newline at end of file diff --git a/docs/html/training/tv/optimizing-navigation-tv.jd b/docs/html/training/tv/optimizing-navigation-tv.jd new file mode 100644 index 0000000..8b5878e --- /dev/null +++ b/docs/html/training/tv/optimizing-navigation-tv.jd @@ -0,0 +1,206 @@ +page.title=Optimizing Navigation for TV +parent.title=Designing for TV +parent.link=index.html + +trainingnavtop=true +previous.title=Optimizing Layouts for TV +previous.link=optimizing-layouts-tv.html +next.title=Handling features not supported on TV +next.link=unsupported-features-tv.html + +@jd:body + +<div id="tb-wrapper"> +<div id="tb"> + +<h2>This lesson teaches you to</h2> +<ol> + <li><a href="#HandleDpadNavigation">Handle D-pad Navigation</a></li> + <li><a href="#HandleFocusSelection">Provide Clear Visual Indication for Focus and Selection</a></li> + <li><a href="#DesignForEasyNavigation">Design for Easy Navigation</a></li> +</ol> + +<h2>You should also read</h2> +<ul> + <li><a href="{@docRoot}training/design-navigation/index.html">Designing Effective Navigation</a></li> +</ul> + +</div> +</div> + +<p> +An important aspect of the user experience when operating a TV is the direct human interface: a remote control. +As you optimize your Android application for TVs, you should pay special attention to how the user actually navigates +around your application when using a remote control instead of a touchscreen. +</p> +<p> +This lesson shows you how to optimize navigation for TV by: +</p> + +<ul> + <li>Ensuring all layout controls are D-pad navigable.</li> + <li>Providing highly obvious feedback for UI navigation.</li> + <li>Placing layout controls for easy access.</li> +</ul> + +<h2 id="HandleDpadNavigation">Handle D-pad Navigation</h2> + +<p> +On a TV, users navigate with controls on a TV remote, using either a D-pad or arrow keys. +This limits movement to up, down, left, and right. +To build a great TV-optimized app, you must provide a navigation scheme in which the user can +quickly learn how to navigate your app using the remote. +</p> + +<p> +When you design navigation for D-pad, follow these guidelines: +</p> + +<ul> + <li>Ensure that the D-pad can navigate to all the visible controls on the screen.</li> + <li>For scrolling lists with focus, D-pad up/down keys scroll the list and Enter key selects an item in the list. Ensure that users can + select an element in the list and that the list still scrolls when an element is selected.</li> + <li>Ensure that movement between controls is straightforward and predictable.</li> +</ul> + +<p> +Android usually handles navigation order between layout elements automatically, so you don't need to do anything extra. If the screen layout +makes navigation difficult, or if you want users to move through the layout in a specific way, you can set up explicit navigation for your +controls. +For example, for an {@code android.widget.EditText}, to define the next control to receive focus, use: +<pre> +<EditText android:id="@+id/LastNameField" android:nextFocusDown="@+id/FirstNameField"\> +</pre> +The following table lists all of the available navigation attributes: +</p> + +<table> +<tr> +<th>Attribute</th> +<th>Function</th> +</tr> +<tr> +<td>{@link android.R.attr#nextFocusDown}</td> +<td>Defines the next view to receive focus when the user navigates down.</td> +</tr> +<tr> +<td>{@link android.R.attr#nextFocusLeft}</td> +<td>Defines the next view to receive focus when the user navigates left.</td> +</tr> +<tr> +<td>{@link android.R.attr#nextFocusRight}</td> +<td>Defines the next view to receive focus when the user navigates right.</td> +</tr> +<tr> +<td>{@link android.R.attr#nextFocusUp}</td> +<td>Defines the next view to receive focus when the user navigates up.</td> +</tr> +</table> + +<p> +To use one of these explicit navigation attributes, set the value to the ID (android:id value) of another widget in the layout. You should set +up the navigation order as a loop, so that the last control directs focus back to the first one. +</p> + +<p> +Note: You should only use these attributes to modify the navigation order if the default order that the system applies does not work well. +</p> + +<h2 id="HandleFocusSelection">Provide Clear Visual Indication for Focus and Selection</h2> + +<p> +Use appropriate color highlights for all navigable and selectable elements in the UI. This makes it easy for users to know whether the control +is currently focused or selected when they navigate with a D-pad. Also, use uniform highlight scheme across your application. +</p> + +<p> +Android provides <a href="{@docRoot}guide/topics/resources/drawable-resource.html#StateList">Drawable State List Resources</a> to implement highlights +for selected and focused controls. For example: +</p> + +res/drawable/button.xml: +<pre> +<?xml version="1.0" encoding="utf-8"?> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_pressed="true" + android:drawable="@drawable/button_pressed" /> <!-- pressed --> + <item android:state_focused="true" + android:drawable="@drawable/button_focused" /> <!-- focused --> + <item android:state_hovered="true" + android:drawable="@drawable/button_focused" /> <!-- hovered --> + <item android:drawable="@drawable/button_normal" /> <!-- default --> +</selector> +</pre> + +<p> +This layout XML applies the above state list drawable to a {@link android.widget.Button}: +</p> +<pre> +<Button + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:background="@drawable/button" /> +</pre> + +<p> +Provide sufficient padding within the focusable and selectable controls so that the highlights around them are clearly visible. +</p> + +<h2 id="DesignForEasyNavigation">Design for Easy Navigation</h2> + +<p> +Users should be able to navigate to any UI control with a couple of D-pad clicks. Navigation should be easy and intuitive to +understand. For any non-intuitive actions, provide users with written help, using a dialog triggered by a help button or action bar icon. +</p> + +<p> +Predict the next screen that the user will want to navigate to and provide one click navigation to it. If the current screen UI is very sparse, +consider making it a multi pane screen. Use fragments for making multi-pane screens. For example, consider the multi-pane UI below with continent names +on the left and list of cool places in each continent on the right. +</p> + +<img src="{@docRoot}images/training/cool-places.png" alt="" /> + +<p> +The above UI consists of three Fragments - <code>left_side_action_controls</code>, <code>continents</code> and +<code>places</code> - as shown in its layout +xml file below. Such multi-pane UIs make D-pad navigation easier and make good use of the horizontal screen space for +TVs. +</p> +res/layout/cool_places.xml +<pre> +<LinearLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="horizontal" + > + <fragment + android:id="@+id/left_side_action_controls" + android:layout_width="0px" + android:layout_height="match_parent" + android:layout_marginLeft="10dip" + android:layout_weight="0.2"/> + <fragment + android:id="@+id/continents" + android:layout_width="0px" + android:layout_height="match_parent" + android:layout_marginLeft="10dip" + android:layout_weight="0.2"/> + + <fragment + android:id="@+id/places" + android:layout_width="0px" + android:layout_height="match_parent" + android:layout_marginLeft="10dip" + android:layout_weight="0.6"/> + +</LinearLayout> +</pre> + +<p> +Also, notice in the UI layout above action controls are on the left hand side of a vertically scrolling list to make +them easily accessible using D-pad. +In general, for layouts with horizontally scrolling components, place action controls on left or right hand side and +vice versa for vertically scrolling components. +</p> + diff --git a/docs/html/training/tv/unsupported-features-tv.jd b/docs/html/training/tv/unsupported-features-tv.jd new file mode 100644 index 0000000..6b0f8c8 --- /dev/null +++ b/docs/html/training/tv/unsupported-features-tv.jd @@ -0,0 +1,156 @@ +page.title=Handling Features Not Supported on TV +parent.title=Designing for TV +parent.link=index.html + +trainingnavtop=true +previous.title=Optimizing Navigation for TV +previous.link=optimizing-navigation-tv.html + +@jd:body + +<div id="tb-wrapper"> +<div id="tb"> + +<h2>This lesson teaches you to</h2> +<ol> + <li><a href="#WorkaroundUnsupportedFeatures">Work Around Features Not Supported on TV</a></li> + <li><a href="#CheckAvailableFeatures">Check for Available Features at Runtime</a></li> +</ol> + +</div> +</div> + +<p> +TVs are much different from other Android-powered devices: +</p> +<ul> + <li>They're not mobile.</li> + <li>Out of habit, people use them for watching media with little or no interaction.</li> + <li>People interact with them from a distance.</li> +</ul> + +<p> +Because TVs have a different purpose from other devices, they usually don't have hardware features +that other Android-powered devices often have. For this reason, the Android system does not +support the following features for a TV device: +<table> +<tr> +<th>Hardware</th> +<th>Android feature descriptor</th> +</tr> +<tr> +<td>Camera</td> +<td>android.hardware.camera</td> +</tr> +<tr> +<td>GPS</td> +<td>android.hardware.location.gps</td> +</tr> +<tr> +<td>Microphone</td> +<td>android.hardware.microphone</td> +</tr> +<tr> +<td>Near Field Communications (NFC)</td> +<td>android.hardware.nfc</td> +</tr> +<tr> +<td>Telephony</td> +<td>android.hardware.telephony</td> +</tr> +<tr> +<td>Touchscreen</td> +<td>android.hardware.touchscreen</td> +</tr> +</table> +</p> + +<p> +This lesson shows you how to work around features that are not available on TV by: +<ul> + <li>Providing work arounds for some non-supported features.</li> + <li>Checking for available features at runtime and conditionally activating/deactivating certain code + paths based on availability of those features.</li> +</ul> +</p> + + +<h2 id="WorkaroundUnsupportedFeatures">Work Around Features Not Supported on TV</h2> + +<p> +Android doesn't support touchscreen interaction for TV devices, most TVs don't have touch screens, +and interacting with a TV using a touchscreen is not consistent with the 10 foot environment. For +these reasons, users interact with Android-powered TVs using a remote. In consideration of this, +ensure that every control in your app can be accessed with the D-pad. Refer back to the previous two lessons +<a href="{@docRoot}training/tv/optimizing-layouts-tv">Optimizing Layouts for TV</a> and +<a href="{@docRoot}training/tv/optimizing-navigation-tv">Optimize Navigation for TV</a> for more details +on this topic. The Android system assumes that a device has a touchscreen, so if you want your application +to run on a TV, you must <strong>explicitly</strong> disable the touchscreen requirement in your manifest file: +<pre> +<uses-feature android:name="android.hardware.touchscreen" android:required="false"/> +</pre> +</p> + +<p> +Although a TV doesn't have a camera, you can still provide a photography-related application on a TV. +For example, if you have an app that takes, views and edits photos, you can disable its picture-taking +functionality for TVs and still allow users to view and even edit photos. The next section talks about how to +deactivate or activate specific functions in the application based on runtime device type detection. +</p> + +<p> +Because TVs are stationary, indoor devices, they don't have built-in GPS. If your application uses location +information, allow users to search for a location or use a "static" location provider to get +a location from the zip code configured during the TV setup. +<pre> +LocationManager locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE); +Location location = locationManager.getLastKnownLocation("static"); +Geocoder geocoder = new Geocoder(this); +Address address = null; + +try { + address = geocoder.getFromLocation(location.getLatitude(), location.getLongitude(), 1).get(0); + Log.d("Zip code", address.getPostalCode()); + +} catch (IOException e) { + Log.e(TAG, "Geocoder error", e); +} +</pre> +</p> + +<p> +TVs usually don't support microphones, but if you have an application that uses voice control, +you can create a mobile device app that takes voice input and then acts as a remote control for a TV. +</p> + +<h2 id="CheckAvailableFeatures">Check for Available Features at Runtime</h2> + +<p> +To check if a feature is available at runtime, call +{@link android.content.pm.PackageManager#hasSystemFeature(String)}. + This method takes a single argument : a string corresponding to the +feature you want to check. For example, to check for touchscreen, use +{@link android.content.pm.PackageManager#hasSystemFeature(String)} with the argument +{@link android.content.pm.PackageManager#FEATURE_TOUCHSCREEN}. +</p> + +<p> +The following code snippet demonstrates how to detect device type at runtime based on supported features: + +<pre> +// Check if android.hardware.telephony feature is available. +if (getPackageManager().hasSystemFeature("android.hardware.telephony")) { + Log.d("Mobile Test", "Running on phone"); +// Check if android.hardware.touchscreen feature is available. +} else if (getPackageManager().hasSystemFeature("android.hardware.touchscreen")) { + Log.d("Tablet Test", "Running on devices that don't support telphony but have a touchscreen."); +} else { + Log.d("TV Test", "Running on a TV!"); +} +</pre> +</p> + +<p> +This is just one example of using runtime checks to deactivate app functionality that depends on features +that aren't available on TVs. +</p>
\ No newline at end of file diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java index 6f939be..ed5b2f6 100644 --- a/graphics/java/android/graphics/Bitmap.java +++ b/graphics/java/android/graphics/Bitmap.java @@ -16,9 +16,12 @@ package android.graphics; +import android.os.Debug; import android.os.Parcel; import android.os.Parcelable; import android.util.DisplayMetrics; +import android.util.Log; + import java.io.OutputStream; import java.nio.Buffer; import java.nio.ByteBuffer; @@ -57,6 +60,7 @@ public final class Bitmap implements Parcelable { private final boolean mIsMutable; private byte[] mNinePatchChunk; // may be null + private int[] mLayoutBounds; // may be null private int mWidth = -1; private int mHeight = -1; private boolean mRecycled; @@ -95,6 +99,19 @@ public final class Bitmap implements Parcelable { */ /*package*/ Bitmap(int nativeBitmap, byte[] buffer, boolean isMutable, byte[] ninePatchChunk, int density) { + this(nativeBitmap, buffer, isMutable, ninePatchChunk, null, density); + } + + /** + * @noinspection UnusedDeclaration + */ + /* Private constructor that must received an already allocated native + bitmap int (pointer). + + This can be called from JNI code. + */ + /*package*/ Bitmap(int nativeBitmap, byte[] buffer, boolean isMutable, byte[] ninePatchChunk, + int[] layoutBounds, int density) { if (nativeBitmap == 0) { throw new RuntimeException("internal error: native bitmap is 0"); } @@ -106,6 +123,7 @@ public final class Bitmap implements Parcelable { mIsMutable = isMutable; mNinePatchChunk = ninePatchChunk; + mLayoutBounds = layoutBounds; if (density >= 0) { mDensity = density; } @@ -164,6 +182,16 @@ public final class Bitmap implements Parcelable { } /** + * Sets the layout bounds as an array of left, top, right, bottom integers + * @param padding the array containing the padding values + * + * @hide + */ + public void setLayoutBounds(int[] bounds) { + mLayoutBounds = bounds; + } + + /** * Free the native object associated with this bitmap, and clear the * reference to the pixel data. This will not free the pixel data synchronously; * it simply allows it to be garbage collected if there are no other references. @@ -690,6 +718,14 @@ public final class Bitmap implements Parcelable { } /** + * @hide + * @return the layout padding [left, right, top, bottom] + */ + public int[] getLayoutBounds() { + return mLayoutBounds; + } + + /** * Specifies the known formats a bitmap can be compressed into */ public enum CompressFormat { diff --git a/graphics/java/android/graphics/BitmapFactory.java b/graphics/java/android/graphics/BitmapFactory.java index c5705f6..1599e40 100644 --- a/graphics/java/android/graphics/BitmapFactory.java +++ b/graphics/java/android/graphics/BitmapFactory.java @@ -424,6 +424,7 @@ public class BitmapFactory { throw new ArrayIndexOutOfBoundsException(); } Bitmap bm = nativeDecodeByteArray(data, offset, length, opts); + if (bm == null && opts != null && opts.inBitmap != null) { throw new IllegalArgumentException("Problem decoding into existing bitmap"); } @@ -554,7 +555,6 @@ public class BitmapFactory { if (targetDensity == 0 || density == targetDensity || density == opts.inScreenDensity) { return bm; } - byte[] np = bm.getNinePatchChunk(); final boolean isNinePatch = np != null && NinePatch.isNinePatchChunk(np); if (opts.inScaled || isNinePatch) { diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java index dcda67d..7e92973 100644 --- a/graphics/java/android/graphics/Canvas.java +++ b/graphics/java/android/graphics/Canvas.java @@ -40,14 +40,8 @@ public class Canvas { // assigned in constructors, freed in finalizer final int mNativeCanvas; - /* Our native canvas can be either a raster, gl, or picture canvas. - If we are raster, then mGL will be null, and mBitmap may or may not be - present (our default constructor creates a raster canvas but no - java-bitmap is). If we are a gl-based, then mBitmap will be null, and - mGL will not be null. Thus both cannot be non-null, but its possible - for both to be null. - */ - private Bitmap mBitmap; // if not null, mGL must be null + // may be null + private Bitmap mBitmap; // optional field set by the caller private DrawFilter mDrawFilter; @@ -66,7 +60,7 @@ public class Canvas { // Used by native code @SuppressWarnings({"UnusedDeclaration"}) - private int mSurfaceFormat; + private int mSurfaceFormat; /** * Flag for drawTextRun indicating left-to-right run direction. diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java index 043adae..86e824b 100644 --- a/graphics/java/android/graphics/drawable/Drawable.java +++ b/graphics/java/android/graphics/drawable/Drawable.java @@ -773,7 +773,13 @@ public abstract class Drawable { np = null; pad = null; } - return drawableFromBitmap(res, bm, np, pad, srcName); + int[] layoutBounds = bm.getLayoutBounds(); + Rect layoutBoundsRect = null; + if (layoutBounds != null) { + layoutBoundsRect = new Rect(layoutBounds[0], layoutBounds[1], + layoutBounds[2], layoutBounds[3]); + } + return drawableFromBitmap(res, bm, np, pad, layoutBoundsRect, srcName); } return null; } @@ -875,7 +881,7 @@ public abstract class Drawable { Bitmap bm = BitmapFactory.decodeFile(pathName); if (bm != null) { - return drawableFromBitmap(null, bm, null, null, pathName); + return drawableFromBitmap(null, bm, null, null, null, pathName); } return null; @@ -956,10 +962,12 @@ public abstract class Drawable { } private static Drawable drawableFromBitmap(Resources res, Bitmap bm, byte[] np, - Rect pad, String srcName) { + Rect pad, Rect layoutBounds, String srcName) { if (np != null) { - return new NinePatchDrawable(res, bm, np, pad, srcName); + NinePatchDrawable npd = new NinePatchDrawable(res, bm, np, pad, srcName); + npd.setLayoutBounds(layoutBounds); + return npd; } return new BitmapDrawable(res, bm); diff --git a/graphics/java/android/graphics/drawable/NinePatchDrawable.java b/graphics/java/android/graphics/drawable/NinePatchDrawable.java index 18b8bc7..1272071 100644 --- a/graphics/java/android/graphics/drawable/NinePatchDrawable.java +++ b/graphics/java/android/graphics/drawable/NinePatchDrawable.java @@ -47,6 +47,7 @@ public class NinePatchDrawable extends Drawable { private NinePatchState mNinePatchState; private NinePatch mNinePatch; private Rect mPadding; + private Rect mLayoutBounds; private Paint mPaint; private boolean mMutated; @@ -98,6 +99,13 @@ public class NinePatchDrawable extends Drawable { mNinePatchState.mTargetDensity = mTargetDensity; } + /** + * @hide + */ + void setLayoutBounds(Rect layoutBounds) { + mLayoutBounds = layoutBounds; + } + private void setNinePatchState(NinePatchState state, Resources res) { mNinePatchState = state; mNinePatch = state.mNinePatch; @@ -258,7 +266,7 @@ public class NinePatchDrawable extends Drawable { } options.inScreenDensity = DisplayMetrics.DENSITY_DEVICE; - final Rect padding = new Rect(); + final Rect padding = new Rect(); Bitmap bitmap = null; try { diff --git a/graphics/java/android/renderscript/Allocation.java b/graphics/java/android/renderscript/Allocation.java index 18a0a0c..cd5300d 100644 --- a/graphics/java/android/renderscript/Allocation.java +++ b/graphics/java/android/renderscript/Allocation.java @@ -184,9 +184,9 @@ public class Allocation extends BaseObj { private int getIDSafe() { if (mAdaptedAllocation != null) { - return mAdaptedAllocation.getID(); + return mAdaptedAllocation.getID(mRS); } - return getID(); + return getID(mRS); } @@ -321,7 +321,7 @@ public class Allocation extends BaseObj { @Override void updateFromNative() { super.updateFromNative(); - int typeID = mRS.nAllocationGetType(getID()); + int typeID = mRS.nAllocationGetType(getID(mRS)); if(typeID != 0) { mType = new Type(typeID, mRS); mType.updateFromNative(); @@ -371,7 +371,7 @@ public class Allocation extends BaseObj { "Can only send buffer if IO_OUTPUT usage specified."); } mRS.validate(); - mRS.nAllocationIoSend(getID()); + mRS.nAllocationIoSend(getID(mRS)); } /** @@ -394,7 +394,7 @@ public class Allocation extends BaseObj { "Can only receive if IO_INPUT usage specified."); } mRS.validate(); - mRS.nAllocationIoReceive(getID()); + mRS.nAllocationIoReceive(getID(mRS)); } /** @@ -411,7 +411,7 @@ public class Allocation extends BaseObj { } int i[] = new int[d.length]; for (int ct=0; ct < d.length; ct++) { - i[ct] = d[ct].getID(); + i[ct] = d[ct].getID(mRS); } copy1DRangeFromUnchecked(0, mCurrentCount, i); } @@ -571,7 +571,7 @@ public class Allocation extends BaseObj { mRS.validate(); validateBitmapSize(b); validateBitmapFormat(b); - mRS.nAllocationCopyFromBitmap(getID(), b); + mRS.nAllocationCopyFromBitmap(getID(mRS), b); } /** @@ -652,7 +652,7 @@ public class Allocation extends BaseObj { * followup sync will be required. */ public void generateMipmaps() { - mRS.nAllocationGenerateMipmaps(getID()); + mRS.nAllocationGenerateMipmaps(getID(mRS)); } /** @@ -780,7 +780,7 @@ public class Allocation extends BaseObj { public void copy1DRangeFrom(int off, int count, Allocation data, int dataOff) { mRS.nAllocationData2D(getIDSafe(), off, 0, mSelectedLOD, mSelectedFace.mID, - count, 1, data.getID(), dataOff, 0, + count, 1, data.getID(mRS), dataOff, 0, data.mSelectedLOD, data.mSelectedFace.mID); } @@ -857,7 +857,7 @@ public class Allocation extends BaseObj { validate2DRange(xoff, yoff, w, h); mRS.nAllocationData2D(getIDSafe(), xoff, yoff, mSelectedLOD, mSelectedFace.mID, - w, h, data.getID(), dataXoff, dataYoff, + w, h, data.getID(mRS), dataXoff, dataYoff, data.mSelectedLOD, data.mSelectedFace.mID); } @@ -888,7 +888,7 @@ public class Allocation extends BaseObj { mRS.validate(); validateBitmapFormat(b); validateBitmapSize(b); - mRS.nAllocationCopyToBitmap(getID(), b); + mRS.nAllocationCopyToBitmap(getID(mRS), b); } /** @@ -901,7 +901,7 @@ public class Allocation extends BaseObj { public void copyTo(byte[] d) { validateIsInt8(); mRS.validate(); - mRS.nAllocationRead(getID(), d); + mRS.nAllocationRead(getID(mRS), d); } /** @@ -914,7 +914,7 @@ public class Allocation extends BaseObj { public void copyTo(short[] d) { validateIsInt16(); mRS.validate(); - mRS.nAllocationRead(getID(), d); + mRS.nAllocationRead(getID(mRS), d); } /** @@ -927,7 +927,7 @@ public class Allocation extends BaseObj { public void copyTo(int[] d) { validateIsInt32(); mRS.validate(); - mRS.nAllocationRead(getID(), d); + mRS.nAllocationRead(getID(mRS), d); } /** @@ -940,7 +940,7 @@ public class Allocation extends BaseObj { public void copyTo(float[] d) { validateIsFloat32(); mRS.validate(); - mRS.nAllocationRead(getID(), d); + mRS.nAllocationRead(getID(mRS), d); } /** @@ -959,10 +959,10 @@ public class Allocation extends BaseObj { if ((mType.getY() > 0)|| (mType.getZ() > 0) || mType.hasFaces() || mType.hasMipmaps()) { throw new RSInvalidStateException("Resize only support for 1D allocations at this time."); } - mRS.nAllocationResize1D(getID(), dimX); + mRS.nAllocationResize1D(getID(mRS), dimX); mRS.finish(); // Necessary because resize is fifoed and update is async. - int typeID = mRS.nAllocationGetType(getID()); + int typeID = mRS.nAllocationGetType(getID(mRS)); mType = new Type(typeID, mRS); mType.updateFromNative(); updateCacheInfo(mType); @@ -991,10 +991,10 @@ public class Allocation extends BaseObj { throw new RSInvalidStateException( "Resize only support for 2D allocations at this time."); } - mRS.nAllocationResize2D(getID(), dimX, dimY); + mRS.nAllocationResize2D(getID(mRS), dimX, dimY); mRS.finish(); // Necessary because resize is fifoed and update is async. - int typeID = mRS.nAllocationGetType(getID()); + int typeID = mRS.nAllocationGetType(getID(mRS)); mType = new Type(typeID, mRS); mType.updateFromNative(); updateCacheInfo(mType); @@ -1019,10 +1019,10 @@ public class Allocation extends BaseObj { */ static public Allocation createTyped(RenderScript rs, Type type, MipmapControl mips, int usage) { rs.validate(); - if (type.getID() == 0) { + if (type.getID(rs) == 0) { throw new RSInvalidStateException("Bad Type"); } - int id = rs.nAllocationCreateTyped(type.getID(), mips.mID, usage, 0); + int id = rs.nAllocationCreateTyped(type.getID(rs), mips.mID, usage, 0); if (id == 0) { throw new RSRuntimeException("Allocation creation failed."); } @@ -1043,10 +1043,10 @@ public class Allocation extends BaseObj { static public Allocation createTyped(RenderScript rs, Type type, MipmapControl mips, int usage, int pointer) { rs.validate(); - if (type.getID() == 0) { + if (type.getID(rs) == 0) { throw new RSInvalidStateException("Bad Type"); } - int id = rs.nAllocationCreateTyped(type.getID(), mips.mID, usage, pointer); + int id = rs.nAllocationCreateTyped(type.getID(rs), mips.mID, usage, pointer); if (id == 0) { throw new RSRuntimeException("Allocation creation failed."); } @@ -1101,7 +1101,7 @@ public class Allocation extends BaseObj { b.setX(count); Type t = b.create(); - int id = rs.nAllocationCreateTyped(t.getID(), MipmapControl.MIPMAP_NONE.mID, usage, 0); + int id = rs.nAllocationCreateTyped(t.getID(rs), MipmapControl.MIPMAP_NONE.mID, usage, 0); if (id == 0) { throw new RSRuntimeException("Allocation creation failed."); } @@ -1168,7 +1168,7 @@ public class Allocation extends BaseObj { rs.validate(); Type t = typeFromBitmap(rs, b, mips); - int id = rs.nAllocationCreateFromBitmap(t.getID(), mips.mID, b, usage); + int id = rs.nAllocationCreateFromBitmap(t.getID(rs), mips.mID, b, usage); if (id == 0) { throw new RSRuntimeException("Load failed."); } @@ -1186,9 +1186,9 @@ public class Allocation extends BaseObj { throw new RSInvalidStateException("Allocation is not a surface texture."); } - int id = mRS.nAllocationGetSurfaceTextureID(getID()); + int id = mRS.nAllocationGetSurfaceTextureID(getID(mRS)); SurfaceTexture st = new SurfaceTexture(id); - mRS.nAllocationGetSurfaceTextureID2(getID(), st); + mRS.nAllocationGetSurfaceTextureID2(getID(mRS), st); return st; } @@ -1211,7 +1211,7 @@ public class Allocation extends BaseObj { throw new RSInvalidStateException("Allocation is not USAGE_IO_OUTPUT."); } - mRS.nAllocationSetSurface(getID(), sur); + mRS.nAllocationSetSurface(getID(mRS), sur); } /** @@ -1224,7 +1224,7 @@ public class Allocation extends BaseObj { } Surface s = new Surface(st); - mRS.nAllocationSetSurface(getID(), s); + mRS.nAllocationSetSurface(getID(mRS), s); } /** @@ -1283,7 +1283,7 @@ public class Allocation extends BaseObj { tb.setMipmaps(mips == MipmapControl.MIPMAP_FULL); Type t = tb.create(); - int id = rs.nAllocationCubeCreateFromBitmap(t.getID(), mips.mID, b, usage); + int id = rs.nAllocationCubeCreateFromBitmap(t.getID(rs), mips.mID, b, usage); if(id == 0) { throw new RSRuntimeException("Load failed for bitmap " + b + " element " + e); } diff --git a/graphics/java/android/renderscript/AllocationAdapter.java b/graphics/java/android/renderscript/AllocationAdapter.java index d38f2df..85d86e5 100644 --- a/graphics/java/android/renderscript/AllocationAdapter.java +++ b/graphics/java/android/renderscript/AllocationAdapter.java @@ -30,7 +30,7 @@ public class AllocationAdapter extends Allocation { mAdaptedAllocation = alloc; } - int getID() { + int getID(RenderScript rs) { throw new RSInvalidStateException( "This operation is not supported with adapters at this time."); } diff --git a/graphics/java/android/renderscript/BaseObj.java b/graphics/java/android/renderscript/BaseObj.java index 2e55c48..f464f9b 100644 --- a/graphics/java/android/renderscript/BaseObj.java +++ b/graphics/java/android/renderscript/BaseObj.java @@ -43,16 +43,22 @@ public class BaseObj { * Lookup the native object ID for this object. Primarily used by the * generated reflected code. * + * @param rs Context to verify against internal context for + * match. * * @return int */ - int getID() { + int getID(RenderScript rs) { + mRS.validate(); if (mDestroyed) { throw new RSInvalidStateException("using a destroyed object."); } if (mID == 0) { throw new RSRuntimeException("Internal error: Object id 0."); } + if ((rs != null) && (rs != mRS)) { + throw new RSInvalidStateException("using object with mismatched context."); + } return mID; } @@ -138,7 +144,7 @@ public class BaseObj { */ void updateFromNative() { mRS.validate(); - mName = mRS.nGetName(getID()); + mName = mRS.nGetName(getID(mRS)); } /** diff --git a/graphics/java/android/renderscript/Element.java b/graphics/java/android/renderscript/Element.java index 3d4951f..d75c951 100644 --- a/graphics/java/android/renderscript/Element.java +++ b/graphics/java/android/renderscript/Element.java @@ -778,7 +778,7 @@ public class Element extends BaseObj { // we will pack mType; mKind; mNormalized; mVectorSize; NumSubElements int[] dataBuffer = new int[5]; - mRS.nElementGetNativeData(getID(), dataBuffer); + mRS.nElementGetNativeData(getID(mRS), dataBuffer); mNormalized = dataBuffer[2] == 1 ? true : false; mVectorSize = dataBuffer[3]; @@ -803,7 +803,7 @@ public class Element extends BaseObj { mOffsetInBytes = new int[numSubElements]; int[] subElementIds = new int[numSubElements]; - mRS.nElementGetSubElements(getID(), subElementIds, mElementNames, mArraySizes); + mRS.nElementGetSubElements(getID(mRS), subElementIds, mElementNames, mArraySizes); for(int i = 0; i < numSubElements; i ++) { mElements[i] = new Element(subElementIds[i], mRS); mElements[i].updateFromNative(); @@ -1062,7 +1062,7 @@ public class Element extends BaseObj { int[] ids = new int[ein.length]; for (int ct = 0; ct < ein.length; ct++ ) { - ids[ct] = ein[ct].getID(); + ids[ct] = ein[ct].getID(mRS); } int id = mRS.nElementCreate2(ids, sin, asin); return new Element(id, mRS, ein, sin, asin); diff --git a/graphics/java/android/renderscript/FieldPacker.java b/graphics/java/android/renderscript/FieldPacker.java index 2739a4b8..a215a57 100644 --- a/graphics/java/android/renderscript/FieldPacker.java +++ b/graphics/java/android/renderscript/FieldPacker.java @@ -143,7 +143,7 @@ public class FieldPacker { public void addObj(BaseObj obj) { if (obj != null) { - addI32(obj.getID()); + addI32(obj.getID(null)); } else { addI32(0); } diff --git a/graphics/java/android/renderscript/FileA3D.java b/graphics/java/android/renderscript/FileA3D.java index b5419a7..6179317 100644 --- a/graphics/java/android/renderscript/FileA3D.java +++ b/graphics/java/android/renderscript/FileA3D.java @@ -165,7 +165,7 @@ public class FileA3D extends BaseObj { } private void initEntries() { - int numFileEntries = mRS.nFileA3DGetNumIndexEntries(getID()); + int numFileEntries = mRS.nFileA3DGetNumIndexEntries(getID(mRS)); if(numFileEntries <= 0) { return; } @@ -174,10 +174,10 @@ public class FileA3D extends BaseObj { int[] ids = new int[numFileEntries]; String[] names = new String[numFileEntries]; - mRS.nFileA3DGetIndexEntries(getID(), numFileEntries, ids, names); + mRS.nFileA3DGetIndexEntries(getID(mRS), numFileEntries, ids, names); for(int i = 0; i < numFileEntries; i ++) { - mFileEntries[i] = new IndexEntry(mRS, i, getID(), names[i], EntryType.toEntryType(ids[i])); + mFileEntries[i] = new IndexEntry(mRS, i, getID(mRS), names[i], EntryType.toEntryType(ids[i])); } } diff --git a/graphics/java/android/renderscript/Mesh.java b/graphics/java/android/renderscript/Mesh.java index f641117..ffbb41d 100644 --- a/graphics/java/android/renderscript/Mesh.java +++ b/graphics/java/android/renderscript/Mesh.java @@ -137,15 +137,15 @@ public class Mesh extends BaseObj { @Override void updateFromNative() { super.updateFromNative(); - int vtxCount = mRS.nMeshGetVertexBufferCount(getID()); - int idxCount = mRS.nMeshGetIndexCount(getID()); + int vtxCount = mRS.nMeshGetVertexBufferCount(getID(mRS)); + int idxCount = mRS.nMeshGetIndexCount(getID(mRS)); int[] vtxIDs = new int[vtxCount]; int[] idxIDs = new int[idxCount]; int[] primitives = new int[idxCount]; - mRS.nMeshGetVertices(getID(), vtxIDs, vtxCount); - mRS.nMeshGetIndices(getID(), idxIDs, primitives, idxCount); + mRS.nMeshGetVertices(getID(mRS), vtxIDs, vtxCount); + mRS.nMeshGetIndices(getID(mRS), idxIDs, primitives, idxCount); mVertexBuffers = new Allocation[vtxCount]; mIndexBuffers = new Allocation[idxCount]; @@ -343,7 +343,7 @@ public class Mesh extends BaseObj { alloc = Allocation.createSized(mRS, entry.e, entry.size, mUsage); } vertexBuffers[ct] = alloc; - vtx[ct] = alloc.getID(); + vtx[ct] = alloc.getID(mRS); } for(int ct = 0; ct < mIndexTypes.size(); ct ++) { @@ -354,7 +354,7 @@ public class Mesh extends BaseObj { } else if(entry.e != null) { alloc = Allocation.createSized(mRS, entry.e, entry.size, mUsage); } - int allocID = (alloc == null) ? 0 : alloc.getID(); + int allocID = (alloc == null) ? 0 : alloc.getID(mRS); indexBuffers[ct] = alloc; primitives[ct] = entry.prim; @@ -483,12 +483,12 @@ public class Mesh extends BaseObj { for(int ct = 0; ct < mVertexTypeCount; ct ++) { Entry entry = mVertexTypes[ct]; vertexBuffers[ct] = entry.a; - vtx[ct] = entry.a.getID(); + vtx[ct] = entry.a.getID(mRS); } for(int ct = 0; ct < mIndexTypes.size(); ct ++) { Entry entry = (Entry)mIndexTypes.elementAt(ct); - int allocID = (entry.a == null) ? 0 : entry.a.getID(); + int allocID = (entry.a == null) ? 0 : entry.a.getID(mRS); indexBuffers[ct] = entry.a; primitives[ct] = entry.prim; diff --git a/graphics/java/android/renderscript/Path.java b/graphics/java/android/renderscript/Path.java index 83ae150..9c4d41b 100644 --- a/graphics/java/android/renderscript/Path.java +++ b/graphics/java/android/renderscript/Path.java @@ -67,7 +67,7 @@ public class Path extends BaseObj { public static Path createStaticPath(RenderScript rs, Primitive p, float quality, Allocation vtx) { - int id = rs.nPathCreate(p.mID, false, vtx.getID(), 0, quality); + int id = rs.nPathCreate(p.mID, false, vtx.getID(rs), 0, quality); Path newPath = new Path(id, rs, p, null, null, quality); return newPath; } diff --git a/graphics/java/android/renderscript/Program.java b/graphics/java/android/renderscript/Program.java index 4d60ac8..104d1cd 100644 --- a/graphics/java/android/renderscript/Program.java +++ b/graphics/java/android/renderscript/Program.java @@ -134,11 +134,11 @@ public class Program extends BaseObj { throw new IllegalArgumentException("Slot ID out of range."); } if (a != null && - a.getType().getID() != mConstants[slot].getID()) { + a.getType().getID(mRS) != mConstants[slot].getID(mRS)) { throw new IllegalArgumentException("Allocation type does not match slot type."); } - int id = a != null ? a.getID() : 0; - mRS.nProgramBindConstants(getID(), slot, id); + int id = a != null ? a.getID(mRS) : 0; + mRS.nProgramBindConstants(getID(mRS), slot, id); } /** @@ -159,8 +159,8 @@ public class Program extends BaseObj { throw new IllegalArgumentException("Cannot bind cubemap to 2d texture slot"); } - int id = va != null ? va.getID() : 0; - mRS.nProgramBindTexture(getID(), slot, id); + int id = va != null ? va.getID(mRS) : 0; + mRS.nProgramBindTexture(getID(mRS), slot, id); } /** @@ -179,8 +179,8 @@ public class Program extends BaseObj { throw new IllegalArgumentException("Slot ID out of range."); } - int id = vs != null ? vs.getID() : 0; - mRS.nProgramBindSampler(getID(), slot, id); + int id = vs != null ? vs.getID(mRS) : 0; + mRS.nProgramBindSampler(getID(mRS), slot, id); } diff --git a/graphics/java/android/renderscript/ProgramFragment.java b/graphics/java/android/renderscript/ProgramFragment.java index ebc15e5..fa6e2d4 100644 --- a/graphics/java/android/renderscript/ProgramFragment.java +++ b/graphics/java/android/renderscript/ProgramFragment.java @@ -64,15 +64,15 @@ public class ProgramFragment extends Program { for (int i=0; i < mInputCount; i++) { tmp[idx++] = ProgramParam.INPUT.mID; - tmp[idx++] = mInputs[i].getID(); + tmp[idx++] = mInputs[i].getID(mRS); } for (int i=0; i < mOutputCount; i++) { tmp[idx++] = ProgramParam.OUTPUT.mID; - tmp[idx++] = mOutputs[i].getID(); + tmp[idx++] = mOutputs[i].getID(mRS); } for (int i=0; i < mConstantCount; i++) { tmp[idx++] = ProgramParam.CONSTANT.mID; - tmp[idx++] = mConstants[i].getID(); + tmp[idx++] = mConstants[i].getID(mRS); } for (int i=0; i < mTextureCount; i++) { tmp[idx++] = ProgramParam.TEXTURE_TYPE.mID; diff --git a/graphics/java/android/renderscript/ProgramFragmentFixedFunction.java b/graphics/java/android/renderscript/ProgramFragmentFixedFunction.java index cd31db3..14f10f1 100644 --- a/graphics/java/android/renderscript/ProgramFragmentFixedFunction.java +++ b/graphics/java/android/renderscript/ProgramFragmentFixedFunction.java @@ -52,15 +52,15 @@ public class ProgramFragmentFixedFunction extends ProgramFragment { for (int i=0; i < mInputCount; i++) { tmp[idx++] = ProgramParam.INPUT.mID; - tmp[idx++] = mInputs[i].getID(); + tmp[idx++] = mInputs[i].getID(mRS); } for (int i=0; i < mOutputCount; i++) { tmp[idx++] = ProgramParam.OUTPUT.mID; - tmp[idx++] = mOutputs[i].getID(); + tmp[idx++] = mOutputs[i].getID(mRS); } for (int i=0; i < mConstantCount; i++) { tmp[idx++] = ProgramParam.CONSTANT.mID; - tmp[idx++] = mConstants[i].getID(); + tmp[idx++] = mConstants[i].getID(mRS); } for (int i=0; i < mTextureCount; i++) { tmp[idx++] = ProgramParam.TEXTURE_TYPE.mID; diff --git a/graphics/java/android/renderscript/ProgramVertex.java b/graphics/java/android/renderscript/ProgramVertex.java index a6cd15b..32c908e 100644 --- a/graphics/java/android/renderscript/ProgramVertex.java +++ b/graphics/java/android/renderscript/ProgramVertex.java @@ -121,15 +121,15 @@ public class ProgramVertex extends Program { for (int i=0; i < mInputCount; i++) { tmp[idx++] = ProgramParam.INPUT.mID; - tmp[idx++] = mInputs[i].getID(); + tmp[idx++] = mInputs[i].getID(mRS); } for (int i=0; i < mOutputCount; i++) { tmp[idx++] = ProgramParam.OUTPUT.mID; - tmp[idx++] = mOutputs[i].getID(); + tmp[idx++] = mOutputs[i].getID(mRS); } for (int i=0; i < mConstantCount; i++) { tmp[idx++] = ProgramParam.CONSTANT.mID; - tmp[idx++] = mConstants[i].getID(); + tmp[idx++] = mConstants[i].getID(mRS); } for (int i=0; i < mTextureCount; i++) { tmp[idx++] = ProgramParam.TEXTURE_TYPE.mID; diff --git a/graphics/java/android/renderscript/ProgramVertexFixedFunction.java b/graphics/java/android/renderscript/ProgramVertexFixedFunction.java index 9a43943..fac4c3d 100644 --- a/graphics/java/android/renderscript/ProgramVertexFixedFunction.java +++ b/graphics/java/android/renderscript/ProgramVertexFixedFunction.java @@ -75,15 +75,15 @@ public class ProgramVertexFixedFunction extends ProgramVertex { for (int i=0; i < mInputCount; i++) { tmp[idx++] = ProgramParam.INPUT.mID; - tmp[idx++] = mInputs[i].getID(); + tmp[idx++] = mInputs[i].getID(mRS); } for (int i=0; i < mOutputCount; i++) { tmp[idx++] = ProgramParam.OUTPUT.mID; - tmp[idx++] = mOutputs[i].getID(); + tmp[idx++] = mOutputs[i].getID(mRS); } for (int i=0; i < mConstantCount; i++) { tmp[idx++] = ProgramParam.CONSTANT.mID; - tmp[idx++] = mConstants[i].getID(); + tmp[idx++] = mConstants[i].getID(mRS); } for (int i=0; i < mTextureCount; i++) { tmp[idx++] = ProgramParam.TEXTURE_TYPE.mID; diff --git a/graphics/java/android/renderscript/RenderScript.java b/graphics/java/android/renderscript/RenderScript.java index dffd400..03294b5 100644 --- a/graphics/java/android/renderscript/RenderScript.java +++ b/graphics/java/android/renderscript/RenderScript.java @@ -1000,7 +1000,7 @@ public class RenderScript { int safeID(BaseObj o) { if(o != null) { - return o.getID(); + return o.getID(this); } return 0; } diff --git a/graphics/java/android/renderscript/Script.java b/graphics/java/android/renderscript/Script.java index d00c428..4f59ae3 100644 --- a/graphics/java/android/renderscript/Script.java +++ b/graphics/java/android/renderscript/Script.java @@ -26,7 +26,7 @@ public class Script extends BaseObj { * @param slot */ protected void invoke(int slot) { - mRS.nScriptInvoke(getID(), slot); + mRS.nScriptInvoke(getID(mRS), slot); } /** @@ -37,9 +37,9 @@ public class Script extends BaseObj { */ protected void invoke(int slot, FieldPacker v) { if (v != null) { - mRS.nScriptInvokeV(getID(), slot, v.getData()); + mRS.nScriptInvokeV(getID(mRS), slot, v.getData()); } else { - mRS.nScriptInvoke(getID(), slot); + mRS.nScriptInvoke(getID(mRS), slot); } } @@ -58,17 +58,17 @@ public class Script extends BaseObj { } int in_id = 0; if (ain != null) { - in_id = ain.getID(); + in_id = ain.getID(mRS); } int out_id = 0; if (aout != null) { - out_id = aout.getID(); + out_id = aout.getID(mRS); } byte[] params = null; if (v != null) { params = v.getData(); } - mRS.nScriptForEach(getID(), slot, in_id, out_id, params); + mRS.nScriptForEach(getID(mRS), slot, in_id, out_id, params); } @@ -86,9 +86,9 @@ public class Script extends BaseObj { public void bindAllocation(Allocation va, int slot) { mRS.validate(); if (va != null) { - mRS.nScriptBindAllocation(getID(), va.getID(), slot); + mRS.nScriptBindAllocation(getID(mRS), va.getID(mRS), slot); } else { - mRS.nScriptBindAllocation(getID(), 0, slot); + mRS.nScriptBindAllocation(getID(mRS), 0, slot); } } @@ -99,7 +99,7 @@ public class Script extends BaseObj { * @param v */ public void setVar(int index, float v) { - mRS.nScriptSetVarF(getID(), index, v); + mRS.nScriptSetVarF(getID(mRS), index, v); } /** @@ -109,7 +109,7 @@ public class Script extends BaseObj { * @param v */ public void setVar(int index, double v) { - mRS.nScriptSetVarD(getID(), index, v); + mRS.nScriptSetVarD(getID(mRS), index, v); } /** @@ -119,7 +119,7 @@ public class Script extends BaseObj { * @param v */ public void setVar(int index, int v) { - mRS.nScriptSetVarI(getID(), index, v); + mRS.nScriptSetVarI(getID(mRS), index, v); } /** @@ -129,7 +129,7 @@ public class Script extends BaseObj { * @param v */ public void setVar(int index, long v) { - mRS.nScriptSetVarJ(getID(), index, v); + mRS.nScriptSetVarJ(getID(mRS), index, v); } /** @@ -139,7 +139,7 @@ public class Script extends BaseObj { * @param v */ public void setVar(int index, boolean v) { - mRS.nScriptSetVarI(getID(), index, v ? 1 : 0); + mRS.nScriptSetVarI(getID(mRS), index, v ? 1 : 0); } /** @@ -149,7 +149,7 @@ public class Script extends BaseObj { * @param o */ public void setVar(int index, BaseObj o) { - mRS.nScriptSetVarObj(getID(), index, (o == null) ? 0 : o.getID()); + mRS.nScriptSetVarObj(getID(mRS), index, (o == null) ? 0 : o.getID(mRS)); } /** @@ -159,13 +159,13 @@ public class Script extends BaseObj { * @param v */ public void setVar(int index, FieldPacker v) { - mRS.nScriptSetVarV(getID(), index, v.getData()); + mRS.nScriptSetVarV(getID(mRS), index, v.getData()); } public void setTimeZone(String timeZone) { mRS.validate(); try { - mRS.nScriptSetTimeZone(getID(), timeZone.getBytes("UTF-8")); + mRS.nScriptSetTimeZone(getID(mRS), timeZone.getBytes("UTF-8")); } catch (java.io.UnsupportedEncodingException e) { throw new RuntimeException(e); } diff --git a/graphics/java/android/renderscript/Type.java b/graphics/java/android/renderscript/Type.java index 70d1de4..a707df2 100644 --- a/graphics/java/android/renderscript/Type.java +++ b/graphics/java/android/renderscript/Type.java @@ -180,7 +180,7 @@ public class Type extends BaseObj { // We have 6 integer to obtain mDimX; mDimY; mDimZ; // mDimLOD; mDimFaces; mElement; int[] dataBuffer = new int[6]; - mRS.nTypeGetNativeData(getID(), dataBuffer); + mRS.nTypeGetNativeData(getID(mRS), dataBuffer); mDimX = dataBuffer[0]; mDimY = dataBuffer[1]; @@ -280,7 +280,8 @@ public class Type extends BaseObj { } } - int id = mRS.nTypeCreate(mElement.getID(), mDimX, mDimY, mDimZ, mDimMipmaps, mDimFaces); + int id = mRS.nTypeCreate(mElement.getID(mRS), + mDimX, mDimY, mDimZ, mDimMipmaps, mDimFaces); Type t = new Type(id, mRS); t.mElement = mElement; t.mDimX = mDimX; diff --git a/libs/hwui/DisplayListRenderer.cpp b/libs/hwui/DisplayListRenderer.cpp index f37bfd2..9f2bacd 100644 --- a/libs/hwui/DisplayListRenderer.cpp +++ b/libs/hwui/DisplayListRenderer.cpp @@ -112,6 +112,7 @@ void DisplayList::initProperties() { mClipChildren = true; mAlpha = 1; mMultipliedAlpha = 255; + mHasOverlappingRendering = true; mTranslationX = 0; mTranslationY = 0; mRotation = 0; @@ -772,18 +773,23 @@ void DisplayList::setViewProperties(OpenGLRenderer& renderer, uint32_t width, ui } } if (mAlpha < 1 && !mCaching) { - // TODO: should be able to store the size of a DL at record time and not - // have to pass it into this call. In fact, this information might be in the - // location/size info that we store with the new native transform data. - int flags = SkCanvas::kHasAlphaLayer_SaveFlag; - if (mClipChildren) { - flags |= SkCanvas::kClipToLayer_SaveFlag; + if (!mHasOverlappingRendering) { + DISPLAY_LIST_LOGD("%s%s %.2f", indent, "SetAlpha", mAlpha); + renderer.setAlpha(mAlpha); + } else { + // TODO: should be able to store the size of a DL at record time and not + // have to pass it into this call. In fact, this information might be in the + // location/size info that we store with the new native transform data. + int flags = SkCanvas::kHasAlphaLayer_SaveFlag; + if (mClipChildren) { + flags |= SkCanvas::kClipToLayer_SaveFlag; + } + DISPLAY_LIST_LOGD("%s%s %.2f, %.2f, %.2f, %.2f, %d, 0x%x", indent, "SaveLayerAlpha", + (float) 0, (float) 0, (float) mRight - mLeft, (float) mBottom - mTop, + mMultipliedAlpha, flags); + renderer.saveLayerAlpha(0, 0, mRight - mLeft, mBottom - mTop, + mMultipliedAlpha, flags); } - DISPLAY_LIST_LOGD("%s%s %.2f, %.2f, %.2f, %.2f, %d, 0x%x", indent, "SaveLayerAlpha", - (float) 0, (float) 0, (float) mRight - mLeft, (float) mBottom - mTop, - mMultipliedAlpha, flags); - renderer.saveLayerAlpha(0, 0, mRight - mLeft, mBottom - mTop, - mMultipliedAlpha, flags); } if (mClipChildren) { DISPLAY_LIST_LOGD("%s%s %.2f, %.2f, %.2f, %.2f", indent, "ClipRect", 0.0f, 0.0f, diff --git a/libs/hwui/DisplayListRenderer.h b/libs/hwui/DisplayListRenderer.h index 38b0a6d..fe0c94d 100644 --- a/libs/hwui/DisplayListRenderer.h +++ b/libs/hwui/DisplayListRenderer.h @@ -181,6 +181,10 @@ public: } } + void setHasOverlappingRendering(bool hasOverlappingRendering) { + mHasOverlappingRendering = hasOverlappingRendering; + } + void setTranslationX(float translationX) { if (translationX != mTranslationX) { mTranslationX = translationX; @@ -496,6 +500,7 @@ private: bool mClipChildren; float mAlpha; int mMultipliedAlpha; + bool mHasOverlappingRendering; float mTranslationX, mTranslationY; float mRotation, mRotationX, mRotationY; float mScaleX, mScaleY; diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp index 115787c..39d2e39 100644 --- a/libs/hwui/OpenGLRenderer.cpp +++ b/libs/hwui/OpenGLRenderer.cpp @@ -631,6 +631,9 @@ void OpenGLRenderer::composeLayer(sp<Snapshot> current, sp<Snapshot> previous) { const bool fboLayer = current->flags & Snapshot::kFlagIsFboLayer; if (fboLayer) { + // Detach the texture from the FBO + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); + // Unbind current FBO and restore previous one glBindFramebuffer(GL_FRAMEBUFFER, previous->fbo); } @@ -671,11 +674,6 @@ void OpenGLRenderer::composeLayer(sp<Snapshot> current, sp<Snapshot> previous) { // code path // See LayerRenderer::destroyLayer(Layer*) - // Detach the texture from the FBO - glBindFramebuffer(GL_FRAMEBUFFER, current->fbo); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); - glBindFramebuffer(GL_FRAMEBUFFER, previous->fbo); - // Put the FBO name back in the cache, if it doesn't fit, it will be destroyed mCaches.fboCache.put(current->fbo); layer->setFbo(0); @@ -1101,6 +1099,7 @@ void OpenGLRenderer::setupDrawColor(int color) { void OpenGLRenderer::setupDrawColor(int color, int alpha) { mColorA = alpha / 255.0f; + mColorA *= mSnapshot->alpha; // Second divide of a by 255 is an optimization, allowing us to simply multiply // the rgb values by a instead of also dividing by 255 const float a = mColorA / 255.0f; @@ -1329,18 +1328,18 @@ void OpenGLRenderer::setupDrawVertices(GLvoid* vertices) { * are set up for each individual segment. */ void OpenGLRenderer::setupDrawAALine(GLvoid* vertices, GLvoid* widthCoords, - GLvoid* lengthCoords, float boundaryWidthProportion) { + GLvoid* lengthCoords, float boundaryWidthProportion, int& widthSlot, int& lengthSlot) { bool force = mCaches.unbindMeshBuffer(); mCaches.bindPositionVertexPointer(force, mCaches.currentProgram->position, vertices, gAAVertexStride); mCaches.resetTexCoordsVertexPointer(); mCaches.unbindIndicesBuffer(); - int widthSlot = mCaches.currentProgram->getAttrib("vtxWidth"); + widthSlot = mCaches.currentProgram->getAttrib("vtxWidth"); glEnableVertexAttribArray(widthSlot); glVertexAttribPointer(widthSlot, 1, GL_FLOAT, GL_FALSE, gAAVertexStride, widthCoords); - int lengthSlot = mCaches.currentProgram->getAttrib("vtxLength"); + lengthSlot = mCaches.currentProgram->getAttrib("vtxLength"); glEnableVertexAttribArray(lengthSlot); glVertexAttribPointer(lengthSlot, 1, GL_FLOAT, GL_FALSE, gAAVertexStride, lengthCoords); @@ -1349,7 +1348,12 @@ void OpenGLRenderer::setupDrawAALine(GLvoid* vertices, GLvoid* widthCoords, // Setting the inverse value saves computations per-fragment in the shader int inverseBoundaryWidthSlot = mCaches.currentProgram->getUniform("inverseBoundaryWidth"); - glUniform1f(inverseBoundaryWidthSlot, (1 / boundaryWidthProportion)); + glUniform1f(inverseBoundaryWidthSlot, 1.0f / boundaryWidthProportion); +} + +void OpenGLRenderer::finishDrawAALine(const int widthSlot, const int lengthSlot) { + glDisableVertexAttribArray(widthSlot); + glDisableVertexAttribArray(lengthSlot); } void OpenGLRenderer::finishDrawTexture() { @@ -1721,13 +1725,18 @@ void OpenGLRenderer::drawAARect(float left, float top, float right, float bottom float width = right - left; float height = bottom - top; + int widthSlot; + int lengthSlot; + float boundaryWidthProportion = (width != 0) ? (2 * boundarySizeX) / width : 0; float boundaryHeightProportion = (height != 0) ? (2 * boundarySizeY) / height : 0; - setupDrawAALine((void*) aaVertices, widthCoords, lengthCoords, boundaryWidthProportion); + setupDrawAALine((void*) aaVertices, widthCoords, lengthCoords, + boundaryWidthProportion, widthSlot, lengthSlot); + int boundaryLengthSlot = mCaches.currentProgram->getUniform("boundaryLength"); int inverseBoundaryLengthSlot = mCaches.currentProgram->getUniform("inverseBoundaryLength"); glUniform1f(boundaryLengthSlot, boundaryHeightProportion); - glUniform1f(inverseBoundaryLengthSlot, (1 / boundaryHeightProportion)); + glUniform1f(inverseBoundaryLengthSlot, (1.0f / boundaryHeightProportion)); if (!quickReject(left, top, right, bottom)) { AAVertex::set(aaVertices++, left, bottom, 1, 1); @@ -1737,6 +1746,8 @@ void OpenGLRenderer::drawAARect(float left, float top, float right, float bottom dirtyLayer(left, top, right, bottom, *mSnapshot->transform); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); } + + finishDrawAALine(widthSlot, lengthSlot); } /** @@ -1767,11 +1778,14 @@ void OpenGLRenderer::drawLines(float* points, int count, SkPaint* paint) { // A stroke width of 0 has a special meaning in Skia: // it draws a line 1 px wide regardless of current transform bool isHairLine = paint->getStrokeWidth() == 0.0f; + float inverseScaleX = 1.0f; float inverseScaleY = 1.0f; bool scaled = false; + int alpha; SkXfermode::Mode mode; + int generatedVerticesCount = 0; int verticesCount = count; if (count > 4) { @@ -1791,10 +1805,13 @@ void OpenGLRenderer::drawLines(float* points, int count, SkPaint* paint) { float m10 = mat->data[Matrix4::kSkewX]; float m11 = mat->data[Matrix4::kScaleX]; float m12 = mat->data[6]; + float scaleX = sqrtf(m00 * m00 + m01 * m01); float scaleY = sqrtf(m10 * m10 + m11 * m11); + inverseScaleX = (scaleX != 0) ? (inverseScaleX / scaleX) : 0; inverseScaleY = (scaleY != 0) ? (inverseScaleY / scaleY) : 0; + if (inverseScaleX != 1.0f || inverseScaleY != 1.0f) { scaled = true; } @@ -1824,10 +1841,16 @@ void OpenGLRenderer::drawLines(float* points, int count, SkPaint* paint) { // Expand boundary to enable AA calculations on the quad border halfStrokeWidth += .5f; } + + int widthSlot; + int lengthSlot; + Vertex lines[verticesCount]; Vertex* vertices = &lines[0]; + AAVertex wLines[verticesCount]; AAVertex* aaVertices = &wLines[0]; + if (CC_UNLIKELY(!isAA)) { setupDrawVertices(vertices); } else { @@ -1839,7 +1862,8 @@ void OpenGLRenderer::drawLines(float* points, int count, SkPaint* paint) { // We will need to calculate the actual width proportion on each segment for // scaled non-hairlines, since the boundary proportion may differ per-axis when scaled. float boundaryWidthProportion = 1 / (2 * halfStrokeWidth); - setupDrawAALine((void*) aaVertices, widthCoords, lengthCoords, boundaryWidthProportion); + setupDrawAALine((void*) aaVertices, widthCoords, lengthCoords, + boundaryWidthProportion, widthSlot, lengthSlot); } AAVertex* prevAAVertex = NULL; @@ -1849,10 +1873,12 @@ void OpenGLRenderer::drawLines(float* points, int count, SkPaint* paint) { int inverseBoundaryLengthSlot = -1; int boundaryWidthSlot = -1; int inverseBoundaryWidthSlot = -1; + for (int i = 0; i < count; i += 4) { // a = start point, b = end point vec2 a(points[i], points[i + 1]); vec2 b(points[i + 2], points[i + 3]); + float length = 0; float boundaryLengthProportion = 0; float boundaryWidthProportion = 0; @@ -1869,6 +1895,7 @@ void OpenGLRenderer::drawLines(float* points, int count, SkPaint* paint) { } n *= wideningFactor; } + if (scaled) { n.x *= inverseScaleX; n.y *= inverseScaleY; @@ -1879,11 +1906,13 @@ void OpenGLRenderer::drawLines(float* points, int count, SkPaint* paint) { extendedN /= 2; extendedN.x *= inverseScaleX; extendedN.y *= inverseScaleY; + float extendedNLength = extendedN.length(); // We need to set this value on the shader prior to drawing boundaryWidthProportion = extendedNLength / (halfStrokeWidth + extendedNLength); n += extendedN; } + float x = n.x; n.x = -n.y; n.y = x; @@ -1893,6 +1922,7 @@ void OpenGLRenderer::drawLines(float* points, int count, SkPaint* paint) { vec2 abVector = (b - a); length = abVector.length(); abVector.normalize(); + if (scaled) { abVector.x *= inverseScaleX; abVector.y *= inverseScaleY; @@ -1901,6 +1931,7 @@ void OpenGLRenderer::drawLines(float* points, int count, SkPaint* paint) { } else { boundaryLengthProportion = .5 / (length + 1); } + abVector /= 2; a -= abVector; b += abVector; @@ -1929,10 +1960,12 @@ void OpenGLRenderer::drawLines(float* points, int count, SkPaint* paint) { Vertex::set(vertices++, p1.x, p1.y); generatedVerticesCount += 2; } + Vertex::set(vertices++, p1.x, p1.y); Vertex::set(vertices++, p2.x, p2.y); Vertex::set(vertices++, p4.x, p4.y); Vertex::set(vertices++, p3.x, p3.y); + prevVertex = vertices - 1; generatedVerticesCount += 4; } else { @@ -1945,14 +1978,17 @@ void OpenGLRenderer::drawLines(float* points, int count, SkPaint* paint) { inverseBoundaryWidthSlot = mCaches.currentProgram->getUniform("inverseBoundaryWidth"); } + glUniform1f(boundaryWidthSlot, boundaryWidthProportion); glUniform1f(inverseBoundaryWidthSlot, (1 / boundaryWidthProportion)); } + if (boundaryLengthSlot < 0) { boundaryLengthSlot = mCaches.currentProgram->getUniform("boundaryLength"); inverseBoundaryLengthSlot = mCaches.currentProgram->getUniform("inverseBoundaryLength"); } + glUniform1f(boundaryLengthSlot, boundaryLengthProportion); glUniform1f(inverseBoundaryLengthSlot, (1 / boundaryLengthProportion)); @@ -1966,21 +2002,29 @@ void OpenGLRenderer::drawLines(float* points, int count, SkPaint* paint) { AAVertex::set(aaVertices++, p4.x, p4.y, 1, 1); generatedVerticesCount += 2; } + AAVertex::set(aaVertices++, p4.x, p4.y, 1, 1); AAVertex::set(aaVertices++, p1.x, p1.y, 1, 0); AAVertex::set(aaVertices++, p3.x, p3.y, 0, 1); AAVertex::set(aaVertices++, p2.x, p2.y, 0, 0); + prevAAVertex = aaVertices - 1; generatedVerticesCount += 4; } + dirtyLayer(a.x == b.x ? left - 1 : left, a.y == b.y ? top - 1 : top, a.x == b.x ? right: right, a.y == b.y ? bottom: bottom, *mSnapshot->transform); } } + if (generatedVerticesCount > 0) { glDrawArrays(GL_TRIANGLE_STRIP, 0, generatedVerticesCount); } + + if (isAA) { + finishDrawAALine(widthSlot, lengthSlot); + } } void OpenGLRenderer::drawPoints(float* points, int count, SkPaint* paint) { @@ -2026,10 +2070,12 @@ void OpenGLRenderer::drawPoints(float* points, int count, SkPaint* paint) { for (int i = 0; i < count; i += 2) { TextureVertex::set(vertex++, points[i], points[i + 1], 0.0f, 0.0f); generatedVerticesCount++; + float left = points[i] - halfWidth; float right = points[i] + halfWidth; float top = points[i + 1] - halfWidth; float bottom = points [i + 1] + halfWidth; + dirtyLayer(left, top, right, bottom, *mSnapshot->transform); } @@ -2800,6 +2846,7 @@ void OpenGLRenderer::getAlphaAndMode(SkPaint* paint, int* alpha, SkXfermode::Mod *mode = SkXfermode::kSrcOver_Mode; *alpha = 255; } + *alpha *= mSnapshot->alpha; } SkXfermode::Mode OpenGLRenderer::getXfermode(SkXfermode* mode) { diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h index b651904..b52d2b0 100644 --- a/libs/hwui/OpenGLRenderer.h +++ b/libs/hwui/OpenGLRenderer.h @@ -85,6 +85,10 @@ public: virtual int saveLayerAlpha(float left, float top, float right, float bottom, int alpha, int flags); + virtual void setAlpha(float alpha) { + mSnapshot->alpha = alpha; + } + virtual void translate(float dx, float dy); virtual void rotate(float degrees); virtual void scale(float sx, float sy); @@ -549,7 +553,8 @@ private: void setupDrawMeshIndices(GLvoid* vertices, GLvoid* texCoords); void setupDrawVertices(GLvoid* vertices); void setupDrawAALine(GLvoid* vertices, GLvoid* distanceCoords, GLvoid* lengthCoords, - float strokeWidth); + float strokeWidth, int& widthSlot, int& lengthSlot); + void finishDrawAALine(const int widthSlot, const int lengthSlot); void finishDrawTexture(); void accountForClear(SkXfermode::Mode mode); diff --git a/libs/hwui/Snapshot.cpp b/libs/hwui/Snapshot.cpp index de2c674..5d5961a 100644 --- a/libs/hwui/Snapshot.cpp +++ b/libs/hwui/Snapshot.cpp @@ -26,7 +26,7 @@ namespace uirenderer { /////////////////////////////////////////////////////////////////////////////// Snapshot::Snapshot(): flags(0), previous(NULL), layer(NULL), fbo(0), - invisible(false), empty(false) { + invisible(false), empty(false), alpha(1.0f) { transform = &mTransformRoot; clipRect = &mClipRectRoot; @@ -41,7 +41,7 @@ Snapshot::Snapshot(): flags(0), previous(NULL), layer(NULL), fbo(0), Snapshot::Snapshot(const sp<Snapshot>& s, int saveFlags): flags(0), previous(s), layer(NULL), fbo(s->fbo), invisible(s->invisible), empty(false), - viewport(s->viewport), height(s->height) { + viewport(s->viewport), height(s->height), alpha(s->alpha) { clipRegion = NULL; diff --git a/libs/hwui/Snapshot.h b/libs/hwui/Snapshot.h index b2bc879..30b03fc 100644 --- a/libs/hwui/Snapshot.h +++ b/libs/hwui/Snapshot.h @@ -208,6 +208,17 @@ public: */ Region* region; + /** + * Current alpha value. This value is 1 by default, but may be set by a DisplayList which + * has translucent rendering in a non-overlapping View. This value will be used by + * the renderer to set the alpha in the current color being used for ensuing drawing + * operations. The value is inherited by child snapshots because the same value should + * be applied to descendents of the current DisplayList (for example, a TextView contains + * the base alpha value which should be applied to the child DisplayLists used for drawing + * the actual text). + */ + float alpha; + private: void ensureClipRegion(); void copyClipRectFromRegion(); diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java index c66a03f..f59848f 100644 --- a/media/java/android/media/AudioService.java +++ b/media/java/android/media/AudioService.java @@ -126,6 +126,7 @@ public class AudioService extends IAudioService.Stub { private static final int MSG_RCDISPLAY_CLEAR = 13; private static final int MSG_RCDISPLAY_UPDATE = 14; private static final int MSG_SET_ALL_VOLUMES = 15; + private static final int MSG_PERSIST_MASTER_VOLUME_MUTE = 16; // flags for MSG_PERSIST_VOLUME indicating if current and/or last audible volume should be @@ -501,6 +502,10 @@ public class AudioService extends IAudioService.Stub { System.MUTE_STREAMS_AFFECTED, ((1 << AudioSystem.STREAM_MUSIC)|(1 << AudioSystem.STREAM_RING)|(1 << AudioSystem.STREAM_SYSTEM))); + boolean masterMute = System.getInt(cr, System.VOLUME_MASTER_MUTE, 0) == 1; + AudioSystem.setMasterMute(masterMute); + broadcastMasterMuteStatus(masterMute); + // Each stream will read its own persisted settings // Broadcast the sticky intent @@ -740,9 +745,14 @@ public class AudioService extends IAudioService.Stub { // UI update and Broadcast Intent private void sendMasterMuteUpdate(boolean muted, int flags) { mVolumePanel.postMasterMuteChanged(flags); + broadcastMasterMuteStatus(muted); + } + private void broadcastMasterMuteStatus(boolean muted) { Intent intent = new Intent(AudioManager.MASTER_MUTE_CHANGED_ACTION); intent.putExtra(AudioManager.EXTRA_MASTER_VOLUME_MUTED, muted); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT + | Intent.FLAG_RECEIVER_REPLACE_PENDING); long origCallerIdentityToken = Binder.clearCallingIdentity(); mContext.sendStickyBroadcast(intent); Binder.restoreCallingIdentity(origCallerIdentityToken); @@ -820,6 +830,9 @@ public class AudioService extends IAudioService.Stub { public void setMasterMute(boolean state, IBinder cb) { if (state != AudioSystem.getMasterMute()) { AudioSystem.setMasterMute(state); + // Post a persist master volume msg + sendMsg(mAudioHandler, MSG_PERSIST_MASTER_VOLUME_MUTE, SENDMSG_REPLACE, state ? 1 + : 0, 0, null, PERSIST_DELAY); sendMasterMuteUpdate(state, AudioManager.FLAG_SHOW_UI); } } @@ -2551,6 +2564,11 @@ public class AudioService extends IAudioService.Stub { (float)msg.arg1 / (float)1000.0); break; + case MSG_PERSIST_MASTER_VOLUME_MUTE: + Settings.System.putInt(mContentResolver, Settings.System.VOLUME_MASTER_MUTE, + msg.arg1); + break; + case MSG_PERSIST_RINGER_MODE: // note that the value persisted is the current ringer mode, not the // value of ringer mode as of the time the request was made to persist @@ -3038,11 +3056,6 @@ public class AudioService extends IAudioService.Stub { adapter.getProfileProxy(mContext, mBluetoothProfileServiceListener, BluetoothProfile.A2DP); } - - if (mUseMasterVolume) { - // Send sticky broadcast for initial master mute state - sendMasterMuteUpdate(false, 0); - } } else if (action.equals(Intent.ACTION_PACKAGE_REMOVED)) { if (!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) { // a package is being removed, not replaced diff --git a/media/java/android/media/Crypto.java b/media/java/android/media/Crypto.java new file mode 100644 index 0000000..43e34fb --- /dev/null +++ b/media/java/android/media/Crypto.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2012 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.media; + +/** + * Crypto class can be used in conjunction with MediaCodec to decode + * encrypted media data. + * @hide +*/ +public final class Crypto { + public static final native boolean isCryptoSchemeSupported(byte[] uuid); + + public Crypto(byte[] uuid, byte[] initData) { + native_setup(uuid, initData); + } + + public final native boolean requiresSecureDecoderComponent(String mime); + + @Override + protected void finalize() { + native_finalize(); + } + + public native final void release(); + private static native final void native_init(); + private native final void native_setup(byte[] uuid, byte[] initData); + private native final void native_finalize(); + + static { + System.loadLibrary("media_jni"); + native_init(); + } + + private int mNativeContext; +} diff --git a/media/java/android/media/MediaCodec.java b/media/java/android/media/MediaCodec.java index 7629d60..66cea9d4 100644 --- a/media/java/android/media/MediaCodec.java +++ b/media/java/android/media/MediaCodec.java @@ -16,6 +16,7 @@ package android.media; +import android.media.Crypto; import android.view.Surface; import java.nio.ByteBuffer; import java.util.Map; @@ -25,8 +26,7 @@ import java.util.Map; * encoder/decoder components. * @hide */ -public class MediaCodec -{ +final public class MediaCodec { /** Per buffer metadata includes an offset and size specifying the range of valid data in the associated codec buffer. */ @@ -113,11 +113,14 @@ public class MediaCodec * * @param surface Specify a surface on which to render the output of this * decoder. + * @param crypto Specify a crypto object to facilitate secure decryption + * of the media data. * @param flags Specify {@link #CONFIGURE_FLAG_ENCODE} to configure the * component as an encoder. */ public void configure( - Map<String, Object> format, Surface surface, int flags) { + Map<String, Object> format, + Surface surface, Crypto crypto, int flags) { String[] keys = null; Object[] values = null; @@ -133,11 +136,12 @@ public class MediaCodec } } - native_configure(keys, values, surface, flags); + native_configure(keys, values, surface, crypto, flags); } private native final void native_configure( - String[] keys, Object[] values, Surface surface, int flags); + String[] keys, Object[] values, + Surface surface, Crypto crypto, int flags); /** After successfully configuring the component, call start. On return * you can query the component for its input/output buffers. diff --git a/media/java/android/media/MediaExtractor.java b/media/java/android/media/MediaExtractor.java index 9ea3d0e..9c3b6a7 100644 --- a/media/java/android/media/MediaExtractor.java +++ b/media/java/android/media/MediaExtractor.java @@ -23,8 +23,7 @@ import java.util.Map; * MediaExtractor * @hide */ -public class MediaExtractor -{ +final public class MediaExtractor { public MediaExtractor(String path) { native_setup(path); } diff --git a/media/java/android/media/MediaScanner.java b/media/java/android/media/MediaScanner.java index 2f4ed89..26089ad 100644 --- a/media/java/android/media/MediaScanner.java +++ b/media/java/android/media/MediaScanner.java @@ -609,6 +609,10 @@ public class MediaScanner mCompilation = parseSubstring(value, 0, 0); } else if (name.equalsIgnoreCase("isdrm")) { mIsDrm = (parseSubstring(value, 0, 0) == 1); + } else if (name.equalsIgnoreCase("width")) { + mWidth = parseSubstring(value, 0, 0); + } else if (name.equalsIgnoreCase("height")) { + mHeight = parseSubstring(value, 0, 0); } else { //Log.v(TAG, "unknown tag: " + name + " (" + mProcessGenres + ")"); } @@ -734,9 +738,11 @@ public class MediaScanner map.put(MediaStore.MediaColumns.MIME_TYPE, mMimeType); map.put(MediaStore.MediaColumns.IS_DRM, mIsDrm); + String resolution = null; if (mWidth > 0 && mHeight > 0) { map.put(MediaStore.MediaColumns.WIDTH, mWidth); map.put(MediaStore.MediaColumns.HEIGHT, mHeight); + resolution = mWidth + "x" + mHeight; } if (!mNoMedia) { @@ -746,7 +752,9 @@ public class MediaScanner map.put(Video.Media.ALBUM, (mAlbum != null && mAlbum.length() > 0 ? mAlbum : MediaStore.UNKNOWN_STRING)); map.put(Video.Media.DURATION, mDuration); - // FIXME - add RESOLUTION + if (resolution != null) { + map.put(Video.Media.RESOLUTION, resolution); + } } else if (MediaFile.isImageFileType(mFileType)) { // FIXME - add DESCRIPTION } else if (MediaFile.isAudioFileType(mFileType)) { diff --git a/media/jni/Android.mk b/media/jni/Android.mk index dd1e505..a3361d4 100644 --- a/media/jni/Android.mk +++ b/media/jni/Android.mk @@ -2,6 +2,7 @@ LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) LOCAL_SRC_FILES:= \ + android_media_Crypto.cpp \ android_media_MediaCodec.cpp \ android_media_MediaCodecList.cpp \ android_media_MediaExtractor.cpp \ diff --git a/media/jni/android_media_Crypto.cpp b/media/jni/android_media_Crypto.cpp new file mode 100644 index 0000000..e1a60a1 --- /dev/null +++ b/media/jni/android_media_Crypto.cpp @@ -0,0 +1,291 @@ +/* + * Copyright 2012, 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. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "Crypto-JNI" +#include <utils/Log.h> + +#include "android_media_Crypto.h" + +#include "android_runtime/AndroidRuntime.h" +#include "jni.h" +#include "JNIHelp.h" + +#include <binder/IServiceManager.h> +#include <media/ICrypto.h> +#include <media/IMediaPlayerService.h> +#include <media/stagefright/foundation/ADebug.h> + +namespace android { + +struct fields_t { + jfieldID context; +}; + +static fields_t gFields; + +static sp<JCrypto> getCrypto(JNIEnv *env, jobject thiz) { + return (JCrypto *)env->GetIntField(thiz, gFields.context); +} + +JCrypto::JCrypto( + JNIEnv *env, jobject thiz, + const uint8_t uuid[16], const void *initData, size_t initSize) { + mObject = env->NewWeakGlobalRef(thiz); + + mCrypto = MakeCrypto(uuid, initData, initSize); +} + +JCrypto::~JCrypto() { + mCrypto.clear(); + + JNIEnv *env = AndroidRuntime::getJNIEnv(); + + env->DeleteWeakGlobalRef(mObject); + mObject = NULL; +} + +// static +sp<ICrypto> JCrypto::MakeCrypto() { + sp<IServiceManager> sm = defaultServiceManager(); + + sp<IBinder> binder = + sm->getService(String16("media.player")); + + sp<IMediaPlayerService> service = + interface_cast<IMediaPlayerService>(binder); + + if (service == NULL) { + return NULL; + } + + sp<ICrypto> crypto = service->makeCrypto(); + + if (crypto == NULL || crypto->initCheck() != OK) { + return NULL; + } + + return crypto; +} + +// static +sp<ICrypto> JCrypto::MakeCrypto( + const uint8_t uuid[16], const void *initData, size_t initSize) { + sp<ICrypto> crypto = MakeCrypto(); + + if (crypto == NULL) { + return NULL; + } + + status_t err = crypto->createPlugin(uuid, initData, initSize); + + if (err != OK) { + return NULL; + } + + return crypto; +} + +bool JCrypto::requiresSecureDecoderComponent(const char *mime) const { + if (mCrypto == NULL) { + return false; + } + + return mCrypto->requiresSecureDecoderComponent(mime); +} + +// static +bool JCrypto::IsCryptoSchemeSupported(const uint8_t uuid[16]) { + sp<ICrypto> crypto = MakeCrypto(); + + if (crypto == NULL) { + return false; + } + + return crypto->isCryptoSchemeSupported(uuid); +} + +status_t JCrypto::initCheck() const { + return mCrypto == NULL ? NO_INIT : OK; +} + +// static +sp<ICrypto> JCrypto::GetCrypto(JNIEnv *env, jobject obj) { + jclass clazz = env->FindClass("android/media/Crypto"); + CHECK(clazz != NULL); + + if (!env->IsInstanceOf(obj, clazz)) { + return NULL; + } + + sp<JCrypto> jcrypto = getCrypto(env, obj); + + if (jcrypto == NULL) { + return NULL; + } + + return jcrypto->mCrypto; +} + +} // namespace android + +using namespace android; + +static sp<JCrypto> setCrypto( + JNIEnv *env, jobject thiz, const sp<JCrypto> &crypto) { + sp<JCrypto> old = (JCrypto *)env->GetIntField(thiz, gFields.context); + if (crypto != NULL) { + crypto->incStrong(thiz); + } + if (old != NULL) { + old->decStrong(thiz); + } + env->SetIntField(thiz, gFields.context, (int)crypto.get()); + + return old; +} + +static void android_media_Crypto_release(JNIEnv *env, jobject thiz) { + setCrypto(env, thiz, NULL); +} + +static void android_media_Crypto_native_init(JNIEnv *env) { + jclass clazz = env->FindClass("android/media/Crypto"); + CHECK(clazz != NULL); + + gFields.context = env->GetFieldID(clazz, "mNativeContext", "I"); + CHECK(gFields.context != NULL); +} + +static void android_media_Crypto_native_setup( + JNIEnv *env, jobject thiz, + jbyteArray uuidObj, jbyteArray initDataObj) { + jsize uuidLength = env->GetArrayLength(uuidObj); + + if (uuidLength != 16) { + jniThrowException( + env, + "java/lang/IllegalArgumentException", + NULL); + return; + } + + jboolean isCopy; + jbyte *uuid = env->GetByteArrayElements(uuidObj, &isCopy); + + jsize initDataLength = env->GetArrayLength(initDataObj); + jbyte *initData = env->GetByteArrayElements(initDataObj, &isCopy); + + sp<JCrypto> crypto = new JCrypto( + env, thiz, (const uint8_t *)uuid, initData, initDataLength); + + status_t err = crypto->initCheck(); + + env->ReleaseByteArrayElements(initDataObj, initData, 0); + initData = NULL; + + env->ReleaseByteArrayElements(uuidObj, uuid, 0); + uuid = NULL; + + if (err != OK) { + jniThrowException( + env, + "java/io/IOException", + "Failed to instantiate crypto object."); + return; + } + + setCrypto(env,thiz, crypto); +} + +static void android_media_Crypto_native_finalize( + JNIEnv *env, jobject thiz) { + android_media_Crypto_release(env, thiz); +} + +static jboolean android_media_Crypto_isCryptoSchemeSupported( + JNIEnv *env, jobject thiz, jbyteArray uuidObj) { + jsize uuidLength = env->GetArrayLength(uuidObj); + + if (uuidLength != 16) { + jniThrowException( + env, + "java/lang/IllegalArgumentException", + NULL); + return false; + } + + jboolean isCopy; + jbyte *uuid = env->GetByteArrayElements(uuidObj, &isCopy); + + bool result = JCrypto::IsCryptoSchemeSupported((const uint8_t *)uuid); + + env->ReleaseByteArrayElements(uuidObj, uuid, 0); + uuid = NULL; + + return result; +} + +static jboolean android_media_Crypto_requiresSecureDecoderComponent( + JNIEnv *env, jobject thiz, jstring mimeObj) { + if (mimeObj == NULL) { + jniThrowException(env, "java/lang/IllegalArgumentException", NULL); + return false; + } + + sp<JCrypto> crypto = getCrypto(env, thiz); + + if (crypto == NULL) { + jniThrowException(env, "java/lang/IllegalArgumentException", NULL); + return false; + } + + const char *mime = env->GetStringUTFChars(mimeObj, NULL); + + if (mime == NULL) { + return false; + } + + bool result = crypto->requiresSecureDecoderComponent(mime); + + env->ReleaseStringUTFChars(mimeObj, mime); + mime = NULL; + + return result; +} + +static JNINativeMethod gMethods[] = { + { "release", "()V", (void *)android_media_Crypto_release }, + { "native_init", "()V", (void *)android_media_Crypto_native_init }, + + { "native_setup", "([B[B)V", + (void *)android_media_Crypto_native_setup }, + + { "native_finalize", "()V", + (void *)android_media_Crypto_native_finalize }, + + { "isCryptoSchemeSupported", "([B)Z", + (void *)android_media_Crypto_isCryptoSchemeSupported }, + + { "requiresSecureDecoderComponent", "(Ljava/lang/String;)Z", + (void *)android_media_Crypto_requiresSecureDecoderComponent }, +}; + +int register_android_media_Crypto(JNIEnv *env) { + return AndroidRuntime::registerNativeMethods(env, + "android/media/Crypto", gMethods, NELEM(gMethods)); +} + diff --git a/media/jni/android_media_Crypto.h b/media/jni/android_media_Crypto.h new file mode 100644 index 0000000..505725e --- /dev/null +++ b/media/jni/android_media_Crypto.h @@ -0,0 +1,59 @@ +/* + * Copyright 2012, 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. + */ + +#ifndef _ANDROID_MEDIA_CRYPTO_H_ +#define _ANDROID_MEDIA_CRYPTO_H_ + +#include "jni.h" + +#include <media/stagefright/foundation/ABase.h> +#include <utils/Errors.h> +#include <utils/RefBase.h> + +namespace android { + +struct ICrypto; + +struct JCrypto : public RefBase { + static bool IsCryptoSchemeSupported(const uint8_t uuid[16]); + + JCrypto(JNIEnv *env, jobject thiz, + const uint8_t uuid[16], const void *initData, size_t initSize); + + status_t initCheck() const; + + bool requiresSecureDecoderComponent(const char *mime) const; + + static sp<ICrypto> GetCrypto(JNIEnv *env, jobject obj); + +protected: + virtual ~JCrypto(); + +private: + jweak mObject; + sp<ICrypto> mCrypto; + + static sp<ICrypto> MakeCrypto(); + + static sp<ICrypto> MakeCrypto( + const uint8_t uuid[16], const void *initData, size_t initSize); + + DISALLOW_EVIL_CONSTRUCTORS(JCrypto); +}; + +} // namespace android + +#endif // _ANDROID_MEDIA_CRYPTO_H_ diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp index 4b7a811..217216a 100644 --- a/media/jni/android_media_MediaCodec.cpp +++ b/media/jni/android_media_MediaCodec.cpp @@ -20,6 +20,7 @@ #include "android_media_MediaCodec.h" +#include "android_media_Crypto.h" #include "android_media_Utils.h" #include "android_runtime/AndroidRuntime.h" #include "android_runtime/android_view_Surface.h" @@ -98,12 +99,13 @@ JMediaCodec::~JMediaCodec() { status_t JMediaCodec::configure( const sp<AMessage> &format, const sp<ISurfaceTexture> &surfaceTexture, + const sp<ICrypto> &crypto, int flags) { sp<SurfaceTextureClient> client; if (surfaceTexture != NULL) { client = new SurfaceTextureClient(surfaceTexture); } - return mCodec->configure(format, client, NULL /* crypto */, flags); + return mCodec->configure(format, client, crypto, flags); } status_t JMediaCodec::start() { @@ -256,6 +258,7 @@ static void android_media_MediaCodec_native_configure( jobject thiz, jobjectArray keys, jobjectArray values, jobject jsurface, + jobject jcrypto, jint flags) { sp<JMediaCodec> codec = getMediaCodec(env, thiz); @@ -286,7 +289,12 @@ static void android_media_MediaCodec_native_configure( } } - err = codec->configure(format, surfaceTexture, flags); + sp<ICrypto> crypto; + if (jcrypto != NULL) { + crypto = JCrypto::GetCrypto(env, jcrypto); + } + + err = codec->configure(format, surfaceTexture, crypto, flags); throwExceptionAsNecessary(env, err); } @@ -513,7 +521,8 @@ static JNINativeMethod gMethods[] = { { "release", "()V", (void *)android_media_MediaCodec_release }, { "native_configure", - "([Ljava/lang/String;[Ljava/lang/Object;Landroid/view/Surface;I)V", + "([Ljava/lang/String;[Ljava/lang/Object;Landroid/view/Surface;" + "Landroid/media/Crypto;I)V", (void *)android_media_MediaCodec_native_configure }, { "start", "()V", (void *)android_media_MediaCodec_start }, diff --git a/media/jni/android_media_MediaCodec.h b/media/jni/android_media_MediaCodec.h index 6b1257d..6bb4071 100644 --- a/media/jni/android_media_MediaCodec.h +++ b/media/jni/android_media_MediaCodec.h @@ -27,6 +27,7 @@ namespace android { struct ALooper; struct AMessage; +struct ICrypto; struct ISurfaceTexture; struct MediaCodec; @@ -40,6 +41,7 @@ struct JMediaCodec : public RefBase { status_t configure( const sp<AMessage> &format, const sp<ISurfaceTexture> &surfaceTexture, + const sp<ICrypto> &crypto, int flags); status_t start(); diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp index 3074bb1..2e74ffd 100644 --- a/media/jni/android_media_MediaPlayer.cpp +++ b/media/jni/android_media_MediaPlayer.cpp @@ -879,6 +879,7 @@ static int register_android_media_MediaPlayer(JNIEnv *env) "android/media/MediaPlayer", gMethods, NELEM(gMethods)); } +extern int register_android_media_Crypto(JNIEnv *env); extern int register_android_media_MediaCodec(JNIEnv *env); extern int register_android_media_MediaExtractor(JNIEnv *env); extern int register_android_media_MediaCodecList(JNIEnv *env); @@ -968,6 +969,11 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) goto bail; } + if (register_android_media_Crypto(env) < 0) { + ALOGE("ERROR: MediaCodec native registration failed"); + goto bail; + } + /* success -- return valid version number */ result = JNI_VERSION_1_4; diff --git a/media/tests/MediaFrameworkTest/AndroidManifest.xml b/media/tests/MediaFrameworkTest/AndroidManifest.xml index dd5e026..b698705 100644 --- a/media/tests/MediaFrameworkTest/AndroidManifest.xml +++ b/media/tests/MediaFrameworkTest/AndroidManifest.xml @@ -4,9 +4,9 @@ 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. @@ -16,7 +16,7 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.mediaframeworktest"> - + <uses-permission android:name="android.permission.RECORD_AUDIO" /> <uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.INTERNET" /> @@ -24,7 +24,7 @@ <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" /> <uses-permission android:name="android.permission.WAKE_LOCK" /> - <application> + <application> <uses-library android:name="android.test.runner" /> <activity android:label="@string/app_name" android:name="MediaFrameworkTest" @@ -34,12 +34,18 @@ <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> - </application> + </application> + + <instrumentation android:name=".CameraStressTestRunner" + android:targetPackage="com.android.mediaframeworktest" + android:label="Camera stress tests InstrumentationRunner"> + </instrumentation> + <instrumentation android:name=".MediaFrameworkTestRunner" android:targetPackage="com.android.mediaframeworktest" android:label="MediaFramework tests InstrumentationRunner"> </instrumentation> - + <instrumentation android:name=".MediaFrameworkPerfTestRunner" android:targetPackage="com.android.mediaframeworktest" android:label="MediaFramework Performance tests InstrumentationRunner"> @@ -49,7 +55,7 @@ android:targetPackage="com.android.mediaframeworktest" android:label="MediaFramework unit tests InstrumentationRunner"> </instrumentation> - + <instrumentation android:name=".MediaRecorderStressTestRunner" android:targetPackage="com.android.mediaframeworktest" android:label="MediaRecorder stress tests InstrumentationRunner"> diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/CameraStressTestRunner.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/CameraStressTestRunner.java new file mode 100644 index 0000000..fa59fa4 --- /dev/null +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/CameraStressTestRunner.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2012 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.mediaframeworktest; + +import android.test.InstrumentationTestRunner; +import android.test.InstrumentationTestSuite; +import com.android.mediaframeworktest.stress.CameraStressTest; + +import junit.framework.TestSuite; + +public class CameraStressTestRunner extends InstrumentationTestRunner { + + @Override + public TestSuite getAllTests() { + TestSuite suite = new InstrumentationTestSuite(this); + suite.addTestSuite(CameraStressTest.class); + return suite; + } + + @Override + public ClassLoader getLoader() { + return CameraStressTestRunner.class.getClassLoader(); + } +} diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/CameraStressTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/CameraStressTest.java new file mode 100644 index 0000000..a9c6119 --- /dev/null +++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/CameraStressTest.java @@ -0,0 +1,280 @@ +/* + * Copyright (C) 2012 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.mediaframeworktest.stress; + +import com.android.mediaframeworktest.MediaFrameworkTest; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FilenameFilter; +import java.io.FileWriter; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.Writer; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; + +import android.hardware.Camera; +import android.hardware.Camera.PictureCallback; +import android.hardware.Camera.ShutterCallback; +import android.os.Environment; +import android.os.Handler; +import android.os.Looper; +import android.test.ActivityInstrumentationTestCase2; +import android.test.suitebuilder.annotation.LargeTest; +import android.util.Log; +import android.view.SurfaceHolder; +import com.android.mediaframeworktest.CameraStressTestRunner; + +import junit.framework.Assert; + +/** + * Junit / Instrumentation test case for the camera zoom api + * + * adb shell am instrument + * -e class com.android.mediaframeworktest.stress.CameraStressTest + * -w com.android.mediaframeworktest/.CameraStressTestRunner + */ +public class CameraStressTest extends ActivityInstrumentationTestCase2<MediaFrameworkTest> { + private String TAG = "CameraStressTest"; + private Camera mCamera; + + private static final int NUMBER_OF_ZOOM_LOOPS = 100; + private static final long WAIT_GENERIC = 3 * 1000; // 3 seconds + private static final long WAIT_TIMEOUT = 10 * 1000; // 10 seconds + private static final long WAIT_ZOOM_ANIMATION = 5 * 1000; // 5 seconds + private static final String CAMERA_STRESS_OUTPUT = + "/sdcard/cameraStressOutput.txt"; + private final CameraErrorCallback mCameraErrorCallback = new CameraErrorCallback(); + + private Thread mLooperThread; + private Handler mHandler; + + public CameraStressTest() { + super("com.android.mediaframeworktest", MediaFrameworkTest.class); + } + + protected void setUp() throws Exception { + final Semaphore sem = new Semaphore(0); + mLooperThread = new Thread() { + @Override + public void run() { + Log.v(TAG, "starting looper"); + Looper.prepare(); + mHandler = new Handler(); + sem.release(); + Looper.loop(); + Log.v(TAG, "quit looper"); + } + }; + mLooperThread.start(); + if (!sem.tryAcquire(WAIT_TIMEOUT, TimeUnit.MILLISECONDS)) { + fail("Failed to start the looper."); + } + getActivity(); + super.setUp(); + } + + @Override + protected void tearDown() throws Exception { + if (mHandler != null) { + mHandler.getLooper().quit(); + mHandler = null; + } + if (mLooperThread != null) { + mLooperThread.join(WAIT_TIMEOUT); + if (mLooperThread.isAlive()) { + fail("Failed to stop the looper."); + } + mLooperThread = null; + } + + super.tearDown(); + } + + private void runOnLooper(final Runnable command) throws InterruptedException { + final Semaphore sem = new Semaphore(0); + mHandler.post(new Runnable() { + @Override + public void run() { + try { + command.run(); + } finally { + sem.release(); + } + } + }); + if (!sem.tryAcquire(WAIT_TIMEOUT, TimeUnit.MILLISECONDS)) { + fail("Failed to run the command on the looper."); + } + } + + private final class CameraErrorCallback implements android.hardware.Camera.ErrorCallback { + public void onError(int error, android.hardware.Camera camera) { + if (error == android.hardware.Camera.CAMERA_ERROR_SERVER_DIED) { + assertTrue("Camera test mediaserver died", false); + } + } + } + + private ShutterCallback shutterCallback = new ShutterCallback() { + @Override + public void onShutter() { + Log.v(TAG, "Shutter"); + } + }; + + private PictureCallback rawCallback = new PictureCallback() { + @Override + public void onPictureTaken(byte[] data, Camera camera) { + Log.v(TAG, "Raw picture taken"); + } + }; + + private PictureCallback jpegCallback = new PictureCallback() { + @Override + public void onPictureTaken(byte[] data, Camera camera) { + FileOutputStream fos = null; + + try { + Log.v(TAG, "JPEG picture taken"); + fos = new FileOutputStream(String.format("%s/zoom-test-%d.jpg", + Environment.getExternalStorageDirectory(), System.currentTimeMillis())); + fos.write(data); + } + catch (FileNotFoundException e) { + Log.v(TAG, "File not found: " + e.toString()); + } + catch (IOException e) { + Log.v(TAG, "Error accessing file: " + e.toString()); + } + finally { + try { + if (fos != null) { + fos.close(); + } + } + catch (IOException e) { + Log.v(TAG, "Error closing file: " + e.toString()); + } + } + } + }; + + // Helper method for cleaning up pics taken during testStressCameraZoom + private void cleanupZoomImages() { + try { + File sdcard = Environment.getExternalStorageDirectory(); + File[] zoomImages = null; + + FilenameFilter filter = new FilenameFilter() { + public boolean accept(File dir, String name) { + return name.startsWith("zoom-test-"); + } + }; + + zoomImages = sdcard.listFiles(filter); + + for (File f : zoomImages) { + f.delete(); + } + } + catch (SecurityException e) { + Log.v(TAG, "Security manager access violation: " + e.toString()); + } + } + + // Test case for stressing the camera zoom in/out feature + @LargeTest + public void testStressCameraZoom() throws Exception { + SurfaceHolder mSurfaceHolder; + mSurfaceHolder = MediaFrameworkTest.mSurfaceView.getHolder(); + File stressOutFile = new File(CAMERA_STRESS_OUTPUT); + Writer output = new BufferedWriter(new FileWriter(stressOutFile, true)); + output.write("Camera zoom stress:\n"); + output.write("Total number of loops: " + NUMBER_OF_ZOOM_LOOPS + "\n"); + + try { + Log.v(TAG, "Start preview"); + output.write("No of loop: "); + + mCamera = Camera.open(); + Camera.Parameters params = mCamera.getParameters(); + mCamera.release(); + + if (!params.isSmoothZoomSupported() && !params.isZoomSupported()) { + Log.v(TAG, "Device camera does not support zoom"); + assertTrue("Camera zoom stress test", false); + } + else { + Log.v(TAG, "Device camera does support zoom"); + + int nextZoomLevel = 0; + + for (int i = 0; i < NUMBER_OF_ZOOM_LOOPS; i++) { + runOnLooper(new Runnable() { + @Override + public void run() { + mCamera = Camera.open(); + } + }); + + mCamera.setErrorCallback(mCameraErrorCallback); + mCamera.setPreviewDisplay(mSurfaceHolder); + mCamera.startPreview(); + Thread.sleep(WAIT_GENERIC); + + params = mCamera.getParameters(); + int currentZoomLevel = params.getZoom(); + + if (nextZoomLevel >= params.getMaxZoom()) { + nextZoomLevel = 0; + } + ++nextZoomLevel; + + if (params.isSmoothZoomSupported()) { + mCamera.startSmoothZoom(nextZoomLevel); + } + else { + params.setZoom(nextZoomLevel); + mCamera.setParameters(params); + } + Log.v(TAG, "Zooming from " + currentZoomLevel + " to " + nextZoomLevel); + + // sleep allows for zoom animation to finish + Thread.sleep(WAIT_ZOOM_ANIMATION); + + // take picture + mCamera.takePicture(shutterCallback, rawCallback, jpegCallback); + Thread.sleep(WAIT_GENERIC); + mCamera.stopPreview(); + mCamera.release(); + output.write(" ," + i); + } + } + + cleanupZoomImages(); + } + catch (Exception e) { + assertTrue("Camera zoom stress test Exception", false); + Log.v(TAG, e.toString()); + } + output.write("\n\n"); + output.close(); + } +} diff --git a/packages/SystemUI/res/layout-land/status_bar_recent_item.xml b/packages/SystemUI/res/layout-land/status_bar_recent_item.xml index aa27861..715ccba 100644 --- a/packages/SystemUI/res/layout-land/status_bar_recent_item.xml +++ b/packages/SystemUI/res/layout-land/status_bar_recent_item.xml @@ -66,7 +66,7 @@ android:layout_height="wrap_content" android:textSize="@dimen/status_bar_recents_app_label_text_size" android:fadingEdge="horizontal" - android:fadingEdgeLength="@dimen/status_bar_recents_fading_edge_length" + android:fadingEdgeLength="@dimen/status_bar_recents_text_fading_edge_length" android:scrollHorizontally="true" android:layout_alignLeft="@id/app_thumbnail" android:layout_below="@id/app_thumbnail" @@ -82,7 +82,7 @@ android:layout_height="wrap_content" android:textSize="@dimen/status_bar_recents_app_description_text_size" android:fadingEdge="horizontal" - android:fadingEdgeLength="@dimen/status_bar_recents_fading_edge_length" + android:fadingEdgeLength="@dimen/status_bar_recents_text_fading_edge_length" android:scrollHorizontally="true" android:layout_alignLeft="@id/app_thumbnail" android:layout_below="@id/app_label" diff --git a/packages/SystemUI/res/layout-land/status_bar_recent_panel.xml b/packages/SystemUI/res/layout-land/status_bar_recent_panel.xml index 180f022..dba1dd9 100644 --- a/packages/SystemUI/res/layout-land/status_bar_recent_panel.xml +++ b/packages/SystemUI/res/layout-land/status_bar_recent_panel.xml @@ -41,7 +41,7 @@ android:stackFromBottom="true" android:fadingEdge="horizontal" android:scrollbars="none" - android:fadingEdgeLength="@dimen/status_bar_recents_fading_edge_length" + android:fadingEdgeLength="@dimen/status_bar_recents_scroll_fading_edge_length" android:layout_gravity="bottom|left" android:orientation="horizontal" android:clipToPadding="false" diff --git a/packages/SystemUI/res/layout-port/status_bar_recent_item.xml b/packages/SystemUI/res/layout-port/status_bar_recent_item.xml index bc389f9..ca72530 100644 --- a/packages/SystemUI/res/layout-port/status_bar_recent_item.xml +++ b/packages/SystemUI/res/layout-port/status_bar_recent_item.xml @@ -35,7 +35,7 @@ android:layout_height="wrap_content" android:textSize="@dimen/status_bar_recents_app_label_text_size" android:fadingEdge="horizontal" - android:fadingEdgeLength="@dimen/status_bar_recents_fading_edge_length" + android:fadingEdgeLength="@dimen/status_bar_recents_text_fading_edge_length" android:scrollHorizontally="true" android:layout_alignParentLeft="true" android:layout_alignTop="@id/app_icon" @@ -89,7 +89,7 @@ android:layout_height="wrap_content" android:textSize="@dimen/status_bar_recents_app_description_text_size" android:fadingEdge="horizontal" - android:fadingEdgeLength="@dimen/status_bar_recents_fading_edge_length" + android:fadingEdgeLength="@dimen/status_bar_recents_text_fading_edge_length" android:scrollHorizontally="true" android:layout_alignParentLeft="true" android:layout_marginLeft="@dimen/status_bar_recents_app_label_left_margin" diff --git a/packages/SystemUI/res/layout-port/status_bar_recent_panel.xml b/packages/SystemUI/res/layout-port/status_bar_recent_panel.xml index e6a077a..73c3da5 100644 --- a/packages/SystemUI/res/layout-port/status_bar_recent_panel.xml +++ b/packages/SystemUI/res/layout-port/status_bar_recent_panel.xml @@ -40,7 +40,7 @@ android:stackFromBottom="true" android:fadingEdge="vertical" android:scrollbars="none" - android:fadingEdgeLength="@*android:dimen/status_bar_height" + android:fadingEdgeLength="@dimen/status_bar_recents_scroll_fading_edge_length" android:layout_gravity="bottom|left" android:clipToPadding="false" android:clipChildren="false"> diff --git a/packages/SystemUI/res/layout-sw600dp/status_bar_recent_item.xml b/packages/SystemUI/res/layout-sw600dp/status_bar_recent_item.xml index 333fcda..7d639ec 100644 --- a/packages/SystemUI/res/layout-sw600dp/status_bar_recent_item.xml +++ b/packages/SystemUI/res/layout-sw600dp/status_bar_recent_item.xml @@ -28,7 +28,7 @@ android:layout_height="wrap_content" android:textSize="@dimen/status_bar_recents_app_label_text_size" android:fadingEdge="horizontal" - android:fadingEdgeLength="@dimen/status_bar_recents_fading_edge_length" + android:fadingEdgeLength="@dimen/status_bar_recents_text_fading_edge_length" android:scrollHorizontally="true" android:layout_alignParentLeft="true" android:layout_alignParentTop="true" @@ -86,7 +86,7 @@ android:layout_height="wrap_content" android:textSize="@dimen/status_bar_recents_app_description_text_size" android:fadingEdge="horizontal" - android:fadingEdgeLength="@dimen/status_bar_recents_fading_edge_length" + android:fadingEdgeLength="@dimen/status_bar_recents_text_fading_edge_length" android:scrollHorizontally="true" android:layout_alignParentLeft="true" android:layout_below="@id/recents_callout_line" diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml index ba1cdfa..8c1ac55 100644 --- a/packages/SystemUI/res/values-sw600dp/dimens.xml +++ b/packages/SystemUI/res/values-sw600dp/dimens.xml @@ -53,8 +53,10 @@ <dimen name="status_bar_recents_app_label_width">97dip</dimen> <!-- Left margin for application label --> <dimen name="status_bar_recents_app_label_left_margin">16dip</dimen> - <!-- Size of fading edge for scroll effect --> - <dimen name="status_bar_recents_fading_edge_length">20dip</dimen> + <!-- Size of fading edge for text --> + <dimen name="status_bar_recents_text_fading_edge_length">20dip</dimen> + <!-- Size of fading edge for scrolling --> + <dimen name="status_bar_recents_scroll_fading_edge_length">10dip</dimen> <!-- Margin between recents container and glow on the right --> <dimen name="status_bar_recents_right_glow_margin">100dip</dimen> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 2c22e3c..4441ca6 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -33,8 +33,10 @@ <dimen name="status_bar_recents_app_label_text_size">14dip</dimen> <!-- Size of application description text --> <dimen name="status_bar_recents_app_description_text_size">14dip</dimen> - <!-- Size of fading edge for scroll effect --> - <dimen name="status_bar_recents_fading_edge_length">20dip</dimen> + <!-- Size of fading edge for text --> + <dimen name="status_bar_recents_text_fading_edge_length">20dip</dimen> + <!-- Size of fading edge for scrolling --> + <dimen name="status_bar_recents_scroll_fading_edge_length">10dip</dimen> <!-- Margin between recents container and glow on the right --> <dimen name="status_bar_recents_right_glow_margin">100dip</dimen> <!-- Amount to offset bottom of notification peek window from top of status bar. --> diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java index ebed522..5d6e315 100644 --- a/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java @@ -68,6 +68,7 @@ public class RecentsPanelView extends FrameLayout implements OnItemClickListener static final boolean DEBUG = TabletStatusBar.DEBUG || PhoneStatusBar.DEBUG || false; private Context mContext; private BaseStatusBar mBar; + private PopupMenu mPopup; private View mRecentsScrim; private View mRecentsNoApps; private ViewGroup mRecentsContainer; @@ -313,6 +314,10 @@ public class RecentsPanelView extends FrameLayout implements OnItemClickListener setFocusable(true); setFocusableInTouchMode(true); requestFocus(); + } else { + if (mPopup != null) { + mPopup.dismiss(); + } } } @@ -325,6 +330,7 @@ public class RecentsPanelView extends FrameLayout implements OnItemClickListener setVisibility(View.GONE); } if (mBar != null) { + // This will indirectly cause show(false, ...) to get called mBar.animateCollapse(); } } @@ -729,10 +735,20 @@ public class RecentsPanelView extends FrameLayout implements OnItemClickListener getContext().startActivity(intent); } + public boolean onInterceptTouchEvent(MotionEvent ev) { + if (mPopup != null) { + return true; + } else { + return super.onInterceptTouchEvent(ev); + } + } + public void handleLongPress( final View selectedView, final View anchorView, final View thumbnailView) { thumbnailView.setSelected(true); - PopupMenu popup = new PopupMenu(mContext, anchorView == null ? selectedView : anchorView); + final PopupMenu popup = + new PopupMenu(mContext, anchorView == null ? selectedView : anchorView); + mPopup = popup; popup.getMenuInflater().inflate(R.menu.recent_popup_menu, popup.getMenu()); popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { public boolean onMenuItemClick(MenuItem item) { @@ -756,6 +772,7 @@ public class RecentsPanelView extends FrameLayout implements OnItemClickListener popup.setOnDismissListener(new PopupMenu.OnDismissListener() { public void onDismiss(PopupMenu menu) { thumbnailView.setSelected(false); + mPopup = null; } }); popup.show(); diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsScrollViewPerformanceHelper.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsScrollViewPerformanceHelper.java index 5529d0c..6bd1826 100644 --- a/packages/SystemUI/src/com/android/systemui/recent/RecentsScrollViewPerformanceHelper.java +++ b/packages/SystemUI/src/com/android/systemui/recent/RecentsScrollViewPerformanceHelper.java @@ -37,15 +37,11 @@ public class RecentsScrollViewPerformanceHelper { public static final boolean OPTIMIZE_SW_RENDERED_RECENTS = true; public static final boolean USE_DARK_FADE_IN_HW_ACCELERATED_MODE = true; private View mScrollView; - private LinearLayout mLinearLayout; private RecentsCallback mCallback; - private boolean mShowBackground = false; private int mFadingEdgeLength; - private Drawable.ConstantState mBackgroundDrawable; private Context mContext; private boolean mIsVertical; - private boolean mFirstTime = true; private boolean mSoftwareRendered = false; private boolean mAttachedToWindow = false; diff --git a/services/java/com/android/server/NsdService.java b/services/java/com/android/server/NsdService.java new file mode 100644 index 0000000..768be7d --- /dev/null +++ b/services/java/com/android/server/NsdService.java @@ -0,0 +1,269 @@ +/* + * 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. + */ + +package com.android.server; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.net.nsd.DnsSdServiceInfo; +import android.net.nsd.DnsSdTxtRecord; +import android.net.nsd.INsdManager; +import android.net.nsd.NsdManager; +import android.os.Binder; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Message; +import android.os.Messenger; +import android.os.IBinder; +import android.util.Slog; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.concurrent.CountDownLatch; +import java.util.List; + +import com.android.internal.app.IBatteryStats; +import com.android.internal.telephony.TelephonyIntents; +import com.android.internal.util.AsyncChannel; +import com.android.server.am.BatteryStatsService; +import com.android.server.NativeDaemonConnector.Command; +import com.android.internal.R; + +/** + * Network Service Discovery Service handles remote service discovery operation requests by + * implementing the INsdManager interface. + * + * @hide + */ +public class NsdService extends INsdManager.Stub { + private static final String TAG = "NsdService"; + private static final String MDNS_TAG = "mDnsConnector"; + + private static final boolean DBG = true; + + private Context mContext; + + /** + * Clients receiving asynchronous messages + */ + private List<AsyncChannel> mClients = new ArrayList<AsyncChannel>(); + + private AsyncChannel mReplyChannel = new AsyncChannel(); + + /** + * Handles client(app) connections + */ + private class AsyncServiceHandler extends Handler { + + AsyncServiceHandler(android.os.Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED: + if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) { + AsyncChannel c = (AsyncChannel) msg.obj; + if (DBG) Slog.d(TAG, "New client listening to asynchronous messages"); + c.sendMessage(AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED); + mClients.add(c); + } else { + Slog.e(TAG, "Client connection failure, error=" + msg.arg1); + } + break; + case AsyncChannel.CMD_CHANNEL_DISCONNECTED: + if (msg.arg1 == AsyncChannel.STATUS_SEND_UNSUCCESSFUL) { + Slog.e(TAG, "Send failed, client connection lost"); + } else { + if (DBG) Slog.d(TAG, "Client connection lost with reason: " + msg.arg1); + } + mClients.remove((AsyncChannel) msg.obj); + break; + case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION: + AsyncChannel ac = new AsyncChannel(); + ac.connect(mContext, this, msg.replyTo); + break; + case NsdManager.DISCOVER_SERVICES: + if (DBG) Slog.d(TAG, "Discover services"); + DnsSdServiceInfo s = (DnsSdServiceInfo) msg.obj; + discoverServices(1, s.getServiceType()); + mReplyChannel.replyToMessage(msg, NsdManager.DISCOVER_SERVICES_STARTED); + break; + case NsdManager.STOP_DISCOVERY: + if (DBG) Slog.d(TAG, "Stop service discovery"); + mReplyChannel.replyToMessage(msg, NsdManager.STOP_DISCOVERY_FAILED); + break; + case NsdManager.REGISTER_SERVICE: + if (DBG) Slog.d(TAG, "Register service"); + mReplyChannel.replyToMessage(msg, NsdManager.REGISTER_SERVICE_FAILED); + break; + case NsdManager.UPDATE_SERVICE: + if (DBG) Slog.d(TAG, "Update service"); + mReplyChannel.replyToMessage(msg, NsdManager.UPDATE_SERVICE_FAILED); + break; + default: + Slog.d(TAG, "NsdServicehandler.handleMessage ignoring msg=" + msg); + break; + } + } + } + private AsyncServiceHandler mAsyncServiceHandler; + + private NativeDaemonConnector mNativeConnector; + private final CountDownLatch mNativeDaemonConnected = new CountDownLatch(1); + + private NsdService(Context context) { + mContext = context; + + HandlerThread nsdThread = new HandlerThread("NsdService"); + nsdThread.start(); + mAsyncServiceHandler = new AsyncServiceHandler(nsdThread.getLooper()); + + /* + mNativeConnector = new NativeDaemonConnector(new NativeCallbackReceiver(), "mdns", 10, + MDNS_TAG, 25); + Thread th = new Thread(mNativeConnector, MDNS_TAG); + th.start(); + */ + } + + public static NsdService create(Context context) throws InterruptedException { + NsdService service = new NsdService(context); + /* service.mNativeDaemonConnected.await(); */ + return service; + } + + public Messenger getMessenger() { + return new Messenger(mAsyncServiceHandler); + } + + /* These should be in sync with system/netd/mDnsResponseCode.h */ + class NativeResponseCode { + public static final int SERVICE_FOUND = 101; + public static final int SERVICE_LOST = 102; + public static final int SERVICE_DISCOVERY_FAILED = 103; + + public static final int SERVICE_REGISTERED = 104; + public static final int SERVICE_REGISTRATION_FAILED = 105; + + public static final int SERVICE_UPDATED = 106; + public static final int SERVICE_UPDATE_FAILED = 107; + + public static final int SERVICE_RESOLVED = 108; + public static final int SERVICE_RESOLUTION_FAILED = 109; + } + + + class NativeCallbackReceiver implements INativeDaemonConnectorCallbacks { + public void onDaemonConnected() { + mNativeDaemonConnected.countDown(); + } + + public boolean onEvent(int code, String raw, String[] cooked) { + switch (code) { + case NativeResponseCode.SERVICE_FOUND: + /* NNN uniqueId serviceName regType */ + break; + case NativeResponseCode.SERVICE_LOST: + /* NNN uniqueId serviceName regType */ + break; + case NativeResponseCode.SERVICE_DISCOVERY_FAILED: + /* NNN uniqueId errorCode */ + break; + case NativeResponseCode.SERVICE_REGISTERED: + /* NNN regId serviceName regType */ + break; + case NativeResponseCode.SERVICE_REGISTRATION_FAILED: + /* NNN regId errorCode */ + break; + case NativeResponseCode.SERVICE_UPDATED: + /* NNN regId */ + break; + case NativeResponseCode.SERVICE_UPDATE_FAILED: + /* NNN regId errorCode */ + break; + case NativeResponseCode.SERVICE_RESOLVED: + /* NNN resolveId fullName hostName port txtlen txtdata */ + break; + case NativeResponseCode.SERVICE_RESOLUTION_FAILED: + /* NNN resovleId errorCode */ + break; + default: + break; + } + return false; + } + } + + private void registerService(int regId, DnsSdServiceInfo service) { + try { + //Add txtlen and txtdata + mNativeConnector.execute("mdnssd", "register", regId, service.getServiceName(), + service.getServiceType(), service.getPort()); + } catch(NativeDaemonConnectorException e) { + Slog.e(TAG, "Failed to execute registerService"); + } + } + + private void updateService(int regId, DnsSdTxtRecord t) { + try { + if (t == null) return; + mNativeConnector.execute("mdnssd", "update", regId, t.size(), t.getRawData()); + } catch(NativeDaemonConnectorException e) { + Slog.e(TAG, "Failed to updateServices"); + } + } + + private void discoverServices(int discoveryId, String serviceType) { + try { + mNativeConnector.execute("mdnssd", "discover", discoveryId, serviceType); + } catch(NativeDaemonConnectorException e) { + Slog.e(TAG, "Failed to discoverServices"); + } + } + + private void stopServiceDiscovery(int discoveryId) { + try { + mNativeConnector.execute("mdnssd", "stopdiscover", discoveryId); + } catch(NativeDaemonConnectorException e) { + Slog.e(TAG, "Failed to stopServiceDiscovery"); + } + } + + private void resolveService(DnsSdServiceInfo service) { + try { + mNativeConnector.execute("mdnssd", "resolve", service.getServiceName(), + service.getServiceType()); + } catch(NativeDaemonConnectorException e) { + Slog.e(TAG, "Failed to resolveService"); + } + } + + @Override + protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) + != PackageManager.PERMISSION_GRANTED) { + pw.println("Permission Denial: can't dump ServiceDiscoverService from from pid=" + + Binder.getCallingPid() + + ", uid=" + Binder.getCallingUid()); + return; + } + + pw.println("Internal state:"); + } +} diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 423dad6..e091edf 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -120,6 +120,7 @@ class ServerThread extends Thread { ConnectivityService connectivity = null; WifiP2pService wifiP2p = null; WifiService wifi = null; + NsdService serviceDiscovery= null; IPackageManager pm = null; Context context = null; WindowManagerService wm = null; @@ -394,6 +395,15 @@ class ServerThread extends Thread { } try { + Slog.i(TAG, "Network Service Discovery Service"); + serviceDiscovery = NsdService.create(context); + ServiceManager.addService( + Context.NSD_SERVICE, serviceDiscovery); + } catch (Throwable e) { + reportWtf("starting Service Discovery Service", e); + } + + try { Slog.i(TAG, "Throttle Service"); throttle = new ThrottleService(context); ServiceManager.addService( diff --git a/services/java/com/android/server/wm/AppWindowAnimator.java b/services/java/com/android/server/wm/AppWindowAnimator.java new file mode 100644 index 0000000..6ba4c35 --- /dev/null +++ b/services/java/com/android/server/wm/AppWindowAnimator.java @@ -0,0 +1,288 @@ +// Copyright 2012 Google Inc. All Rights Reserved. + +package com.android.server.wm; + +import android.graphics.Matrix; +import android.util.Slog; +import android.view.Surface; +import android.view.WindowManagerPolicy; +import android.view.animation.Animation; +import android.view.animation.Transformation; + +import java.io.PrintWriter; + +/** + * + */ +public class AppWindowAnimator { + + final AppWindowToken mAppToken; + final WindowManagerService mService; + final WindowAnimator mAnimator; + + boolean animating; + Animation animation; + boolean animInitialized; + boolean hasTransformation; + final Transformation transformation = new Transformation(); + + // Have we been asked to have this token keep the screen frozen? + // Protect with mAnimator. + boolean freezingScreen; + + // Offset to the window of all layers in the token, for use by + // AppWindowToken animations. + int animLayerAdjustment; + + // Special surface for thumbnail animation. + Surface thumbnail; + int thumbnailTransactionSeq; + int thumbnailX; + int thumbnailY; + int thumbnailLayer; + Animation thumbnailAnimation; + final Transformation thumbnailTransformation = new Transformation(); + + public AppWindowAnimator(final WindowManagerService service, final AppWindowToken atoken) { + mService = service; + mAppToken = atoken; + mAnimator = service.mAnimator; + } + + public void setAnimation(Animation anim, boolean initialized) { + if (WindowManagerService.localLOGV) Slog.v( + WindowManagerService.TAG, "Setting animation in " + this + ": " + anim); + animation = anim; + animating = false; + animInitialized = initialized; + anim.restrictDuration(WindowManagerService.MAX_ANIMATION_DURATION); + anim.scaleCurrentDuration(mService.mTransitionAnimationScale); + int zorder = anim.getZAdjustment(); + int adj = 0; + if (zorder == Animation.ZORDER_TOP) { + adj = WindowManagerService.TYPE_LAYER_OFFSET; + } else if (zorder == Animation.ZORDER_BOTTOM) { + adj = -WindowManagerService.TYPE_LAYER_OFFSET; + } + + if (animLayerAdjustment != adj) { + animLayerAdjustment = adj; + updateLayers(); + } + // Start out animation gone if window is gone, or visible if window is visible. + transformation.clear(); + transformation.setAlpha(mAppToken.reportedVisible ? 1 : 0); + hasTransformation = true; + } + + public void setDummyAnimation() { + if (animation == null) { + if (WindowManagerService.localLOGV) Slog.v( + WindowManagerService.TAG, "Setting dummy animation in " + this); + animation = WindowManagerService.sDummyAnimation; + animInitialized = false; + } + } + + public void clearAnimation() { + if (animation != null) { + animation = null; + animating = true; + animInitialized = false; + } + clearThumbnail(); + } + + public void clearThumbnail() { + if (thumbnail != null) { + thumbnail.destroy(); + thumbnail = null; + } + } + + void updateLayers() { + final int N = mAppToken.allAppWindows.size(); + final int adj = animLayerAdjustment; + thumbnailLayer = -1; + for (int i=0; i<N; i++) { + final WindowState w = mAppToken.allAppWindows.get(i); + final WindowStateAnimator winAnimator = w.mWinAnimator; + winAnimator.mAnimLayer = w.mLayer + adj; + if (winAnimator.mAnimLayer > thumbnailLayer) { + thumbnailLayer = winAnimator.mAnimLayer; + } + if (WindowManagerService.DEBUG_LAYERS) Slog.v(WindowManagerService.TAG, "Updating layer " + w + ": " + + winAnimator.mAnimLayer); + if (w == mService.mInputMethodTarget && !mService.mInputMethodTargetWaitingAnim) { + mService.setInputMethodAnimLayerAdjustment(adj); + } + if (w == mService.mWallpaperTarget && mService.mLowerWallpaperTarget == null) { + mService.setWallpaperAnimLayerAdjustmentLocked(adj); + } + } + } + + private void stepThumbnailAnimation(long currentTime) { + thumbnailTransformation.clear(); + thumbnailAnimation.getTransformation(currentTime, thumbnailTransformation); + thumbnailTransformation.getMatrix().preTranslate(thumbnailX, thumbnailY); + final boolean screenAnimation = mAnimator.mScreenRotationAnimation != null + && mAnimator.mScreenRotationAnimation.isAnimating(); + if (screenAnimation) { + thumbnailTransformation.postCompose( + mAnimator.mScreenRotationAnimation.getEnterTransformation()); + } + // cache often used attributes locally + final float tmpFloats[] = mService.mTmpFloats; + thumbnailTransformation.getMatrix().getValues(tmpFloats); + if (WindowManagerService.SHOW_TRANSACTIONS) WindowManagerService.logSurface(thumbnail, + "thumbnail", "POS " + tmpFloats[Matrix.MTRANS_X] + + ", " + tmpFloats[Matrix.MTRANS_Y], null); + thumbnail.setPosition(tmpFloats[Matrix.MTRANS_X], tmpFloats[Matrix.MTRANS_Y]); + if (WindowManagerService.SHOW_TRANSACTIONS) WindowManagerService.logSurface(thumbnail, + "thumbnail", "alpha=" + thumbnailTransformation.getAlpha() + + " layer=" + thumbnailLayer + + " matrix=[" + tmpFloats[Matrix.MSCALE_X] + + "," + tmpFloats[Matrix.MSKEW_Y] + + "][" + tmpFloats[Matrix.MSKEW_X] + + "," + tmpFloats[Matrix.MSCALE_Y] + "]", null); + thumbnail.setAlpha(thumbnailTransformation.getAlpha()); + // The thumbnail is layered below the window immediately above this + // token's anim layer. + thumbnail.setLayer(thumbnailLayer + WindowManagerService.WINDOW_LAYER_MULTIPLIER + - WindowManagerService.LAYER_OFFSET_THUMBNAIL); + thumbnail.setMatrix(tmpFloats[Matrix.MSCALE_X], tmpFloats[Matrix.MSKEW_Y], + tmpFloats[Matrix.MSKEW_X], tmpFloats[Matrix.MSCALE_Y]); + } + + private boolean stepAnimation(long currentTime) { + if (animation == null) { + return false; + } + transformation.clear(); + final boolean more = animation.getTransformation(currentTime, transformation); + if (WindowManagerService.DEBUG_ANIM) Slog.v( + WindowManagerService.TAG, "Stepped animation in " + this + + ": more=" + more + ", xform=" + transformation); + if (!more) { + animation = null; + clearThumbnail(); + if (WindowManagerService.DEBUG_ANIM) Slog.v( + WindowManagerService.TAG, "Finished animation in " + this + + " @ " + currentTime); + } + hasTransformation = more; + return more; + } + + // This must be called while inside a transaction. + boolean stepAnimationLocked(long currentTime, int dw, int dh) { + if (mService.okToDisplay()) { + // We will run animations as long as the display isn't frozen. + + if (animation == WindowManagerService.sDummyAnimation) { + // This guy is going to animate, but not yet. For now count + // it as not animating for purposes of scheduling transactions; + // when it is really time to animate, this will be set to + // a real animation and the next call will execute normally. + return false; + } + + if ((mAppToken.allDrawn || animating || mAppToken.startingDisplayed) + && animation != null) { + if (!animating) { + if (WindowManagerService.DEBUG_ANIM) Slog.v( + WindowManagerService.TAG, "Starting animation in " + this + + " @ " + currentTime + ": dw=" + dw + " dh=" + dh + + " scale=" + mService.mTransitionAnimationScale + + " allDrawn=" + mAppToken.allDrawn + " animating=" + animating); + if (!animInitialized) { + animation.initialize(dw, dh, dw, dh); + } + animation.setStartTime(currentTime); + animating = true; + if (thumbnail != null) { + thumbnail.show(); + thumbnailAnimation.setStartTime(currentTime); + } + } + if (stepAnimation(currentTime)) { + // animation isn't over, step any thumbnail and that's + // it for now. + if (thumbnail != null) { + stepThumbnailAnimation(currentTime); + } + return true; + } + } + } else if (animation != null) { + // If the display is frozen, and there is a pending animation, + // clear it and make sure we run the cleanup code. + animating = true; + animation = null; + } + + hasTransformation = false; + + if (!animating) { + return false; + } + + mAnimator.mPendingLayoutChanges |= WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM; + if (WindowManagerService.DEBUG_LAYOUT_REPEATS) { + mService.debugLayoutRepeats("AppWindowToken", mAnimator.mPendingLayoutChanges); + } + + clearAnimation(); + animating = false; + if (animLayerAdjustment != 0) { + animLayerAdjustment = 0; + updateLayers(); + } + if (mService.mInputMethodTarget != null + && mService.mInputMethodTarget.mAppToken == mAppToken) { + mService.moveInputMethodWindowsIfNeededLocked(true); + } + + if (WindowManagerService.DEBUG_ANIM) Slog.v( + WindowManagerService.TAG, "Animation done in " + this + + ": reportedVisible=" + mAppToken.reportedVisible); + + transformation.clear(); + + final int N = mAppToken.windows.size(); + for (int i=0; i<N; i++) { + mAppToken.windows.get(i).mWinAnimator.finishExit(); + } + mAppToken.updateReportedVisibilityLocked(); + + return false; + } + + void dump(PrintWriter pw, String prefix) { + if (freezingScreen) { + pw.print(prefix); pw.print(" freezingScreen="); pw.println(freezingScreen); + } + if (animating || animation != null) { + pw.print(prefix); pw.print("animating="); pw.print(animating); + pw.print(" animation="); pw.println(animation); + } + if (hasTransformation) { + pw.print(prefix); pw.print("XForm: "); + transformation.printShortString(pw); + pw.println(); + } + if (animLayerAdjustment != 0) { + pw.print(prefix); pw.print("animLayerAdjustment="); pw.println(animLayerAdjustment); + } + if (thumbnail != null) { + pw.print(prefix); pw.print("thumbnail="); pw.print(thumbnail); + pw.print(" x="); pw.print(thumbnailX); + pw.print(" y="); pw.print(thumbnailY); + pw.print(" layer="); pw.println(thumbnailLayer); + pw.print(prefix); pw.print("thumbnailAnimation="); pw.println(thumbnailAnimation); + pw.print(prefix); pw.print("thumbnailTransformation="); + pw.println(thumbnailTransformation.toShortString()); + } + } +} diff --git a/services/java/com/android/server/wm/AppWindowToken.java b/services/java/com/android/server/wm/AppWindowToken.java index 3069b74..e3d46d8 100644 --- a/services/java/com/android/server/wm/AppWindowToken.java +++ b/services/java/com/android/server/wm/AppWindowToken.java @@ -21,17 +21,12 @@ import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; import com.android.server.wm.WindowManagerService.H; import android.content.pm.ActivityInfo; -import android.graphics.Matrix; import android.os.Message; import android.os.RemoteException; import android.util.Slog; import android.view.IApplicationToken; -import android.view.Surface; import android.view.View; import android.view.WindowManager; -import android.view.WindowManagerPolicy; -import android.view.animation.Animation; -import android.view.animation.Transformation; import java.io.PrintWriter; import java.util.ArrayList; @@ -47,6 +42,9 @@ class AppWindowToken extends WindowToken { // All of the windows and child windows that are included in this // application token. Note this list is NOT sorted! final ArrayList<WindowState> allAppWindows = new ArrayList<WindowState>(); + final AppWindowAnimator mAppAnimator; + + final WindowAnimator mAnimator; int groupId = -1; boolean appFullscreen; @@ -87,19 +85,6 @@ class AppWindowToken extends WindowToken { // Set to true when the token has been removed from the window mgr. boolean removed; - // Have we been asked to have this token keep the screen frozen? - boolean freezingScreen; - - boolean animating; - Animation animation; - boolean animInitialized; - boolean hasTransformation; - final Transformation transformation = new Transformation(); - - // Offset to the window of all layers in the token, for use by - // AppWindowToken animations. - int animLayerAdjustment; - // Information about an application starting window if displayed. StartingData startingData; WindowState startingWindow; @@ -108,15 +93,6 @@ class AppWindowToken extends WindowToken { boolean startingMoved; boolean firstWindowDrawn; - // Special surface for thumbnail animation. - Surface thumbnail; - int thumbnailTransactionSeq; - int thumbnailX; - int thumbnailY; - int thumbnailLayer; - Animation thumbnailAnimation; - final Transformation thumbnailTransformation = new Transformation(); - // Input application handle used by the input dispatcher. final InputApplicationHandle mInputApplicationHandle; @@ -126,79 +102,8 @@ class AppWindowToken extends WindowToken { appWindowToken = this; appToken = _token; mInputApplicationHandle = new InputApplicationHandle(this); - } - - public void setAnimation(Animation anim, boolean initialized) { - if (WindowManagerService.localLOGV) Slog.v( - WindowManagerService.TAG, "Setting animation in " + this + ": " + anim); - animation = anim; - animating = false; - animInitialized = initialized; - anim.restrictDuration(WindowManagerService.MAX_ANIMATION_DURATION); - anim.scaleCurrentDuration(service.mTransitionAnimationScale); - int zorder = anim.getZAdjustment(); - int adj = 0; - if (zorder == Animation.ZORDER_TOP) { - adj = WindowManagerService.TYPE_LAYER_OFFSET; - } else if (zorder == Animation.ZORDER_BOTTOM) { - adj = -WindowManagerService.TYPE_LAYER_OFFSET; - } - - if (animLayerAdjustment != adj) { - animLayerAdjustment = adj; - updateLayers(); - } - // Start out animation gone if window is gone, or visible if window is visible. - transformation.clear(); - transformation.setAlpha(reportedVisible ? 1 : 0); - hasTransformation = true; - } - - public void setDummyAnimation() { - if (animation == null) { - if (WindowManagerService.localLOGV) Slog.v( - WindowManagerService.TAG, "Setting dummy animation in " + this); - animation = WindowManagerService.sDummyAnimation; - animInitialized = false; - } - } - - public void clearAnimation() { - if (animation != null) { - animation = null; - animating = true; - animInitialized = false; - } - clearThumbnail(); - } - - public void clearThumbnail() { - if (thumbnail != null) { - thumbnail.destroy(); - thumbnail = null; - } - } - - void updateLayers() { - final int N = allAppWindows.size(); - final int adj = animLayerAdjustment; - thumbnailLayer = -1; - for (int i=0; i<N; i++) { - final WindowState w = allAppWindows.get(i); - final WindowStateAnimator winAnimator = w.mWinAnimator; - winAnimator.mAnimLayer = w.mLayer + adj; - if (winAnimator.mAnimLayer > thumbnailLayer) { - thumbnailLayer = winAnimator.mAnimLayer; - } - if (WindowManagerService.DEBUG_LAYERS) Slog.v(WindowManagerService.TAG, "Updating layer " + w + ": " - + winAnimator.mAnimLayer); - if (w == service.mInputMethodTarget && !service.mInputMethodTargetWaitingAnim) { - service.setInputMethodAnimLayerAdjustment(adj); - } - if (w == service.mWallpaperTarget && service.mLowerWallpaperTarget == null) { - service.setWallpaperAnimLayerAdjustmentLocked(adj); - } - } + mAnimator = service.mAnimator; + mAppAnimator = new AppWindowAnimator(_service, this); } void sendAppVisibilityToClients() { @@ -231,141 +136,6 @@ class AppWindowToken extends WindowToken { return isAnimating; } - private void stepThumbnailAnimation(long currentTime) { - thumbnailTransformation.clear(); - thumbnailAnimation.getTransformation(currentTime, thumbnailTransformation); - thumbnailTransformation.getMatrix().preTranslate(thumbnailX, thumbnailY); - final boolean screenAnimation = service.mAnimator.mScreenRotationAnimation != null - && service.mAnimator.mScreenRotationAnimation.isAnimating(); - if (screenAnimation) { - thumbnailTransformation.postCompose( - service.mAnimator.mScreenRotationAnimation.getEnterTransformation()); - } - // cache often used attributes locally - final float tmpFloats[] = service.mTmpFloats; - thumbnailTransformation.getMatrix().getValues(tmpFloats); - if (WindowManagerService.SHOW_TRANSACTIONS) WindowManagerService.logSurface(thumbnail, - "thumbnail", "POS " + tmpFloats[Matrix.MTRANS_X] - + ", " + tmpFloats[Matrix.MTRANS_Y], null); - thumbnail.setPosition(tmpFloats[Matrix.MTRANS_X], tmpFloats[Matrix.MTRANS_Y]); - if (WindowManagerService.SHOW_TRANSACTIONS) WindowManagerService.logSurface(thumbnail, - "thumbnail", "alpha=" + thumbnailTransformation.getAlpha() - + " layer=" + thumbnailLayer - + " matrix=[" + tmpFloats[Matrix.MSCALE_X] - + "," + tmpFloats[Matrix.MSKEW_Y] - + "][" + tmpFloats[Matrix.MSKEW_X] - + "," + tmpFloats[Matrix.MSCALE_Y] + "]", null); - thumbnail.setAlpha(thumbnailTransformation.getAlpha()); - // The thumbnail is layered below the window immediately above this - // token's anim layer. - thumbnail.setLayer(thumbnailLayer + WindowManagerService.WINDOW_LAYER_MULTIPLIER - - WindowManagerService.LAYER_OFFSET_THUMBNAIL); - thumbnail.setMatrix(tmpFloats[Matrix.MSCALE_X], tmpFloats[Matrix.MSKEW_Y], - tmpFloats[Matrix.MSKEW_X], tmpFloats[Matrix.MSCALE_Y]); - } - - private boolean stepAnimation(long currentTime) { - if (animation == null) { - return false; - } - transformation.clear(); - final boolean more = animation.getTransformation(currentTime, transformation); - if (WindowManagerService.DEBUG_ANIM) Slog.v( - WindowManagerService.TAG, "Stepped animation in " + this + - ": more=" + more + ", xform=" + transformation); - if (!more) { - animation = null; - clearThumbnail(); - if (WindowManagerService.DEBUG_ANIM) Slog.v( - WindowManagerService.TAG, "Finished animation in " + this + - " @ " + currentTime); - } - hasTransformation = more; - return more; - } - - // This must be called while inside a transaction. - boolean stepAnimationLocked(long currentTime, int dw, int dh) { - if (service.okToDisplay()) { - // We will run animations as long as the display isn't frozen. - - if (animation == WindowManagerService.sDummyAnimation) { - // This guy is going to animate, but not yet. For now count - // it as not animating for purposes of scheduling transactions; - // when it is really time to animate, this will be set to - // a real animation and the next call will execute normally. - return false; - } - - if ((allDrawn || animating || startingDisplayed) && animation != null) { - if (!animating) { - if (WindowManagerService.DEBUG_ANIM) Slog.v( - WindowManagerService.TAG, "Starting animation in " + this + - " @ " + currentTime + ": dw=" + dw + " dh=" + dh - + " scale=" + service.mTransitionAnimationScale - + " allDrawn=" + allDrawn + " animating=" + animating); - if (!animInitialized) { - animation.initialize(dw, dh, dw, dh); - } - animation.setStartTime(currentTime); - animating = true; - if (thumbnail != null) { - thumbnail.show(); - thumbnailAnimation.setStartTime(currentTime); - } - } - if (stepAnimation(currentTime)) { - // animation isn't over, step any thumbnail and that's - // it for now. - if (thumbnail != null) { - stepThumbnailAnimation(currentTime); - } - return true; - } - } - } else if (animation != null) { - // If the display is frozen, and there is a pending animation, - // clear it and make sure we run the cleanup code. - animating = true; - animation = null; - } - - hasTransformation = false; - - if (!animating) { - return false; - } - - service.mPendingLayoutChanges |= WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM; - if (WindowManagerService.DEBUG_LAYOUT_REPEATS) { - service.debugLayoutRepeats("AppWindowToken"); - } - - clearAnimation(); - animating = false; - if (animLayerAdjustment != 0) { - animLayerAdjustment = 0; - updateLayers(); - } - if (service.mInputMethodTarget != null && service.mInputMethodTarget.mAppToken == this) { - service.moveInputMethodWindowsIfNeededLocked(true); - } - - if (WindowManagerService.DEBUG_ANIM) Slog.v( - WindowManagerService.TAG, "Animation done in " + this - + ": reportedVisible=" + reportedVisible); - - transformation.clear(); - - final int N = windows.size(); - for (int i=0; i<N; i++) { - windows.get(i).mWinAnimator.finishExit(); - } - updateReportedVisibilityLocked(); - - return false; - } - void updateReportedVisibilityLocked() { if (appToken == null) { return; @@ -479,9 +249,8 @@ class AppWindowToken extends WindowToken { pw.print(" willBeHidden="); pw.print(willBeHidden); pw.print(" reportedDrawn="); pw.print(reportedDrawn); pw.print(" reportedVisible="); pw.println(reportedVisible); - if (paused || freezingScreen) { - pw.print(prefix); pw.print("paused="); pw.print(paused); - pw.print(" freezingScreen="); pw.println(freezingScreen); + if (paused) { + pw.print(prefix); pw.print("paused="); pw.println(paused); } if (numInterestingWindows != 0 || numDrawnWindows != 0 || inPendingTransaction || allDrawn) { @@ -491,18 +260,6 @@ class AppWindowToken extends WindowToken { pw.print(" inPendingTransaction="); pw.print(inPendingTransaction); pw.print(" allDrawn="); pw.println(allDrawn); } - if (animating || animation != null) { - pw.print(prefix); pw.print("animating="); pw.print(animating); - pw.print(" animation="); pw.println(animation); - } - if (hasTransformation) { - pw.print(prefix); pw.print("XForm: "); - transformation.printShortString(pw); - pw.println(); - } - if (animLayerAdjustment != 0) { - pw.print(prefix); pw.print("animLayerAdjustment="); pw.println(animLayerAdjustment); - } if (startingData != null || removed || firstWindowDrawn) { pw.print(prefix); pw.print("startingData="); pw.print(startingData); pw.print(" removed="); pw.print(removed); @@ -515,15 +272,6 @@ class AppWindowToken extends WindowToken { pw.print(" startingDisplayed="); pw.print(startingDisplayed); pw.print(" startingMoved"); pw.println(startingMoved); } - if (thumbnail != null) { - pw.print(prefix); pw.print("thumbnail="); pw.print(thumbnail); - pw.print(" x="); pw.print(thumbnailX); - pw.print(" y="); pw.print(thumbnailY); - pw.print(" layer="); pw.println(thumbnailLayer); - pw.print(prefix); pw.print("thumbnailAnimation="); pw.println(thumbnailAnimation); - pw.print(prefix); pw.print("thumbnailTransformation="); - pw.println(thumbnailTransformation.toShortString()); - } } @Override diff --git a/services/java/com/android/server/wm/DimAnimator.java b/services/java/com/android/server/wm/DimAnimator.java index 0051d98..405dd04 100644 --- a/services/java/com/android/server/wm/DimAnimator.java +++ b/services/java/com/android/server/wm/DimAnimator.java @@ -57,12 +57,17 @@ class DimAnimator { } /** - * Show the dim surface. + * Set's the dim surface's layer and update dim parameters that will be used in + * {@link #updateSurface} after all windows are examined. */ - void show(int dw, int dh) { + void updateParameters(final Resources res, final Parameters params, final long currentTime) { + final int dw = params.mDimWidth; + final int dh = params.mDimHeight; + final WindowStateAnimator winAnimator = params.mDimWinAnimator; + final float target = params.mDimTarget; if (!mDimShown) { - if (WindowManagerService.SHOW_TRANSACTIONS) Slog.i(WindowManagerService.TAG, " DIM " + mDimSurface + ": SHOW pos=(0,0) (" + - dw + "x" + dh + ")"); + if (WindowManagerService.SHOW_TRANSACTIONS) Slog.i(WindowManagerService.TAG, + " DIM " + mDimSurface + ": SHOW pos=(0,0) (" + dw + "x" + dh + ")"); mDimShown = true; try { mLastDimWidth = dw; @@ -78,17 +83,9 @@ class DimAnimator { mLastDimHeight = dh; mDimSurface.setSize(dw, dh); } - } - /** - * Set's the dim surface's layer and update dim parameters that will be used in - * {@link #updateSurface} after all windows are examined. - */ - void updateParameters(Resources res, WindowState w, long currentTime) { - final WindowStateAnimator winAnimator = w.mWinAnimator; mDimSurface.setLayer(winAnimator.mAnimLayer - WindowManagerService.LAYER_OFFSET_DIM); - final float target = w.mExiting ? 0 : w.mAttrs.dimAmount; if (WindowManagerService.SHOW_TRANSACTIONS) Slog.i(WindowManagerService.TAG, " DIM " + mDimSurface + ": layer=" + (winAnimator.mAnimLayer-1) + " target=" + target); if (mDimTargetAlpha != target) { @@ -189,4 +186,18 @@ class DimAnimator { pw.print(" delta="); pw.print(mDimDeltaPerMs); pw.print(" lastAnimTime="); pw.println(mLastDimAnimTime); } + + static class Parameters { + final WindowStateAnimator mDimWinAnimator; + final int mDimWidth; + final int mDimHeight; + final float mDimTarget; + Parameters(final WindowStateAnimator dimWinAnimator, final int dimWidth, + final int dimHeight, final float dimTarget) { + mDimWinAnimator = dimWinAnimator; + mDimWidth = dimWidth; + mDimHeight = dimHeight; + mDimTarget = dimTarget; + } + } }
\ No newline at end of file diff --git a/services/java/com/android/server/wm/WindowAnimator.java b/services/java/com/android/server/wm/WindowAnimator.java index 5a104b2..77f94d9 100644 --- a/services/java/com/android/server/wm/WindowAnimator.java +++ b/services/java/com/android/server/wm/WindowAnimator.java @@ -6,6 +6,9 @@ import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; import static com.android.server.wm.WindowManagerService.LayoutFields.SET_UPDATE_ROTATION; import static com.android.server.wm.WindowManagerService.LayoutFields.SET_WALLPAPER_MAY_CHANGE; +import static com.android.server.wm.WindowManagerService.LayoutFields.SET_FORCE_HIDING_CHANGED; + +import static com.android.server.wm.WindowManagerService.H.SET_DIM_PARAMETERS; import android.content.Context; import android.os.SystemClock; @@ -21,7 +24,6 @@ import com.android.internal.policy.impl.PhoneWindowManager; import java.io.PrintWriter; /** - * @author cmautner@google.com (Craig Mautner) * Singleton class that carries out the animations and Surface operations in a separate task * on behalf of WindowManagerService. */ @@ -67,6 +69,9 @@ public class WindowAnimator { int mBulkUpdateParams = 0; + DimAnimator mDimAnimator = null; + DimAnimator.Parameters mDimParams = null; + WindowAnimator(final WindowManagerService service, final Context context, final WindowManagerPolicy policy) { mService = service; @@ -91,7 +96,8 @@ public class WindowAnimator { if (mService.mWallpaperTarget == target || mService.mLowerWallpaperTarget == target || mService.mUpperWallpaperTarget == target) { - for (int i=0; i<mService.mWindows.size(); i++) { + final int N = mService.mWindows.size(); + for (int i = 0; i < N; i++) { WindowState w = mService.mWindows.get(i); if (w.mIsWallpaper) { target = w; @@ -116,32 +122,34 @@ public class WindowAnimator { int i; final int NAT = mService.mAppTokens.size(); for (i=0; i<NAT; i++) { - final AppWindowToken appToken = mService.mAppTokens.get(i); - final boolean wasAnimating = appToken.animation != null - && appToken.animation != WindowManagerService.sDummyAnimation; - if (appToken.stepAnimationLocked(mCurrentTime, mInnerDw, mInnerDh)) { + final AppWindowAnimator appAnimator = mService.mAppTokens.get(i).mAppAnimator; + final boolean wasAnimating = appAnimator.animation != null + && appAnimator.animation != WindowManagerService.sDummyAnimation; + if (appAnimator.stepAnimationLocked(mCurrentTime, mInnerDw, mInnerDh)) { mAnimating = true; } else if (wasAnimating) { // stopped animating, do one more pass through the layout mPendingLayoutChanges |= WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; if (WindowManagerService.DEBUG_LAYOUT_REPEATS) { - mService.debugLayoutRepeats("appToken " + appToken + " done"); + mService.debugLayoutRepeats("appToken " + appAnimator.mAppToken + " done", + mPendingLayoutChanges); } } } final int NEAT = mService.mExitingAppTokens.size(); for (i=0; i<NEAT; i++) { - final AppWindowToken appToken = mService.mExitingAppTokens.get(i); - final boolean wasAnimating = appToken.animation != null - && appToken.animation != WindowManagerService.sDummyAnimation; - if (appToken.stepAnimationLocked(mCurrentTime, mInnerDw, mInnerDh)) { + final AppWindowAnimator appAnimator = mService.mAppTokens.get(i).mAppAnimator; + final boolean wasAnimating = appAnimator.animation != null + && appAnimator.animation != WindowManagerService.sDummyAnimation; + if (appAnimator.stepAnimationLocked(mCurrentTime, mInnerDw, mInnerDh)) { mAnimating = true; } else if (wasAnimating) { // stopped animating, do one more pass through the layout mPendingLayoutChanges |= WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; if (WindowManagerService.DEBUG_LAYOUT_REPEATS) { - mService.debugLayoutRepeats("exiting appToken " + appToken + " done"); + mService.debugLayoutRepeats("exiting appToken " + appAnimator.mAppToken + + " done", mPendingLayoutChanges); } } } @@ -185,13 +193,13 @@ public class WindowAnimator { && winAnimator.mAnimation.getDetachWallpaper()) { mDetachedWallpaper = w; } - if (winAnimator.mAnimation.getBackgroundColor() != 0) { + final int backgroundColor = winAnimator.mAnimation.getBackgroundColor(); + if (backgroundColor != 0) { if (mWindowAnimationBackground == null || (winAnimator.mAnimLayer < mWindowAnimationBackground.mWinAnimator.mAnimLayer)) { mWindowAnimationBackground = w; - mWindowAnimationBackgroundColor = - winAnimator.mAnimation.getBackgroundColor(); + mWindowAnimationBackgroundColor = backgroundColor; } } } @@ -201,19 +209,21 @@ public class WindowAnimator { // If this window's app token is running a detached wallpaper // animation, make a note so we can ensure the wallpaper is // displayed behind it. - if (w.mAppToken != null && w.mAppToken.animation != null - && w.mAppToken.animating) { + final AppWindowAnimator appAnimator = + w.mAppToken == null ? null : w.mAppToken.mAppAnimator; + if (appAnimator != null && appAnimator.animation != null + && appAnimator.animating) { if ((attrs.flags&FLAG_SHOW_WALLPAPER) != 0 - && w.mAppToken.animation.getDetachWallpaper()) { + && appAnimator.animation.getDetachWallpaper()) { mDetachedWallpaper = w; } - if (w.mAppToken.animation.getBackgroundColor() != 0) { + final int backgroundColor = appAnimator.animation.getBackgroundColor(); + if (backgroundColor != 0) { if (mWindowAnimationBackground == null || (winAnimator.mAnimLayer < mWindowAnimationBackground.mWinAnimator.mAnimLayer)) { mWindowAnimationBackground = w; - mWindowAnimationBackgroundColor = - w.mAppToken.animation.getBackgroundColor(); + mWindowAnimationBackgroundColor = backgroundColor; } } } @@ -222,7 +232,8 @@ public class WindowAnimator { mBulkUpdateParams |= SET_WALLPAPER_MAY_CHANGE; mPendingLayoutChanges |= WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; if (WindowManagerService.DEBUG_LAYOUT_REPEATS) { - mService.debugLayoutRepeats("updateWindowsAndWallpaperLocked 2"); + mService.debugLayoutRepeats("updateWindowsAndWallpaperLocked 2", + mPendingLayoutChanges); } } @@ -231,17 +242,18 @@ public class WindowAnimator { if (WindowManagerService.DEBUG_VISIBILITY) Slog.v(TAG, "Animation started that could impact force hide: " + w); - mService.mInnerFields.mWallpaperForceHidingChanged = true; + mBulkUpdateParams |= SET_FORCE_HIDING_CHANGED; mPendingLayoutChanges |= WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; if (WindowManagerService.DEBUG_LAYOUT_REPEATS) { - mService.debugLayoutRepeats("updateWindowsAndWallpaperLocked 3"); + mService.debugLayoutRepeats("updateWindowsAndWallpaperLocked 3", + mPendingLayoutChanges); } mService.mFocusMayChange = true; } else if (w.isReadyForDisplay() && winAnimator.mAnimation == null) { mForceHiding = true; } } else if (mPolicy.canBeForceHidden(w, attrs)) { - boolean changed; + final boolean changed; if (mForceHiding) { changed = w.hideLw(false, false); if (WindowManagerService.DEBUG_VISIBILITY && changed) Slog.v(TAG, @@ -251,7 +263,7 @@ public class WindowAnimator { if (WindowManagerService.DEBUG_VISIBILITY && changed) Slog.v(TAG, "Now policy shown: " + w); if (changed) { - if (mService.mInnerFields.mWallpaperForceHidingChanged + if ((mBulkUpdateParams & SET_FORCE_HIDING_CHANGED) != 0 && w.isVisibleNow() /*w.isReadyForDisplay()*/) { // Assume we will need to animate. If // we don't (because the wallpaper will @@ -275,14 +287,15 @@ public class WindowAnimator { mBulkUpdateParams |= SET_WALLPAPER_MAY_CHANGE; mPendingLayoutChanges |= WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; if (WindowManagerService.DEBUG_LAYOUT_REPEATS) { - mService.debugLayoutRepeats("updateWindowsAndWallpaperLocked 4"); + mService.debugLayoutRepeats("updateWindowsAndWallpaperLocked 4", + mPendingLayoutChanges); } } } } final AppWindowToken atoken = w.mAppToken; - if (atoken != null && (!atoken.allDrawn || atoken.freezingScreen)) { + if (atoken != null && (!atoken.allDrawn || atoken.mAppAnimator.freezingScreen)) { if (atoken.lastTransactionSequence != mTransactionSequence) { atoken.lastTransactionSequence = mTransactionSequence; atoken.numInterestingWindows = atoken.numDrawnWindows = 0; @@ -306,14 +319,14 @@ public class WindowAnimator { } } if (w != atoken.startingWindow) { - if (!atoken.freezingScreen || !w.mAppFreezing) { + if (!atoken.mAppAnimator.freezingScreen || !w.mAppFreezing) { atoken.numInterestingWindows++; if (w.isDrawnLw()) { atoken.numDrawnWindows++; if (WindowManagerService.DEBUG_VISIBILITY || WindowManagerService.DEBUG_ORIENTATION) Slog.v(TAG, "tokenMayBeDrawn: " + atoken - + " freezingScreen=" + atoken.freezingScreen + + " freezingScreen=" + atoken.mAppAnimator.freezingScreen + " mAppFreezing=" + w.mAppFreezing); mTokenMayBeDrawn = true; } @@ -326,17 +339,20 @@ public class WindowAnimator { if (winAnimator.performShowLocked()) { mPendingLayoutChanges |= WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM; if (WindowManagerService.DEBUG_LAYOUT_REPEATS) { - mService.debugLayoutRepeats("updateWindowsAndWallpaperLocked 5"); + mService.debugLayoutRepeats("updateWindowsAndWallpaperLocked 5", + mPendingLayoutChanges); } } } - if (atoken != null && atoken.thumbnail != null) { - if (atoken.thumbnailTransactionSeq != mTransactionSequence) { - atoken.thumbnailTransactionSeq = mTransactionSequence; - atoken.thumbnailLayer = 0; + final AppWindowAnimator appAnimator = + atoken == null ? null : atoken.mAppAnimator; + if (appAnimator != null && appAnimator.thumbnail != null) { + if (appAnimator.thumbnailTransactionSeq != mTransactionSequence) { + appAnimator.thumbnailTransactionSeq = mTransactionSequence; + appAnimator.thumbnailLayer = 0; } - if (atoken.thumbnailLayer < winAnimator.mAnimLayer) { - atoken.thumbnailLayer = winAnimator.mAnimLayer; + if (appAnimator.thumbnailLayer < winAnimator.mAnimLayer) { + appAnimator.thumbnailLayer = winAnimator.mAnimLayer; } } } // end forall windows @@ -348,7 +364,7 @@ public class WindowAnimator { final int NT = mService.mAppTokens.size(); for (int i=0; i<NT; i++) { AppWindowToken wtoken = mService.mAppTokens.get(i); - if (wtoken.freezingScreen) { + if (wtoken.mAppAnimator.freezingScreen) { int numInteresting = wtoken.numInterestingWindows; if (numInteresting > 0 && wtoken.numDrawnWindows >= numInteresting) { if (WindowManagerService.DEBUG_VISIBILITY) Slog.v(TAG, @@ -361,7 +377,6 @@ public class WindowAnimator { "Setting mOrientationChangeComplete=true because wtoken " + wtoken + " numInteresting=" + numInteresting + " numDrawn=" + wtoken.numDrawnWindows); - mService.mInnerFields.mOrientationChangeComplete = true; } } else if (!wtoken.allDrawn) { int numInteresting = wtoken.numInterestingWindows; @@ -373,7 +388,8 @@ public class WindowAnimator { wtoken.allDrawn = true; mPendingLayoutChanges |= PhoneWindowManager.FINISH_LAYOUT_REDO_ANIM; if (WindowManagerService.DEBUG_LAYOUT_REPEATS) { - mService.debugLayoutRepeats("testTokenMayBeDrawnLocked"); + mService.debugLayoutRepeats("testTokenMayBeDrawnLocked", + mPendingLayoutChanges); } // We can now show all of the drawn windows! @@ -419,14 +435,13 @@ public class WindowAnimator { mScreenRotationAnimation.updateSurfaces(); } - final int N = mService.mWindows.size(); - for (int i=N-1; i>=0; i--) { + for (int i = mService.mWindows.size() - 1; i >= 0; i--) { WindowState w = mService.mWindows.get(i); w.mWinAnimator.prepareSurfaceLocked(true); } - if (mService.mDimAnimator != null && mService.mDimAnimator.mDimShown) { - mAnimating |= mService.mDimAnimator.updateSurface(mService.mInnerFields.mDimming, + if (mDimAnimator != null && mDimAnimator.mDimShown) { + mAnimating |= mDimAnimator.updateSurface(mService.mInnerFields.mDimming, mCurrentTime, !mService.okToDisplay()); } @@ -438,6 +453,10 @@ public class WindowAnimator { mService.mBlackFrame.clearMatrix(); } } + + if (mDimParams != null) { + mDimAnimator.updateParameters(mContext.getResources(), mDimParams, mCurrentTime); + } } catch (RuntimeException e) { Log.wtf(TAG, "Unhandled exception in Window Manager", e); } finally { @@ -450,7 +469,7 @@ public class WindowAnimator { } WindowState mCurrentFocus; - void setCurrentFocus(WindowState currentFocus) { + void setCurrentFocus(final WindowState currentFocus) { mCurrentFocus = currentFocus; } @@ -462,6 +481,20 @@ public class WindowAnimator { mInnerDh = appHeight; } + void startDimming(final WindowStateAnimator winAnimator, final float target, + final int width, final int height) { + if (mDimAnimator == null) { + mDimAnimator = new DimAnimator(mService.mFxSession); + } + mService.mH.sendMessage(mService.mH.obtainMessage(SET_DIM_PARAMETERS, + new DimAnimator.Parameters(winAnimator, width, height, target))); + } + + // TODO(cmautner): Move into Handler + void stopDimming() { + mService.mH.sendMessage(mService.mH.obtainMessage(SET_DIM_PARAMETERS, null)); + } + public void dump(PrintWriter pw, String prefix, boolean dumpAll) { if (mWindowDetachedWallpaper != null) { pw.print(" mWindowDetachedWallpaper="); pw.println(mWindowDetachedWallpaper); @@ -470,5 +503,11 @@ public class WindowAnimator { pw.println(" mWindowAnimationBackgroundSurface:"); mWindowAnimationBackgroundSurface.printTo(" ", pw); } + if (mDimAnimator != null) { + pw.println(" mDimAnimator:"); + mDimAnimator.printTo(" ", pw); + } else { + pw.println( " no DimAnimator "); + } } } diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java index afbc348..7eca401 100644 --- a/services/java/com/android/server/wm/WindowManagerService.java +++ b/services/java/com/android/server/wm/WindowManagerService.java @@ -149,7 +149,7 @@ public class WindowManagerService extends IWindowManager.Stub implements Watchdog.Monitor, WindowManagerPolicy.WindowManagerFuncs { static final String TAG = "WindowManager"; static final boolean DEBUG = false; - static final boolean DEBUG_ADD_REMOVE = true; + static final boolean DEBUG_ADD_REMOVE = false; static final boolean DEBUG_FOCUS = false; static final boolean DEBUG_ANIM = false; static final boolean DEBUG_LAYOUT = false; @@ -158,7 +158,7 @@ public class WindowManagerService extends IWindowManager.Stub static final boolean DEBUG_INPUT = false; static final boolean DEBUG_INPUT_METHOD = false; static final boolean DEBUG_VISIBILITY = false; - static final boolean DEBUG_WINDOW_MOVEMENT = true; + static final boolean DEBUG_WINDOW_MOVEMENT = false; static final boolean DEBUG_TOKEN_MOVEMENT = false; static final boolean DEBUG_ORIENTATION = false; static final boolean DEBUG_APP_ORIENTATION = false; @@ -171,7 +171,7 @@ public class WindowManagerService extends IWindowManager.Stub static final boolean DEBUG_SCREEN_ON = false; static final boolean DEBUG_SCREENSHOT = false; static final boolean DEBUG_BOOT = false; - static final boolean DEBUG_LAYOUT_REPEATS = false; + static final boolean DEBUG_LAYOUT_REPEATS = true; static final boolean SHOW_SURFACE_ALLOC = false; static final boolean SHOW_TRANSACTIONS = false; static final boolean SHOW_LIGHT_TRANSACTIONS = false || SHOW_TRANSACTIONS; @@ -353,7 +353,32 @@ public class WindowManagerService extends IWindowManager.Stub * controlling the ordering of windows in different applications. This * contains AppWindowToken objects. */ - final ArrayList<AppWindowToken> mAppTokens = new ArrayList<AppWindowToken>(); + final ArrayList<AppWindowToken> mAppTokens = new ArrayList<AppWindowToken>() { + @Override + public void add(int index, AppWindowToken object) { + synchronized (mAnimator) { + super.add(index, object); + } + }; + @Override + public boolean add(AppWindowToken object) { + synchronized (mAnimator) { + return super.add(object); + } + }; + @Override + public AppWindowToken remove(int index) { + synchronized (mAnimator) { + return super.remove(index); + } + }; + @Override + public boolean remove(Object object) { + synchronized (mAnimator) { + return super.remove(object); + } + }; + }; /** * Application tokens that are in the process of exiting, but still @@ -370,7 +395,32 @@ public class WindowManagerService extends IWindowManager.Stub /** * Z-ordered (bottom-most first) list of all Window objects. */ - final ArrayList<WindowState> mWindows = new ArrayList<WindowState>(); + final ArrayList<WindowState> mWindows = new ArrayList<WindowState>() { + @Override + public void add(int index, WindowState object) { + synchronized (mAnimator) { + super.add(index, object); + } + }; + @Override + public boolean add(WindowState object) { + synchronized (mAnimator) { + return super.add(object); + } + }; + @Override + public WindowState remove(int index) { + synchronized (mAnimator) { + return super.remove(index); + } + }; + @Override + public boolean remove(Object object) { + synchronized (mAnimator) { + return super.remove(object); + } + }; + }; /** * Fake windows added to the window manager. Note: ordered from top to @@ -427,7 +477,6 @@ public class WindowManagerService extends IWindowManager.Stub IInputMethodManager mInputMethodManager; SurfaceSession mFxSession; - DimAnimator mDimAnimator = null; Watermark mWatermark; StrictModeFlash mStrictModeFlash; @@ -590,8 +639,10 @@ public class WindowManagerService extends IWindowManager.Stub /** Pulled out of performLayoutAndPlaceSurfacesLockedInner in order to refactor into multiple * methods. */ class LayoutFields { - static final int SET_UPDATE_ROTATION = 1 << 0; - static final int SET_WALLPAPER_MAY_CHANGE = 1 << 1; + static final int SET_UPDATE_ROTATION = 1 << 0; + static final int SET_WALLPAPER_MAY_CHANGE = 1 << 1; + static final int SET_FORCE_HIDING_CHANGED = 1 << 2; + static final int CLEAR_ORIENTATION_CHANGE_COMPLETE = 1 << 3; boolean mWallpaperForceHidingChanged = false; boolean mWallpaperMayChange = false; @@ -1214,7 +1265,7 @@ public class WindowManagerService extends IWindowManager.Stub AppWindowToken token = curTarget.mAppToken; WindowState highestTarget = null; int highestPos = 0; - if (token.animating || token.animation != null) { + if (token.mAppAnimator.animating || token.mAppAnimator.animation != null) { int pos = localmWindows.indexOf(curTarget); while (pos >= 0) { WindowState win = localmWindows.get(pos); @@ -1274,7 +1325,7 @@ public class WindowManagerService extends IWindowManager.Stub mInputMethodTarget = w; mInputMethodTargetWaitingAnim = false; if (w.mAppToken != null) { - setInputMethodAnimLayerAdjustment(w.mAppToken.animLayerAdjustment); + setInputMethodAnimLayerAdjustment(w.mAppToken.mAppAnimator.animLayerAdjustment); } else { setInputMethodAnimLayerAdjustment(0); } @@ -1537,12 +1588,12 @@ public class WindowManagerService extends IWindowManager.Stub if (DEBUG_WALLPAPER) Slog.v(TAG, "Wallpaper vis: target " + wallpaperTarget + ", obscured=" + (wallpaperTarget != null ? Boolean.toString(wallpaperTarget.mObscured) : "??") + " anim=" + ((wallpaperTarget != null && wallpaperTarget.mAppToken != null) - ? wallpaperTarget.mAppToken.animation : null) + ? wallpaperTarget.mAppToken.mAppAnimator.animation : null) + " upper=" + mUpperWallpaperTarget + " lower=" + mLowerWallpaperTarget); return (wallpaperTarget != null && (!wallpaperTarget.mObscured || (wallpaperTarget.mAppToken != null - && wallpaperTarget.mAppToken.animation != null))) + && wallpaperTarget.mAppToken.mAppAnimator.animation != null))) || mUpperWallpaperTarget != null || mLowerWallpaperTarget != null; } @@ -1582,7 +1633,7 @@ public class WindowManagerService extends IWindowManager.Stub if (w != mAnimator.mWindowDetachedWallpaper && w.mAppToken != null) { // If this window's app token is hidden and not animating, // it is of no interest to us. - if (w.mAppToken.hidden && w.mAppToken.animation == null) { + if (w.mAppToken.hidden && w.mAppToken.mAppAnimator.animation == null) { if (DEBUG_WALLPAPER) Slog.v(TAG, "Skipping not hidden or animating token: " + w); continue; @@ -1597,7 +1648,7 @@ public class WindowManagerService extends IWindowManager.Stub foundW = w; foundI = i; if (w == mWallpaperTarget && ((w.mAppToken != null - && w.mAppToken.animation != null) + && w.mAppToken.mAppAnimator.animation != null) || w.mWinAnimator.mAnimation != null)) { // The current wallpaper target is animating, so we'll // look behind it for another possible target and figure @@ -1656,9 +1707,11 @@ public class WindowManagerService extends IWindowManager.Stub // animating, then we are in our super special mode! if (foundW != null && oldW != null) { boolean oldAnim = oldW.mWinAnimator.mAnimation != null - || (oldW.mAppToken != null && oldW.mAppToken.animation != null); + || (oldW.mAppToken != null + && oldW.mAppToken.mAppAnimator.animation != null); boolean foundAnim = foundW.mWinAnimator.mAnimation != null - || (foundW.mAppToken != null && foundW.mAppToken.animation != null); + || (foundW.mAppToken != null && + foundW.mAppToken.mAppAnimator.animation != null); if (DEBUG_WALLPAPER) { Slog.v(TAG, "New animation: " + foundAnim + " old animation: " + oldAnim); @@ -1711,10 +1764,10 @@ public class WindowManagerService extends IWindowManager.Stub // Is it time to stop animating? boolean lowerAnimating = mLowerWallpaperTarget.mWinAnimator.mAnimation != null || (mLowerWallpaperTarget.mAppToken != null - && mLowerWallpaperTarget.mAppToken.animation != null); + && mLowerWallpaperTarget.mAppToken.mAppAnimator.animation != null); boolean upperAnimating = mUpperWallpaperTarget.mWinAnimator.mAnimation != null || (mUpperWallpaperTarget.mAppToken != null - && mUpperWallpaperTarget.mAppToken.animation != null); + && mUpperWallpaperTarget.mAppToken.mAppAnimator.animation != null); if (!lowerAnimating || !upperAnimating) { if (DEBUG_WALLPAPER) { Slog.v(TAG, "No longer animating wallpaper targets!"); @@ -1736,7 +1789,7 @@ public class WindowManagerService extends IWindowManager.Stub // between two wallpaper targets. mWallpaperAnimLayerAdjustment = (mLowerWallpaperTarget == null && foundW.mAppToken != null) - ? foundW.mAppToken.animLayerAdjustment : 0; + ? foundW.mAppToken.mAppAnimator.animLayerAdjustment : 0; final int maxLayer = mPolicy.getMaxWallpaperLayer() * TYPE_LAYER_MULTIPLIER @@ -2007,6 +2060,7 @@ public class WindowManagerService extends IWindowManager.Stub winAnimator.computeShownFrameLocked(); // No need to lay out the windows - we can just set the wallpaper position // directly. + // TODO(cmautner): Don't move this from here, just lock the WindowAnimator. if (winAnimator.mSurfaceX != wallpaper.mShownFrame.left || winAnimator.mSurfaceY != wallpaper.mShownFrame.top) { Surface.openTransaction(); @@ -2306,7 +2360,7 @@ public class WindowManagerService extends IWindowManager.Stub + " mExiting=" + win.mExiting + " isAnimating=" + win.mWinAnimator.isAnimating() + " app-animation=" - + (win.mAppToken != null ? win.mAppToken.animation : null) + + (win.mAppToken != null ? win.mAppToken.mAppAnimator.animation : null) + " inPendingTransaction=" + (win.mAppToken != null ? win.mAppToken.inPendingTransaction : false) + " mDisplayFrozen=" + mDisplayFrozen); @@ -3174,13 +3228,13 @@ public class WindowManagerService extends IWindowManager.Stub } Slog.v(TAG, "Loaded animation " + a + " for " + wtoken, e); } - wtoken.setAnimation(a, initialized); + wtoken.mAppAnimator.setAnimation(a, initialized); } } else { - wtoken.clearAnimation(); + wtoken.mAppAnimator.clearAnimation(); } - return wtoken.animation != null; + return wtoken.mAppAnimator.animation != null; } // ------------------------------------------------------------- @@ -3325,7 +3379,7 @@ public class WindowManagerService extends IWindowManager.Stub "addAppToken()")) { throw new SecurityException("Requires MANAGE_APP_TOKENS permission"); } - + // Get the dispatching timeout here while we are not holding any locks so that it // can be cached by the AppWindowToken. The timeout value is used later by the // input dispatcher in code that does hold locks. If we did not cache the value @@ -3828,14 +3882,16 @@ public class WindowManagerService extends IWindowManager.Stub wtoken.clientHidden = ttoken.clientHidden; wtoken.sendAppVisibilityToClients(); } - if (ttoken.animation != null) { - wtoken.animation = ttoken.animation; - wtoken.animating = ttoken.animating; - wtoken.animLayerAdjustment = ttoken.animLayerAdjustment; - ttoken.animation = null; - ttoken.animLayerAdjustment = 0; - wtoken.updateLayers(); - ttoken.updateLayers(); + final AppWindowAnimator tAppAnimator = ttoken.mAppAnimator; + final AppWindowAnimator wAppAnimator = wtoken.mAppAnimator; + if (tAppAnimator.animation != null) { + wAppAnimator.animation = tAppAnimator.animation; + wAppAnimator.animating = tAppAnimator.animating; + wAppAnimator.animLayerAdjustment = tAppAnimator.animLayerAdjustment; + tAppAnimator.animation = null; + tAppAnimator.animLayerAdjustment = 0; + wAppAnimator.updateLayers(); + tAppAnimator.updateLayers(); } updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES, @@ -3860,18 +3916,20 @@ public class WindowManagerService extends IWindowManager.Stub mH.sendMessageAtFrontOfQueue(m); return; } - if (ttoken.thumbnail != null) { + final AppWindowAnimator tAppAnimator = ttoken.mAppAnimator; + final AppWindowAnimator wAppAnimator = wtoken.mAppAnimator; + if (tAppAnimator.thumbnail != null) { // The old token is animating with a thumbnail, transfer // that to the new token. - if (wtoken.thumbnail != null) { - wtoken.thumbnail.destroy(); + if (wAppAnimator.thumbnail != null) { + wAppAnimator.thumbnail.destroy(); } - wtoken.thumbnail = ttoken.thumbnail; - wtoken.thumbnailX = ttoken.thumbnailX; - wtoken.thumbnailY = ttoken.thumbnailY; - wtoken.thumbnailLayer = ttoken.thumbnailLayer; - wtoken.thumbnailAnimation = ttoken.thumbnailAnimation; - ttoken.thumbnail = null; + wAppAnimator.thumbnail = tAppAnimator.thumbnail; + wAppAnimator.thumbnailX = tAppAnimator.thumbnailX; + wAppAnimator.thumbnailY = tAppAnimator.thumbnailY; + wAppAnimator.thumbnailLayer = tAppAnimator.thumbnailLayer; + wAppAnimator.thumbnailAnimation = tAppAnimator.thumbnailAnimation; + tAppAnimator.thumbnail = null; } } } @@ -3961,12 +4019,12 @@ public class WindowManagerService extends IWindowManager.Stub boolean runningAppAnimation = false; if (transit != WindowManagerPolicy.TRANSIT_UNSET) { - if (wtoken.animation == sDummyAnimation) { - wtoken.animation = null; + if (wtoken.mAppAnimator.animation == sDummyAnimation) { + wtoken.mAppAnimator.animation = null; } applyAnimationLocked(wtoken, lp, transit, visible); changed = true; - if (wtoken.animation != null) { + if (wtoken.mAppAnimator.animation != null) { delayed = runningAppAnimation = true; } } @@ -4029,7 +4087,7 @@ public class WindowManagerService extends IWindowManager.Stub } } - if (wtoken.animation != null) { + if (wtoken.mAppAnimator.animation != null) { delayed = true; } @@ -4074,7 +4132,7 @@ public class WindowManagerService extends IWindowManager.Stub if (DEBUG_APP_TRANSITIONS) Slog.v( TAG, "Setting dummy animation on: " + wtoken); - wtoken.setDummyAnimation(); + wtoken.mAppAnimator.setDummyAnimation(); mOpeningApps.remove(wtoken); mClosingApps.remove(wtoken); wtoken.waitingToShow = wtoken.waitingToHide = false; @@ -4124,7 +4182,7 @@ public class WindowManagerService extends IWindowManager.Stub void unsetAppFreezingScreenLocked(AppWindowToken wtoken, boolean unfreezeSurfaceNow, boolean force) { - if (wtoken.freezingScreen) { + if (wtoken.mAppAnimator.freezingScreen) { if (DEBUG_ORIENTATION) Slog.v(TAG, "Clear freezing of " + wtoken + " force=" + force); final int N = wtoken.allAppWindows.size(); @@ -4142,7 +4200,7 @@ public class WindowManagerService extends IWindowManager.Stub } if (force || unfrozeWindows) { if (DEBUG_ORIENTATION) Slog.v(TAG, "No longer freezing: " + wtoken); - wtoken.freezingScreen = false; + wtoken.mAppAnimator.freezingScreen = false; mAppsFreezingScreen--; } if (unfreezeSurfaceNow) { @@ -4165,11 +4223,11 @@ public class WindowManagerService extends IWindowManager.Stub } Slog.i(TAG, "Set freezing of " + wtoken.appToken + ": hidden=" + wtoken.hidden + " freezing=" - + wtoken.freezingScreen, e); + + wtoken.mAppAnimator.freezingScreen, e); } if (!wtoken.hiddenRequested) { - if (!wtoken.freezingScreen) { - wtoken.freezingScreen = true; + if (!wtoken.mAppAnimator.freezingScreen) { + wtoken.mAppAnimator.freezingScreen = true; mAppsFreezingScreen++; if (mAppsFreezingScreen == 1) { startFreezingDisplayLocked(false); @@ -4222,7 +4280,7 @@ public class WindowManagerService extends IWindowManager.Stub } final long origId = Binder.clearCallingIdentity(); if (DEBUG_ORIENTATION) Slog.v(TAG, "Clear freezing of " + token - + ": hidden=" + wtoken.hidden + " freezing=" + wtoken.freezingScreen); + + ": hidden=" + wtoken.hidden + " freezing=" + wtoken.mAppAnimator.freezingScreen); unsetAppFreezingScreenLocked(wtoken, true, force); Binder.restoreCallingIdentity(origId); } @@ -4257,8 +4315,8 @@ public class WindowManagerService extends IWindowManager.Stub } if (DEBUG_APP_TRANSITIONS) Slog.v( TAG, "Removing app " + wtoken + " delayed=" + delayed - + " animation=" + wtoken.animation - + " animating=" + wtoken.animating); + + " animation=" + wtoken.mAppAnimator.animation + + " animating=" + wtoken.mAppAnimator.animating); if (delayed) { // set the token aside because it has an active animation to be finished if (DEBUG_ADD_REMOVE || DEBUG_TOKEN_MOVEMENT) Slog.v(TAG, @@ -4268,9 +4326,8 @@ public class WindowManagerService extends IWindowManager.Stub // Make sure there is no animation running on this token, // so any windows associated with it will be removed as // soon as their animations are complete - wtoken.clearAnimation(); - wtoken.animation = null; - wtoken.animating = false; + wtoken.mAppAnimator.clearAnimation(); + wtoken.mAppAnimator.animating = false; } if (DEBUG_ADD_REMOVE || DEBUG_TOKEN_MOVEMENT) Slog.v(TAG, "removeAppToken: " + wtoken); @@ -6657,6 +6714,7 @@ public class WindowManagerService extends IWindowManager.Stub public static final int ANIMATOR_WHAT_OFFSET = 100000; public static final int SET_TRANSPARENT_REGION = ANIMATOR_WHAT_OFFSET + 1; public static final int SET_WALLPAPER_OFFSET = ANIMATOR_WHAT_OFFSET + 2; + public static final int SET_DIM_PARAMETERS = ANIMATOR_WHAT_OFFSET + 3; private Session mLastReportedHold; @@ -6980,14 +7038,16 @@ public class WindowManagerService extends IWindowManager.Stub case APP_FREEZE_TIMEOUT: { synchronized (mWindowMap) { - Slog.w(TAG, "App freeze timeout expired."); - int i = mAppTokens.size(); - while (i > 0) { - i--; - AppWindowToken tok = mAppTokens.get(i); - if (tok.freezingScreen) { - Slog.w(TAG, "Force clearing freeze: " + tok); - unsetAppFreezingScreenLocked(tok, true, true); + synchronized (mAnimator) { + Slog.w(TAG, "App freeze timeout expired."); + int i = mAppTokens.size(); + while (i > 0) { + i--; + AppWindowToken tok = mAppTokens.get(i); + if (tok.mAppAnimator.freezingScreen) { + Slog.w(TAG, "Force clearing freeze: " + tok); + unsetAppFreezingScreenLocked(tok, true, true); + } } } } @@ -7069,6 +7129,7 @@ public class WindowManagerService extends IWindowManager.Stub } case BULK_UPDATE_PARAMETERS: { + // Used to send multiple changes from the animation side to the layout side. synchronized (mWindowMap) { // TODO(cmautner): As the number of bits grows, use masks of bit groups to // eliminate unnecessary tests. @@ -7078,6 +7139,12 @@ public class WindowManagerService extends IWindowManager.Stub if ((msg.arg1 & LayoutFields.SET_WALLPAPER_MAY_CHANGE) != 0) { mInnerFields.mWallpaperMayChange = true; } + if ((msg.arg1 & LayoutFields.SET_FORCE_HIDING_CHANGED) != 0) { + mInnerFields.mWallpaperForceHidingChanged = true; + } + if ((msg.arg1 & LayoutFields.CLEAR_ORIENTATION_CHANGE_COMPLETE) != 0) { + mInnerFields.mOrientationChangeComplete = false; + } requestTraversalLocked(); } @@ -7087,21 +7154,34 @@ public class WindowManagerService extends IWindowManager.Stub // Animation messages. Move to Window{State}Animator case SET_TRANSPARENT_REGION: { // TODO(cmautner): Remove sync. - synchronized (mWindowMap) { + synchronized (mAnimator) { Pair<WindowStateAnimator, Region> pair = (Pair<WindowStateAnimator, Region>) msg.obj; final WindowStateAnimator winAnimator = pair.first; winAnimator.setTransparentRegionHint(pair.second); } + + scheduleAnimationLocked(); break; } case SET_WALLPAPER_OFFSET: { // TODO(cmautner): Remove sync. - synchronized (mWindowMap) { + synchronized (mAnimator) { final WindowStateAnimator winAnimator = (WindowStateAnimator) msg.obj; winAnimator.setWallpaperOffset(msg.arg1, msg.arg2); } + + scheduleAnimationLocked(); + break; + } + + case SET_DIM_PARAMETERS: { + synchronized (mAnimator) { + mAnimator.mDimParams = (DimAnimator.Parameters) msg.obj; + } + + scheduleAnimationLocked(); break; } } @@ -7450,9 +7530,11 @@ public class WindowManagerService extends IWindowManager.Stub w.mLayer = curLayer; } if (w.mTargetAppToken != null) { - w.mWinAnimator.mAnimLayer = w.mLayer + w.mTargetAppToken.animLayerAdjustment; + w.mWinAnimator.mAnimLayer = + w.mLayer + w.mTargetAppToken.mAppAnimator.animLayerAdjustment; } else if (w.mAppToken != null) { - w.mWinAnimator.mAnimLayer = w.mLayer + w.mAppToken.animLayerAdjustment; + w.mWinAnimator.mAnimLayer = + w.mLayer + w.mAppToken.mAppAnimator.animLayerAdjustment; } else { w.mWinAnimator.mAnimLayer = w.mLayer; } @@ -7886,10 +7968,10 @@ public class WindowManagerService extends IWindowManager.Stub AppWindowToken wtoken = mOpeningApps.get(i); if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Now opening app" + wtoken); - wtoken.clearThumbnail(); + wtoken.mAppAnimator.clearThumbnail(); wtoken.reportedVisible = false; wtoken.inPendingTransaction = false; - wtoken.animation = null; + wtoken.mAppAnimator.animation = null; setTokenVisibilityLocked(wtoken, animLp, true, transit, false); wtoken.updateReportedVisibilityLocked(); @@ -7914,9 +7996,9 @@ public class WindowManagerService extends IWindowManager.Stub AppWindowToken wtoken = mClosingApps.get(i); if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Now closing app" + wtoken); - wtoken.clearThumbnail(); + wtoken.mAppAnimator.clearThumbnail(); wtoken.inPendingTransaction = false; - wtoken.animation = null; + wtoken.mAppAnimator.animation = null; setTokenVisibilityLocked(wtoken, animLp, false, transit, false); wtoken.updateReportedVisibilityLocked(); @@ -7928,7 +8010,7 @@ public class WindowManagerService extends IWindowManager.Stub } if (mNextAppTransitionThumbnail != null && topOpeningApp != null - && topOpeningApp.animation != null) { + && topOpeningApp.mAppAnimator.animation != null) { // This thumbnail animation is very special, we need to have // an extra surface with the thumbnail included with the animation. Rect dirty = new Rect(0, 0, mNextAppTransitionThumbnail.getWidth(), @@ -7937,7 +8019,7 @@ public class WindowManagerService extends IWindowManager.Stub Surface surface = new Surface(mFxSession, Process.myPid(), "thumbnail anim", 0, dirty.width(), dirty.height(), PixelFormat.TRANSLUCENT, Surface.HIDDEN); - topOpeningApp.thumbnail = surface; + topOpeningApp.mAppAnimator.thumbnail = surface; if (SHOW_TRANSACTIONS) Slog.i(TAG, " THUMBNAIL " + surface + ": CREATE"); Surface drawSurface = new Surface(); @@ -7946,17 +8028,17 @@ public class WindowManagerService extends IWindowManager.Stub c.drawBitmap(mNextAppTransitionThumbnail, 0, 0, null); drawSurface.unlockCanvasAndPost(c); drawSurface.release(); - topOpeningApp.thumbnailLayer = topOpeningLayer; + topOpeningApp.mAppAnimator.thumbnailLayer = topOpeningLayer; Animation anim = createThumbnailAnimationLocked(transit, true, true); - topOpeningApp.thumbnailAnimation = anim; + topOpeningApp.mAppAnimator.thumbnailAnimation = anim; anim.restrictDuration(MAX_ANIMATION_DURATION); anim.scaleCurrentDuration(mTransitionAnimationScale); - topOpeningApp.thumbnailX = mNextAppTransitionStartX; - topOpeningApp.thumbnailY = mNextAppTransitionStartY; + topOpeningApp.mAppAnimator.thumbnailX = mNextAppTransitionStartX; + topOpeningApp.mAppAnimator.thumbnailY = mNextAppTransitionStartY; } catch (Surface.OutOfResourcesException e) { Slog.e(TAG, "Can't allocate thumbnail surface w=" + dirty.width() + " h=" + dirty.height(), e); - topOpeningApp.clearThumbnail(); + topOpeningApp.mAppAnimator.clearThumbnail(); } } @@ -8038,7 +8120,6 @@ public class WindowManagerService extends IWindowManager.Stub } } mInnerFields.mAdjResult |= adjustWallpaperWindowsLocked(); - mInnerFields.mWallpaperForceHidingChanged = false; if (DEBUG_WALLPAPER) Slog.v(TAG, "****** OLD: " + oldWallpaper + " NEW: " + mWallpaperTarget + " LOWER: " + mLowerWallpaperTarget); @@ -8177,16 +8258,16 @@ public class WindowManagerService extends IWindowManager.Stub if (!mInnerFields.mDimming) { //Slog.i(TAG, "DIM BEHIND: " + w); mInnerFields.mDimming = true; - if (mDimAnimator == null) { - mDimAnimator = new DimAnimator(mFxSession); - } + final int width, height; if (attrs.type == WindowManager.LayoutParams.TYPE_BOOT_PROGRESS) { - mDimAnimator.show(mCurDisplayWidth, mCurDisplayHeight); + width = mCurDisplayWidth; + height = mCurDisplayHeight; } else { - mDimAnimator.show(innerDw, innerDh); + width = innerDw; + height = innerDh; } - mDimAnimator.updateParameters(mContext.getResources(), - w, currentTime); + mAnimator.startDimming(w.mWinAnimator, w.mExiting ? 0 : w.mAttrs.dimAmount, + width, height); } } } @@ -8223,7 +8304,6 @@ public class WindowManagerService extends IWindowManager.Stub mExitingAppTokens.get(i).hasVisible = false; } - mInnerFields.mOrientationChangeComplete = true; mInnerFields.mHoldScreen = null; mInnerFields.mScreenBrightness = -1; mInnerFields.mButtonBrightness = -1; @@ -8252,7 +8332,6 @@ public class WindowManagerService extends IWindowManager.Stub } try { - mInnerFields.mWallpaperForceHidingChanged = false; int repeats = 0; do { @@ -8263,7 +8342,8 @@ public class WindowManagerService extends IWindowManager.Stub break; } - if (DEBUG_LAYOUT_REPEATS) debugLayoutRepeats("On entry to LockedInner"); + if (DEBUG_LAYOUT_REPEATS) debugLayoutRepeats("On entry to LockedInner", + mPendingLayoutChanges); if ((mPendingLayoutChanges & WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER) != 0) { if ((adjustWallpaperWindowsLocked()&ADJUST_WALLPAPER_LAYERS_CHANGED) != 0) { @@ -8294,7 +8374,8 @@ public class WindowManagerService extends IWindowManager.Stub // FIRST AND ONE HALF LOOP: Make WindowManagerPolicy think // it is animating. mPendingLayoutChanges = 0; - if (DEBUG_LAYOUT_REPEATS) debugLayoutRepeats("loop number " + mLayoutRepeatCount); + if (DEBUG_LAYOUT_REPEATS) debugLayoutRepeats("loop number " + mLayoutRepeatCount, + mPendingLayoutChanges); mPolicy.beginAnimationLw(dw, dh); for (i = mWindows.size() - 1; i >= 0; i--) { WindowState w = mWindows.get(i); @@ -8303,7 +8384,8 @@ public class WindowManagerService extends IWindowManager.Stub } } mPendingLayoutChanges |= mPolicy.finishAnimationLw(); - if (DEBUG_LAYOUT_REPEATS) debugLayoutRepeats("after finishAnimationLw"); + if (DEBUG_LAYOUT_REPEATS) debugLayoutRepeats("after finishAnimationLw", + mPendingLayoutChanges); } while (mPendingLayoutChanges != 0); final boolean someoneLosingFocus = !mLosingFocus.isEmpty(); @@ -8337,6 +8419,9 @@ public class WindowManagerService extends IWindowManager.Stub updateWallpaperVisibilityLocked(); } } + if (!mInnerFields.mDimming) { + mAnimator.stopDimming(); + } } catch (RuntimeException e) { Log.wtf(TAG, "Unhandled exception in Window Manager", e); } finally { @@ -8348,7 +8433,8 @@ public class WindowManagerService extends IWindowManager.Stub // to go. if (mAppTransitionReady) { mPendingLayoutChanges |= handleAppTransitionReadyLocked(); - if (DEBUG_LAYOUT_REPEATS) debugLayoutRepeats("after handleAppTransitionReadyLocked"); + if (DEBUG_LAYOUT_REPEATS) debugLayoutRepeats("after handleAppTransitionReadyLocked", + mPendingLayoutChanges); } mInnerFields.mAdjResult = 0; @@ -8361,7 +8447,8 @@ public class WindowManagerService extends IWindowManager.Stub // be out of sync with it. So here we will just rebuild the // entire app window list. Fun! mPendingLayoutChanges |= handleAnimatingStoppedAndTransitionLocked(); - if (DEBUG_LAYOUT_REPEATS) debugLayoutRepeats("after handleAnimStopAndXitionLock"); + if (DEBUG_LAYOUT_REPEATS) debugLayoutRepeats("after handleAnimStopAndXitionLock", + mPendingLayoutChanges); } if (mInnerFields.mWallpaperForceHidingChanged && mPendingLayoutChanges == 0 && @@ -8373,9 +8460,10 @@ public class WindowManagerService extends IWindowManager.Stub // hard -- the wallpaper now needs to be shown behind // something that was hidden. mPendingLayoutChanges |= animateAwayWallpaperLocked(); - if (DEBUG_LAYOUT_REPEATS) debugLayoutRepeats("after animateAwayWallpaperLocked"); + if (DEBUG_LAYOUT_REPEATS) debugLayoutRepeats("after animateAwayWallpaperLocked", + mPendingLayoutChanges); } - + mInnerFields.mWallpaperForceHidingChanged = false; if (mInnerFields.mWallpaperMayChange) { if (WindowManagerService.DEBUG_WALLPAPER) Slog.v(TAG, @@ -8405,7 +8493,7 @@ public class WindowManagerService extends IWindowManager.Stub if (mLayoutNeeded) { mPendingLayoutChanges |= PhoneWindowManager.FINISH_LAYOUT_REDO_LAYOUT; - if (DEBUG_LAYOUT_REPEATS) debugLayoutRepeats("mLayoutNeeded"); + if (DEBUG_LAYOUT_REPEATS) debugLayoutRepeats("mLayoutNeeded", mPendingLayoutChanges); } final int N = mWindows.size(); @@ -8426,7 +8514,8 @@ public class WindowManagerService extends IWindowManager.Stub mInnerFields.mWallpaperMayChange = true; mPendingLayoutChanges |= WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; if (WindowManagerService.DEBUG_LAYOUT_REPEATS) { - debugLayoutRepeats("updateWindowsAndWallpaperLocked 1"); + debugLayoutRepeats("updateWindowsAndWallpaperLocked 1", + mPendingLayoutChanges); } } } @@ -8455,7 +8544,7 @@ public class WindowManagerService extends IWindowManager.Stub // associated with exiting/removed apps mAnimator.animate(); mPendingLayoutChanges |= mAnimator.mPendingLayoutChanges; - if (DEBUG_LAYOUT_REPEATS) debugLayoutRepeats("after animate()"); + if (DEBUG_LAYOUT_REPEATS) debugLayoutRepeats("after animate()", mPendingLayoutChanges); if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION performLayoutAndPlaceSurfaces"); @@ -8550,9 +8639,8 @@ public class WindowManagerService extends IWindowManager.Stub // Make sure there is no animation running on this token, // so any windows associated with it will be removed as // soon as their animations are complete - token.clearAnimation(); - token.animation = null; - token.animating = false; + token.mAppAnimator.clearAnimation(); + token.mAppAnimator.animating = false; if (DEBUG_ADD_REMOVE || DEBUG_TOKEN_MOVEMENT) Slog.v(TAG, "performLayout: App token exiting now removed" + token); mAppTokens.remove(token); @@ -8629,6 +8717,7 @@ public class WindowManagerService extends IWindowManager.Stub !mInnerFields.mUpdateRotation) { checkDrawnWindowsLocked(); } + mInnerFields.mOrientationChangeComplete = true; // Check to see if we are now in a state where the screen should // be enabled, because the window obscured flags have changed. @@ -8822,7 +8911,7 @@ public class WindowManagerService extends IWindowManager.Stub TAG, "Changing focus from " + mCurrentFocus + " to " + newFocus); final WindowState oldFocus = mCurrentFocus; mCurrentFocus = newFocus; - mAnimator.setCurrentFocus(mCurrentFocus); + mAnimator.setCurrentFocus(newFocus); mLosingFocus.remove(newFocus); int focusChanged = mPolicy.focusChangedLw(oldFocus, newFocus); @@ -8869,12 +8958,11 @@ public class WindowManagerService extends IWindowManager.Stub WindowState result = null; WindowState win; - int i = mWindows.size() - 1; int nextAppIndex = mAppTokens.size()-1; WindowToken nextApp = nextAppIndex >= 0 ? mAppTokens.get(nextAppIndex) : null; - while (i >= 0) { + for (int i = mWindows.size() - 1; i >= 0; i--) { win = mWindows.get(i); if (localLOGV || DEBUG_FOCUS) Slog.v( @@ -8887,7 +8975,6 @@ public class WindowManagerService extends IWindowManager.Stub // If this window's application has been removed, just skip it. if (thisApp != null && thisApp.removed) { - i--; continue; } @@ -8927,8 +9014,6 @@ public class WindowManagerService extends IWindowManager.Stub result = win; break; } - - i--; } return result; @@ -9476,12 +9561,6 @@ public class WindowManagerService extends IWindowManager.Stub pw.print(" mSystemBooted="); pw.print(mSystemBooted); pw.print(" mDisplayEnabled="); pw.println(mDisplayEnabled); pw.print(" mLayoutNeeded="); pw.println(mLayoutNeeded); - if (mDimAnimator != null) { - pw.println(" mDimAnimator:"); - mDimAnimator.printTo(" ", pw); - } else { - pw.println( " no DimAnimator "); - } pw.print(" mDisplayFrozen="); pw.print(mDisplayFrozen); pw.print(" mWindowsFreezingScreen="); pw.print(mWindowsFreezingScreen); pw.print(" mAppsFreezingScreen="); pw.print(mAppsFreezingScreen); @@ -9691,10 +9770,10 @@ public class WindowManagerService extends IWindowManager.Stub requestTraversalLocked(); } - void debugLayoutRepeats(final String msg) { + void debugLayoutRepeats(final String msg, int pendingLayoutChanges) { if (mLayoutRepeatCount >= LAYOUT_REPEAT_THRESHOLD) { - Slog.v(TAG, "Layouts looping: " + msg); - Slog.v(TAG, "mPendingLayoutChanges = 0x" + Integer.toHexString(mPendingLayoutChanges)); + Slog.v(TAG, "Layouts looping: " + msg + ", mPendingLayoutChanges = 0x" + + Integer.toHexString(pendingLayoutChanges)); } } diff --git a/services/java/com/android/server/wm/WindowState.java b/services/java/com/android/server/wm/WindowState.java index b74aa61..05797a4 100644 --- a/services/java/com/android/server/wm/WindowState.java +++ b/services/java/com/android/server/wm/WindowState.java @@ -65,6 +65,9 @@ final class WindowState implements WindowManagerPolicy.WindowState { WindowToken mRootToken; AppWindowToken mAppToken; AppWindowToken mTargetAppToken; + + // mAttrs.flags is tested in animation without being locked. If the bits tested are ever + // modified they will need to be locked. final WindowManager.LayoutParams mAttrs = new WindowManager.LayoutParams(); final DeathRecipient mDeathRecipient; final WindowState mAttachedWindow; @@ -621,7 +624,7 @@ final class WindowState implements WindowManagerPolicy.WindowState { } final AppWindowToken atoken = mAppToken; final boolean animating = atoken != null - ? (atoken.animation != null) : false; + ? (atoken.mAppAnimator.animation != null) : false; return mHasSurface && !mDestroying && !mExiting && (atoken == null ? mPolicyVisibility : !atoken.hiddenRequested) && ((!mAttachedHidden && mViewVisibility == View.VISIBLE @@ -637,7 +640,7 @@ final class WindowState implements WindowManagerPolicy.WindowState { public boolean isWinVisibleLw() { final AppWindowToken atoken = mAppToken; return mHasSurface && mPolicyVisibility && !mAttachedHidden - && (atoken == null || !atoken.hiddenRequested || atoken.animating) + && (atoken == null || !atoken.hiddenRequested || atoken.mAppAnimator.animating) && !mExiting && !mDestroying; } @@ -685,7 +688,7 @@ final class WindowState implements WindowManagerPolicy.WindowState { final AppWindowToken atoken = mAppToken; if (atoken != null) { return ((!mAttachedHidden && !atoken.hiddenRequested) - || mWinAnimator.mAnimation != null || atoken.animation != null); + || mWinAnimator.mAnimation != null || atoken.mAppAnimator.animation != null); } return !mAttachedHidden || mWinAnimator.mAnimation != null; } @@ -703,7 +706,7 @@ final class WindowState implements WindowManagerPolicy.WindowState { && ((!mAttachedHidden && mViewVisibility == View.VISIBLE && !mRootToken.hidden) || mWinAnimator.mAnimation != null - || ((mAppToken != null) && (mAppToken.animation != null))); + || ((mAppToken != null) && (mAppToken.mAppAnimator.animation != null))); } /** @@ -746,7 +749,7 @@ final class WindowState implements WindowManagerPolicy.WindowState { return (mAttrs.format == PixelFormat.OPAQUE || mAttrs.type == TYPE_WALLPAPER) && isDrawnLw() && mWinAnimator.mAnimation == null - && (mAppToken == null || mAppToken.animation == null); + && (mAppToken == null || mAppToken.mAppAnimator.animation == null); } /** @@ -962,8 +965,9 @@ final class WindowState implements WindowManagerPolicy.WindowState { pw.print(prefix); pw.print("mBaseLayer="); pw.print(mBaseLayer); pw.print(" mSubLayer="); pw.print(mSubLayer); pw.print(" mAnimLayer="); pw.print(mLayer); pw.print("+"); - pw.print((mTargetAppToken != null ? mTargetAppToken.animLayerAdjustment - : (mAppToken != null ? mAppToken.animLayerAdjustment : 0))); + pw.print((mTargetAppToken != null ? + mTargetAppToken.mAppAnimator.animLayerAdjustment + : (mAppToken != null ? mAppToken.mAppAnimator.animLayerAdjustment : 0))); pw.print("="); pw.print(mWinAnimator.mAnimLayer); pw.print(" mLastLayer="); pw.println(mWinAnimator.mLastLayer); } diff --git a/services/java/com/android/server/wm/WindowStateAnimator.java b/services/java/com/android/server/wm/WindowStateAnimator.java index 4979a4c..6d0921e 100644 --- a/services/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/java/com/android/server/wm/WindowStateAnimator.java @@ -5,6 +5,8 @@ package com.android.server.wm; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; +import static com.android.server.wm.WindowManagerService.LayoutFields.CLEAR_ORIENTATION_CHANGE_COMPLETE; + import android.content.Context; import android.graphics.Matrix; import android.graphics.PixelFormat; @@ -160,7 +162,7 @@ class WindowStateAnimator { return mAnimation != null || (attached != null && attached.mWinAnimator.mAnimation != null) || (atoken != null && - (atoken.animation != null + (atoken.mAppAnimator.animation != null || atoken.inPendingTransaction)); } @@ -226,7 +228,7 @@ class WindowStateAnimator { } mHasLocalTransformation = false; if ((!mLocalAnimating || mAnimationIsEntrance) && mWin.mAppToken != null - && mWin.mAppToken.animation != null) { + && mWin.mAppToken.mAppAnimator.animation != null) { // When our app token is animating, we kind-of pretend like // we are as well. Note the mLocalAnimating mAnimationIsEntrance // part of this check means that we will only do this if @@ -313,8 +315,9 @@ class WindowStateAnimator { } finishExit(); - mService.mPendingLayoutChanges |= WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM; - if (WindowManagerService.DEBUG_LAYOUT_REPEATS) mService.debugLayoutRepeats("WindowState"); + mAnimator.mPendingLayoutChanges |= WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM; + if (WindowManagerService.DEBUG_LAYOUT_REPEATS) mService.debugLayoutRepeats( + "WindowStateAnimator", mAnimator.mPendingLayoutChanges); if (mWin.mAppToken != null) { mWin.mAppToken.updateReportedVisibilityLocked(); @@ -613,9 +616,10 @@ class WindowStateAnimator { Transformation attachedTransformation = (mAttachedWindow != null && mAttachedWindow.mWinAnimator.mHasLocalTransformation) ? mAttachedWindow.mWinAnimator.mTransformation : null; - Transformation appTransformation = - (mWin.mAppToken != null && mWin.mAppToken.hasTransformation) - ? mWin.mAppToken.transformation : null; + final AppWindowAnimator appAnimator = + mWin.mAppToken == null ? null : mWin.mAppToken.mAppAnimator; + Transformation appTransformation = (appAnimator != null && appAnimator.hasTransformation) + ? appAnimator.transformation : null; // Wallpapers are animated based on the "real" window they // are currently targeting. @@ -629,11 +633,13 @@ class WindowStateAnimator { Slog.v(TAG, "WP target attached xform: " + attachedTransformation); } } - if (mService.mWallpaperTarget.mAppToken != null && - mService.mWallpaperTarget.mAppToken.hasTransformation && - mService.mWallpaperTarget.mAppToken.animation != null && - !mService.mWallpaperTarget.mAppToken.animation.getDetachWallpaper()) { - appTransformation = mService.mWallpaperTarget.mAppToken.transformation; + final AppWindowAnimator wpAppAnimator = mService.mWallpaperTarget.mAppToken == null + ? null : mService.mWallpaperTarget.mAppToken.mAppAnimator; + if (wpAppAnimator != null && + wpAppAnimator.hasTransformation && + wpAppAnimator.animation != null && + !wpAppAnimator.animation.getDetachWallpaper()) { + appTransformation = wpAppAnimator.transformation; if (WindowManagerService.DEBUG_WALLPAPER && appTransformation != null) { Slog.v(TAG, "WP target app xform: " + appTransformation); } @@ -916,7 +922,7 @@ class WindowStateAnimator { if (displayed) { if (w.mOrientationChanging) { if (!w.isDrawnLw()) { - mService.mInnerFields.mOrientationChangeComplete = false; + mAnimator.mBulkUpdateParams |= CLEAR_ORIENTATION_CHANGE_COMPLETE; if (DEBUG_ORIENTATION) Slog.v(TAG, "Orientation continue waiting for draw in " + w); } else { @@ -981,7 +987,7 @@ class WindowStateAnimator { + (mWin.mAppToken != null ? mWin.mAppToken.hidden : false) + " animating=" + mAnimating + " tok animating=" - + (mWin.mAppToken != null ? mWin.mAppToken.animating : false)); + + (mWin.mAppToken != null ? mWin.mAppToken.mAppAnimator.animating : false)); if (!showSurfaceRobustlyLocked()) { return false; } diff --git a/services/sensorservice/SensorService.cpp b/services/sensorservice/SensorService.cpp index dd6c426..16ddd91 100644 --- a/services/sensorservice/SensorService.cpp +++ b/services/sensorservice/SensorService.cpp @@ -35,6 +35,7 @@ #include <gui/ISensorServer.h> #include <gui/ISensorEventConnection.h> +#include <gui/SensorEventQueue.h> #include <hardware/sensors.h> @@ -587,10 +588,9 @@ status_t SensorService::SensorEventConnection::sendEvents( count = numEvents; } - if (count == 0) - return 0; - - ssize_t size = mChannel->write(scratch, count*sizeof(sensors_event_t)); + // NOTE: ASensorEvent and sensors_event_t are the same type + ssize_t size = SensorEventQueue::write(mChannel, + reinterpret_cast<ASensorEvent const*>(scratch), count); if (size == -EAGAIN) { // the destination doesn't accept events anymore, it's probably // full. For now, we just drop the events on the floor. @@ -598,9 +598,6 @@ status_t SensorService::SensorEventConnection::sendEvents( return size; } - //ALOGE_IF(size<0, "dropping %d events on the floor (%s)", - // count, strerror(-size)); - return size < 0 ? status_t(size) : status_t(NO_ERROR); } diff --git a/test-runner/src/android/test/AndroidTestRunner.java b/test-runner/src/android/test/AndroidTestRunner.java index fc9832c..30876d0 100644 --- a/test-runner/src/android/test/AndroidTestRunner.java +++ b/test-runner/src/android/test/AndroidTestRunner.java @@ -28,6 +28,7 @@ import junit.framework.TestResult; import junit.framework.TestSuite; import junit.runner.BaseTestRunner; +import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.List; @@ -91,15 +92,35 @@ public class AndroidTestRunner extends BaseTestRunner { private TestCase buildSingleTestMethod(Class testClass, String testMethodName) { try { - TestCase testCase = (TestCase) testClass.newInstance(); + Constructor c = testClass.getConstructor(); + return newSingleTestMethod(testClass, testMethodName, c); + } catch (NoSuchMethodException e) { + } + + try { + Constructor c = testClass.getConstructor(String.class); + return newSingleTestMethod(testClass, testMethodName, c, testMethodName); + } catch (NoSuchMethodException e) { + } + + return null; + } + + private TestCase newSingleTestMethod(Class testClass, String testMethodName, + Constructor constructor, Object... args) { + try { + TestCase testCase = (TestCase) constructor.newInstance(args); testCase.setName(testMethodName); return testCase; } catch (IllegalAccessException e) { runFailed("Could not access test class. Class: " + testClass.getName()); } catch (InstantiationException e) { runFailed("Could not instantiate test class. Class: " + testClass.getName()); + } catch (IllegalArgumentException e) { + runFailed("Illegal argument passed to constructor. Class: " + testClass.getName()); + } catch (InvocationTargetException e) { + runFailed("Constructor thew an exception. Class: " + testClass.getName()); } - return null; } diff --git a/tests/BiDiTests/res/layout/textview_alignment_ltr.xml b/tests/BiDiTests/res/layout/textview_alignment_ltr.xml new file mode 100644 index 0000000..0e1adba --- /dev/null +++ b/tests/BiDiTests/res/layout/textview_alignment_ltr.xml @@ -0,0 +1,578 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2012 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. +--> + +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/textview_alignment_ltr" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:layoutDirection="ltr"> + + <TableLayout android:orientation="vertical" + android:layout_width="wrap_content" + android:layout_height="wrap_content"> + + <TableRow> + <TextView android:text="(unspecified)" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/textview_text" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/hebrew_text" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + <TableRow> + <TextView android:text="gravity (default)" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/textview_text" + android:textAlignment="gravity" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/hebrew_text" + android:textAlignment="gravity" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + <TableRow> + <TextView android:text="gravity left" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/textview_text" + android:textAlignment="gravity" + android:gravity="left" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/hebrew_text" + android:textAlignment="gravity" + android:gravity="left" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + <TableRow> + <TextView android:text="gravity right" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/textview_text" + android:textAlignment="gravity" + android:gravity="right" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/hebrew_text" + android:textAlignment="gravity" + android:gravity="right" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + <TableRow> + <TextView android:text="gravity start" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/textview_text" + android:textAlignment="gravity" + android:gravity="start" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/hebrew_text" + android:textAlignment="gravity" + android:gravity="start" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + <TableRow> + <TextView android:text="gravity end" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/textview_text" + android:textAlignment="gravity" + android:gravity="end" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/hebrew_text" + android:textAlignment="gravity" + android:gravity="end" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + <TableRow> + <TextView android:text="gravity center" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/textview_text" + android:textAlignment="gravity" + android:gravity="center" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/hebrew_text" + android:textAlignment="gravity" + android:gravity="center" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + <TableRow> + <TextView android:text="gravity center_horizontal" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/textview_text" + android:textAlignment="gravity" + android:gravity="center_horizontal" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/hebrew_text" + android:textAlignment="gravity" + android:gravity="center_horizontal" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + <TableRow> + <TextView android:text="center" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/textview_text" + android:textAlignment="center" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/hebrew_text" + android:textAlignment="center" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + <TableRow> + <TextView android:text="textStart" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/textview_text" + android:textAlignment="textStart" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/hebrew_text" + android:textAlignment="textStart" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + <TableRow> + <TextView android:text="textEnd" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/textview_text" + android:textAlignment="textEnd" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/hebrew_text" + android:textAlignment="textEnd" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + <TableRow> + <TextView android:text="viewStart" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginRight="7dip" + android:layout_marginLeft="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/textview_text" + android:textAlignment="viewStart" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/hebrew_text" + android:textAlignment="viewStart" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + <TableRow> + <TextView android:text="viewEnd" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginRight="7dip" + android:layout_marginLeft="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/textview_text" + android:textAlignment="viewEnd" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/hebrew_text" + android:textAlignment="viewEnd" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + <TableRow android:textAlignment="gravity" + android:gravity="center_horizontal"> + + <TextView android:text="inherit gravity (default)" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginRight="7dip" + android:layout_marginLeft="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/textview_text" + android:textAlignment="inherit" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/hebrew_text" + android:textAlignment="inherit" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + <TableRow android:textAlignment="center"> + + <TextView android:text="inherit gravity center" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginRight="7dip" + android:layout_marginLeft="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/textview_text" + android:textAlignment="inherit" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/hebrew_text" + android:textAlignment="inherit" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + <TableRow android:textAlignment="textStart"> + + <TextView android:text="inherit textStart" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginRight="7dip" + android:layout_marginLeft="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/textview_text" + android:textAlignment="inherit" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/hebrew_text" + android:textAlignment="inherit" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + <TableRow android:textAlignment="textEnd"> + + <TextView android:text="inherit textEnd" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginRight="7dip" + android:layout_marginLeft="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/textview_text" + android:textAlignment="inherit" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/hebrew_text" + android:textAlignment="inherit" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + <TableRow android:textAlignment="viewStart"> + + <TextView android:text="inherit viewStart" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginRight="7dip" + android:layout_marginLeft="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/textview_text" + android:textAlignment="inherit" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/hebrew_text" + android:textAlignment="inherit" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + <TableRow android:textAlignment="viewEnd"> + + <TextView android:text="inherit viewEnd" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginRight="7dip" + android:layout_marginLeft="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/textview_text" + android:textAlignment="inherit" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/hebrew_text" + android:textAlignment="inherit" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + </TableLayout> + +</FrameLayout> diff --git a/tests/BiDiTests/res/layout/textview_alignment_rtl.xml b/tests/BiDiTests/res/layout/textview_alignment_rtl.xml new file mode 100644 index 0000000..12a90d5 --- /dev/null +++ b/tests/BiDiTests/res/layout/textview_alignment_rtl.xml @@ -0,0 +1,578 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2012 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. +--> + +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/textview_alignment_rtl" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:layoutDirection="rtl"> + + <TableLayout android:orientation="vertical" + android:layout_width="wrap_content" + android:layout_height="wrap_content"> + + <TableRow> + <TextView android:text="(unspecified)" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/textview_text" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/hebrew_text" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + <TableRow> + <TextView android:text="gravity (default)" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/textview_text" + android:textAlignment="gravity" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/hebrew_text" + android:textAlignment="gravity" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + <TableRow> + <TextView android:text="gravity left" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/textview_text" + android:textAlignment="gravity" + android:gravity="left" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/hebrew_text" + android:textAlignment="gravity" + android:gravity="left" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + <TableRow> + <TextView android:text="gravity right" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/textview_text" + android:textAlignment="gravity" + android:gravity="right" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/hebrew_text" + android:textAlignment="gravity" + android:gravity="right" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + <TableRow> + <TextView android:text="gravity start" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/textview_text" + android:textAlignment="gravity" + android:gravity="start" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/hebrew_text" + android:textAlignment="gravity" + android:gravity="start" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + <TableRow> + <TextView android:text="gravity end" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/textview_text" + android:textAlignment="gravity" + android:gravity="end" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/hebrew_text" + android:textAlignment="gravity" + android:gravity="end" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + <TableRow> + <TextView android:text="gravity center" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/textview_text" + android:textAlignment="gravity" + android:gravity="center" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/hebrew_text" + android:textAlignment="gravity" + android:gravity="center" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + <TableRow> + <TextView android:text="gravity center_horizontal" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/textview_text" + android:textAlignment="gravity" + android:gravity="center_horizontal" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/hebrew_text" + android:textAlignment="gravity" + android:gravity="center_horizontal" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + <TableRow> + <TextView android:text="center" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/textview_text" + android:textAlignment="center" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/hebrew_text" + android:textAlignment="center" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + <TableRow> + <TextView android:text="textStart" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/textview_text" + android:textAlignment="textStart" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/hebrew_text" + android:textAlignment="textStart" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + <TableRow> + <TextView android:text="textEnd" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/textview_text" + android:textAlignment="textEnd" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/hebrew_text" + android:textAlignment="textEnd" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + <TableRow> + <TextView android:text="viewStart" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginRight="7dip" + android:layout_marginLeft="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/textview_text" + android:textAlignment="viewStart" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/hebrew_text" + android:textAlignment="viewStart" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + <TableRow> + <TextView android:text="viewEnd" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginRight="7dip" + android:layout_marginLeft="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/textview_text" + android:textAlignment="viewEnd" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/hebrew_text" + android:textAlignment="viewEnd" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + <TableRow android:textAlignment="gravity" + android:gravity="center_horizontal"> + + <TextView android:text="inherit gravity (default)" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginRight="7dip" + android:layout_marginLeft="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/textview_text" + android:textAlignment="inherit" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/hebrew_text" + android:textAlignment="inherit" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + <TableRow android:textAlignment="center"> + + <TextView android:text="inherit gravity center" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginRight="7dip" + android:layout_marginLeft="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/textview_text" + android:textAlignment="inherit" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/hebrew_text" + android:textAlignment="inherit" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + <TableRow android:textAlignment="textStart"> + + <TextView android:text="inherit textStart" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginRight="7dip" + android:layout_marginLeft="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/textview_text" + android:textAlignment="inherit" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/hebrew_text" + android:textAlignment="inherit" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + <TableRow android:textAlignment="textEnd"> + + <TextView android:text="inherit textEnd" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginRight="7dip" + android:layout_marginLeft="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/textview_text" + android:textAlignment="inherit" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/hebrew_text" + android:textAlignment="inherit" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + <TableRow android:textAlignment="viewStart"> + + <TextView android:text="inherit viewStart" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginRight="7dip" + android:layout_marginLeft="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/textview_text" + android:textAlignment="inherit" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/hebrew_text" + android:textAlignment="inherit" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + <TableRow android:textAlignment="viewEnd"> + + <TextView android:text="inherit viewEnd" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:typeface="serif" + android:layout_marginRight="7dip" + android:layout_marginLeft="7dip" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/textview_text" + android:textAlignment="inherit" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + <TextView android:layout_height="wrap_content" + android:layout_width="200dip" + android:textSize="24dip" + android:text="@string/hebrew_text" + android:textAlignment="inherit" + android:layout_marginLeft="7dip" + android:layout_marginRight="7dip" + android:background="#444444" + /> + </TableRow> + + </TableLayout> + +</FrameLayout> diff --git a/tests/BiDiTests/src/com/android/bidi/BiDiTestActivity.java b/tests/BiDiTests/src/com/android/bidi/BiDiTestActivity.java index c5a1235..209597e 100644 --- a/tests/BiDiTests/src/com/android/bidi/BiDiTestActivity.java +++ b/tests/BiDiTests/src/com/android/bidi/BiDiTestActivity.java @@ -104,6 +104,16 @@ public class BiDiTestActivity extends Activity { addItem(result, "Canvas", BiDiTestCanvas.class, R.id.canvas); addItem(result, "Canvas2", BiDiTestCanvas2.class, R.id.canvas2); + addItem(result, "TextView LTR", BiDiTestTextViewLtr.class, R.id.textview_ltr); + addItem(result, "TextView RTL", BiDiTestTextViewRtl.class, R.id.textview_rtl); + addItem(result, "TextView LOC", BiDiTestTextViewLocale.class, R.id.textview_locale); + + addItem(result, "TextDirection LTR", BiDiTestTextViewDirectionLtr.class, R.id.textview_direction_ltr); + addItem(result, "TextDirection RTL", BiDiTestTextViewDirectionRtl.class, R.id.textview_direction_rtl); + + addItem(result, "TextAlignment LTR", BiDiTestTextViewAlignmentLtr.class, R.id.textview_alignment_ltr); + addItem(result, "TextAlignment RTL", BiDiTestTextViewAlignmentRtl.class, R.id.textview_alignment_rtl); + addItem(result, "Linear LTR", BiDiTestLinearLayoutLtr.class, R.id.linear_layout_ltr); addItem(result, "Linear RTL", BiDiTestLinearLayoutRtl.class, R.id.linear_layout_rtl); addItem(result, "Linear LOC", BiDiTestLinearLayoutLocale.class, R.id.linear_layout_locale); @@ -134,15 +144,9 @@ public class BiDiTestActivity extends Activity { addItem(result, "Margin MIXED", BiDiTestViewGroupMarginMixed.class, R.id.view_group_margin_mixed); - addItem(result, "TextView LTR", BiDiTestTextViewLtr.class, R.id.textview_ltr); - addItem(result, "TextView RTL", BiDiTestTextViewRtl.class, R.id.textview_rtl); - addItem(result, "TextView LOC", BiDiTestTextViewLocale.class, R.id.textview_locale); - - addItem(result, "TextDirection LTR", BiDiTestTextViewDirectionLtr.class, R.id.textview_direction_ltr); - addItem(result, "TextDirection RTL", BiDiTestTextViewDirectionRtl.class, R.id.textview_direction_rtl); - addItem(result, "TextView Drawables LTR", BiDiTestTextViewDrawablesLtr.class, R.id.textview_drawables_ltr); addItem(result, "TextView Drawables RTL", BiDiTestTextViewDrawablesRtl.class, R.id.textview_drawables_rtl); + addItem(result, "Gallery LTR", BiDiTestGalleryLtr.class, R.id.gallery_ltr); addItem(result, "Gallery RTL", BiDiTestGalleryRtl.class, R.id.gallery_rtl); diff --git a/tests/BiDiTests/src/com/android/bidi/BiDiTestTextViewAlignmentLtr.java b/tests/BiDiTests/src/com/android/bidi/BiDiTestTextViewAlignmentLtr.java new file mode 100644 index 0000000..5ea5d81 --- /dev/null +++ b/tests/BiDiTests/src/com/android/bidi/BiDiTestTextViewAlignmentLtr.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2012 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.bidi; + +import android.app.Fragment; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +public class BiDiTestTextViewAlignmentLtr extends Fragment { + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + return inflater.inflate(R.layout.textview_alignment_ltr, container, false); + } +} diff --git a/tests/BiDiTests/src/com/android/bidi/BiDiTestTextViewAlignmentRtl.java b/tests/BiDiTests/src/com/android/bidi/BiDiTestTextViewAlignmentRtl.java new file mode 100644 index 0000000..fcc7a5d --- /dev/null +++ b/tests/BiDiTests/src/com/android/bidi/BiDiTestTextViewAlignmentRtl.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2012 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.bidi; + +import android.app.Fragment; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +public class BiDiTestTextViewAlignmentRtl extends Fragment { + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + return inflater.inflate(R.layout.textview_alignment_rtl, container, false); + } +} diff --git a/tests/HwAccelerationTest/AndroidManifest.xml b/tests/HwAccelerationTest/AndroidManifest.xml index f4c0841..3775f9f 100644 --- a/tests/HwAccelerationTest/AndroidManifest.xml +++ b/tests/HwAccelerationTest/AndroidManifest.xml @@ -42,6 +42,15 @@ </activity> <activity + android:name="DatePickerActivity" + android:label="_DatePicker"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + + <activity android:name="ClipRegionActivity" android:label="_ClipRegion"> <intent-filter> @@ -657,5 +666,14 @@ </intent-filter> </activity> + <activity + android:name="ViewPropertyAlphaActivity" + android:label="_ViewPropAlpha"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + </application> </manifest> diff --git a/tests/HwAccelerationTest/res/layout/date_picker.xml b/tests/HwAccelerationTest/res/layout/date_picker.xml new file mode 100644 index 0000000..742a03b --- /dev/null +++ b/tests/HwAccelerationTest/res/layout/date_picker.xml @@ -0,0 +1,83 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +** +** Copyright 2012, 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. +*/ +--> + +<!-- Layout of date picker--> + +<!-- The width of this container is manually set a little bigger than the one of the children + contained in it. This helps to prevent rounding errors when toggling the "Show year" option --> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" + android:layout_gravity="center_horizontal" + android:layout_width="270dip" + android:layout_height="wrap_content"> + + <CheckBox + android:id="@+id/yearToggle" + android:text="Provide a year" + android:paddingTop="5dip" + android:paddingBottom="5dip" + android:textAppearance="?android:attr/textAppearanceLarge" + android:layout_gravity="center_horizontal" + android:layout_width="wrap_content" + android:layout_height="wrap_content"/> + <!-- Warning: everything within the parent is removed and re-ordered depending + on the date format selected by the user. --> + <LinearLayout + android:id="@+id/parent" + android:orientation="horizontal" + android:layout_gravity="center_horizontal" + android:animateLayoutChanges="true" + android:layout_width="wrap_content" + android:layout_height="wrap_content"> + + <!-- Month --> + <NumberPicker + android:id="@+id/month" + android:layout_width="80dip" + android:layout_height="wrap_content" + android:layout_marginLeft="1dip" + android:layout_marginRight="1dip" + android:focusable="true" + android:focusableInTouchMode="true" + /> + + <!-- Day --> + <NumberPicker + android:id="@+id/day" + android:layout_width="80dip" + android:layout_height="wrap_content" + android:layout_marginLeft="1dip" + android:layout_marginRight="1dip" + android:focusable="true" + android:focusableInTouchMode="true" + /> + + <!-- Year --> + <NumberPicker + android:id="@+id/year" + android:layout_width="95dip" + android:layout_height="wrap_content" + android:layout_marginLeft="1dip" + android:layout_marginRight="1dip" + android:focusable="true" + android:focusableInTouchMode="true" + /> + </LinearLayout> +</LinearLayout> diff --git a/tests/HwAccelerationTest/res/layout/view_properties.xml b/tests/HwAccelerationTest/res/layout/view_properties.xml new file mode 100644 index 0000000..d7ed819 --- /dev/null +++ b/tests/HwAccelerationTest/res/layout/view_properties.xml @@ -0,0 +1,81 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2012 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:id="@+id/container"> + <Button + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Invalidate" + android:id="@+id/invalidateButton"/> + <Button + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Button" + android:id="@+id/button"/> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Some text" + android:id="@+id/textview"/> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@+id/spantext"/> + <EditText + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Edit text" + android:id="@+id/edittext"/> + <EditText + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Selected text" + android:id="@+id/selectedtext"/> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Some text" + android:background="#00ff00" + android:id="@+id/textviewbackground"/> + <ImageView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:src="@drawable/icon" + android:id="@+id/imageview"/> + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:id="@+id/layout"> + <Button + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Button"/> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Some text"/> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Some text" + android:background="#00ff00"/> + </LinearLayout> +</LinearLayout>
\ No newline at end of file diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/DatePicker.java b/tests/HwAccelerationTest/src/com/android/test/hwui/DatePicker.java new file mode 100644 index 0000000..db247e3 --- /dev/null +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/DatePicker.java @@ -0,0 +1,474 @@ +/* + * Copyright (C) 2012 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.test.hwui; + +import android.annotation.Widget; +import android.content.Context; +import android.content.res.TypedArray; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.format.DateFormat; +import android.util.AttributeSet; +import android.util.SparseArray; +import android.view.ContextThemeWrapper; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.CheckBox; +import android.widget.CompoundButton; +import android.widget.FrameLayout; +import android.widget.LinearLayout; +import android.widget.NumberPicker; + +import java.text.DateFormatSymbols; +import java.text.SimpleDateFormat; +import java.util.Calendar; + +/** + * A view for selecting a month / year / day based on a calendar like layout. + * + * <p>See the <a href="{@docRoot}resources/tutorials/views/hello-datepicker.html">Date Picker + * tutorial</a>.</p> + * + * For a dialog using this view, see {@link android.app.DatePickerDialog}. + */ +@Widget +public class DatePicker extends FrameLayout { + + private static final int DEFAULT_START_YEAR = 1900; + private static final int DEFAULT_END_YEAR = 2100; + + /* UI Components */ + private final CheckBox mYearToggle; + private final NumberPicker mDayPicker; + private final NumberPicker mMonthPicker; + private final NumberPicker mYearPicker; + + /** + * How we notify users the date has changed. + */ + private OnDateChangedListener mOnDateChangedListener; + + private int mDay; + private int mMonth; + private int mYear; + private boolean mYearOptional = true; + private boolean mHasYear; + + /** + * The callback used to indicate the user changes the date. + */ + public interface OnDateChangedListener { + + /** + * @param view The view associated with this listener. + * @param year The year that was set. + * @param monthOfYear The month that was set (0-11) for compatibility + * with {@link java.util.Calendar}. + * @param dayOfMonth The day of the month that was set. + */ + void onDateChanged(DatePicker view, int year, int monthOfYear, int dayOfMonth); + } + + public DatePicker(Context context) { + this(context, null); + } + + public DatePicker(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + @SuppressWarnings("deprecation") + public DatePicker(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + + ContextThemeWrapper themed = new ContextThemeWrapper(context, + com.android.internal.R.style.Theme_Holo_Light_Dialog_Alert); + LayoutInflater inflater = (LayoutInflater) themed.getSystemService( + Context.LAYOUT_INFLATER_SERVICE); + inflater.inflate(R.layout.date_picker, this, true); + + mDayPicker = (NumberPicker) findViewById(R.id.day); + mDayPicker.setFormatter(NumberPicker.TWO_DIGIT_FORMATTER); + mDayPicker.setOnLongPressUpdateInterval(100); + mDayPicker.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() { + public void onValueChange(NumberPicker picker, int oldVal, int newVal) { + mDay = newVal; + notifyDateChanged(); + } + }); + mMonthPicker = (NumberPicker) findViewById(R.id.month); + mMonthPicker.setFormatter(NumberPicker.TWO_DIGIT_FORMATTER); + DateFormatSymbols dfs = new DateFormatSymbols(); + String[] months = dfs.getShortMonths(); + + /* + * If the user is in a locale where the month names are numeric, + * use just the number instead of the "month" character for + * consistency with the other fields. + */ + if (months[0].startsWith("1")) { + for (int i = 0; i < months.length; i++) { + months[i] = String.valueOf(i + 1); + } + mMonthPicker.setMinValue(1); + mMonthPicker.setMaxValue(12); + } else { + mMonthPicker.setMinValue(1); + mMonthPicker.setMaxValue(12); + mMonthPicker.setDisplayedValues(months); + } + + mMonthPicker.setOnLongPressUpdateInterval(200); + mMonthPicker.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() { + public void onValueChange(NumberPicker picker, int oldVal, int newVal) { + + /* We display the month 1-12 but store it 0-11 so always + * subtract by one to ensure our internal state is always 0-11 + */ + mMonth = newVal - 1; + // Adjust max day of the month + adjustMaxDay(); + notifyDateChanged(); + updateDaySpinner(); + } + }); + mYearPicker = (NumberPicker) findViewById(R.id.year); + mYearPicker.setOnLongPressUpdateInterval(100); + mYearPicker.setOnValueChangedListener(new NumberPicker.OnValueChangeListener() { + public void onValueChange(NumberPicker picker, int oldVal, int newVal) { + mYear = newVal; + // Adjust max day for leap years if needed + adjustMaxDay(); + notifyDateChanged(); + updateDaySpinner(); + } + }); + + mYearToggle = (CheckBox) findViewById(R.id.yearToggle); + mYearToggle.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + mHasYear = isChecked; + adjustMaxDay(); + notifyDateChanged(); + updateSpinners(); + } + }); + + // attributes + TypedArray a = context.obtainStyledAttributes(attrs, + com.android.internal.R.styleable.DatePicker); + + int mStartYear = + a.getInt(com.android.internal.R.styleable.DatePicker_startYear, DEFAULT_START_YEAR); + int mEndYear = + a.getInt(com.android.internal.R.styleable.DatePicker_endYear, DEFAULT_END_YEAR); + mYearPicker.setMinValue(mStartYear); + mYearPicker.setMaxValue(mEndYear); + + a.recycle(); + + // initialize to current date + Calendar cal = Calendar.getInstance(); + init(cal.get(Calendar.YEAR), cal.get(Calendar.MONTH), cal.get(Calendar.DAY_OF_MONTH), null); + + // re-order the number pickers to match the current date format + reorderPickers(months); + + if (!isEnabled()) { + setEnabled(false); + } + } + + @Override + public void setEnabled(boolean enabled) { + super.setEnabled(enabled); + mDayPicker.setEnabled(enabled); + mMonthPicker.setEnabled(enabled); + mYearPicker.setEnabled(enabled); + } + + private void reorderPickers(String[] months) { + java.text.DateFormat format; + String order; + + /* + * If the user is in a locale where the medium date format is + * still numeric (Japanese and Czech, for example), respect + * the date format order setting. Otherwise, use the order + * that the locale says is appropriate for a spelled-out date. + */ + + if (months[0].startsWith("1")) { + format = DateFormat.getDateFormat(getContext()); + } else { + format = DateFormat.getMediumDateFormat(getContext()); + } + + if (format instanceof SimpleDateFormat) { + order = ((SimpleDateFormat) format).toPattern(); + } else { + // Shouldn't happen, but just in case. + order = new String(DateFormat.getDateFormatOrder(getContext())); + } + + /* Remove the 3 pickers from their parent and then add them back in the + * required order. + */ + LinearLayout parent = (LinearLayout) findViewById(R.id.parent); + parent.removeAllViews(); + + boolean quoted = false; + boolean didDay = false, didMonth = false, didYear = false; + + for (int i = 0; i < order.length(); i++) { + char c = order.charAt(i); + + if (c == '\'') { + quoted = !quoted; + } + + if (!quoted) { + if (c == DateFormat.DATE && !didDay) { + parent.addView(mDayPicker); + didDay = true; + } else if ((c == DateFormat.MONTH || c == 'L') && !didMonth) { + parent.addView(mMonthPicker); + didMonth = true; + } else if (c == DateFormat.YEAR && !didYear) { + parent.addView (mYearPicker); + didYear = true; + } + } + } + + // Shouldn't happen, but just in case. + if (!didMonth) { + parent.addView(mMonthPicker); + } + if (!didDay) { + parent.addView(mDayPicker); + } + if (!didYear) { + parent.addView(mYearPicker); + } + } + + public void updateDate(int year, int monthOfYear, int dayOfMonth) { + if (mYear != year || mMonth != monthOfYear || mDay != dayOfMonth) { + mYear = (mYearOptional && year == 0) ? getCurrentYear() : year; + mMonth = monthOfYear; + mDay = dayOfMonth; + updateSpinners(); + reorderPickers(new DateFormatSymbols().getShortMonths()); + notifyDateChanged(); + } + } + + private static int getCurrentYear() { + return Calendar.getInstance().get(Calendar.YEAR); + } + + private static class SavedState extends BaseSavedState { + + private final int mYear; + private final int mMonth; + private final int mDay; + private final boolean mHasYear; + private final boolean mYearOptional; + + /** + * Constructor called from {@link DatePicker#onSaveInstanceState()} + */ + private SavedState(Parcelable superState, int year, int month, int day, boolean hasYear, + boolean yearOptional) { + super(superState); + mYear = year; + mMonth = month; + mDay = day; + mHasYear = hasYear; + mYearOptional = yearOptional; + } + + /** + * Constructor called from {@link #CREATOR} + */ + private SavedState(Parcel in) { + super(in); + mYear = in.readInt(); + mMonth = in.readInt(); + mDay = in.readInt(); + mHasYear = in.readInt() != 0; + mYearOptional = in.readInt() != 0; + } + + public int getYear() { + return mYear; + } + + public int getMonth() { + return mMonth; + } + + public int getDay() { + return mDay; + } + + public boolean hasYear() { + return mHasYear; + } + + public boolean isYearOptional() { + return mYearOptional; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeInt(mYear); + dest.writeInt(mMonth); + dest.writeInt(mDay); + dest.writeInt(mHasYear ? 1 : 0); + dest.writeInt(mYearOptional ? 1 : 0); + } + + @SuppressWarnings("unused") + public static final Parcelable.Creator<SavedState> CREATOR = + new Creator<SavedState>() { + + public SavedState createFromParcel(Parcel in) { + return new SavedState(in); + } + + public SavedState[] newArray(int size) { + return new SavedState[size]; + } + }; + } + + + /** + * Override so we are in complete control of save / restore for this widget. + */ + @Override + protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) { + dispatchThawSelfOnly(container); + } + + @Override + protected Parcelable onSaveInstanceState() { + Parcelable superState = super.onSaveInstanceState(); + + return new SavedState(superState, mYear, mMonth, mDay, mHasYear, mYearOptional); + } + + @Override + protected void onRestoreInstanceState(Parcelable state) { + SavedState ss = (SavedState) state; + super.onRestoreInstanceState(ss.getSuperState()); + mYear = ss.getYear(); + mMonth = ss.getMonth(); + mDay = ss.getDay(); + mHasYear = ss.hasYear(); + mYearOptional = ss.isYearOptional(); + updateSpinners(); + } + + /** + * Initialize the state. + * @param year The initial year. + * @param monthOfYear The initial month. + * @param dayOfMonth The initial day of the month. + * @param onDateChangedListener How user is notified date is changed by user, can be null. + */ + public void init(int year, int monthOfYear, int dayOfMonth, + OnDateChangedListener onDateChangedListener) { + init(year, monthOfYear, dayOfMonth, false, onDateChangedListener); + } + + /** + * Initialize the state. + * @param year The initial year or 0 if no year has been specified + * @param monthOfYear The initial month. + * @param dayOfMonth The initial day of the month. + * @param yearOptional True if the user can toggle the year + * @param onDateChangedListener How user is notified date is changed by user, can be null. + */ + public void init(int year, int monthOfYear, int dayOfMonth, boolean yearOptional, + OnDateChangedListener onDateChangedListener) { + mYear = (yearOptional && year == 0) ? getCurrentYear() : year; + mMonth = monthOfYear; + mDay = dayOfMonth; + mYearOptional = yearOptional; + mHasYear = !yearOptional || (year != 0); + mOnDateChangedListener = onDateChangedListener; + updateSpinners(); + } + + private void updateSpinners() { + updateDaySpinner(); + mYearToggle.setChecked(mHasYear); + mYearToggle.setVisibility(mYearOptional ? View.VISIBLE : View.GONE); + mYearPicker.setValue(mYear); + mYearPicker.setVisibility(mHasYear ? View.VISIBLE : View.GONE); + + /* The month display uses 1-12 but our internal state stores it + * 0-11 so add one when setting the display. + */ + mMonthPicker.setValue(mMonth + 1); + } + + private void updateDaySpinner() { + Calendar cal = Calendar.getInstance(); + // if year was not set, use 2000 as it was a leap year + cal.set(mHasYear ? mYear : 2000, mMonth, 1); + int max = cal.getActualMaximum(Calendar.DAY_OF_MONTH); + mDayPicker.setMinValue(1); + mDayPicker.setMaxValue(max); + mDayPicker.setValue(mDay); + } + + public int getYear() { + return (mYearOptional && !mHasYear) ? 0 : mYear; + } + + public int getMonth() { + return mMonth; + } + + public int getDayOfMonth() { + return mDay; + } + + private void adjustMaxDay(){ + Calendar cal = Calendar.getInstance(); + // if year was not set, use 2000 as it was a leap year + cal.set(Calendar.YEAR, mHasYear ? mYear : 2000); + cal.set(Calendar.MONTH, mMonth); + int max = cal.getActualMaximum(Calendar.DAY_OF_MONTH); + if (mDay > max) { + mDay = max; + } + } + + private void notifyDateChanged() { + if (mOnDateChangedListener != null) { + int year = (mYearOptional && !mHasYear) ? 0 : mYear; + mOnDateChangedListener.onDateChanged(DatePicker.this, year, mMonth, mDay); + } + } +} diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/DatePickerActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/DatePickerActivity.java new file mode 100644 index 0000000..5482ee2 --- /dev/null +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/DatePickerActivity.java @@ -0,0 +1,38 @@ +/* + * 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. + */ + +package com.android.test.hwui; + +import android.app.Activity; +import android.graphics.drawable.ColorDrawable; +import android.os.Bundle; + +@SuppressWarnings({"UnusedDeclaration"}) +public class DatePickerActivity extends Activity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + DatePicker picker = new DatePicker(this); + picker.init(2012, 3, 3, true, new DatePicker.OnDateChangedListener() { + @Override + public void onDateChanged(DatePicker view, int year, int monthOfYear, int dayOfMonth) { + } + }); + setContentView(picker); + getWindow().setBackgroundDrawable(new ColorDrawable(0xffffffff)); + } +} diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/ViewPropertyAlphaActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/ViewPropertyAlphaActivity.java new file mode 100644 index 0000000..738801d --- /dev/null +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/ViewPropertyAlphaActivity.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2012 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.test.hwui; + +import android.animation.ObjectAnimator; +import android.animation.ValueAnimator; +import android.app.Activity; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.os.Bundle; +import android.text.Spannable; +import android.text.SpannableStringBuilder; +import android.text.style.BackgroundColorSpan; +import android.text.style.ForegroundColorSpan; +import android.text.style.ImageSpan; +import android.text.style.SuggestionSpan; +import android.text.style.UnderlineSpan; +import android.view.View; +import android.widget.Button; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.TextView; + +public class ViewPropertyAlphaActivity extends Activity { + + MyView myViewAlphaDefault, myViewAlphaHandled; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.view_properties); + + getWindow().getDecorView().postDelayed(new Runnable() { + @Override + public void run() { + startAnim(R.id.button); + startAnim(R.id.textview); + startAnim(R.id.spantext); + startAnim(R.id.edittext); + startAnim(R.id.selectedtext); + startAnim(R.id.textviewbackground); + startAnim(R.id.layout); + startAnim(R.id.imageview); + startAnim(myViewAlphaDefault); + startAnim(myViewAlphaHandled); + EditText selectedText = (EditText) findViewById(R.id.selectedtext); + selectedText.setSelection(3, 8); + } + }, 2000); + + Button invalidator = (Button) findViewById(R.id.invalidateButton); + invalidator.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + findViewById(R.id.textview).invalidate(); + findViewById(R.id.spantext).invalidate(); + } + }); + + TextView textView = (TextView) findViewById(R.id.spantext); + if (textView != null) { + SpannableStringBuilder text = + new SpannableStringBuilder("Now this is a short text message with spans"); + + text.setSpan(new BackgroundColorSpan(Color.RED), 0, 3, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + text.setSpan(new ForegroundColorSpan(Color.BLUE), 4, 9, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + text.setSpan(new SuggestionSpan(this, new String[]{"longer"}, 3), 11, 16, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + text.setSpan(new UnderlineSpan(), 17, 20, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + text.setSpan(new ImageSpan(this, R.drawable.icon), 21, 22, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + + textView.setText(text); + } + + LinearLayout container = (LinearLayout) findViewById(R.id.container); + myViewAlphaDefault = new MyView(this, false); + myViewAlphaDefault.setLayoutParams(new LinearLayout.LayoutParams(75, 75)); + container.addView(myViewAlphaDefault); + myViewAlphaHandled = new MyView(this, true); + myViewAlphaHandled.setLayoutParams(new LinearLayout.LayoutParams(75, 75)); + container.addView(myViewAlphaHandled); + } + + private void startAnim(View target) { + ObjectAnimator anim = ObjectAnimator.ofFloat(target, View.ALPHA, 0); + anim.setRepeatCount(ValueAnimator.INFINITE); + anim.setRepeatMode(ValueAnimator.REVERSE); + anim.setDuration(1000); + anim.start(); + } + private void startAnim(int id) { + startAnim(findViewById(id)); + } + + private static class MyView extends View { + private int mMyAlpha = 255; + private boolean mHandleAlpha; + private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + + private MyView(Context context, boolean handleAlpha) { + super(context); + mHandleAlpha = handleAlpha; + mPaint.setColor(Color.RED); + } + + @Override + protected void onDraw(Canvas canvas) { + if (mHandleAlpha) { + mPaint.setAlpha(mMyAlpha); + } + canvas.drawCircle(30, 30, 30, mPaint); + } + + @Override + protected boolean onSetAlpha(int alpha) { + if (mHandleAlpha) { + mMyAlpha = alpha; + return true; + } + return super.onSetAlpha(alpha); + } + } + +} diff --git a/tests/TileBenchmark/src/com/test/tilebenchmark/ProfiledWebView.java b/tests/TileBenchmark/src/com/test/tilebenchmark/ProfiledWebView.java index 87baf76..7c03313 100644 --- a/tests/TileBenchmark/src/com/test/tilebenchmark/ProfiledWebView.java +++ b/tests/TileBenchmark/src/com/test/tilebenchmark/ProfiledWebView.java @@ -23,7 +23,6 @@ import android.util.Log; import android.webkit.WebSettingsClassic; import android.webkit.WebView; import android.webkit.WebViewClassic; -import android.widget.Toast; import java.util.ArrayList; @@ -72,10 +71,7 @@ public class ProfiledWebView extends WebView implements WebViewClassic.PageSwapD mContext = c; } - /** Show a toast from the web page */ public void animationComplete() { - Toast.makeText(mContext, "Animation complete!", Toast.LENGTH_SHORT).show(); - //Log.d(LOGTAG, "anim complete"); mAnimationTime = System.currentTimeMillis(); } } diff --git a/tools/aapt/Images.cpp b/tools/aapt/Images.cpp index 2b9b056..9de685a 100644 --- a/tools/aapt/Images.cpp +++ b/tools/aapt/Images.cpp @@ -57,6 +57,13 @@ struct image_info bool is9Patch; Res_png_9patch info9Patch; + // Layout padding, if relevant + bool haveLayoutBounds; + int32_t layoutBoundsLeft; + int32_t layoutBoundsTop; + int32_t layoutBoundsRight; + int32_t layoutBoundsBottom; + png_uint_32 allocHeight; png_bytepp allocRows; }; @@ -129,33 +136,62 @@ static void read_png(const char* imageName, &interlace_type, &compression_type, NULL); } -static bool is_tick(png_bytep p, bool transparent, const char** outError) +#define COLOR_TRANSPARENT 0 +#define COLOR_WHITE 0xFFFFFFFF +#define COLOR_TICK 0xFF000000 +#define COLOR_LAYOUT_BOUNDS_TICK 0xFF0000FF + +enum { + TICK_TYPE_NONE, + TICK_TYPE_TICK, + TICK_TYPE_LAYOUT_BOUNDS, + TICK_TYPE_BOTH +}; + +static int tick_type(png_bytep p, bool transparent, const char** outError) { + png_uint_32 color = p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24); + if (transparent) { if (p[3] == 0) { - return false; + return TICK_TYPE_NONE; + } + if (color == COLOR_LAYOUT_BOUNDS_TICK) { + return TICK_TYPE_LAYOUT_BOUNDS; } + if (color == COLOR_TICK) { + return TICK_TYPE_TICK; + } + + // Error cases if (p[3] != 0xff) { *outError = "Frame pixels must be either solid or transparent (not intermediate alphas)"; - return false; + return TICK_TYPE_NONE; } if (p[0] != 0 || p[1] != 0 || p[2] != 0) { - *outError = "Ticks in transparent frame must be black"; + *outError = "Ticks in transparent frame must be black or red"; } - return true; + return TICK_TYPE_TICK; } if (p[3] != 0xFF) { *outError = "White frame must be a solid color (no alpha)"; } - if (p[0] == 0xFF && p[1] == 0xFF && p[2] == 0xFF) { - return false; + if (color == COLOR_WHITE) { + return TICK_TYPE_NONE; + } + if (color == COLOR_TICK) { + return TICK_TYPE_TICK; } + if (color == COLOR_LAYOUT_BOUNDS_TICK) { + return TICK_TYPE_LAYOUT_BOUNDS; + } + if (p[0] != 0 || p[1] != 0 || p[2] != 0) { - *outError = "Ticks in white frame must be black"; - return false; + *outError = "Ticks in white frame must be black or red"; + return TICK_TYPE_NONE; } - return true; + return TICK_TYPE_TICK; } enum { @@ -175,7 +211,7 @@ static status_t get_horizontal_ticks( bool found = false; for (i=1; i<width-1; i++) { - if (is_tick(row+i*4, transparent, outError)) { + if (TICK_TYPE_TICK == tick_type(row+i*4, transparent, outError)) { if (state == TICK_START || (state == TICK_OUTSIDE_1 && multipleAllowed)) { *outLeft = i-1; @@ -224,7 +260,7 @@ static status_t get_vertical_ticks( bool found = false; for (i=1; i<height-1; i++) { - if (is_tick(rows[i]+offset, transparent, outError)) { + if (TICK_TYPE_TICK == tick_type(rows[i]+offset, transparent, outError)) { if (state == TICK_START || (state == TICK_OUTSIDE_1 && multipleAllowed)) { *outTop = i-1; @@ -262,6 +298,83 @@ static status_t get_vertical_ticks( return NO_ERROR; } +static status_t get_horizontal_layout_bounds_ticks( + png_bytep row, int width, bool transparent, bool required, + int32_t* outLeft, int32_t* outRight, const char** outError) +{ + int i; + *outLeft = *outRight = 0; + + // Look for left tick + if (TICK_TYPE_LAYOUT_BOUNDS == tick_type(row + 4, transparent, outError)) { + // Starting with a layout padding tick + i = 1; + while (i < width - 1) { + (*outLeft)++; + i++; + int tick = tick_type(row + i * 4, transparent, outError); + if (tick != TICK_TYPE_LAYOUT_BOUNDS) { + break; + } + } + } + + // Look for right tick + if (TICK_TYPE_LAYOUT_BOUNDS == tick_type(row + (width - 2) * 4, transparent, outError)) { + // Ending with a layout padding tick + i = width - 2; + while (i > 1) { + (*outRight)++; + i--; + int tick = tick_type(row+i*4, transparent, outError); + if (tick != TICK_TYPE_LAYOUT_BOUNDS) { + break; + } + } + } + + return NO_ERROR; +} + +static status_t get_vertical_layout_bounds_ticks( + png_bytepp rows, int offset, int height, bool transparent, bool required, + int32_t* outTop, int32_t* outBottom, const char** outError) +{ + int i; + *outTop = *outBottom = 0; + + // Look for top tick + if (TICK_TYPE_LAYOUT_BOUNDS == tick_type(rows[1] + offset, transparent, outError)) { + // Starting with a layout padding tick + i = 1; + while (i < height - 1) { + (*outTop)++; + i++; + int tick = tick_type(rows[i] + offset, transparent, outError); + if (tick != TICK_TYPE_LAYOUT_BOUNDS) { + break; + } + } + } + + // Look for bottom tick + if (TICK_TYPE_LAYOUT_BOUNDS == tick_type(rows[height - 2] + offset, transparent, outError)) { + // Ending with a layout padding tick + i = height - 2; + while (i > 1) { + (*outBottom)++; + i--; + int tick = tick_type(rows[i] + offset, transparent, outError); + if (tick != TICK_TYPE_LAYOUT_BOUNDS) { + break; + } + } + } + + return NO_ERROR; +} + + static uint32_t get_color( png_bytepp rows, int left, int top, int right, int bottom) { @@ -353,6 +466,9 @@ static status_t do_9patch(const char* imageName, image_info* image) image->info9Patch.paddingLeft = image->info9Patch.paddingRight = image->info9Patch.paddingTop = image->info9Patch.paddingBottom = -1; + image->layoutBoundsLeft = image->layoutBoundsRight = + image->layoutBoundsTop = image->layoutBoundsBottom = 0; + png_bytep p = image->rows[0]; bool transparent = p[3] == 0; bool hasColor = false; @@ -408,6 +524,25 @@ static status_t do_9patch(const char* imageName, image_info* image) goto getout; } + // Find left and right of layout padding... + get_horizontal_layout_bounds_ticks(image->rows[H-1], W, transparent, false, + &image->layoutBoundsLeft, + &image->layoutBoundsRight, &errorMsg); + + get_vertical_layout_bounds_ticks(image->rows, (W-1)*4, H, transparent, false, + &image->layoutBoundsTop, + &image->layoutBoundsBottom, &errorMsg); + + image->haveLayoutBounds = image->layoutBoundsLeft != 0 + || image->layoutBoundsRight != 0 + || image->layoutBoundsTop != 0 + || image->layoutBoundsBottom != 0; + + if (image->haveLayoutBounds) { + NOISY(printf("layoutBounds=%d %d %d %d\n", image->layoutBoundsLeft, image->layoutBoundsTop, + image->layoutBoundsRight, image->layoutBoundsBottom)); + } + // Copy patch data into image image->info9Patch.numXDivs = numXDivs; image->info9Patch.numYDivs = numYDivs; @@ -845,8 +980,9 @@ static void write_png(const char* imageName, int bit_depth, interlace_type, compression_type; int i; - png_unknown_chunk unknowns[1]; + png_unknown_chunk unknowns[2]; unknowns[0].data = NULL; + unknowns[1].data = NULL; png_bytepp outRows = (png_bytepp) malloc((int) imageInfo.height * png_sizeof(png_bytep)); if (outRows == (png_bytepp) 0) { @@ -916,23 +1052,42 @@ static void write_png(const char* imageName, } if (imageInfo.is9Patch) { + int chunk_count = 1 + (imageInfo.haveLayoutBounds ? 1 : 0); + int p_index = imageInfo.haveLayoutBounds ? 1 : 0; + int b_index = 0; + png_byte *chunk_names = imageInfo.haveLayoutBounds + ? (png_byte*)"npLb\0npTc\0" + : (png_byte*)"npTc"; NOISY(printf("Adding 9-patch info...\n")); - strcpy((char*)unknowns[0].name, "npTc"); - unknowns[0].data = (png_byte*)imageInfo.info9Patch.serialize(); - unknowns[0].size = imageInfo.info9Patch.serializedSize(); + strcpy((char*)unknowns[p_index].name, "npTc"); + unknowns[p_index].data = (png_byte*)imageInfo.info9Patch.serialize(); + unknowns[p_index].size = imageInfo.info9Patch.serializedSize(); // TODO: remove the check below when everything works - checkNinePatchSerialization(&imageInfo.info9Patch, unknowns[0].data); + checkNinePatchSerialization(&imageInfo.info9Patch, unknowns[p_index].data); + + if (imageInfo.haveLayoutBounds) { + int chunk_size = sizeof(png_uint_32) * 4; + strcpy((char*)unknowns[b_index].name, "npLb"); + unknowns[b_index].data = (png_byte*) calloc(chunk_size, 1); + memcpy(unknowns[b_index].data, &imageInfo.layoutBoundsLeft, chunk_size); + unknowns[b_index].size = chunk_size; + } + png_set_keep_unknown_chunks(write_ptr, PNG_HANDLE_CHUNK_ALWAYS, - (png_byte*)"npTc", 1); - png_set_unknown_chunks(write_ptr, write_info, unknowns, 1); + chunk_names, chunk_count); + png_set_unknown_chunks(write_ptr, write_info, unknowns, chunk_count); // XXX I can't get this to work without forcibly changing // the location to what I want... which apparently is supposed // to be a private API, but everything else I have tried results // in the location being set to what I -last- wrote so I never // get written. :p png_set_unknown_chunk_location(write_ptr, write_info, 0, PNG_HAVE_PLTE); + if (imageInfo.haveLayoutBounds) { + png_set_unknown_chunk_location(write_ptr, write_info, 1, PNG_HAVE_PLTE); + } } + png_write_info(write_ptr, write_info); png_bytepp rows; @@ -954,6 +1109,7 @@ static void write_png(const char* imageName, } free(outRows); free(unknowns[0].data); + free(unknowns[1].data); png_get_IHDR(write_ptr, write_info, &width, &height, &bit_depth, &color_type, &interlace_type, diff --git a/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java index eadec02..b76b8cf 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java @@ -524,7 +524,8 @@ public final class Bitmap_Delegate { int nativeInt = sManager.addNewDelegate(delegate); // and create/return a new Bitmap with it - return new Bitmap(nativeInt, null /* buffer */, isMutable, null /*ninePatchChunk*/, density); + return new Bitmap(nativeInt, null /* buffer */, isMutable, null /*ninePatchChunk*/, + density); } /** diff --git a/voip/java/com/android/server/sip/SipService.java b/voip/java/com/android/server/sip/SipService.java index 97afc81..a477fd1 100644 --- a/voip/java/com/android/server/sip/SipService.java +++ b/voip/java/com/android/server/sip/SipService.java @@ -453,9 +453,8 @@ public final class SipService extends ISipService.Stub { public SipSessionGroupExt(SipProfile localProfile, PendingIntent incomingCallPendingIntent, ISipSessionListener listener) throws SipException { - String password = localProfile.getPassword(); - SipProfile p = duplicate(localProfile); - mSipGroup = createSipSessionGroup(mLocalIp, p, password); + mSipGroup = new SipSessionGroup(duplicate(localProfile), + localProfile.getPassword(), mTimer, mMyWakeLock); mIncomingCallPendingIntent = incomingCallPendingIntent; mAutoRegistration.setListener(listener); } @@ -478,27 +477,6 @@ public final class SipService extends ISipService.Stub { mSipGroup.setWakeupTimer(timer); } - // network connectivity is tricky because network can be disconnected - // at any instant so need to deal with exceptions carefully even when - // you think you are connected - private SipSessionGroup createSipSessionGroup(String localIp, - SipProfile localProfile, String password) throws SipException { - try { - return new SipSessionGroup(localIp, localProfile, password, - mTimer, mMyWakeLock); - } catch (IOException e) { - // network disconnected - Log.w(TAG, "createSipSessionGroup(): network disconnected?"); - if (localIp != null) { - return createSipSessionGroup(null, localProfile, password); - } else { - // recursive - Log.wtf(TAG, "impossible! recursive!"); - throw new RuntimeException("createSipSessionGroup"); - } - } - } - private SipProfile duplicate(SipProfile p) { try { return new SipProfile.Builder(p).setPassword("*").build(); @@ -530,7 +508,7 @@ public final class SipService extends ISipService.Stub { throws SipException { mSipGroup.onConnectivityChanged(); if (connected) { - resetGroup(mLocalIp); + mSipGroup.reset(); if (mOpenedToReceiveCalls) openToReceiveCalls(); } else { // close mSipGroup but remember mOpenedToReceiveCalls @@ -541,22 +519,6 @@ public final class SipService extends ISipService.Stub { } } - private void resetGroup(String localIp) throws SipException { - try { - mSipGroup.reset(localIp); - } catch (IOException e) { - // network disconnected - Log.w(TAG, "resetGroup(): network disconnected?"); - if (localIp != null) { - resetGroup(null); // reset w/o local IP - } else { - // recursive - Log.wtf(TAG, "impossible!"); - throw new RuntimeException("resetGroup"); - } - } - } - public void close() { mOpenedToReceiveCalls = false; mSipGroup.close(); diff --git a/voip/java/com/android/server/sip/SipSessionGroup.java b/voip/java/com/android/server/sip/SipSessionGroup.java index 877a0a4..6acd456 100644 --- a/voip/java/com/android/server/sip/SipSessionGroup.java +++ b/voip/java/com/android/server/sip/SipSessionGroup.java @@ -40,6 +40,7 @@ import android.util.Log; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.DatagramSocket; +import java.net.InetAddress; import java.net.UnknownHostException; import java.text.ParseException; import java.util.Collection; @@ -47,13 +48,11 @@ import java.util.EventObject; import java.util.HashMap; import java.util.Map; import java.util.Properties; -import java.util.TooManyListenersException; import javax.sip.ClientTransaction; import javax.sip.Dialog; import javax.sip.DialogTerminatedEvent; import javax.sip.IOExceptionEvent; -import javax.sip.InvalidArgumentException; import javax.sip.ListeningPoint; import javax.sip.ObjectInUseException; import javax.sip.RequestEvent; @@ -132,18 +131,17 @@ class SipSessionGroup implements SipListener { private int mExternalPort; /** - * @param myself the local profile with password crossed out + * @param profile the local profile with password crossed out * @param password the password of the profile * @throws IOException if cannot assign requested address */ - public SipSessionGroup(String localIp, SipProfile myself, String password, - SipWakeupTimer timer, SipWakeLock wakeLock) throws SipException, - IOException { - mLocalProfile = myself; + public SipSessionGroup(SipProfile profile, String password, + SipWakeupTimer timer, SipWakeLock wakeLock) throws SipException { + mLocalProfile = profile; mPassword = password; mWakeupTimer = timer; mWakeLock = wakeLock; - reset(localIp); + reset(); } // TODO: remove this method once SipWakeupTimer can better handle variety @@ -152,43 +150,64 @@ class SipSessionGroup implements SipListener { mWakeupTimer = timer; } - synchronized void reset(String localIp) throws SipException, IOException { - mLocalIp = localIp; - if (localIp == null) return; - - SipProfile myself = mLocalProfile; - SipFactory sipFactory = SipFactory.getInstance(); + synchronized void reset() throws SipException { Properties properties = new Properties(); + + String protocol = mLocalProfile.getProtocol(); + int port = mLocalProfile.getPort(); + String server = mLocalProfile.getProxyAddress(); + + if (!TextUtils.isEmpty(server)) { + properties.setProperty("javax.sip.OUTBOUND_PROXY", + server + ':' + port + '/' + protocol); + } else { + server = mLocalProfile.getSipDomain(); + } + if (server.startsWith("[") && server.endsWith("]")) { + server = server.substring(1, server.length() - 1); + } + + String local = null; + try { + for (InetAddress remote : InetAddress.getAllByName(server)) { + DatagramSocket socket = new DatagramSocket(); + socket.connect(remote, port); + if (socket.isConnected()) { + local = socket.getLocalAddress().getHostAddress(); + port = socket.getLocalPort(); + socket.close(); + break; + } + socket.close(); + } + } catch (Exception e) { + // ignore. + } + if (local == null) { + // We are unable to reach the server. Just bail out. + return; + } + + close(); + mLocalIp = local; + properties.setProperty("javax.sip.STACK_NAME", getStackName()); properties.setProperty( "gov.nist.javax.sip.THREAD_POOL_SIZE", THREAD_POOL_SIZE); - String outboundProxy = myself.getProxyAddress(); - if (!TextUtils.isEmpty(outboundProxy)) { - Log.v(TAG, "outboundProxy is " + outboundProxy); - properties.setProperty("javax.sip.OUTBOUND_PROXY", outboundProxy - + ":" + myself.getPort() + "/" + myself.getProtocol()); - } - SipStack stack = mSipStack = sipFactory.createSipStack(properties); - + mSipStack = SipFactory.getInstance().createSipStack(properties); try { - SipProvider provider = stack.createSipProvider( - stack.createListeningPoint(localIp, allocateLocalPort(), - myself.getProtocol())); + SipProvider provider = mSipStack.createSipProvider( + mSipStack.createListeningPoint(local, port, protocol)); provider.addSipListener(this); - mSipHelper = new SipHelper(stack, provider); - } catch (InvalidArgumentException e) { - throw new IOException(e.getMessage()); - } catch (TooManyListenersException e) { - // must never happen - throw new SipException("SipSessionGroup constructor", e); + mSipHelper = new SipHelper(mSipStack, provider); + } catch (SipException e) { + throw e; + } catch (Exception e) { + throw new SipException("failed to initialize SIP stack", e); } - Log.d(TAG, " start stack for " + myself.getUriString()); - stack.start(); - - mCallReceiverSession = null; - mSessionMap.clear(); - resetExternalAddress(); + Log.d(TAG, " start stack for " + mLocalProfile.getUriString()); + mSipStack.start(); } synchronized void onConnectivityChanged() { @@ -234,6 +253,7 @@ class SipSessionGroup implements SipListener { mSipStack = null; mSipHelper = null; } + resetExternalAddress(); } public synchronized boolean isClosed() { @@ -257,17 +277,6 @@ class SipSessionGroup implements SipListener { return (isClosed() ? null : new SipSessionImpl(listener)); } - private static int allocateLocalPort() throws SipException { - try { - DatagramSocket s = new DatagramSocket(); - int localPort = s.getLocalPort(); - s.close(); - return localPort; - } catch (IOException e) { - throw new SipException("allocateLocalPort()", e); - } - } - synchronized boolean containsSession(String callId) { return mSessionMap.containsKey(callId); } diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pManager.java b/wifi/java/android/net/wifi/p2p/WifiP2pManager.java index 2fc6c20..c7f6bf0 100644 --- a/wifi/java/android/net/wifi/p2p/WifiP2pManager.java +++ b/wifi/java/android/net/wifi/p2p/WifiP2pManager.java @@ -380,6 +380,7 @@ public class WifiP2pManager { mHandler = new P2pHandler(looper); mChannelListener = l; } + private final static int INVALID_LISTENER_KEY = 0; private ChannelListener mChannelListener; private HashMap<Integer, Object> mListenerMap = new HashMap<Integer, Object>(); private Object mListenerMapLock = new Object(); @@ -450,16 +451,19 @@ public class WifiP2pManager { } int putListener(Object listener) { - if (listener == null) return 0; + if (listener == null) return INVALID_LISTENER_KEY; int key; synchronized (mListenerMapLock) { - key = mListenerKey++; + do { + key = mListenerKey++; + } while (key == INVALID_LISTENER_KEY); mListenerMap.put(key, listener); } return key; } Object getListener(int key) { + if (key == INVALID_LISTENER_KEY) return null; synchronized (mListenerMapLock) { return mListenerMap.remove(key); } |