diff options
58 files changed, 2282 insertions, 1294 deletions
diff --git a/api/current.xml b/api/current.xml index 94225529169b..2964fb0af178 100644 --- a/api/current.xml +++ b/api/current.xml @@ -58662,6 +58662,21 @@ <parameter name="flags" type="int"> </parameter> </method> +<method name="setInstallerPackageName" + return="void" + abstract="true" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="targetPackage" type="java.lang.String"> +</parameter> +<parameter name="installerPackageName" type="java.lang.String"> +</parameter> +</method> <field name="COMPONENT_ENABLED_STATE_DEFAULT" type="int" transient="false" @@ -175729,6 +175744,21 @@ <parameter name="flags" type="int"> </parameter> </method> +<method name="setInstallerPackageName" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="targetPackage" type="java.lang.String"> +</parameter> +<parameter name="installerPackageName" type="java.lang.String"> +</parameter> +</method> <method name="setPackageObbPath" return="void" abstract="false" @@ -248151,7 +248181,7 @@ deprecated="not deprecated" visibility="public" > -<parameter name="arg0" type="T"> +<parameter name="t" type="T"> </parameter> </method> </interface> diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 3cead1174da6..c0714e3bba25 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -42,6 +42,9 @@ import android.database.sqlite.SQLiteDebug; import android.database.sqlite.SQLiteDebug.DbStats; import android.graphics.Bitmap; import android.graphics.Canvas; +import android.net.IConnectivityManager; +import android.net.Proxy; +import android.net.ProxyProperties; import android.os.Build; import android.os.Bundle; import android.os.Debug; @@ -272,7 +275,7 @@ public final class ActivityThread { super(resultCode, resultData, resultExtras, TYPE_COMPONENT, ordered, sticky, token); this.intent = intent; } - + Intent intent; ActivityInfo info; public String toString() { @@ -592,6 +595,10 @@ public final class ActivityThread { InetAddress.clearDnsCache(); } + public void setHttpProxy(String host, String port, String exclList) { + Proxy.setHttpProxySystemProperty(host, port, exclList); + } + public void processInBackground() { mH.removeMessages(H.GC_WHEN_IDLE); mH.sendMessage(mH.obtainMessage(H.GC_WHEN_IDLE)); @@ -3253,6 +3260,16 @@ public final class ActivityThread { } } + /** + * Initialize the default http proxy in this process for the reasons we set the time zone. + */ + IBinder b = ServiceManager.getService(Context.CONNECTIVITY_SERVICE); + IConnectivityManager service = IConnectivityManager.Stub.asInterface(b); + try { + ProxyProperties proxyProperties = service.getProxy(); + Proxy.setHttpProxySystemProperty(proxyProperties); + } catch (RemoteException e) {} + if (data.instrumentationName != null) { ContextImpl appContext = new ContextImpl(); appContext.init(data.info, null, this); diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index ce9501a6b156..abb26e3398f8 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -914,6 +914,16 @@ final class ApplicationPackageManager extends PackageManager { } @Override + public void setInstallerPackageName(String targetPackage, + String installerPackageName) { + try { + mPM.setInstallerPackageName(targetPackage, installerPackageName); + } catch (RemoteException e) { + // Should never happen! + } + } + + @Override public void movePackage(String packageName, IPackageMoveObserver observer, int flags) { try { mPM.movePackage(packageName, observer, flags); diff --git a/core/java/android/app/ApplicationThreadNative.java b/core/java/android/app/ApplicationThreadNative.java index b19fb5979e1c..801c3f94bfb2 100644 --- a/core/java/android/app/ApplicationThreadNative.java +++ b/core/java/android/app/ApplicationThreadNative.java @@ -276,7 +276,7 @@ public abstract class ApplicationThreadNative extends Binder requestThumbnail(b); return true; } - + case SCHEDULE_CONFIGURATION_CHANGED_TRANSACTION: { data.enforceInterface(IApplicationThread.descriptor); @@ -297,12 +297,21 @@ public abstract class ApplicationThreadNative extends Binder return true; } + case SET_HTTP_PROXY_TRANSACTION: { + data.enforceInterface(IApplicationThread.descriptor); + final String proxy = data.readString(); + final String port = data.readString(); + final String exclList = data.readString(); + setHttpProxy(proxy, port, exclList); + return true; + } + case PROCESS_IN_BACKGROUND_TRANSACTION: { data.enforceInterface(IApplicationThread.descriptor); processInBackground(); return true; } - + case DUMP_SERVICE_TRANSACTION: { data.enforceInterface(IApplicationThread.descriptor); ParcelFileDescriptor fd = data.readFileDescriptor(); @@ -758,6 +767,16 @@ class ApplicationThreadProxy implements IApplicationThread { data.recycle(); } + public void setHttpProxy(String proxy, String port, String exclList) throws RemoteException { + Parcel data = Parcel.obtain(); + data.writeInterfaceToken(IApplicationThread.descriptor); + data.writeString(proxy); + data.writeString(port); + data.writeString(exclList); + mRemote.transact(SET_HTTP_PROXY_TRANSACTION, data, null, IBinder.FLAG_ONEWAY); + data.recycle(); + } + public void processInBackground() throws RemoteException { Parcel data = Parcel.obtain(); data.writeInterfaceToken(IApplicationThread.descriptor); diff --git a/core/java/android/app/IApplicationThread.java b/core/java/android/app/IApplicationThread.java index 830c7024b5d2..eca84efec33f 100644 --- a/core/java/android/app/IApplicationThread.java +++ b/core/java/android/app/IApplicationThread.java @@ -88,6 +88,7 @@ public interface IApplicationThread extends IInterface { void scheduleConfigurationChanged(Configuration config) throws RemoteException; void updateTimeZone() throws RemoteException; void clearDnsCache() throws RemoteException; + void setHttpProxy(String proxy, String port, String exclList) throws RemoteException; void processInBackground() throws RemoteException; void dumpService(FileDescriptor fd, IBinder servicetoken, String[] args) throws RemoteException; @@ -148,4 +149,5 @@ public interface IApplicationThread extends IInterface { int DUMP_HEAP_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+35; int DUMP_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+36; int CLEAR_DNS_CACHE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+37; + int SET_HTTP_PROXY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+38; } diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index 4cff3bb21538..d01a68a83add 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -158,6 +158,8 @@ interface IPackageManager { void finishPackageInstall(int token); + void setInstallerPackageName(in String targetPackage, in String installerPackageName); + /** * Delete a package. * diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index b5d165383097..ac7a95ae7e2c 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -1918,6 +1918,24 @@ public abstract class PackageManager { String installerPackageName); /** + * Change the installer associated with a given package. There are limitations + * on how the installer package can be changed; in particular: + * <ul> + * <li> A SecurityException will be thrown if <var>installerPackageName</var> + * is not signed with the same certificate as the calling application. + * <li> A SecurityException will be thrown if <var>targetPackage</var> already + * has an installer package, and that installer package is not signed with + * the same certificate as the calling application. + * </ul> + * + * @param targetPackage The installed package whose installer will be changed. + * @param installerPackageName The package name of the new installer. May be + * null to clear the association. + */ + public abstract void setInstallerPackageName(String targetPackage, + String installerPackageName); + + /** * Attempts to delete a package. Since this may take a little while, the result will * be posted back to the given observer. A deletion will fail if the calling context * lacks the {@link android.Manifest.permission#DELETE_PACKAGES} permission, if the diff --git a/core/java/android/content/res/StringBlock.java b/core/java/android/content/res/StringBlock.java index 5e90b9110e71..23a6f97c37db 100644 --- a/core/java/android/content/res/StringBlock.java +++ b/core/java/android/content/res/StringBlock.java @@ -87,21 +87,48 @@ final class StringBlock { if (style != null) { if (mStyleIDs == null) { mStyleIDs = new StyleIDs(); - mStyleIDs.boldId = nativeIndexOfString(mNative, "b"); - mStyleIDs.italicId = nativeIndexOfString(mNative, "i"); - mStyleIDs.underlineId = nativeIndexOfString(mNative, "u"); - mStyleIDs.ttId = nativeIndexOfString(mNative, "tt"); - mStyleIDs.bigId = nativeIndexOfString(mNative, "big"); - mStyleIDs.smallId = nativeIndexOfString(mNative, "small"); - mStyleIDs.supId = nativeIndexOfString(mNative, "sup"); - mStyleIDs.subId = nativeIndexOfString(mNative, "sub"); - mStyleIDs.strikeId = nativeIndexOfString(mNative, "strike"); - mStyleIDs.listItemId = nativeIndexOfString(mNative, "li"); - mStyleIDs.marqueeId = nativeIndexOfString(mNative, "marquee"); - - if (localLOGV) Log.v(TAG, "BoldId=" + mStyleIDs.boldId - + ", ItalicId=" + mStyleIDs.italicId - + ", UnderlineId=" + mStyleIDs.underlineId); + } + + // the style array is a flat array of <type, start, end> hence + // the magic constant 3. + for (int styleIndex = 0; styleIndex < style.length; styleIndex += 3) { + int styleId = style[styleIndex]; + + if (styleId == mStyleIDs.boldId || styleId == mStyleIDs.italicId + || styleId == mStyleIDs.underlineId || styleId == mStyleIDs.ttId + || styleId == mStyleIDs.bigId || styleId == mStyleIDs.smallId + || styleId == mStyleIDs.subId || styleId == mStyleIDs.supId + || styleId == mStyleIDs.strikeId || styleId == mStyleIDs.listItemId + || styleId == mStyleIDs.marqueeId) { + // id already found skip to next style + continue; + } + + String styleTag = nativeGetString(mNative, styleId); + + if (styleTag.equals("b")) { + mStyleIDs.boldId = styleId; + } else if (styleTag.equals("i")) { + mStyleIDs.italicId = styleId; + } else if (styleTag.equals("u")) { + mStyleIDs.underlineId = styleId; + } else if (styleTag.equals("tt")) { + mStyleIDs.ttId = styleId; + } else if (styleTag.equals("big")) { + mStyleIDs.bigId = styleId; + } else if (styleTag.equals("small")) { + mStyleIDs.smallId = styleId; + } else if (styleTag.equals("sup")) { + mStyleIDs.supId = styleId; + } else if (styleTag.equals("sub")) { + mStyleIDs.subId = styleId; + } else if (styleTag.equals("strike")) { + mStyleIDs.strikeId = styleId; + } else if (styleTag.equals("li")) { + mStyleIDs.listItemId = styleId; + } else if (styleTag.equals("marquee")) { + mStyleIDs.marqueeId = styleId; + } } res = applyStyles(str, style, mStyleIDs); @@ -119,17 +146,17 @@ final class StringBlock { } static final class StyleIDs { - private int boldId; - private int italicId; - private int underlineId; - private int ttId; - private int bigId; - private int smallId; - private int subId; - private int supId; - private int strikeId; - private int listItemId; - private int marqueeId; + private int boldId = -1; + private int italicId = -1; + private int underlineId = -1; + private int ttId = -1; + private int bigId = -1; + private int smallId = -1; + private int subId = -1; + private int supId = -1; + private int strikeId = -1; + private int listItemId = -1; + private int marqueeId = -1; } private CharSequence applyStyles(String str, int[] style, StyleIDs ids) { @@ -403,6 +430,5 @@ final class StringBlock { private static final native int nativeGetSize(int obj); private static final native String nativeGetString(int obj, int idx); private static final native int[] nativeGetStyle(int obj, int idx); - private static final native int nativeIndexOfString(int obj, String str); private static final native void nativeDestroy(int obj); } diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index dd9c8f09bd16..ecfa2c1c5989 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -624,4 +624,39 @@ public class ConnectivityManager } catch (RemoteException e) { } } + + /** + * @param proxyProperties The definition for the new global http proxy + * {@hide} + */ + public void setGlobalProxy(ProxyProperties p) { + try { + mService.setGlobalProxy(p); + } catch (RemoteException e) { + } + } + + /** + * @return proxyProperties for the current global proxy + * {@hide} + */ + public ProxyProperties getGlobalProxy() { + try { + return mService.getGlobalProxy(); + } catch (RemoteException e) { + return null; + } + } + + /** + * @return proxyProperties for the current proxy (global if set, network specific if not) + * {@hide} + */ + public ProxyProperties getProxy() { + try { + return mService.getProxy(); + } catch (RemoteException e) { + return null; + } + } } diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl index 35054d6c4094..70ab4f153ef7 100644 --- a/core/java/android/net/IConnectivityManager.aidl +++ b/core/java/android/net/IConnectivityManager.aidl @@ -18,6 +18,7 @@ package android.net; import android.net.LinkProperties; import android.net.NetworkInfo; +import android.net.ProxyProperties; import android.os.IBinder; /** @@ -85,4 +86,10 @@ interface IConnectivityManager void requestNetworkTransitionWakelock(in String forWhom); void reportInetCondition(int networkType, int percentage); + + ProxyProperties getGlobalProxy(); + + void setGlobalProxy(in ProxyProperties p); + + ProxyProperties getProxy(); } diff --git a/core/java/android/net/Proxy.java b/core/java/android/net/Proxy.java index 21c485e70e18..3b9b9fe8c6e8 100644 --- a/core/java/android/net/Proxy.java +++ b/core/java/android/net/Proxy.java @@ -16,9 +16,12 @@ package android.net; +import android.annotation.SdkConstant; +import android.annotation.SdkConstant.SdkConstantType; import android.content.ContentResolver; import android.content.Context; import android.database.ContentObserver; +import android.net.ProxyProperties; import android.os.Handler; import android.os.SystemProperties; import android.text.TextUtils; @@ -55,17 +58,22 @@ public final class Proxy { // Set to true to enable extra debugging. private static final boolean DEBUG = false; + private static final String TAG = "Proxy"; - // Used to notify an app that's caching the default connection proxy - // that either the default connection or its proxy has changed - public static final String PROXY_CHANGE_ACTION = - "android.intent.action.PROXY_CHANGE"; - - private static ReadWriteLock sProxyInfoLock = new ReentrantReadWriteLock(); - - private static SettingsObserver sGlobalProxyChangedObserver = null; - - private static ProxySpec sGlobalProxySpec = null; + /** + * Used to notify an app that's caching the default connection proxy + * that either the default connection or its proxy has changed. + * The intent will have the following extra value:</p> + * <ul> + * <li><em>EXTRA_PROXY_INFO</em> - The ProxyProperties for the proxy + * </ul> + * + * <p class="note">This is a protected intent that can only be sent by the system + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String PROXY_CHANGE_ACTION = "android.intent.action.PROXY_CHANGE"; + /** {@hide} **/ + public static final String EXTRA_PROXY_INFO = "proxy"; private static ConnectivityManager sConnectivityManager = null; @@ -88,62 +96,6 @@ public final class Proxy { EXCLLIST_PATTERN = Pattern.compile(EXCLLIST_REGEXP); } - // useful because it holds the processed exclusion list - don't want to reparse it each time - private static class ProxySpec { - String[] exclusionList; - InetSocketAddress address = null; - public ProxySpec() { - exclusionList = new String[0]; - }; - } - - private static boolean isURLInExclusionList(String url, String[] exclusionList) { - if (url == null) { - return false; - } - Uri u = Uri.parse(url); - String urlDomain = u.getHost(); - // If the domain is defined as ".android.com" or "android.com", we wish to match - // http://android.com as well as http://xxx.android.com , but not - // http://myandroid.com . This code works out the logic. - for (String excludedDomain : exclusionList) { - String dotDomain = "." + excludedDomain; - if (urlDomain.equals(excludedDomain)) { - return true; - } - if (urlDomain.endsWith(dotDomain)) { - return true; - } - } - // No match - return false; - } - - private static String parseHost(String proxySpec) { - int i = proxySpec.indexOf(':'); - if (i == -1) { - if (DEBUG) { - Assert.assertTrue(proxySpec.length() == 0); - } - return null; - } - return proxySpec.substring(0, i); - } - - private static int parsePort(String proxySpec) { - int i = proxySpec.indexOf(':'); - if (i == -1) { - if (DEBUG) { - Assert.assertTrue(proxySpec.length() == 0); - } - return -1; - } - if (DEBUG) { - Assert.assertTrue(i < proxySpec.length()); - } - return Integer.parseInt(proxySpec.substring(i+1)); - } - /** * Return the proxy object to be used for the URL given as parameter. * @param ctx A Context used to get the settings for the proxy host. @@ -154,34 +106,31 @@ public final class Proxy { * {@hide} */ public static final java.net.Proxy getProxy(Context ctx, String url) { - sProxyInfoLock.readLock().lock(); - java.net.Proxy retval; - try { - if (sGlobalProxyChangedObserver == null) { - registerContentObserversReadLocked(ctx); - parseGlobalProxyInfoReadLocked(ctx); + String host = ""; + if (url != null) { + URI uri = URI.create(url); + host = uri.getHost(); + } + + if (!isLocalHost(host)) { + if (sConnectivityManager == null) { + sConnectivityManager = (ConnectivityManager)ctx.getSystemService( + Context.CONNECTIVITY_SERVICE); } - if (sGlobalProxySpec != null) { - // Proxy defined - Apply exclusion rules - if (isURLInExclusionList(url, sGlobalProxySpec.exclusionList)) { - // Return no proxy - retval = java.net.Proxy.NO_PROXY; - } else { - retval = - new java.net.Proxy(java.net.Proxy.Type.HTTP, sGlobalProxySpec.address); + if (sConnectivityManager == null) return java.net.Proxy.NO_PROXY; + + ProxyProperties proxyProperties = sConnectivityManager.getProxy(); + + if (proxyProperties != null) { + if (!proxyProperties.isExcluded(host)) { + return proxyProperties.makeProxy(); } - } else { - retval = getDefaultProxy(ctx, url); } - } finally { - sProxyInfoLock.readLock().unlock(); - } - if ((retval != java.net.Proxy.NO_PROXY) && (isLocalHost(url))) { - retval = java.net.Proxy.NO_PROXY; } - return retval; + return java.net.Proxy.NO_PROXY; } + // TODO: deprecate this function /** * Return the proxy host set by the user. @@ -236,35 +185,6 @@ public final class Proxy { return -1; } - // TODO - cache the details for each network so we don't have to fetch and parse - // on each request - private static final java.net.Proxy getDefaultProxy(Context context, String url) { - if (sConnectivityManager == null) { - sConnectivityManager = (ConnectivityManager)context.getSystemService( - Context.CONNECTIVITY_SERVICE); - } - if (sConnectivityManager == null) return java.net.Proxy.NO_PROXY; - - LinkProperties linkProperties = sConnectivityManager.getActiveLinkProperties(); - - if (linkProperties != null) { - ProxyProperties proxyProperties = linkProperties.getHttpProxy(); - - if (proxyProperties != null) { - String exclusionList = proxyProperties.getExclusionList(); - SocketAddress socketAddr = proxyProperties.getSocketAddress(); - if (socketAddr != null) { - String[] parsedExclusionArray = - parsedExclusionArray = parseExclusionList(exclusionList); - if (!isURLInExclusionList(url, parsedExclusionArray)) { - return new java.net.Proxy(java.net.Proxy.Type.HTTP, socketAddr); - } - } - } - } - return java.net.Proxy.NO_PROXY; - } - // TODO: remove this function / deprecate /** * Returns the preferred proxy to be used by clients. This is a wrapper @@ -291,13 +211,11 @@ public final class Proxy { } } - private static final boolean isLocalHost(String url) { - if (url == null) { + private static final boolean isLocalHost(String host) { + if (host == null) { return false; } try { - final URI uri = URI.create(url); - final String host = uri.getHost(); if (host != null) { if (host.equalsIgnoreCase("localhost")) { return true; @@ -317,92 +235,6 @@ public final class Proxy { return false; } - private static class SettingsObserver extends ContentObserver { - - private Context mContext; - - SettingsObserver(Context ctx) { - super(new Handler(ctx.getMainLooper())); - mContext = ctx; - } - - @Override - public void onChange(boolean selfChange) { - sProxyInfoLock.readLock().lock(); - parseGlobalProxyInfoReadLocked(mContext); - sProxyInfoLock.readLock().unlock(); - } - } - - private static final void registerContentObserversReadLocked(Context ctx) { - Uri uriGlobalProxy = Settings.Secure.getUriFor(Settings.Secure.HTTP_PROXY); - Uri uriGlobalExclList = - Settings.Secure.getUriFor(Settings.Secure.HTTP_PROXY_EXCLUSION_LIST); - - // No lock upgrading (from read to write) allowed - sProxyInfoLock.readLock().unlock(); - sProxyInfoLock.writeLock().lock(); - try { - sGlobalProxyChangedObserver = new SettingsObserver(ctx); - } finally { - // Downgrading locks (from write to read) is allowed - sProxyInfoLock.readLock().lock(); - sProxyInfoLock.writeLock().unlock(); - } - ctx.getContentResolver().registerContentObserver(uriGlobalProxy, false, - sGlobalProxyChangedObserver); - ctx.getContentResolver().registerContentObserver(uriGlobalExclList, false, - sGlobalProxyChangedObserver); - } - - private static final void parseGlobalProxyInfoReadLocked(Context ctx) { - ContentResolver contentResolver = ctx.getContentResolver(); - String proxyHost = Settings.Secure.getString( - contentResolver, - Settings.Secure.HTTP_PROXY); - if (TextUtils.isEmpty(proxyHost)) { - // Clear signal - sProxyInfoLock.readLock().unlock(); - sProxyInfoLock.writeLock().lock(); - sGlobalProxySpec = null; - sProxyInfoLock.readLock().lock(); - sProxyInfoLock.writeLock().unlock(); - return; - } - String exclusionListSpec = Settings.Secure.getString( - contentResolver, - Settings.Secure.HTTP_PROXY_EXCLUSION_LIST); - String host = parseHost(proxyHost); - int port = parsePort(proxyHost); - ProxySpec tmpProxySpec = null; - if (proxyHost != null) { - tmpProxySpec = new ProxySpec(); - tmpProxySpec.address = new InetSocketAddress(host, port); - tmpProxySpec.exclusionList = parseExclusionList(exclusionListSpec); - } - sProxyInfoLock.readLock().unlock(); - sProxyInfoLock.writeLock().lock(); - sGlobalProxySpec = tmpProxySpec; - sProxyInfoLock.readLock().lock(); - sProxyInfoLock.writeLock().unlock(); - } - - private static String[] parseExclusionList(String exclusionList) { - String[] processedArray = new String[0]; - if (!TextUtils.isEmpty(exclusionList)) { - String[] exclusionListArray = exclusionList.toLowerCase().split(","); - processedArray = new String[exclusionListArray.length]; - for (int i = 0; i < exclusionListArray.length; i++) { - String entry = exclusionListArray[i].trim(); - if (entry.startsWith(".")) { - entry = entry.substring(1); - } - processedArray[i] = entry; - } - } - return processedArray; - } - /** * Validate syntax of hostname, port and exclusion list entries * {@hide} @@ -480,4 +312,44 @@ public final class Proxy { new SchemeRegistry(), ProxySelector.getDefault(), context); return ret; } + + /** @hide */ + public static final void setHttpProxySystemProperty(ProxyProperties p) { + String host = null; + String port = null; + String exclList = null; + if (p != null) { + host = p.getHost(); + port = Integer.toString(p.getPort()); + exclList = p.getExclusionList(); + } + setHttpProxySystemProperty(host, port, exclList); + } + + /** @hide */ + public static final void setHttpProxySystemProperty(String host, String port, String exclList) { + if (exclList != null) exclList = exclList.replace(",", "|"); + if (false) Log.d(TAG, "setHttpProxySystemProperty :"+host+":"+port+" - "+exclList); + if (host != null) { + System.setProperty("http.proxyHost", host); + System.setProperty("https.proxyHost", host); + } else { + System.clearProperty("http.proxyHost"); + System.clearProperty("https.proxyHost"); + } + if (port != null) { + System.setProperty("http.proxyPort", port); + System.setProperty("https.proxyPort", port); + } else { + System.clearProperty("http.proxyPort"); + System.clearProperty("https.proxyPort"); + } + if (exclList != null) { + System.setProperty("http.nonProxyHosts", exclList); + System.setProperty("https.nonProxyHosts", exclList); + } else { + System.clearProperty("http.nonProxyHosts"); + System.clearProperty("https.nonProxyHosts"); + } + } } diff --git a/core/java/android/net/ProxyProperties.aidl b/core/java/android/net/ProxyProperties.aidl new file mode 100644 index 000000000000..02ea15d769f9 --- /dev/null +++ b/core/java/android/net/ProxyProperties.aidl @@ -0,0 +1,21 @@ +/* +** +** 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 android.net; + +parcelable ProxyProperties; + diff --git a/core/java/android/net/ProxyProperties.java b/core/java/android/net/ProxyProperties.java index 5fd0d89c72eb..cbe4445a3a2d 100644 --- a/core/java/android/net/ProxyProperties.java +++ b/core/java/android/net/ProxyProperties.java @@ -19,6 +19,8 @@ package android.net; import android.os.Parcel; import android.os.Parcelable; +import android.text.TextUtils; +import android.util.Log; import java.net.InetAddress; import java.net.InetSocketAddress; @@ -30,44 +32,108 @@ import java.net.UnknownHostException; */ public class ProxyProperties implements Parcelable { - private InetSocketAddress mProxy; + private String mHost; + private int mPort; private String mExclusionList; + private String[] mParsedExclusionList; - public ProxyProperties() { + public ProxyProperties(String host, int port, String exclList) { + mHost = host; + mPort = port; + setExclusionList(exclList); + } + + private ProxyProperties(String host, int port, String exclList, String[] parsedExclList) { + mHost = host; + mPort = port; + mExclusionList = exclList; + mParsedExclusionList = parsedExclList; } // copy constructor instead of clone public ProxyProperties(ProxyProperties source) { if (source != null) { - mProxy = source.getSocketAddress(); - String exclusionList = source.getExclusionList(); - if (exclusionList != null) { - mExclusionList = new String(exclusionList); - } + mHost = source.getHost(); + mPort = source.getPort(); + mExclusionList = source.getExclusionList(); + mParsedExclusionList = source.mParsedExclusionList; } } public InetSocketAddress getSocketAddress() { - return mProxy; + InetSocketAddress inetSocketAddress = null; + try { + inetSocketAddress = new InetSocketAddress(mHost, mPort); + } catch (IllegalArgumentException e) { } + return inetSocketAddress; } - public void setSocketAddress(InetSocketAddress proxy) { - mProxy = proxy; + public String getHost() { + return mHost; } + public int getPort() { + return mPort; + } + + // comma separated public String getExclusionList() { return mExclusionList; } - public void setExclusionList(String exclusionList) { + // comma separated + private void setExclusionList(String exclusionList) { mExclusionList = exclusionList; + if (mExclusionList == null) { + mParsedExclusionList = new String[0]; + } else { + String splitExclusionList[] = exclusionList.toLowerCase().split(","); + mParsedExclusionList = new String[splitExclusionList.length * 2]; + for (int i = 0; i < splitExclusionList.length; i++) { + String s = splitExclusionList[i].trim(); + if (s.startsWith(".")) s = s.substring(1); + mParsedExclusionList[i*2] = s; + mParsedExclusionList[(i*2)+1] = "." + s; + } + } + } + + public boolean isExcluded(String url) { + if (TextUtils.isEmpty(url) || mParsedExclusionList == null || + mParsedExclusionList.length == 0) return false; + + Uri u = Uri.parse(url); + String urlDomain = u.getHost(); + if (urlDomain == null) return false; + for (int i = 0; i< mParsedExclusionList.length; i+=2) { + if (urlDomain.equals(mParsedExclusionList[i]) || + urlDomain.endsWith(mParsedExclusionList[i+1])) { + return true; + } + } + return false; + } + + public java.net.Proxy makeProxy() { + java.net.Proxy proxy = java.net.Proxy.NO_PROXY; + if (mHost != null) { + try { + InetSocketAddress inetSocketAddress = new InetSocketAddress(mHost, mPort); + proxy = new java.net.Proxy(java.net.Proxy.Type.HTTP, inetSocketAddress); + } catch (IllegalArgumentException e) { + } + } + return proxy; } @Override public String toString() { StringBuilder sb = new StringBuilder(); - if (mProxy != null) { - sb.append(mProxy.toString()); + if (mHost != null) { + sb.append("["); + sb.append(mHost); + sb.append("] "); + sb.append(Integer.toString(mPort)); if (mExclusionList != null) { sb.append(" xl=").append(mExclusionList); } @@ -75,6 +141,20 @@ public class ProxyProperties implements Parcelable { return sb.toString(); } + @Override + public boolean equals(Object o) { + if (!(o instanceof ProxyProperties)) return false; + ProxyProperties p = (ProxyProperties)o; + if (mExclusionList != null && !mExclusionList.equals(p.getExclusionList())) return false; + if (mHost != null && p.getHost() != null && mHost.equals(p.getHost()) == false) { + return false; + } + if (mHost != null && p.mHost == null) return false; + if (mHost == null && p.mHost != null) return false; + if (mPort != p.mPort) return false; + return true; + } + /** * Implement the Parcelable interface * @hide @@ -88,27 +168,15 @@ public class ProxyProperties implements Parcelable { * @hide */ public void writeToParcel(Parcel dest, int flags) { - String host = null; - if (mProxy != null) { - try { - InetAddress addr = mProxy.getAddress(); - if (addr != null) { - host = addr.getHostAddress(); - } else { - /* Does not resolve when addr is null */ - host = mProxy.getHostName(); - } - } catch (Exception e) { } - } - - if (host != null) { + if (mHost != null) { dest.writeByte((byte)1); - dest.writeString(host); - dest.writeInt(mProxy.getPort()); + dest.writeString(mHost); + dest.writeInt(mPort); } else { dest.writeByte((byte)0); } dest.writeString(mExclusionList); + dest.writeStringArray(mParsedExclusionList); } /** @@ -118,16 +186,16 @@ public class ProxyProperties implements Parcelable { public static final Creator<ProxyProperties> CREATOR = new Creator<ProxyProperties>() { public ProxyProperties createFromParcel(Parcel in) { - ProxyProperties proxyProperties = new ProxyProperties(); + String host = null; + int port = 0; if (in.readByte() == 1) { - try { - String host = in.readString(); - int port = in.readInt(); - proxyProperties.setSocketAddress(InetSocketAddress.createUnresolved( - host, port)); - } catch (IllegalArgumentException e) { } + host = in.readString(); + port = in.readInt(); } - proxyProperties.setExclusionList(in.readString()); + String exclList = in.readString(); + String[] parsedExclList = in.readStringArray(); + ProxyProperties proxyProperties = + new ProxyProperties(host, port, exclList, parsedExclList); return proxyProperties; } diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java index 93542c6ee355..c836e56d2026 100644 --- a/core/java/android/os/StrictMode.java +++ b/core/java/android/os/StrictMode.java @@ -104,7 +104,7 @@ import java.util.HashMap; */ public final class StrictMode { private static final String TAG = "StrictMode"; - private static final boolean LOG_V = false; + private static final boolean LOG_V = Log.isLoggable(TAG, Log.VERBOSE); private static final boolean IS_USER_BUILD = "user".equals(Build.TYPE); diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 1fe2c5a94094..70757740655f 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -2424,17 +2424,32 @@ public final class Settings { public static final String DISABLED_SYSTEM_INPUT_METHODS = "disabled_system_input_methods"; /** - * Host name and port for global proxy. + * Host name and port for global http proxy. Uses ':' seperator for between host and port + * TODO - deprecate in favor of global_http_proxy_host, etc */ public static final String HTTP_PROXY = "http_proxy"; /** + * Host name for global http proxy. Set via ConnectivityManager. + * @hide + */ + public static final String GLOBAL_HTTP_PROXY_HOST = "global_http_proxy_host"; + + /** + * Integer host port for global http proxy. Set via ConnectivityManager. + * @hide + */ + public static final String GLOBAL_HTTP_PROXY_PORT = "global_http_proxy_port"; + + /** * Exclusion list for global proxy. This string contains a list of comma-separated * domains where the global proxy does not apply. Domains should be listed in a comma- * separated list. Example of acceptable formats: ".domain1.com,my.domain2.com" + * Use ConnectivityManager to set/get. * @hide */ - public static final String HTTP_PROXY_EXCLUSION_LIST = "http_proxy_exclusion_list"; + public static final String GLOBAL_HTTP_PROXY_EXCLUSION_LIST = + "global_http_proxy_exclusion_list"; /** * Enables the UI setting to allow the user to specify the global HTTP proxy diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java index cc969cb9f50c..3dd1ecd34ad8 100644 --- a/core/java/android/text/StaticLayout.java +++ b/core/java/android/text/StaticLayout.java @@ -21,10 +21,10 @@ import com.android.internal.util.ArrayUtils; import android.graphics.Bitmap; import android.graphics.Paint; import android.text.style.LeadingMarginSpan; +import android.text.style.LeadingMarginSpan.LeadingMarginSpan2; import android.text.style.LineHeightSpan; import android.text.style.MetricAffectingSpan; import android.text.style.TabStopSpan; -import android.text.style.LeadingMarginSpan.LeadingMarginSpan2; /** * StaticLayout is a Layout for text that will not be edited after it @@ -36,9 +36,7 @@ import android.text.style.LeadingMarginSpan.LeadingMarginSpan2; * float, float, android.graphics.Paint) * Canvas.drawText()} directly.</p> */ -public class -StaticLayout -extends Layout +public class StaticLayout extends Layout { public StaticLayout(CharSequence source, TextPaint paint, int width, @@ -260,7 +258,7 @@ extends Layout float before = w; if (c == '\n') { - ; + // intentionally left empty } else if (c == '\t') { if (hasTab == false) { hasTab = true; @@ -837,6 +835,7 @@ extends Layout // rather than relying on member functions. // The logic mirrors that of Layout.getLineForVertical // FIXME: It may be faster to do a linear search for layouts without many lines. + @Override public int getLineForVertical(int vertical) { int high = mLineCount; int low = -1; @@ -857,38 +856,47 @@ extends Layout } } + @Override public int getLineCount() { return mLineCount; } + @Override public int getLineTop(int line) { return mLines[mColumns * line + TOP]; } + @Override public int getLineDescent(int line) { return mLines[mColumns * line + DESCENT]; } + @Override public int getLineStart(int line) { return mLines[mColumns * line + START] & START_MASK; } + @Override public int getParagraphDirection(int line) { return mLines[mColumns * line + DIR] >> DIR_SHIFT; } + @Override public boolean getLineContainsTab(int line) { return (mLines[mColumns * line + TAB] & TAB_MASK) != 0; } + @Override public final Directions getLineDirections(int line) { return mLineDirections[line]; } + @Override public int getTopPadding() { return mTopPadding; } + @Override public int getBottomPadding() { return mBottomPadding; } @@ -935,7 +943,6 @@ extends Layout private Directions[] mLineDirections; private static final int START_MASK = 0x1FFFFFFF; - private static final int DIR_MASK = 0xC0000000; private static final int DIR_SHIFT = 30; private static final int TAB_MASK = 0x20000000; diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index d38898587bf5..a3e9bc9f3398 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -3018,6 +3018,11 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); clearScrollingCache(); mScroller.abortAnimation(); + + if (mFlingStrictSpan != null) { + mFlingStrictSpan.finish(); + mFlingStrictSpan = null; + } } void flywheelTouch() { @@ -3086,11 +3091,6 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te Debug.stopMethodTracing(); mFlingProfilingStarted = false; } - - if (mFlingStrictSpan != null) { - mFlingStrictSpan.finish(); - mFlingStrictSpan = null; - } } } } diff --git a/core/java/android/widget/DatePicker.java b/core/java/android/widget/DatePicker.java index a3712902b85e..595b4871c9e6 100644 --- a/core/java/android/widget/DatePicker.java +++ b/core/java/android/widget/DatePicker.java @@ -22,6 +22,7 @@ import android.content.res.TypedArray; import android.os.Parcel; import android.os.Parcelable; import android.text.format.DateFormat; +import android.text.format.DateUtils; import android.util.AttributeSet; import android.util.SparseArray; import android.view.LayoutInflater; @@ -33,6 +34,7 @@ import com.android.internal.R; import java.text.DateFormatSymbols; import java.text.SimpleDateFormat; import java.util.Calendar; +import java.util.Locale; /** * A view for selecting a month / year / day based on a calendar like layout. @@ -47,7 +49,10 @@ public class DatePicker extends FrameLayout { private static final int DEFAULT_START_YEAR = 1900; private static final int DEFAULT_END_YEAR = 2100; - + + // This ignores Undecimber, but we only support real Gregorian calendars. + private static final int NUMBER_OF_MONTHS = 12; + /* UI Components */ private final NumberPicker mDayPicker; private final NumberPicker mMonthPicker; @@ -62,6 +67,10 @@ public class DatePicker extends FrameLayout { private int mMonth; private int mYear; + private Object mMonthUpdateLock = new Object(); + private volatile Locale mMonthLocale; + private String[] mShortMonths; + /** * The callback used to indicate the user changes the date. */ @@ -102,8 +111,7 @@ public class DatePicker extends FrameLayout { }); mMonthPicker = (NumberPicker) findViewById(R.id.month); mMonthPicker.setFormatter(NumberPicker.TWO_DIGIT_FORMATTER); - DateFormatSymbols dfs = new DateFormatSymbols(); - String[] months = dfs.getShortMonths(); + final String[] months = getShortMonths(); /* * If the user is in a locale where the month names are numeric, @@ -114,9 +122,9 @@ public class DatePicker extends FrameLayout { for (int i = 0; i < months.length; i++) { months[i] = String.valueOf(i + 1); } - mMonthPicker.setRange(1, 12); + mMonthPicker.setRange(1, NUMBER_OF_MONTHS); } else { - mMonthPicker.setRange(1, 12, months); + mMonthPicker.setRange(1, NUMBER_OF_MONTHS, months); } mMonthPicker.setSpeed(200); @@ -246,11 +254,30 @@ public class DatePicker extends FrameLayout { mMonth = monthOfYear; mDay = dayOfMonth; updateSpinners(); - reorderPickers(new DateFormatSymbols().getShortMonths()); + reorderPickers(getShortMonths()); notifyDateChanged(); } } + private String[] getShortMonths() { + final Locale currentLocale = Locale.getDefault(); + if (currentLocale.equals(mMonthLocale) && mShortMonths != null) { + return mShortMonths; + } else { + synchronized (mMonthUpdateLock) { + if (!currentLocale.equals(mMonthLocale)) { + mShortMonths = new String[NUMBER_OF_MONTHS]; + for (int i = 0; i < NUMBER_OF_MONTHS; i++) { + mShortMonths[i] = DateUtils.getMonthString(Calendar.JANUARY + i, + DateUtils.LENGTH_MEDIUM); + } + mMonthLocale = currentLocale; + } + } + return mShortMonths; + } + } + private static class SavedState extends BaseSavedState { private final int mYear; diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index e6503a9b2c9b..80a6a2f385a0 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -7117,7 +7117,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener csr = new CommitSelectionReceiver(oldSelStart, oldSelEnd); } - handled |= imm.showSoftInput(this, 0, csr) && (csr != null); + if (!mTextIsSelectable) { + // Selection in read-only text should not bring up the IME. + handled |= imm.showSoftInput(this, 0, csr) && (csr != null); + } // Cannot be done by CommitSelectionReceiver, which might not always be called, // for instance when dealing with an ExtractEditText. diff --git a/core/jni/android_util_StringBlock.cpp b/core/jni/android_util_StringBlock.cpp index 641fbce5c2a6..a021efda763a 100644 --- a/core/jni/android_util_StringBlock.cpp +++ b/core/jni/android_util_StringBlock.cpp @@ -147,25 +147,6 @@ static jintArray android_content_StringBlock_nativeGetStyle(JNIEnv* env, jobject return array; } -static jint android_content_StringBlock_nativeIndexOfString(JNIEnv* env, jobject clazz, - jint token, jstring str) -{ - ResStringPool* osb = (ResStringPool*)token; - if (osb == NULL || str == NULL) { - doThrow(env, "java/lang/NullPointerException"); - return 0; - } - - const char16_t* str16 = env->GetStringChars(str, NULL); - jsize strLen = env->GetStringLength(str); - - ssize_t idx = osb->indexOfString(str16, strLen); - - env->ReleaseStringChars(str, str16); - - return idx; -} - static void android_content_StringBlock_nativeDestroy(JNIEnv* env, jobject clazz, jint token) { @@ -193,8 +174,6 @@ static JNINativeMethod gStringBlockMethods[] = { (void*) android_content_StringBlock_nativeGetString }, { "nativeGetStyle", "(II)[I", (void*) android_content_StringBlock_nativeGetStyle }, - { "nativeIndexOfString","(ILjava/lang/String;)I", - (void*) android_content_StringBlock_nativeIndexOfString }, { "nativeDestroy", "(I)V", (void*) android_content_StringBlock_nativeDestroy }, }; diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index d02ca64fdcf0..ddc63ddb8681 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -92,6 +92,7 @@ <protected-broadcast android:name="android.nfc.action.LLCP_LINK_STATE_CHANGED" /> <protected-broadcast android:name="android.nfc.action.TRANSACTION_DETECTED" /> <protected-broadcast android:name="android.intent.action.CLEAR_DNS_CACHE" /> + <protected-broadcast android:name="android.intent.action.PROXY_CHANGE" /> <!-- ====================================== --> <!-- Permissions for things that cost money --> diff --git a/docs/html/guide/appendix/media-formats.jd b/docs/html/guide/appendix/media-formats.jd index 94a6471c9b87..87099943c6e6 100644 --- a/docs/html/guide/appendix/media-formats.jd +++ b/docs/html/guide/appendix/media-formats.jd @@ -22,7 +22,7 @@ page.title=Android Supported Media Formats <tr> <td rowspan="9">Audio</td> <td>AAC LC/LTP</td> -<td> </td> +<td style="text-align: center;">X</td> <td style="text-align: center;">X</td> <td rowspan="3">Mono/Stereo content in any combination of standard bit rates up to 160 kbps and sampling rates from 8 to 48kHz</td> <td rowspan="3">3GPP (.3gp) and MPEG-4 (.mp4, .m4a). No support for raw AAC (.aac)</td> @@ -51,7 +51,7 @@ page.title=Android Supported Media Formats <tr> <td>AMR-WB</td> -<td> </td> +<td style="text-align: center;">X</td> <td style="text-align: center;">X</td> <td>9 rates from 6.60 kbit/s to 23.85 kbit/s sampled @ 16kHz</td> <td>3GPP (.3gp)</td> @@ -109,7 +109,7 @@ page.title=Android Supported Media Formats <tr> <td>PNG</td> -<td> </td> +<td style="text-align: center;">X</td> <td style="text-align: center;">X</td> <td> </td> <td>PNG (.png)</td> diff --git a/docs/html/guide/guide_toc.cs b/docs/html/guide/guide_toc.cs index 545807eb1894..3cc18060841f 100644 --- a/docs/html/guide/guide_toc.cs +++ b/docs/html/guide/guide_toc.cs @@ -196,6 +196,9 @@ </li> </ul> <ul> + <li><a href="<?cs var:toroot ?>guide/topics/fragments/index.html"> + <span class="en">Fragments</span> + </a> <span class="new">new!</span></li> <li class="toggle-list"> <div><a href="<?cs var:toroot ?>guide/topics/graphics/index.html"> <span class="en">Graphics</span> diff --git a/docs/html/guide/topics/fragments/index.jd b/docs/html/guide/topics/fragments/index.jd new file mode 100644 index 000000000000..ce10ef7d50e1 --- /dev/null +++ b/docs/html/guide/topics/fragments/index.jd @@ -0,0 +1,648 @@ +page.title=Fragments +@jd:body + +<div id="qv-wrapper"> +<div id="qv"> + + <h2>Quickview</h2> + <ul> + <li>Decompose application functionality and UI into reusable modules</li> + <li>Add multiple fragments to a screen to avoid switching activities</li> + <li>Fragments have their own lifecycle, state, and back stack</li> + <li>Fragments require API Level HONEYCOMB or greater</li> + </ul> + + <h2>In this document</h2> + <ol> + <li><a href="#Creating">Creating fragments</a></li> + <li><a href="#Adding">Adding a fragment to an activity</a></li> + <li><a href="#Managing">Managing fragments</a></li> + <li><a href="#Lifecycle">Handling the lifecycle</a></li> + <li><a href="#Integrating">Integrating with the activity</a></li> + <li><a href="#Menus">Adding menus</a></li> + </ol> + + <h2>Key classes</h2> + <ol> + <li>{@link android.app.Fragment}</li> + <li>{@link android.app.FragmentManager}</li> + <li>{@link android.app.FragmentTransaction}</li> + </ol> + + <!-- + <h2>Related samples</h2> + <ol> + <li><a +href="{@docRoot}resources/samples/NotePad/index.html">NotePad</a></li> + </ol> + --> +</div> +</div> + + +<p>An {@link android.app.Activity} is always the window in which users interact with your +application, but a {@link android.app.Fragment} can be responsible for distinct operations and UI +that's embedded in an activity. So, when using fragments, your activity becomes more like a +container for fragments that define the activity's behavior and UI.</p> + +<p>Fragments have their own +set of lifecylce callback methods and recieve their own user input events. A fragment must always be +embedded in an activity and the fragment's lifecycle is directly affected by the activity's +lifecycle. For example, when the activity is stopped, so are all fragments in it, and when +the activity is destroyed, so are all fragments. However, while an activity +is active (in the "resumed" lifecycle stage), you can manipulate the lifecycle of each fragment +independently. For example, you can add and remove fragments while the activity is active and you +can add each fragment to a back stack within the activity—each back stack entry in the +activity is actually a record of a "transaction" that occurred with the activity's fragments, so +that the user can reverse the transaction with the BACK key (this is discussed more later).</p> + +<div class="figure" style="width:314px"> +<img src="{@docRoot}images/fragment_lifecycle.png" alt="" /> +<p class="img-caption"><strong>Figure 1.</strong> The lifecycle of a fragment (while its +activity is running).</p> +</div> + +<p>Android introduced fragments in Android X.X (API Level HONEYCOMB), with the primary intention to +support more dynamic and flexible UI designs on large screen devices, such as tablets. Because a +tablet has a much larger screen than a mobile phone, there's more room to interchange UI +elements. Fragments allow that without the need for you to start a new activity or manage complex +changes to the view hierarchy. By dividing the layout of an activity into fragments, the code +that defines your activity becomes more modular and interchangable, allowing you to modify the +activity's appearance at runtime and for different types of screens.</p> + +<p>For example, a news application can use one fragment to show a list of articles on the +left and another fragment to display an article on the right—both fragments appear in one +activity, side by side, and each fragment has its own set of lifecycle callback methods and handle +their own user input events. Thus, instead using one activity to select an article and another +activity to read the article, the user can select an article and read it all within the same +activity.</p> + +<!-- ** TODO: Save this for later or move it down in the doc so the intro isn't overwhelming ** + +<p>A fragment can be a modular and reusable component in your application. That is, because +the fragment defines its own behavior using its own set of lifecycle callbacks, you can +include one fragment in multiple activities. This also enables you to create one version of your +application for multiple screen sizes. For instance, on an extra large screen (<em>xlarge</em> +screen configuration), you can embed two or more fragments in one activity, but on a normal-sized +screen (<em>normal</em> screen configuration), you can embed just one fragment in an activity and +then start other activities in order to display the other fragments.</p> +--> + +<p>When you use a fragment as a part of your layout, it technically lives within a {@link +android.view.View} of the activity's layout and defines its own layout of views. You can insert a +fragment into your activity layout by declaring the fragment in the activity's XML layout file, as +a {@code <fragment>} element, or from your application code by adding it to an existing {@link +android.view.View}. However, a fragment is not required to be a part of the activity +layout—you might use a fragment as an invisible worker for the activity (more about that +later).</p> + +<p>The rest of this document describes how to build your application to use fragments, including +how fragments can contribute to the activity options menu and action bar, create context menus, +maintain their state when added to the activity's back stack, and more.</p> + + + +<h2 id="Creating">Creating a Fragment</h2> + +<p>An implementation of the {@link android.app.Fragment} class contains code that looks a lot like +the code in an {@link android.app.Activity}. In fact, if you're +converting an existing Android application to use fragments, you'll move code +from your {@link android.app.Activity} implementation into your {@link android.app.Fragment} class +implementation, and into some of the same callback methods. A fragment contains callback methods +similar to an activity, such as {@link android.app.Fragment#onCreate onCreate()}, {@link +android.app.Fragment#onStart onStart()}, {@link android.app.Fragment#onPause onPause()}, and {@link +android.app.Fragment#onStop onStop()}.</p> + +<p>If you're creating a fragment to be a modular piece of an activity UI, then your +implementation of {@link android.app.Fragment} should include most of the same lifecycle +callback methods traditionally implemented by the activity to initialize elements of the UI and +save and restore state information. Usually, you'll want to implement the following methods:</p> + +<dl> + <dt>{@link android.app.Fragment#onCreate onCreate()}</dt> + <dd>The system calls this when creating the fragment. Within your implementation, you should +initialize the essential components of the fragment that should be retained when the fragment is +paused or stopped.</dd> + <dt>{@link android.app.Fragment#onCreateView onCreateView()}</dt> + <dd>The system calls this when it's time for the fragment to draw its user interface for the +first time. To draw a UI for your fragment, you must return a {@link android.view.View} from this +method that is the root of your fragment's layout. You can return null if the fragment does not +provide a UI.</dd> + <dt>{@link android.app.Activity#onPause onPause()}</dt> + <dd>The system calls this method as the first indication that the user is leaving the +fragment (though it does not always mean the fragment is being destroyed). This is usually where you +should commit any changes that should be persisted beyond the current user session (because +the user might not come back).</dd> +</dl> + +<p>Most applications should implement at least these three methods for each fragment, but there are +several other lifecycle callback methods that you should also use in order to provide the best +user experience when switching fragments and when the activity is paused or stopped. All of the +lifecycle callback methods are discussed more later, in +the section about <a href="#Lifecycle">Handling the Lifecycle</a>.</p> + + +<p>There are also a few different subclasses of {@link android.app.Fragment} that you might want +to use:</p> + +<dl> + <dt>{@link android.app.DialogFragment}</dt> + <dd>Displays a floating dialog. Using this class to create a dialog is a good alternative to using +the dialog helper methods in the {@link android.app.Activity} class, because the dialog can be +incorporated into the fragment back stack managed by the activity.</dd> + + <dt>{@link android.app.ListFragment}</dt> + <dd>Displays a list of items that are managed by an adapter (such as a {@link +android.widget.SimpleCursorAdapter}), similar to {@link android.app.ListActivity}. Provides methods +for managing a list, such as the {@link +android.app.ListFragment#onListItemClick(ListView,View,int,long) onListItemClick()} callback to +handle click events on list items.</dd> + + <dt>{@link android.preference.PreferenceFragment}</dt> + <dd>Displays a hierarchy of {@link android.preference.Preference} objects as a list, similar to +{@link android.preference.PreferenceActivity}. </dd> +</dl> + +<p>However, subclassing the standard {@link android.app.Fragment} class is most common, if +you're not creating a dialog, a list, or displaying preferences.</p> + + +<h3 id="UI">Providing a user interface</h3> + +<p>To provide a UI layout for a fragment, you must implement +the {@link android.app.Fragment#onCreateView onCreateView()} +callback method in your {@link android.app.Fragment} (unless your fragment is a subclass of +{@link android.app.ListFragment}, which returns a {@link android.widget.ListView} from this method +by default). The Android system calls {@link android.app.Fragment#onCreateView onCreateView()} when +it's time for the fragment to draw its layout. Your implementation of this method must return a +{@link android.view.View} that is the root of your fragment's layout.</p> + +<p>The easiest way to provide your layout is to inflate it from a <a +href="{@docRoot}guide/topics/resources/layout-resource.html">layout resource</a>. To help you +inflate a layout, the {@link android.app.Fragment#onCreateView onCreateView()} method passes a +{@link android.view.LayoutInflater} that you can use to get your layout. For example, here's a +simple subclass of {@link android.app.Fragment} that contains an implementation of {@link +android.app.Fragment#onCreateView onCreateView()} that loads the fragment's layout from a +resource:</p> + +<pre> +public static class SimpleFragment extends Fragment { + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + // Inflate the layout for this fragment + return inflater.inflate(R.layout.simple_fragment, container, false); + } +} +</pre> + +<p>The {@link android.view.LayoutInflater#inflate(int,ViewGroup,boolean) inflate()} method takes +three arguments:</p> +<ul> + <li>The resource ID of the layout you want to inflate</li> + <li>The {@link android.view.ViewGroup} to be the parent of the +inflated layout (supplying this is important in order to apply layout parameters from the parent +view)</li> + <li>And a boolean indicating whether the inflated layout should be attached to the {@link +android.view.ViewGroup} (from the second parameter) during inflation (in this case, this +is false because the system is already going to insert the layout into the appropriate parent +view—doing otherwise would create a redundant view group in the final layout)</li> +</ul> + +<p>The {@code container} parameter passed to {@link android.app.Fragment#onCreateView +onCreateView()} provides the parent {@link android.view.ViewGroup} in which your fragment layout +will be inserted, which you can use to generate layout parameters for your +fragment layout. The {@code savedInstanceState} parameter is a {@link android.os.Bundle} that +provides data about the previous instance of the fragment, if the fragment is being resumed +(restoring state is discussed more in the section about <a href="#Lifecycle">Handling the +Lifecycle</a>.</p> + + +<h3 id="Adding">Adding a Fragment to an Activity</h3> + +<p>Each fragment is embedded into the layout of its container activity as a part of the overall view +hierarchy, whether or not it actually provides a UI. If a fragment is not embedded into the activity +layout, then it is never created (it does not receive any lifecycle callbacks). There are two ways +you can add a fragment to the activity layout:</p> + +<ul> + <li><b>Declare the fragment inside the activity's layout XML file.</b> +<p>In this case, you can +specify layout properties for the fragment as if it were a view itself and the fragment's layout +fills that space. For example:</p> +<pre> +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="horizontal" + android:layout_width="match_parent" + android:layout_height="match_parent"> + <fragment android:name="com.example.news.ArticleListFragment" + android:id="@+id/list" + android:layout_weight="1" + android:layout_width="0dp" + android:layout_height="match_parent" /> + <fragment android:name="com.example.news.ArticleReaderFragment" + android:id="@+id/viewer" + android:layout_weight="2" + android:layout_width="0dp" + android:layout_height="match_parent" /> +</LinearLayout> +</pre> + <p>The {@code <fragment>} element uses the {@code android:name} attribute to specify the +{@link android.app.Fragment} class to instantiate and insert into the layout. When the activity +layout is created, the system instantiates each fragment in the layout and calls its {@link +android.app.Fragment#onCreateView onCreateView()} method in order to retrieve the fragment's +layout. The {@link android.view.View} object returned by {@link +android.app.Fragment#onCreateView onCreateView()} is then +placed directly in the activity layout in place of the {@code <fragment>} element.</p> + +<div class="note"> + <p><strong>Note:</strong> Each fragment requires a unique identifier that +the system can use to restore the fragment if the activity is restarted (and which you can use to +perform fragment transactions). There are three ways to identify a fragment:</p> + <ul> + <li>Supply the {@code android:id} attribute with a unique ID, in the {@code +<fragment>}</li> + <li>Supply the {@code android:tag} attribute with a unique string ID, in the {@code + <fragment>}</li> + <li>If neither of the previous two are provided, the system uses the ID of the container + view.</li> + </ul> +</div> + </li> + + <li><b>Or, programmatically add the fragment to an existing {@link android.view.ViewGroup}.</b> +<p>At any time while your activity is running (in the "resumed" state), you can add (and remove) +fragments to your activity layout. You simply need to specify a {@link android.view.ViewGroup} in +which to place the fragment.</p> + <p>To make any fragment transactions in your activity (such as add, remove, or replace a +fragment), you must use APIs from {@link android.app.FragmentTransaction}. You can get an instance +of {@link android.app.FragmentTransaction} from your {@link android.app.Activity} using {@link +android.app.Activity#openFragmentTransaction()}. You can then add a fragment using the {@link +android.app.FragmentTransaction#add add()} method, specifying the fragment to add and the view in +which to insert it. For example:</p> +<pre> +MyFragment fragment = new MyFragment(); +openFragmentTransaction().add(R.id.fragment_container, fragment).commit(); +</pre> + <p>The first argument passed to {@link android.app.FragmentTransaction#add add()} +is the {@link android.view.ViewGroup} in which the fragment should be placed, specified by +resource ID, and the second parameter is the fragment object.</p> + <p>Once you've made your changes using +{@link android.app.FragmentTransaction}, you must +call {@link android.app.FragmentTransaction#commit} in order for the changes to take effect.</p> + </li> +</ul> + + +<h3 id="Example1">Example: simple fragments</h3> + +<p>In the last couple sections, you saw how to declare layout for a fragment and add it to an +activity. What follows is some code that brings it all together, like a "Hello World" for +fragments.</p> + +<p>First, here's a layout file for a fragment:</p> +<pre> +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent" > +<TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/hello" /> +<TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/hello" /> +</LinearLayout> +</pre> + +<p>With that file saved at {@code res/layout/simple_fragment.xml}, the following {@link +android.app.Fragment} uses it for its layout:</p> + +<pre> +public static class SimpleFragment extends Fragment { + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + // Inflate the layout for this fragment + return inflater.inflate(R.layout.simple_fragment, null); + } +} +</pre> + +<p>And the following layout for an activity applies the fragment twice, side by side:</p> + +<pre> +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="horizontal" + android:layout_width="match_parent" + android:layout_height="match_parent"> + <fragment android:name="com.example.SimpleFragment" + android:id="@+id/list" + android:layout_weight="1" + android:layout_width="0dp" + android:layout_height="match_parent" /> + <fragment android:name="com.example.SimpleFragment" + android:id="@+id/viewer" + android:layout_weight="1" + android:layout_width="0dp" + android:layout_height="match_parent" /> +</LinearLayout> +</pre> + +<p>That's it. When an activity applies the previous layout as its content, the {@code +SimpleFragment} class is instantiated for each occurence in the layout, applying the fragment +layout when it receives the call to {@link android.app.Fragment#onCreateView onCreateView()}.</p> + +<p>Although the fragment in this example implements only the {@link +android.app.Fragment#onCreateView onCreateView()} callback, there are several other lifecycle +callback methods that you should implement in your application. For example, {@link +android.app.Fragment#onCreate onCreate()}, {@link android.app.Fragment#onPause onPause()}, {@link +android.app.Fragment#onStop onStop()} and others that coincide with the fragment's lifecycle.</p> + + + +<h2 id="Managing">Managing Fragments</h2> + +<p>A useful feature of fragments is the ability to add, remove, replace, and perform other +operations on a fragment as the user interacts with the activity, alowing for more rich user +experiences without changing activities. In order to perform these operations, you must use {@link +android.app.FragmentTransaction} to perform fragment "transactions." You can acquire {@link +android.app.FragmentTransaction} from your activity with {@link +android.app.Activity#openFragmentTransaction}.</p> + +<p>Common transactions you can perform with fragments include:</p> + +<dl> + <dt>{@link android.app.FragmentTransaction#add add()}</dt> + <dd>Add a {@link android.app.Fragment} to the {@link android.app.Activity} layout.</dd> + <dt>{@link android.app.FragmentTransaction#remove remove()}</dt> + <dd>Remove a {@link android.app.Fragment} from the {@link android.app.Activity} layout.</dd> + <dt>{@link android.app.FragmentTransaction#replace replace()}</dt> + <dd>Replace an existing {@link android.app.Fragment} with another one.</dd> +</dl> + +<p>For every transaction (or set of transactions) you perform, you must call {@link +android.app.FragmentTransaction#commit} in order for the transactions made with {@link +android.app.FragmentTransaction} to be applied. Before you do, however, you can call {@link +android.app.FragmentTransaction#addToBackStack} to add the current fragment state to the +activity's back stack, so that the user can return to the previous fragment state with the BACK key. +For example, here's how a new fragment can replace another one, but keep the previous fragment +in the back stack:</p> + +<pre> +// Create new fragment +Fragment newFragment = new MyFragment(); +FragmentTransaction ft = openFragmentTransaction(); +// Replace and add to back stack +ft.replace(newFragment, R.id.myfragment); +ft.addToBackStack(null); +// Apply changes +ft.commit(); +</pre> + +<p>In this example, {@code newFragment} replaces whatever fragment is currently in the +layout container identified by the {@code R.id.myfragment} ID. By calling {@link +android.app.FragmentTransaction#addToBackStack addToBackStack()}, this transaction (the replace) is +saved to the activity's back stack so that the user can reverse this change and bring back the +previous fragment by pressing the BACK key.</p> + +<p>If you perform multiple transactions and call {@link +android.app.FragmentTransaction#addToBackStack addToBackStack()}, then all transactions performed +before {@link android.app.FragmentTransaction#commit} are added to the activity's back stack as a +single event and the BACK key will reverse them all together.</p> + + + +<h2 id="Lifecycle">Handling the Lifecycle</h2> + +<p>A fragment has a lifecycle that corresponds to the lifecycle of the activity in which it +resides. For example, a fragment has callback methods {@link +android.app.Fragment#onCreate onCreate()}, {@link android.app.Fragment#onStart onStart()}, {@link +android.app.Fragment#onPause onPause()}, {@link android.app.Fragment#onStop onStop()}, and more.</p> + +<p>The lifecycle of the activity directly affects the lifecycle of the fragment, such that each +lifecycle callback for the activity results in a similar callback for each fragment (for +example, when the activity receives {@link android.app.Activity#onPause}, each fragment receives +{@link android.app.Fragment#onPause}). However, the +fragment's lifecycle can also change independently—but only while the activity is +resumed (while it is in the foreground)—because you can dynamically +add, remove, and replace fragments without any change to the lifecycle of the activity.</p> + +<p>To accomodate backward navigation with the +BACK key, you can optionally maintain a back stack of fragment transactions, as described in the +previous section. So, if you +replace one fragment with another, the user can press the BACK key and view the previous +fragment. Additionally, each fragment can maintain its own state, such that +when the user navigates back to a previous fragment, the state of that fragment can be restored in +the same manner as the state of an activity is restored when it is stopped and restarted.</p> + +<p>Managing the lifecycle of a fragment is a lot like managing the lifecycle of an activity. A +fragment and an activity both have an "resumed," "paused," and "stopped" state, and they can both +retain their state using a {@link android.os.Bundle}. The only significant difference is that an +activity is placed into a the task's back stack by default (so that the user can navigate to +the previous activity with the BACK key), but a fragment is placed into the activity's back stack +only when you explicitly call {@link android.app.FragmentTransaction#addToBackStack(String) +addToBackStack()} before you {@link android.app.FragmentTransaction#commit()} a fragment +transaction.</p> + +<p>The order in which you perform transactions with {@link android.app.FragmentTransaction} doesn't +matter, except:</p> +<ul> + <li>You must call {@link android.app.FragmentTransaction#commit()} last</li> + <li>If you're adding multiple fragments to the same container, then the order in which +you add them determines the order they appear</li> +</ul> +<p>If you do not call {@link android.app.FragmentTransaction#addToBackStack(String) +addToBackStack()} when you perform a transaction that removes a fragment, then that fragment is +destroyed when the transaction is committed.</p> + + +<h3 id="CoordinatingWithTheActivity">Coordinating with the activity lifecycle</h3> + +<p>The lifecycle of an activity directly affects the lifecycle of a {@link android.app.Fragment} +embedded in that activity. The {@link android.app.Fragment} class has lifecycle callback +methods that match those in the {@link android.app.Activity} class.</p> + +<p>Fragments have a few extra lifecycle callbacks, however, that handle unique interaction with the +activity in order to perform actions such as build and destroy the fragment's UI. These additional +callback methods are:</p> + +<dl> + <dt>{@link android.app.Fragment#onAttach onAttach()}</dt> + <dd>Called when the fragment has been associated with the activity (the {@link +android.app.Activity} is passed in here).</dd> + <dt>{@link android.app.Fragment#onCreateView onCreateView()}</dt> + <dd>Called to create the view hierarchy associated with the fragment.</dd> + <dt>{@link android.app.Fragment#onActivityCreated onActivityCreated()}</dt> + <dd>Called when the activity's own {@link android.app.Activity#onCreate +onCreate()} has finished.</dd> + <dt>{@link android.app.Fragment#onDestroyView onDestroyView()}</dt> + <dd>Called when the view hierarchy associated with the fragment is being removed.</dd> + <dt>{@link android.app.Fragment#onDetach onDetach()}</dt> + <dd>Called when the fragment is being disassociated from the activity.</dd> +</dl> + +<p>The flow of a fragment's lifecycle, as it is affected by its container activity, is illustrated +by figure 3. In this figure, you can see how each successive state of the activity determines which +callback methods the fragment may receive. For example, when the activity has received +its {@link android.app.Activity#onCreate onCreate()} callback, the fragment receives no more +than the {@link android.app.Fragment#onActivityCreated onActivityCreated()} callback. However, +once the activity reaches the resumed state, you can freely add and remove fragments to the +activity, so the fragment lifecycle is no longer inhibitted by the state of the activity. Yet, +when the activity leaves the resumed state, the fragment again is pushed through its lifecycle by +the activity (unless you explicitly destroy the fragment sooner).</p> + + +<img src="{@docRoot}images/activity_fragment_lifecycle.png" alt=""/> +<p class="img-caption"><strong>Figure 3.</strong> The activity lifecycle's affect on the lifecycle +of a fragment.</p> + + +<h3 id="Integrating">Integrating with the Activity</h3> + +<p>Although a {@link android.app.Fragment} is implemented separate from an {@link +android.app.Activity} and can be used inside multiple activities, a fragment is directly tied to its +container activity and can access the Activity instance with {@link +android.app.Fragment#getActivity()}. So, a fragment can +easily perform tasks such as find a view in the activity:</p> + +<pre> +View listView = {@link android.app.Fragment#getActivity()}.{@link android.app.Activity#findViewById findViewById}(R.id.list); +</pre> + +<p>This makes it easy for your fragment to call public methods in the activity.</p> + +<p>Likewise, your activity can call public methods in the fragment when you have a reference to the +{@link android.app.Fragment}. You can acquire a reference to the fragment with {@link +android.app.Activity#findFragmentById findFragmentById()} and cast it to your implementation of +{@link android.app.Fragment}. For example:</p> + +<pre> +MyFragment fragment = (MyFragment) findFragmentById(R.id.myfragment); +fragment.refreshList(); +</pre> + + +<h4 id="Callbacks">Creating event callbacks to the activity</h4> + +<p>In some cases, you might need a fragment to share events with the activity. A good way to do that +is to define a callback interface inside the fragment and require that the host activity implement +it. When the activity receives a callback, it can share the information with other fragments in the layout as +necessary.</p> + +<p>For example, if a news application has two fragments in an activity—one to show a list of +articles (fragment A) and another to display an article (fragment B)—then fragment A must tell +the activity when a list item is selected so that it can tell fragment B to display the article. In +this case, the following interface is defined inside fragment A:</p> + +<pre> +public static class FragmentA extends ListFragment { + ... + // Container Activity must implement this interface + public interface SelectedCallback { + public void onArticleSelected(Uri articleUri); + } + ... +} +</pre> + +<p>Then the activity that hosts the fragment implements the {@code SelectedCallback} interface and +overrides {@code onArticleSelected()} to notify fragment B of the event from fragment A. To ensure +that the host activity implements this interface, fragment A's {@link +android.app.Fragment#onAttach onAttach()} callback method (called when +the fragment is added to the activity) instantiates an instance of {@code SelectedCallback} by +casting the {@link android.app.Activity} that is passed into {@link android.app.Fragment#onAttach +onAttach()}:</p> + +<pre> +public static class FragmentA extends ListFragment { + ... + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + try { + mCallback = (SelectedCallback) activity; + } catch (ClassCastException e) { + activity.finish(); + throw new ClassCastException(activity.toString() + " must implement SelectedCallback"); + } + } + ... +} +</pre> + +<p>If the activity has not implemented the interface, then a {@link java.lang.ClassCastException} is +thrown and the activity is shut down. On success, the {@code mCallback} member holds a reference to +the {@link android.app.Activity}, so that fragment A can share events with the activity by calling +methods defined by the {@code SelectedCallback} interface. For example, if fragment A is an +extension of {@link android.app.ListFragment}, each time +the user clicks a list item, the system calls {@link android.app.ListFragment#onListItemClick +onListItemClick()} in the fragment, which then calls {@code onArticleSelected()} to share +the event with the activity:</p> + +<pre> +public static class FragmentA extends ListFragment { + ... + @Override + public void onListItemClick(ListView l, View v, int position, long id) { + // Append the clicked item's row ID with the content provider Uri + Uri noteUri = ContentUris.{@link android.content.ContentUris#withAppendedId withAppendedId}(ArticleColumns.CONTENT_URI, id); + // Send the event and Uri to the host activity + mCallback.onArticleSelected(noteUri); + } + ... +} +</pre> + +<p>The {@code id} parameter passed to {@link +android.app.ListFragment#onListItemClick onListItemClick()} is the row ID of the clicked item, +which the activity (or other fragment) uses to fetch the article from the application's {@link +android.content.ContentProvider}.</p> + +<p><!--To see a complete implementation of this kind of callback interface, see the <a +href="{@docRoot}resources/samples/NotePad/index.html">NotePad sample</a>. -->More information about +using a content provider is available in the <a +href="{@docRoot}guide/topics/providers/content-providers.html">Content Providers</a> document.</p> + + + + + + +<h2 id="Menus">Adding Action Items to the Activity</h2> + +<p>Your fragments can contribute action items to the activity's <a +href="{@docRoot}guide/topics/ui/actionbar.html">action bar</a> (and menu items to the options menu) +using the callback methods +{@link android.app.Fragment#onCreateOptionsMenu(Menu,MenuInflater) onCreateOptionsMenu()}. In order +for this method to receive calls, however, you must call {@link +android.app.Fragment#setHasOptionsMenu(boolean) setHasOptionsMenu()} during the {@link +android.app.Fragment#onCreate(Bundle) onCreate()} callback in order to indicate that the fragment +would like to receive a call to {@link android.app.Fragment#onCreateOptionsMenu(Menu,MenuInflater) +onCreateOptionsMenu()}. Any action or menu items that you add from the fragment are appended to the +existing +items for the options menu (including those added by other fragments in the activity). The +fragment also receives item-selected events with the {@link +android.app.Fragment#onOptionsItemSelected(MenuItem) onOptionsItemSelected()} callback method.</p> + +<p>The {@link android.app.Fragment} class also contains methods to handle context menus. You can +register a view to provide a context menu with {@link +android.app.Fragment#registerForContextMenu(View) registerForContextMenu()}. When the user opens +the context menu, the fragment receives a call to {@link +android.app.Fragment#onCreateContextMenu(ContextMenu,View,ContextMenu.ContextMenuInfo) +onCreateContextMenu()}. When the user selects an item, the fragment receives a call to {@link +android.app.Fragment#onContextItemSelected(MenuItem) onContextItemSelected()}.</p> + +<p>For more information, see <a href="{@docRoot}guide/topics/ui/menus.html">Creating +Menus</a> and <a href="{@docRoot}guide/topics/ui/actionbar.html">Using the Action Bar</a>.</p> + + + + + + diff --git a/docs/html/guide/topics/manifest/activity-element.jd b/docs/html/guide/topics/manifest/activity-element.jd index e030a4c48e5e..2648cb7a4737 100644 --- a/docs/html/guide/topics/manifest/activity-element.jd +++ b/docs/html/guide/topics/manifest/activity-element.jd @@ -290,7 +290,8 @@ activity is ignored. The activity is not re-parented, but destroyed. <dd>An icon representing the activity. The icon is displayed to users when a representation of the activity is required on-screen. For example, icons for activities that initiate tasks are displayed in the launcher window. -The icon is often accompanied by a label (see the {@code label} attribute). +The icon is often accompanied by a label (see the <a href="#label">{@code +android:label}</a> attribute). </p> <p> diff --git a/docs/html/guide/topics/manifest/application-element.jd b/docs/html/guide/topics/manifest/application-element.jd index 9ac07fd884e5..1fadc6e32c85 100644 --- a/docs/html/guide/topics/manifest/application-element.jd +++ b/docs/html/guide/topics/manifest/application-element.jd @@ -12,6 +12,7 @@ page.title=<application> android:<a href="#icon">icon</a>="<i>drawable resource</i>" android:<a href="#killrst">killAfterRestore</a>=["true" | "false"] android:<a href="#label">label</a>="<i>string resource</i>" + android:<a href="#logo">logo</a>="<i>drawable resource</i>" android:<a href="#space">manageSpaceActivity</a>="<i>string</i>" android:<a href="#nm">name</a>="<i>string</i>" android:<a href="#prmsn">permission</a>="<i>string</i>" @@ -121,7 +122,7 @@ each of the application's components. See the individual <p> This attribute must be set as a reference to a drawable resource containing -the image definition. There is no default icon. +the image (for example {@code "@drawable/icon"}). There is no default icon. </p></dd> <dt><a name="killrst"></a>{@code android:killAfterRestore}</dt> @@ -154,6 +155,11 @@ However, as a convenience while you're developing the application, it can also be set as a raw string. </p></dd> +<dt><a name="logo"></a>{@code android:logo}</dt> +<dd>A logo for the application as whole, and the default logo for activities. +<p>This attribute must be set as a reference to a drawable resource containing +the image (for example {@code "@drawable/logo"}). There is no default logo.</p></dd> + <dt><a name="space"></a>{@code android:manageSpaceActivity}</dt> <dd>The fully qualified name of an Activity subclass that the system can launch to let users manage the memory occupied by the application diff --git a/docs/html/images/activity_fragment_lifecycle.png b/docs/html/images/activity_fragment_lifecycle.png Binary files differnew file mode 100644 index 000000000000..156aa40edfe4 --- /dev/null +++ b/docs/html/images/activity_fragment_lifecycle.png diff --git a/docs/html/images/fragment_lifecycle.png b/docs/html/images/fragment_lifecycle.png Binary files differnew file mode 100644 index 000000000000..ce9d395bfdec --- /dev/null +++ b/docs/html/images/fragment_lifecycle.png diff --git a/docs/html/resources/resources-data.js b/docs/html/resources/resources-data.js index d06b695351fe..221406c459f3 100644 --- a/docs/html/resources/resources-data.js +++ b/docs/html/resources/resources-data.js @@ -465,7 +465,7 @@ var ANDROID_RESOURCES = [ } }, { - tags: ['sample', 'ui', 'search', 'new'], + tags: ['sample', 'ui', 'search'], path: 'samples/SearchableDictionary/index.html', title: { en: 'Searchable Dictionary v2' @@ -485,7 +485,7 @@ var ANDROID_RESOURCES = [ } }, { - tags: ['sample', 'testing', 'new'], + tags: ['sample', 'testing'], path: 'samples/Spinner/index.html', title: { en: 'Spinner' @@ -495,7 +495,7 @@ var ANDROID_RESOURCES = [ } }, { - tags: ['sample', 'testing', 'new'], + tags: ['sample', 'testing'], path: 'samples/SpinnerTest/index.html', title: { en: 'SpinnerTest' @@ -505,7 +505,7 @@ var ANDROID_RESOURCES = [ } }, { - tags: ['sample', 'newfeature', 'new'], + tags: ['sample', 'newfeature'], path: 'samples/TicTacToeLib/index.html', title: { en: 'TicTacToeLib' @@ -515,7 +515,7 @@ var ANDROID_RESOURCES = [ } }, { - tags: ['sample', 'newfeature', 'new'], + tags: ['sample', 'newfeature',], path: 'samples/TicTacToeMain/index.html', title: { en: 'TicTacToeMain' @@ -610,7 +610,7 @@ var ANDROID_RESOURCES = [ } }, { - tags: ['tutorial', 'testing', 'new'], + tags: ['tutorial', 'testing'], path: 'tutorials/testing/helloandroid_test.html', title: { en: 'Hello Testing' @@ -620,7 +620,7 @@ var ANDROID_RESOURCES = [ } }, { - tags: ['tutorial', 'testing', 'new'], + tags: ['tutorial', 'testing'], path: 'tutorials/testing/activity_test.html', title: { en: 'Activity Testing' diff --git a/include/utils/Tokenizer.h b/include/utils/Tokenizer.h index bfe89232dd1a..21e58e62ce1d 100644 --- a/include/utils/Tokenizer.h +++ b/include/utils/Tokenizer.h @@ -19,6 +19,7 @@ #include <assert.h> #include <utils/Errors.h> +#include <utils/FileMap.h> #include <utils/String8.h> namespace android { @@ -27,7 +28,7 @@ namespace android { * A simple tokenizer for loading and parsing ASCII text files line by line. */ class Tokenizer { - Tokenizer(const String8& filename, const char* buffer, size_t length); + Tokenizer(const String8& filename, FileMap* fileMap, const char* buffer, size_t length); public: ~Tokenizer(); @@ -108,6 +109,7 @@ private: Tokenizer(const Tokenizer& other); // not copyable String8 mFilename; + FileMap* mFileMap; const char* mBuffer; size_t mLength; diff --git a/libs/utils/Tokenizer.cpp b/libs/utils/Tokenizer.cpp index 19dadf08d557..9251973ca1f0 100644 --- a/libs/utils/Tokenizer.cpp +++ b/libs/utils/Tokenizer.cpp @@ -22,7 +22,6 @@ #include <errno.h> #include <sys/types.h> #include <sys/stat.h> -#include <sys/mman.h> #include <utils/Log.h> #include <utils/Tokenizer.h> @@ -37,13 +36,16 @@ static inline bool isDelimiter(char ch, const char* delimiters) { } -Tokenizer::Tokenizer(const String8& filename, const char* buffer, size_t length) : - mFilename(filename), mBuffer(buffer), mLength(length), +Tokenizer::Tokenizer(const String8& filename, FileMap* fileMap, + const char* buffer, size_t length) : + mFilename(filename), mFileMap(fileMap), mBuffer(buffer), mLength(length), mCurrent(buffer), mLineNumber(1) { } Tokenizer::~Tokenizer() { - munmap((void*)mBuffer, mLength); + if (mFileMap) { + mFileMap->release(); + } } status_t Tokenizer::open(const String8& filename, Tokenizer** outTokenizer) { @@ -55,29 +57,29 @@ status_t Tokenizer::open(const String8& filename, Tokenizer** outTokenizer) { result = -errno; LOGE("Error opening file '%s', %s.", filename.string(), strerror(errno)); } else { - struct stat64 stat; - if (fstat64(fd, &stat)) { + struct stat stat; + if (fstat(fd, &stat)) { result = -errno; LOGE("Error getting size of file '%s', %s.", filename.string(), strerror(errno)); } else { size_t length = size_t(stat.st_size); - void* buffer = mmap(NULL, length, PROT_READ, MAP_FILE | MAP_PRIVATE, fd, 0); - if (buffer == MAP_FAILED) { - result = -errno; + FileMap* fileMap = new FileMap(); + if (!fileMap->create(NULL, fd, 0, length, true)) { + result = NO_MEMORY; LOGE("Error mapping file '%s', %s.", filename.string(), strerror(errno)); } else { - if (madvise(buffer, length, MADV_SEQUENTIAL)) { - LOGW("Error calling madvise for mmapped file '%s', %s.", filename.string(), - strerror(errno)); - } + fileMap->advise(FileMap::SEQUENTIAL); - *outTokenizer = new Tokenizer(filename, static_cast<const char*>(buffer), length); + *outTokenizer = new Tokenizer(filename, fileMap, + static_cast<const char*>(fileMap->getDataPtr()), length); if (!*outTokenizer) { result = NO_MEMORY; LOGE("Error allocating tokenizer for file=%s.", filename.string()); - munmap(buffer, length); } } + if (result) { + fileMap->release(); + } } close(fd); } diff --git a/media/libstagefright/Android.mk b/media/libstagefright/Android.mk index 8fe1d4d6009d..4ad1eb42ed68 100644 --- a/media/libstagefright/Android.mk +++ b/media/libstagefright/Android.mk @@ -49,7 +49,6 @@ LOCAL_SRC_FILES:= \ WVMExtractor.cpp \ XINGSeeker.cpp \ avc_utils.cpp \ - string.cpp LOCAL_C_INCLUDES:= \ $(JNI_H_INCLUDE) \ diff --git a/media/libstagefright/HTTPStream.cpp b/media/libstagefright/HTTPStream.cpp index ccc6a340fca6..e7f00aa7fd2e 100644 --- a/media/libstagefright/HTTPStream.cpp +++ b/media/libstagefright/HTTPStream.cpp @@ -36,7 +36,7 @@ namespace android { // static -const char *HTTPStream::kStatusKey = ":status:"; +const char *HTTPStream::kStatusKey = ":status:"; // MUST be lowercase. HTTPStream::HTTPStream() : mState(READY), @@ -220,7 +220,7 @@ status_t HTTPStream::receive_header(int *http_status) { return err; } - mHeaders.add(string(kStatusKey), string(line)); + mHeaders.add(AString(kStatusKey), AString(line)); char *spacePos = strchr(line, ' '); if (spacePos == NULL) { @@ -264,7 +264,10 @@ status_t HTTPStream::receive_header(int *http_status) { char *colonPos = strchr(line, ':'); if (colonPos == NULL) { - mHeaders.add(string(line), string()); + AString key = line; + key.tolower(); + + mHeaders.add(key, AString()); } else { char *end_of_key = colonPos; while (end_of_key > line && isspace(end_of_key[-1])) { @@ -278,7 +281,10 @@ status_t HTTPStream::receive_header(int *http_status) { *end_of_key = '\0'; - mHeaders.add(string(line), string(start_of_value)); + AString key = line; + key.tolower(); + + mHeaders.add(key, AString(start_of_value)); } } @@ -314,8 +320,11 @@ ssize_t HTTPStream::receive(void *data, size_t size) { return (ssize_t)total; } -bool HTTPStream::find_header_value(const string &key, string *value) const { - ssize_t index = mHeaders.indexOfKey(key); +bool HTTPStream::find_header_value(const AString &key, AString *value) const { + AString key_lower = key; + key_lower.tolower(); + + ssize_t index = mHeaders.indexOfKey(key_lower); if (index < 0) { value->clear(); return false; diff --git a/media/libstagefright/NuCachedSource2.cpp b/media/libstagefright/NuCachedSource2.cpp index 7f765ca0e818..829ab2016fc6 100644 --- a/media/libstagefright/NuCachedSource2.cpp +++ b/media/libstagefright/NuCachedSource2.cpp @@ -465,7 +465,7 @@ status_t NuCachedSource2::seekInternal_l(off64_t offset) { return OK; } - LOGI("new range: offset= %ld", offset); + LOGI("new range: offset= %lld", offset); mCacheOffset = offset; diff --git a/media/libstagefright/NuHTTPDataSource.cpp b/media/libstagefright/NuHTTPDataSource.cpp index 15c9ac6098b9..269b23345a75 100644 --- a/media/libstagefright/NuHTTPDataSource.cpp +++ b/media/libstagefright/NuHTTPDataSource.cpp @@ -178,7 +178,7 @@ status_t NuHTTPDataSource::connect( } if (IsRedirectStatusCode(httpStatus)) { - string value; + AString value; CHECK(mHTTP.find_header_value("Location", &value)); mState = DISCONNECTED; @@ -198,9 +198,8 @@ status_t NuHTTPDataSource::connect( mHasChunkedTransferEncoding = false; { - string value; - if (mHTTP.find_header_value("Transfer-Encoding", &value) - || mHTTP.find_header_value("Transfer-encoding", &value)) { + AString value; + if (mHTTP.find_header_value("Transfer-Encoding", &value)) { // We don't currently support any transfer encodings but // chunked. @@ -222,9 +221,9 @@ status_t NuHTTPDataSource::connect( applyTimeoutResponse(); if (offset == 0) { - string value; + AString value; unsigned long x; - if (mHTTP.find_header_value(string("Content-Length"), &value) + if (mHTTP.find_header_value(AString("Content-Length"), &value) && ParseSingleUnsignedLong(value.c_str(), &x)) { mContentLength = (off64_t)x; mContentLengthValid = true; @@ -239,9 +238,9 @@ status_t NuHTTPDataSource::connect( return ERROR_UNSUPPORTED; } - string value; + AString value; unsigned long x; - if (mHTTP.find_header_value(string("Content-Range"), &value)) { + if (mHTTP.find_header_value(AString("Content-Range"), &value)) { const char *slashPos = strchr(value.c_str(), '/'); if (slashPos != NULL && ParseSingleUnsignedLong(slashPos + 1, &x)) { @@ -439,7 +438,7 @@ void NuHTTPDataSource::MakeFullHeaders( } void NuHTTPDataSource::applyTimeoutResponse() { - string timeout; + AString timeout; if (mHTTP.find_header_value("X-SocketTimeout", &timeout)) { const char *s = timeout.c_str(); char *end; diff --git a/media/libstagefright/ShoutcastSource.cpp b/media/libstagefright/ShoutcastSource.cpp index 23b768133463..783f2d0382f8 100644 --- a/media/libstagefright/ShoutcastSource.cpp +++ b/media/libstagefright/ShoutcastSource.cpp @@ -14,7 +14,6 @@ * limitations under the License. */ -#include "include/stagefright_string.h" #include "include/HTTPStream.h" #include <stdlib.h> @@ -34,7 +33,7 @@ ShoutcastSource::ShoutcastSource(HTTPStream *http) mBytesUntilMetaData(0), mGroup(NULL), mStarted(false) { - string metaint; + AString metaint; if (mHttp->find_header_value("icy-metaint", &metaint)) { char *end; const char *start = metaint.c_str(); diff --git a/media/libstagefright/include/HTTPStream.h b/media/libstagefright/include/HTTPStream.h index 793798f0942a..545cd0c431ed 100644 --- a/media/libstagefright/include/HTTPStream.h +++ b/media/libstagefright/include/HTTPStream.h @@ -18,10 +18,9 @@ #define HTTP_STREAM_H_ -#include "stagefright_string.h" - #include <sys/types.h> +#include <media/stagefright/foundation/AString.h> #include <media/stagefright/MediaErrors.h> #include <utils/KeyedVector.h> #include <utils/threads.h> @@ -50,7 +49,7 @@ public: static const char *kStatusKey; bool find_header_value( - const string &key, string *value) const; + const AString &key, AString *value) const; // Pass a negative value to disable the timeout. void setReceiveTimeout(int seconds); @@ -70,7 +69,7 @@ private: Mutex mLock; int mSocket; - KeyedVector<string, string> mHeaders; + KeyedVector<AString, AString> mHeaders; HTTPStream(const HTTPStream &); HTTPStream &operator=(const HTTPStream &); diff --git a/media/libstagefright/include/stagefright_string.h b/media/libstagefright/include/stagefright_string.h deleted file mode 100644 index 5dc711653f04..000000000000 --- a/media/libstagefright/include/stagefright_string.h +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) 2009 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 STRING_H_ - -#define STRING_H_ - -#include <utils/String8.h> - -namespace android { - -class string { -public: - typedef size_t size_type; - static size_type npos; - - string(); - string(const char *s); - string(const char *s, size_t length); - string(const string &from, size_type start, size_type length = npos); - - const char *c_str() const; - size_type size() const; - - void clear(); - void erase(size_type from, size_type length); - - size_type find(char c) const; - - bool operator<(const string &other) const; - bool operator==(const string &other) const; - - string &operator+=(char c); - -private: - String8 mString; -}; - -} // namespace android - -#endif // STRING_H_ diff --git a/media/libstagefright/string.cpp b/media/libstagefright/string.cpp deleted file mode 100644 index 8b2c36c2d9d6..000000000000 --- a/media/libstagefright/string.cpp +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (C) 2009 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. - */ - -#include "include/stagefright_string.h" - -#include <media/stagefright/MediaDebug.h> - -namespace android { - -// static -string::size_type string::npos = (string::size_type)-1; - -string::string() { -} - -string::string(const char *s, size_t length) - : mString(s, length) { -} - -string::string(const string &from, size_type start, size_type length) { - CHECK(start <= from.size()); - if (length == npos) { - length = from.size() - start; - } else { - CHECK(start + length <= from.size()); - } - - mString.setTo(from.c_str() + start, length); -} - -string::string(const char *s) - : mString(s) { -} - -const char *string::c_str() const { - return mString.string(); -} - -string::size_type string::size() const { - return mString.length(); -} - -void string::clear() { - mString = String8(); -} - -string::size_type string::find(char c) const { - char s[2]; - s[0] = c; - s[1] = '\0'; - - ssize_t index = mString.find(s); - - return index < 0 ? npos : (size_type)index; -} - -bool string::operator<(const string &other) const { - return mString < other.mString; -} - -bool string::operator==(const string &other) const { - return mString == other.mString; -} - -string &string::operator+=(char c) { - mString.append(&c, 1); - - return *this; -} - -void string::erase(size_t from, size_t length) { - String8 s(mString.string(), from); - s.append(mString.string() + from + length); - - mString = s; -} - -} // namespace android - diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java index 6c84afd65e70..d11a18e6d5fe 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java @@ -61,7 +61,7 @@ public class DatabaseHelper extends SQLiteOpenHelper { // database gets upgraded properly. At a minimum, please confirm that 'upgradeVersion' // is properly propagated through your change. Not doing so will result in a loss of user // settings. - private static final int DATABASE_VERSION = 61; + private static final int DATABASE_VERSION = 62; private Context mContext; @@ -793,22 +793,15 @@ public class DatabaseHelper extends SQLiteOpenHelper { } if (upgradeVersion == 60) { - // Increase screen timeout for tablet - db.beginTransaction(); - SQLiteStatement stmt = null; - try { - stmt = db.compileStatement("INSERT OR REPLACE INTO system(name,value)" - + " VALUES(?,?);"); - loadIntegerSetting(stmt, Settings.System.SCREEN_OFF_TIMEOUT, - R.integer.def_screen_off_timeout); - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - if (stmt != null) stmt.close(); - } + upgradeScreenTimeout(db); upgradeVersion = 61; } + if (upgradeVersion == 61) { + upgradeScreenTimeout(db); + upgradeVersion = 62; + } + // *** Remember to update DATABASE_VERSION above! if (upgradeVersion != currentVersion) { @@ -914,6 +907,23 @@ public class DatabaseHelper extends SQLiteOpenHelper { } } + private void upgradeScreenTimeout(SQLiteDatabase db) { + // Change screen timeout to current default + db.beginTransaction(); + SQLiteStatement stmt = null; + try { + stmt = db.compileStatement("INSERT OR REPLACE INTO system(name,value)" + + " VALUES(?,?);"); + loadIntegerSetting(stmt, Settings.System.SCREEN_OFF_TIMEOUT, + R.integer.def_screen_off_timeout); + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + if (stmt != null) + stmt.close(); + } + } + /** * Loads the default set of bookmarked shortcuts from an xml file. * diff --git a/packages/SystemUI/res/layout-xlarge/status_bar.xml b/packages/SystemUI/res/layout-xlarge/status_bar.xml index d4a61362fe77..d11e6da009e2 100644 --- a/packages/SystemUI/res/layout-xlarge/status_bar.xml +++ b/packages/SystemUI/res/layout-xlarge/status_bar.xml @@ -25,7 +25,6 @@ android:id="@+id/bar_contents" android:layout_width="match_parent" android:layout_height="match_parent" - android:animateLayoutChanges="false" > <!-- notification icons & panel access --> @@ -105,7 +104,6 @@ android:layout_height="match_parent" android:layout_alignParentLeft="true" android:orientation="horizontal" - android:animateLayoutChanges="false" > <com.android.systemui.statusbar.policy.KeyButtonView android:id="@+id/back" @@ -178,62 +176,68 @@ </LinearLayout> <!-- lights out mode: "shadow" views --> - <ImageView - android:id="@+id/notification_shadow" - android:layout_width="176dip" + <RelativeLayout + android:id="@+id/shadows" + android:layout_width="match_parent" android:layout_height="match_parent" - android:paddingRight="48dip" - android:layout_alignParentRight="true" - android:layout_alignParentBottom="true" - android:src="@drawable/ic_sysbar_shadow" - android:visibility="gone" - android:scaleType="fitXY" - /> + > + <ImageView + android:id="@+id/notification_shadow" + android:layout_width="176dip" + android:layout_height="match_parent" + android:paddingRight="48dip" + android:layout_alignParentRight="true" + android:layout_alignParentBottom="true" + android:src="@drawable/ic_sysbar_shadow" + android:visibility="gone" + android:scaleType="fitXY" + /> - <ImageView - android:id="@+id/back_shadow" - android:layout_width="96dip" - android:layout_height="match_parent" - android:paddingLeft="18dip" - android:paddingRight="18dip" - android:layout_alignParentLeft="true" - android:layout_alignParentBottom="true" - android:src="@drawable/ic_sysbar_shadow" - android:visibility="gone" - /> - <ImageView - android:id="@+id/home_shadow" - android:layout_width="96dip" - android:layout_height="match_parent" - android:paddingLeft="18dip" - android:paddingRight="18dip" - android:layout_toRightOf="@id/back_shadow" - android:layout_alignParentBottom="true" - android:src="@drawable/ic_sysbar_shadow" - android:visibility="gone" - /> - <ImageView - android:id="@+id/recent_shadow" - android:layout_width="96dip" - android:layout_height="match_parent" - android:paddingLeft="18dip" - android:paddingRight="18dip" - android:layout_toRightOf="@id/home_shadow" - android:layout_alignParentBottom="true" - android:src="@drawable/ic_sysbar_shadow" - android:visibility="gone" - /> - <ImageView - android:id="@+id/menu_shadow" - android:layout_width="96dip" - android:layout_height="match_parent" - android:paddingLeft="18dip" - android:paddingRight="18dip" - android:layout_toRightOf="@id/recent_shadow" - android:layout_alignParentBottom="true" - android:src="@drawable/ic_sysbar_shadow" - android:visibility="gone" - /> + <ImageView + android:id="@+id/back_shadow" + android:layout_width="96dip" + android:layout_height="match_parent" + android:paddingLeft="18dip" + android:paddingRight="18dip" + android:layout_alignParentLeft="true" + android:layout_alignParentBottom="true" + android:src="@drawable/ic_sysbar_shadow" + android:visibility="gone" + /> + <ImageView + android:id="@+id/home_shadow" + android:layout_width="96dip" + android:layout_height="match_parent" + android:paddingLeft="18dip" + android:paddingRight="18dip" + android:layout_toRightOf="@id/back_shadow" + android:layout_alignParentBottom="true" + android:src="@drawable/ic_sysbar_shadow" + android:visibility="gone" + /> + <ImageView + android:id="@+id/recent_shadow" + android:layout_width="96dip" + android:layout_height="match_parent" + android:paddingLeft="18dip" + android:paddingRight="18dip" + android:layout_toRightOf="@id/home_shadow" + android:layout_alignParentBottom="true" + android:src="@drawable/ic_sysbar_shadow" + android:visibility="gone" + /> + <ImageView + android:id="@+id/menu_shadow" + android:layout_width="96dip" + android:layout_height="match_parent" + android:paddingLeft="18dip" + android:paddingRight="18dip" + android:layout_toRightOf="@id/recent_shadow" + android:layout_alignParentBottom="true" + android:src="@drawable/ic_sysbar_shadow" + android:visibility="gone" + /> + </RelativeLayout> <!-- ticker: transient incoming notification information --> <FrameLayout @@ -244,7 +248,6 @@ android:layout_toRightOf="@+id/systemInfo" android:paddingLeft="6dip" android:gravity="center_vertical" - android:animateLayoutChanges="true" /> </RelativeLayout> diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java index 7c97ac7671ca..233ac454d04c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java @@ -19,9 +19,12 @@ package com.android.systemui.statusbar.tablet; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Map; +import java.util.IdentityHashMap; import android.animation.LayoutTransition; import android.animation.ObjectAnimator; +import android.animation.AnimatorSet; import android.app.ActivityManagerNative; import android.app.PendingIntent; import android.app.Notification; @@ -257,21 +260,6 @@ public class TabletStatusBar extends StatusBar { mBarContents = sb.findViewById(R.id.bar_contents); - // "shadows" of the status bar features, for lights-out mode - mBackShadow = sb.findViewById(R.id.back_shadow); - mHomeShadow = sb.findViewById(R.id.home_shadow); - mRecentShadow = sb.findViewById(R.id.recent_shadow); - mMenuShadow = sb.findViewById(R.id.menu_shadow); - mNotificationShadow = sb.findViewById(R.id.notification_shadow); - - mShadowController = new ShadowController(false); - - mBackShadow.setOnTouchListener(mShadowController.makeTouchListener()); - mHomeShadow.setOnTouchListener(mShadowController.makeTouchListener()); - mRecentShadow.setOnTouchListener(mShadowController.makeTouchListener()); - mMenuShadow.setOnTouchListener(mShadowController.makeTouchListener()); - mNotificationShadow.setOnTouchListener(mShadowController.makeTouchListener()); - // the whole right-hand side of the bar mNotificationArea = sb.findViewById(R.id.notificationArea); @@ -310,6 +298,20 @@ public class TabletStatusBar extends StatusBar { // The bar contents buttons mInputMethodButton = (InputMethodButton) sb.findViewById(R.id.imeButton); + // "shadows" of the status bar features, for lights-out mode + mBackShadow = sb.findViewById(R.id.back_shadow); + mHomeShadow = sb.findViewById(R.id.home_shadow); + mRecentShadow = sb.findViewById(R.id.recent_shadow); + mMenuShadow = sb.findViewById(R.id.menu_shadow); + mNotificationShadow = sb.findViewById(R.id.notification_shadow); + + mShadowController = new ShadowController(false); + mShadowController.add(mBackButton, mBackShadow); + mShadowController.add(mHomeButton, mHomeShadow); + mShadowController.add(mRecentButton, mRecentShadow); + mShadowController.add(mMenuButton, mMenuShadow); + mShadowController.add(mNotificationArea, mNotificationShadow); + // set the initial view visibility setAreThereNotifications(); refreshNotificationTrigger(); @@ -386,8 +388,8 @@ public class TabletStatusBar extends StatusBar { mNotificationPanel.setVisibility(View.VISIBLE); - // XXX: need to synchronize with shadows here - mNotificationArea.setVisibility(View.GONE); + // synchronize with current shadow state + mShadowController.hideElement(mNotificationArea); } break; case MSG_CLOSE_NOTIFICATION_PANEL: @@ -395,8 +397,8 @@ public class TabletStatusBar extends StatusBar { if (mNotificationPanel.getVisibility() == View.VISIBLE) { mNotificationPanel.setVisibility(View.GONE); - // XXX: need to synchronize with shadows here - mNotificationArea.setVisibility(View.VISIBLE); + // synchronize with current shadow state + mShadowController.showElement(mNotificationArea); } break; case MSG_OPEN_RECENTS_PANEL: @@ -576,11 +578,13 @@ public class TabletStatusBar extends StatusBar { if ((diff & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) { if ((state & StatusBarManager.DISABLE_NOTIFICATION_ICONS) != 0) { Slog.d(TAG, "DISABLE_NOTIFICATION_ICONS: yes"); - mNotificationIconArea.setVisibility(View.GONE); + // synchronize with current shadow state + mShadowController.hideElement(mNotificationArea); mTicker.halt(); } else { Slog.d(TAG, "DISABLE_NOTIFICATION_ICONS: no"); - mNotificationIconArea.setVisibility(View.VISIBLE); + // synchronize with current shadow state + mShadowController.showElement(mNotificationArea); } } else if ((diff & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) { if ((state & StatusBarManager.DISABLE_NOTIFICATION_TICKER) != 0) { @@ -1047,11 +1051,57 @@ public class TabletStatusBar extends StatusBar { public class ShadowController { boolean mShowShadows; + Map<View, View> mShadowsForElements = new IdentityHashMap<View, View>(7); + Map<View, View> mElementsForShadows = new IdentityHashMap<View, View>(7); + LayoutTransition mElementTransition, mShadowTransition; + View mTouchTarget; ShadowController(boolean showShadows) { mShowShadows = showShadows; mTouchTarget = null; + + mElementTransition = new LayoutTransition(); +// AnimatorSet s = new AnimatorSet(); +// s.play(ObjectAnimator.ofInt(null, "top", 48, 0)) +// .with(ObjectAnimator.ofFloat(null, "scaleY", 0.5f, 1f)) +// .with(ObjectAnimator.ofFloat(null, "alpha", 0.5f, 1f)) +// ; + mElementTransition.setAnimator(LayoutTransition.APPEARING, //s); + ObjectAnimator.ofInt(null, "top", 48, 0)); + mElementTransition.setDuration(LayoutTransition.APPEARING, 100); + mElementTransition.setStartDelay(LayoutTransition.APPEARING, 0); + +// s = new AnimatorSet(); +// s.play(ObjectAnimator.ofInt(null, "top", 0, 48)) +// .with(ObjectAnimator.ofFloat(null, "scaleY", 1f, 0.5f)) +// .with(ObjectAnimator.ofFloat(null, "alpha", 1f, 0.5f)) +// ; + mElementTransition.setAnimator(LayoutTransition.DISAPPEARING, //s); + ObjectAnimator.ofInt(null, "top", 0, 48)); + mElementTransition.setDuration(LayoutTransition.DISAPPEARING, 400); + + mShadowTransition = new LayoutTransition(); + mShadowTransition.setAnimator(LayoutTransition.APPEARING, + ObjectAnimator.ofFloat(null, "alpha", 0f, 1f)); + mShadowTransition.setDuration(LayoutTransition.APPEARING, 200); + mShadowTransition.setStartDelay(LayoutTransition.APPEARING, 100); + mShadowTransition.setAnimator(LayoutTransition.DISAPPEARING, + ObjectAnimator.ofFloat(null, "alpha", 1f, 0f)); + mShadowTransition.setDuration(LayoutTransition.DISAPPEARING, 100); + + ViewGroup bar = (ViewGroup) TabletStatusBar.this.mBarContents; + bar.setLayoutTransition(mElementTransition); + ViewGroup nav = (ViewGroup) TabletStatusBar.this.mNavigationArea; + nav.setLayoutTransition(mElementTransition); + ViewGroup shadowGroup = (ViewGroup) bar.findViewById(R.id.shadows); + shadowGroup.setLayoutTransition(mShadowTransition); + } + + public void add(View element, View shadow) { + shadow.setOnTouchListener(makeTouchListener()); + mShadowsForElements.put(element, shadow); + mElementsForShadows.put(shadow, element); } public boolean getShadowState() { @@ -1067,17 +1117,7 @@ public class TabletStatusBar extends StatusBar { // currently redirecting events? if (mTouchTarget == null) { - if (v == mBackShadow) { - mTouchTarget = mBackButton; - } else if (v == mHomeShadow) { - mTouchTarget = mHomeButton; - } else if (v == mMenuShadow) { - mTouchTarget = mMenuButton; - } else if (v == mRecentShadow) { - mTouchTarget = mRecentButton; - } else if (v == mNotificationShadow) { - mTouchTarget = mNotificationArea; - } + mTouchTarget = mElementsForShadows.get(v); } if (mTouchTarget != null && mTouchTarget.getVisibility() != View.GONE) { @@ -1094,7 +1134,7 @@ public class TabletStatusBar extends StatusBar { break; case MotionEvent.ACTION_DOWN: mHandler.removeMessages(MSG_RESTORE_SHADOWS); - setShadowForButton(mTouchTarget, false); + setElementShadow(mTouchTarget, false); break; } mTouchTarget.dispatchTouchEvent(ev); @@ -1108,11 +1148,9 @@ public class TabletStatusBar extends StatusBar { } public void refresh() { - setShadowForButton(mBackButton, mShowShadows); - setShadowForButton(mHomeButton, mShowShadows); - setShadowForButton(mRecentButton, mShowShadows); - setShadowForButton(mMenuButton, mShowShadows); - setShadowForButton(mNotificationArea, mShowShadows); + for (View element : mShadowsForElements.keySet()) { + setElementShadow(element, mShowShadows); + } } public void showAllShadows() { @@ -1127,19 +1165,8 @@ public class TabletStatusBar extends StatusBar { // Use View.INVISIBLE for things hidden due to shadowing, and View.GONE for things that are // disabled (and should not be shadowed or re-shown) - public void setShadowForButton(View button, boolean shade) { - View shadow = null; - if (button == mBackButton) { - shadow = mBackShadow; - } else if (button == mHomeButton) { - shadow = mHomeShadow; - } else if (button == mMenuButton) { - shadow = mMenuShadow; - } else if (button == mRecentButton) { - shadow = mRecentShadow; - } else if (button == mNotificationArea) { - shadow = mNotificationShadow; - } + public void setElementShadow(View button, boolean shade) { + View shadow = mShadowsForElements.get(button); if (shadow != null) { if (button.getVisibility() != View.GONE) { shadow.setVisibility(shade ? View.VISIBLE : View.INVISIBLE); @@ -1147,6 +1174,26 @@ public class TabletStatusBar extends StatusBar { } } } + + // Hide both element and shadow, using default layout animations. + public void hideElement(View button) { + Slog.d(TAG, "hiding: " + button); + View shadow = mShadowsForElements.get(button); + if (shadow != null) { + shadow.setVisibility(View.GONE); + } + button.setVisibility(View.GONE); + } + + // Honoring the current shadow state. + public void showElement(View button) { + Slog.d(TAG, "showing: " + button); + View shadow = mShadowsForElements.get(button); + if (shadow != null) { + shadow.setVisibility(mShowShadows ? View.VISIBLE : View.INVISIBLE); + } + button.setVisibility(mShowShadows ? View.INVISIBLE : View.VISIBLE); + } } public class TouchOutsideListener implements View.OnTouchListener { @@ -1171,15 +1218,6 @@ public class TabletStatusBar extends StatusBar { } } - private void setViewVisibility(View v, int vis, int anim) { - if (v.getVisibility() != vis) { - //Slog.d(TAG, "setViewVisibility vis=" + (vis == View.VISIBLE) + " v=" + v); - v.setAnimation(AnimationUtils.loadAnimation(mContext, anim)); - v.setVisibility(vis); - } - } - - public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.print("mDisabled=0x"); pw.println(Integer.toHexString(mDisabled)); diff --git a/packages/TtsService/src/android/tts/TtsService.java b/packages/TtsService/src/android/tts/TtsService.java index c977ba3dac51..08bbfb28c29f 100755 --- a/packages/TtsService/src/android/tts/TtsService.java +++ b/packages/TtsService/src/android/tts/TtsService.java @@ -496,7 +496,7 @@ public class TtsService extends Service implements OnCompletionListener { * engines. */ private int speak(String callingApp, String text, int queueMode, ArrayList<String> params) { - Log.v(SERVICE_TAG, "TTS service received " + text); + // Log.v(SERVICE_TAG, "TTS service received " + text); if (queueMode == TextToSpeech.QUEUE_FLUSH) { stop(callingApp); } else if (queueMode == 2) { @@ -705,27 +705,27 @@ public class TtsService extends Service implements OnCompletionListener { } } - public void onCompletion(MediaPlayer arg0) {
- // mCurrentSpeechItem may become null if it is stopped at the same
- // time it completes.
- SpeechItem currentSpeechItemCopy = mCurrentSpeechItem;
- if (currentSpeechItemCopy != null) {
- String callingApp = currentSpeechItemCopy.mCallingApp;
- ArrayList<String> params = currentSpeechItemCopy.mParams;
- String utteranceId = "";
- if (params != null) {
- for (int i = 0; i < params.size() - 1; i = i + 2) {
- String param = params.get(i);
- if (param.equals(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID)) {
- utteranceId = params.get(i + 1);
- }
- }
- }
- if (utteranceId.length() > 0) {
- dispatchUtteranceCompletedCallback(utteranceId, callingApp);
- }
- }
- processSpeechQueue();
+ public void onCompletion(MediaPlayer arg0) { + // mCurrentSpeechItem may become null if it is stopped at the same + // time it completes. + SpeechItem currentSpeechItemCopy = mCurrentSpeechItem; + if (currentSpeechItemCopy != null) { + String callingApp = currentSpeechItemCopy.mCallingApp; + ArrayList<String> params = currentSpeechItemCopy.mParams; + String utteranceId = ""; + if (params != null) { + for (int i = 0; i < params.size() - 1; i = i + 2) { + String param = params.get(i); + if (param.equals(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID)) { + utteranceId = params.get(i + 1); + } + } + } + if (utteranceId.length() > 0) { + dispatchUtteranceCompletedCallback(utteranceId, callingApp); + } + } + processSpeechQueue(); } private int playSilence(String callingApp, long duration, int queueMode, @@ -1064,7 +1064,7 @@ public class TtsService extends Service implements OnCompletionListener { SoundResource sr = getSoundResource(mCurrentSpeechItem); // Synth speech as needed - synthesizer should call // processSpeechQueue to continue running the queue - Log.v(SERVICE_TAG, "TTS processing: " + mCurrentSpeechItem.mText); + // Log.v(SERVICE_TAG, "TTS processing: " + mCurrentSpeechItem.mText); if (sr == null) { if (mCurrentSpeechItem.mType == SpeechItem.TEXT) { mCurrentSpeechItem = splitCurrentTextIfNeeded(mCurrentSpeechItem); @@ -1462,8 +1462,8 @@ public class TtsService extends Service implements OnCompletionListener { * * @return SUCCESS or ERROR as defined in android.speech.tts.TextToSpeech. */ - public int setEngineByPackageName(String packageName) {
- return mSelf.setEngine(packageName);
+ public int setEngineByPackageName(String packageName) { + return mSelf.setEngine(packageName); } /** diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java index c18262e1d388..5c67da7f4df4 100644 --- a/services/java/com/android/server/ConnectivityService.java +++ b/services/java/com/android/server/ConnectivityService.java @@ -22,6 +22,7 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; +import android.database.ContentObserver; import android.net.ConnectivityManager; import android.net.IConnectivityManager; import android.net.MobileDataStateTracker; @@ -29,8 +30,9 @@ import android.net.NetworkInfo; import android.net.LinkProperties; import android.net.NetworkStateTracker; import android.net.NetworkUtils; +import android.net.Proxy; +import android.net.ProxyProperties; import android.net.wifi.WifiStateTracker; -import android.net.NetworkUtils; import android.os.Binder; import android.os.Handler; import android.os.HandlerThread; @@ -55,13 +57,12 @@ import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; import java.net.InetAddress; +import java.net.InetSocketAddress; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Collection; import java.util.GregorianCalendar; import java.util.List; -import java.net.InetAddress; -import java.net.UnknownHostException; /** * @hide @@ -179,6 +180,12 @@ public class ConnectivityService extends IConnectivityManager.Stub { private static final int EVENT_CLEAR_NET_TRANSITION_WAKELOCK = MAX_NETWORK_STATE_TRACKER_EVENT + 8; + /** + * used internally to reload global proxy settings + */ + private static final int EVENT_APPLY_GLOBAL_HTTP_PROXY = + MAX_NETWORK_STATE_TRACKER_EVENT + 9; + private Handler mHandler; // list of DeathRecipients used to make sure features are turned off when @@ -199,6 +206,14 @@ public class ConnectivityService extends IConnectivityManager.Stub { private static final int INET_CONDITION_LOG_MAX_SIZE = 15; private ArrayList mInetLog; + // track the current default http proxy - tell the world if we get a new one (real change) + private ProxyProperties mDefaultProxy = null; + // track the global proxy. + private ProxyProperties mGlobalProxy = null; + private final Object mGlobalProxyLock = new Object(); + + private SettingsObserver mSettingsObserver; + private static class NetworkAttributes { /** * Class for holding settings read from resources. @@ -412,6 +427,9 @@ public class ConnectivityService extends IConnectivityManager.Stub { if (DBG) { mInetLog = new ArrayList(); } + + mSettingsObserver = new SettingsObserver(mHandler, EVENT_APPLY_GLOBAL_HTTP_PROXY); + mSettingsObserver.observe(mContext); } @@ -1303,6 +1321,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { mInitialBroadcast = null; } } + // load the global proxy at startup + mHandler.sendMessage(mHandler.obtainMessage(EVENT_APPLY_GLOBAL_HTTP_PROXY)); } private void handleConnect(NetworkInfo info) { @@ -1380,6 +1400,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { if (mNetTrackers[netType].getNetworkInfo().isConnected()) { if (mNetAttributes[netType].isDefault()) { + handleApplyDefaultProxy(netType); addDefaultRoute(mNetTrackers[netType]); } else { addPrivateDnsRoutes(mNetTrackers[netType]); @@ -1783,10 +1804,9 @@ public class ConnectivityService extends IConnectivityManager.Stub { } break; case NetworkStateTracker.EVENT_CONFIGURATION_CHANGED: - // TODO - make this handle ip/proxy/gateway/dns changes info = (NetworkInfo) msg.obj; type = info.getType(); - handleDnsConfigurationChange(type); + handleConnectivityChange(type); break; case EVENT_CLEAR_NET_TRANSITION_WAKELOCK: String causedBy = null; @@ -1838,6 +1858,10 @@ public class ConnectivityService extends IConnectivityManager.Stub { handleSetMobileData(enabled); break; } + case EVENT_APPLY_GLOBAL_HTTP_PROXY: + { + handleDeprecatedGlobalHttpProxy(); + } } } } @@ -2037,4 +2061,113 @@ public class ConnectivityService extends IConnectivityManager.Stub { sendInetConditionBroadcast(networkInfo); return; } + + public synchronized ProxyProperties getProxy() { + if (mGlobalProxy != null) return mGlobalProxy; + if (mDefaultProxy != null) return mDefaultProxy; + return null; + } + + public void setGlobalProxy(ProxyProperties proxyProperties) { + enforceChangePermission(); + synchronized (mGlobalProxyLock) { + if (proxyProperties == mGlobalProxy) return; + if (proxyProperties != null && proxyProperties.equals(mGlobalProxy)) return; + if (mGlobalProxy != null && mGlobalProxy.equals(proxyProperties)) return; + + String host = ""; + int port = 0; + String exclList = ""; + if (proxyProperties != null && !TextUtils.isEmpty(proxyProperties.getHost())) { + mGlobalProxy = new ProxyProperties(proxyProperties); + host = mGlobalProxy.getHost(); + port = mGlobalProxy.getPort(); + exclList = mGlobalProxy.getExclusionList(); + } else { + mGlobalProxy = null; + } + ContentResolver res = mContext.getContentResolver(); + Settings.Secure.putString(res, Settings.Secure.GLOBAL_HTTP_PROXY_HOST, host); + Settings.Secure.putInt(res, Settings.Secure.GLOBAL_HTTP_PROXY_PORT, port); + Settings.Secure.putString(res,Settings.Secure.GLOBAL_HTTP_PROXY_EXCLUSION_LIST, + exclList); + } + + if (mGlobalProxy == null) { + proxyProperties = mDefaultProxy; + } + sendProxyBroadcast(proxyProperties); + } + + public ProxyProperties getGlobalProxy() { + synchronized (mGlobalProxyLock) { + return mGlobalProxy; + } + } + + private void handleApplyDefaultProxy(int type) { + // check if new default - push it out to all VM if so + ProxyProperties proxy = mNetTrackers[type].getLinkProperties().getHttpProxy(); + synchronized (this) { + if (mDefaultProxy != null && mDefaultProxy.equals(proxy)) return; + if (mDefaultProxy == proxy) return; + if (!TextUtils.isEmpty(proxy.getHost())) { + mDefaultProxy = proxy; + } else { + mDefaultProxy = null; + } + } + if (DBG) Slog.d(TAG, "changing default proxy to " + proxy); + if ((proxy == null && mGlobalProxy == null) || proxy.equals(mGlobalProxy)) return; + if (mGlobalProxy != null) return; + sendProxyBroadcast(proxy); + } + + private void handleDeprecatedGlobalHttpProxy() { + String proxy = Settings.Secure.getString(mContext.getContentResolver(), + Settings.Secure.HTTP_PROXY); + if (!TextUtils.isEmpty(proxy)) { + String data[] = proxy.split(":"); + String proxyHost = data[0]; + int proxyPort = 8080; + if (data.length > 1) { + try { + proxyPort = Integer.parseInt(data[1]); + } catch (NumberFormatException e) { + return; + } + } + ProxyProperties p = new ProxyProperties(data[0], proxyPort, ""); + setGlobalProxy(p); + } + } + + private void sendProxyBroadcast(ProxyProperties proxy) { + Slog.d(TAG, "sending Proxy Broadcast for " + proxy); + Intent intent = new Intent(Proxy.PROXY_CHANGE_ACTION); + intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); + intent.putExtra(Proxy.EXTRA_PROXY_INFO, proxy); + mContext.sendBroadcast(intent); + } + + private static class SettingsObserver extends ContentObserver { + private int mWhat; + private Handler mHandler; + SettingsObserver(Handler handler, int what) { + super(handler); + mHandler = handler; + mWhat = what; + } + + void observe(Context context) { + ContentResolver resolver = context.getContentResolver(); + resolver.registerContentObserver(Settings.Secure.getUriFor( + Settings.Secure.HTTP_PROXY), false, this); + } + + @Override + public void onChange(boolean selfChange) { + mHandler.obtainMessage(mWhat).sendToTarget(); + } + } } diff --git a/services/java/com/android/server/DevicePolicyManagerService.java b/services/java/com/android/server/DevicePolicyManagerService.java index 3dcad3855879..2b43b0131cd8 100644 --- a/services/java/com/android/server/DevicePolicyManagerService.java +++ b/services/java/com/android/server/DevicePolicyManagerService.java @@ -1757,10 +1757,19 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } // Remove white spaces proxySpec = proxySpec.trim(); + String data[] = proxySpec.split(":"); + int proxyPort = 8080; + if (data.length > 1) { + try { + proxyPort = Integer.parseInt(data[1]); + } catch (NumberFormatException e) {} + } exclusionList = exclusionList.trim(); ContentResolver res = mContext.getContentResolver(); - Settings.Secure.putString(res, Settings.Secure.HTTP_PROXY, proxySpec); - Settings.Secure.putString(res, Settings.Secure.HTTP_PROXY_EXCLUSION_LIST, exclusionList); + Settings.Secure.putString(res, Settings.Secure.GLOBAL_HTTP_PROXY_HOST, data[0]); + Settings.Secure.putInt(res, Settings.Secure.GLOBAL_HTTP_PROXY_PORT, proxyPort); + Settings.Secure.putString(res, Settings.Secure.GLOBAL_HTTP_PROXY_EXCLUSION_LIST, + exclusionList); } @Override diff --git a/services/java/com/android/server/PackageManagerService.java b/services/java/com/android/server/PackageManagerService.java index a0a19748f9eb..c1218080873f 100644 --- a/services/java/com/android/server/PackageManagerService.java +++ b/services/java/com/android/server/PackageManagerService.java @@ -4578,6 +4578,80 @@ class PackageManagerService extends IPackageManager.Stub { mHandler.sendMessage(msg); } + public void setInstallerPackageName(String targetPackage, + String installerPackageName) { + PackageSetting pkgSetting; + final int uid = Binder.getCallingUid(); + final int permission = mContext.checkCallingPermission( + android.Manifest.permission.INSTALL_PACKAGES); + final boolean allowedByPermission = (permission == PackageManager.PERMISSION_GRANTED); + synchronized (mPackages) { + PackageSetting targetPackageSetting = mSettings.mPackages.get(targetPackage); + if (targetPackageSetting == null) { + throw new IllegalArgumentException("Unknown target package: " + targetPackage); + } + + PackageSetting installerPackageSetting; + if (installerPackageName != null) { + installerPackageSetting = mSettings.mPackages.get(installerPackageName); + if (installerPackageSetting == null) { + throw new IllegalArgumentException("Unknown installer package: " + + installerPackageName); + } + } else { + installerPackageSetting = null; + } + + Signature[] callerSignature; + Object obj = mSettings.getUserIdLP(uid); + if (obj != null) { + if (obj instanceof SharedUserSetting) { + callerSignature = ((SharedUserSetting)obj).signatures.mSignatures; + } else if (obj instanceof PackageSetting) { + callerSignature = ((PackageSetting)obj).signatures.mSignatures; + } else { + throw new SecurityException("Bad object " + obj + " for uid " + uid); + } + } else { + throw new SecurityException("Unknown calling uid " + uid); + } + + // Verify: can't set installerPackageName to a package that is + // not signed with the same cert as the caller. + if (installerPackageSetting != null) { + if (checkSignaturesLP(callerSignature, + installerPackageSetting.signatures.mSignatures) + != PackageManager.SIGNATURE_MATCH) { + throw new SecurityException( + "Caller does not have same cert as new installer package " + + installerPackageName); + } + } + + // Verify: if target already has an installer package, it must + // be signed with the same cert as the caller. + if (targetPackageSetting.installerPackageName != null) { + PackageSetting setting = mSettings.mPackages.get( + targetPackageSetting.installerPackageName); + // If the currently set package isn't valid, then it's always + // okay to change it. + if (setting != null) { + if (checkSignaturesLP(callerSignature, + setting.signatures.mSignatures) + != PackageManager.SIGNATURE_MATCH) { + throw new SecurityException( + "Caller does not have same cert as old installer package " + + targetPackageSetting.installerPackageName); + } + } + } + + // Okay! + targetPackageSetting.installerPackageName = installerPackageName; + scheduleWriteSettingsLocked(); + } + } + public void setPackageObbPath(String packageName, String path) { if (DEBUG_OBB) Log.v(TAG, "Setting .obb path for " + packageName + " to: " + path); diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java index 1ec8a22d20b5..e81552433a60 100644 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -76,6 +76,8 @@ import android.content.pm.ServiceInfo; import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Configuration; import android.graphics.Bitmap; +import android.net.Proxy; +import android.net.ProxyProperties; import android.net.Uri; import android.os.Binder; import android.os.Build; @@ -127,6 +129,7 @@ import java.io.InputStreamReader; import java.io.PrintWriter; import java.lang.IllegalStateException; import java.lang.ref.WeakReference; +import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -960,6 +963,7 @@ public final class ActivityManagerService extends ActivityManagerNative static final int SHOW_STRICT_MODE_VIOLATION_MSG = 26; static final int CHECK_EXCESSIVE_WAKE_LOCKS_MSG = 27; static final int CLEAR_DNS_CACHE = 28; + static final int UPDATE_HTTP_PROXY = 29; AlertDialog mUidAlert; @@ -1125,6 +1129,30 @@ public final class ActivityManagerService extends ActivityManagerNative } } } break; + case UPDATE_HTTP_PROXY: { + ProxyProperties proxy = (ProxyProperties)msg.obj; + String host = ""; + String port = ""; + String exclList = ""; + if (proxy != null) { + host = proxy.getHost(); + port = Integer.toString(proxy.getPort()); + exclList = proxy.getExclusionList(); + } + synchronized (ActivityManagerService.this) { + for (int i = mLruProcesses.size() - 1 ; i >= 0 ; i--) { + ProcessRecord r = mLruProcesses.get(i); + if (r.thread != null) { + try { + r.thread.setHttpProxy(host, port, exclList); + } catch (RemoteException ex) { + Slog.w(TAG, "Failed to update http proxy for: " + + r.info.processName); + } + } + } + } + } break; case SHOW_UID_ERROR_MSG: { // XXX This is a temporary dialog, no need to localize. AlertDialog d = new BaseErrorDialog(mContext); @@ -10402,6 +10430,11 @@ public final class ActivityManagerService extends ActivityManagerNative mHandler.sendEmptyMessage(CLEAR_DNS_CACHE); } + if (Proxy.PROXY_CHANGE_ACTION.equals(intent.getAction())) { + ProxyProperties proxy = intent.getParcelableExtra("proxy"); + mHandler.sendMessage(mHandler.obtainMessage(UPDATE_HTTP_PROXY, proxy)); + } + /* * Prevent non-system code (defined here to be non-persistent * processes) from sending protected broadcasts. diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java index 70328f721e4b..06c7c1baf6fa 100644 --- a/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java +++ b/telephony/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java @@ -1007,14 +1007,9 @@ public final class GsmDataConnectionTracker extends DataConnectionTracker { ApnSetting apn = mPendingDataConnection.getApn(); if (apn.proxy != null && apn.proxy.length() != 0) { try { - ProxyProperties proxy = new ProxyProperties(); - proxy.setSocketAddress(new InetSocketAddress(InetAddress.getByName(apn.proxy), - Integer.parseInt(apn.port))); + ProxyProperties proxy = new ProxyProperties(apn.proxy, + Integer.parseInt(apn.port), null); mLinkProperties.setHttpProxy(proxy); - } catch (UnknownHostException e) { - loge("UnknownHostException making ProxyProperties: " + e); - } catch (SecurityException e) { - loge("SecurityException making ProxyProperties: " + e); } catch (NumberFormatException e) { loge("NumberFormatException making ProxyProperties (" + apn.port + "): " + e); diff --git a/test-runner/src/android/test/mock/MockPackageManager.java b/test-runner/src/android/test/mock/MockPackageManager.java index f0cbaa0a69ee..615870b11c93 100644 --- a/test-runner/src/android/test/mock/MockPackageManager.java +++ b/test-runner/src/android/test/mock/MockPackageManager.java @@ -341,6 +341,12 @@ public class MockPackageManager extends PackageManager { throw new UnsupportedOperationException(); } + @Override + public void setInstallerPackageName(String targetPackage, + String installerPackageName) { + throw new UnsupportedOperationException(); + } + /** * @hide - to match hiding in superclass */ diff --git a/tools/layoutlib/bridge/src/android/graphics/Matrix_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Matrix_Delegate.java index 77de32daeb21..6e802680958d 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Matrix_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/Matrix_Delegate.java @@ -813,6 +813,10 @@ public final class Matrix_Delegate { return mask; } + private Matrix_Delegate() { + reset(); + } + /** * Adds the given transformation to the current Matrix * <p/>This in effect does this = this*matrix diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java index 02db2cfbac13..ac7fada6df3a 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java @@ -65,6 +65,7 @@ import java.io.InputStream; import java.util.HashMap; import java.util.IdentityHashMap; import java.util.Map; +import java.util.Stack; import java.util.TreeMap; import java.util.Map.Entry; @@ -99,6 +100,8 @@ public final class BridgeContext extends Activity { private final ILayoutLog mLogger; private BridgeContentResolver mContentResolver; + private final Stack<BridgeXmlBlockParser> mParserStack = new Stack<BridgeXmlBlockParser>(); + /** * @param projectKey An Object identifying the project. This is used for the cache mechanism. * @param metrics the {@link DisplayMetrics}. @@ -188,6 +191,21 @@ public final class BridgeContext extends Activity { return mDefaultPropMaps.get(key); } + public void pushParser(BridgeXmlBlockParser parser) { + mParserStack.push(parser); + } + + public void popParser() { + mParserStack.pop(); + } + + public BridgeXmlBlockParser getPreviousParser() { + if (mParserStack.size() < 2) { + return null; + } + return mParserStack.get(mParserStack.size() - 2); + } + // ------------- Activity Methods @Override diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeInflater.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeInflater.java index b4a28a6d9790..d9e26e258ba8 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeInflater.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeInflater.java @@ -35,17 +35,17 @@ import java.io.File; import java.io.FileReader; /** - * Custom implementation of {@link LayoutInflater} to handle custom views. + * Custom implementation of {@link LayoutInflater} to handle custom views. */ public final class BridgeInflater extends LayoutInflater { - + private final IProjectCallback mProjectCallback; /** * List of class prefixes which are tried first by default. * <p/> * This should match the list in com.android.internal.policy.impl.PhoneLayoutInflater. - */ + */ private static final String[] sClassPrefixList = { "android.widget.", "android.webkit." @@ -55,10 +55,10 @@ public final class BridgeInflater extends LayoutInflater { super(original, newContext); mProjectCallback = null; } - + /** * Instantiate a new BridgeInflater with an {@link IProjectCallback} object. - * + * * @param context The Android application context. * @param projectCallback the {@link IProjectCallback} object. */ @@ -84,7 +84,7 @@ public final class BridgeInflater extends LayoutInflater { // Ignore. We'll try again using the base class below. } } - + // Next try using the parent loader. This will most likely only work for // fully-qualified class names. try { @@ -94,7 +94,7 @@ public final class BridgeInflater extends LayoutInflater { } catch (ClassNotFoundException e) { // Ignore. We'll try again using the custom view loader below. } - + // Finally try again using the custom view loader try { if (view == null) { @@ -111,12 +111,12 @@ public final class BridgeInflater extends LayoutInflater { ClassNotFoundException exception = new ClassNotFoundException("onCreateView", e); throw exception; } - + setupViewInContext(view, attrs); - + return view; } - + @Override public View createViewFromTag(View parent, String name, AttributeSet attrs) { View view = null; @@ -130,7 +130,7 @@ public final class BridgeInflater extends LayoutInflater { // Wrap the real exception in an InflateException so that the calling // method can deal with it. InflateException exception = new InflateException(); - if (e2.getClass().equals(ClassNotFoundException.class) == false) { + if (e2.getClass().equals(ClassNotFoundException.class) == false) { exception.initCause(e2); } else { exception.initCause(e); @@ -138,18 +138,18 @@ public final class BridgeInflater extends LayoutInflater { throw exception; } } - + setupViewInContext(view, attrs); - + return view; } - + @Override public View inflate(int resource, ViewGroup root) { Context context = getContext(); if (context instanceof BridgeContext) { BridgeContext bridgeContext = (BridgeContext)context; - + IResourceValue value = null; String[] layoutInfo = Bridge.resolveResourceValue(resource); @@ -158,7 +158,7 @@ public final class BridgeInflater extends LayoutInflater { layoutInfo[0]); } else { layoutInfo = mProjectCallback.resolveResourceValue(resource); - + if (layoutInfo != null) { value = bridgeContext.getProjectResource(BridgeConstants.RES_LAYOUT, layoutInfo[0]); @@ -172,10 +172,10 @@ public final class BridgeInflater extends LayoutInflater { KXmlParser parser = new KXmlParser(); parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); parser.setInput(new FileReader(f)); - + BridgeXmlBlockParser bridgeParser = new BridgeXmlBlockParser( parser, bridgeContext, false); - + return inflate(bridgeParser, root); } catch (Exception e) { bridgeContext.getLogger().error(e); @@ -186,7 +186,7 @@ public final class BridgeInflater extends LayoutInflater { } return null; } - + private View loadCustomView(String name, AttributeSet attrs) throws ClassNotFoundException, Exception{ if (mProjectCallback != null) { @@ -194,12 +194,12 @@ public final class BridgeInflater extends LayoutInflater { if (name.equals("view")) { name = attrs.getAttributeValue(null, "class"); } - + mConstructorArgs[1] = attrs; Object customView = mProjectCallback.loadView(name, mConstructorSignature, mConstructorArgs); - + if (customView instanceof View) { return (View)customView; } @@ -207,14 +207,27 @@ public final class BridgeInflater extends LayoutInflater { return null; } - - - + + + private void setupViewInContext(View view, AttributeSet attrs) { if (getContext() instanceof BridgeContext) { BridgeContext bc = (BridgeContext) getContext(); if (attrs instanceof BridgeXmlBlockParser) { - Object viewKey = ((BridgeXmlBlockParser) attrs).getViewKey(); + BridgeXmlBlockParser parser = (BridgeXmlBlockParser) attrs; + + // get the view key + Object viewKey = parser.getViewKey(); + + // if there's no view key and the depth is 1 (ie this is the first tag), + // look for a previous parser in the context, and check if this one has a viewkey. + if (viewKey == null && parser.getDepth() == 1) { + BridgeXmlBlockParser previousParser = bc.getPreviousParser(); + if (previousParser != null) { + viewKey = previousParser.getViewKey(); + } + } + if (viewKey != null) { bc.addViewKey(view, viewKey); } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParser.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParser.java index 24f61c818dff..073a0191a808 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParser.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParser.java @@ -40,10 +40,9 @@ public class BridgeXmlBlockParser implements XmlResourceParser { private XmlPullAttributes mAttrib; private boolean mStarted = false; - private boolean mDecNextDepth = false; - private int mDepth = 0; private int mEventType = START_DOCUMENT; private final boolean mPlatformFile; + private final BridgeContext mContext; /** * Builds a {@link BridgeXmlBlockParser}. @@ -53,10 +52,13 @@ public class BridgeXmlBlockParser implements XmlResourceParser { */ public BridgeXmlBlockParser(XmlPullParser parser, BridgeContext context, boolean platformFile) { mParser = parser; + mContext = context; mPlatformFile = platformFile; mAttrib = new BridgeXmlPullAttributes(parser, context, mPlatformFile); + + mContext.pushParser(this); } - + public boolean isPlatformFile() { return mPlatformFile; } @@ -68,10 +70,9 @@ public class BridgeXmlBlockParser implements XmlResourceParser { return null; } - - + // ------- XmlResourceParser implementation - + public void setFeature(String name, boolean state) throws XmlPullParserException { if (FEATURE_PROCESS_NAMESPACES.equals(name) && state) { @@ -145,7 +146,7 @@ public class BridgeXmlBlockParser implements XmlResourceParser { } public int getDepth() { - return mDepth; + return mParser.getDepth(); } public String getText() { @@ -236,17 +237,10 @@ public class BridgeXmlBlockParser implements XmlResourceParser { return START_DOCUMENT; } int ev = mParser.next(); - if (mDecNextDepth) { - mDepth--; - mDecNextDepth = false; - } - switch (ev) { - case START_TAG: - mDepth++; - break; - case END_TAG: - mDecNextDepth = true; - break; + + if (ev == END_TAG && mParser.getDepth() == 1) { + // done with parser remove it from the context stack. + mContext.popParser(); } mEventType = ev; return ev; @@ -301,7 +295,7 @@ public class BridgeXmlBlockParser implements XmlResourceParser { // AttributeSet implementation - + public void close() { // pass } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/LayoutSceneImpl.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/LayoutSceneImpl.java index b0316a3567c6..2e3f9a8ee3c5 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/LayoutSceneImpl.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/LayoutSceneImpl.java @@ -25,6 +25,7 @@ import com.android.layoutlib.api.SceneParams; import com.android.layoutlib.api.SceneResult; import com.android.layoutlib.api.ViewInfo; import com.android.layoutlib.api.IDensityBasedResourceValue.Density; +import com.android.layoutlib.api.SceneParams.RenderingMode; import com.android.layoutlib.bridge.BridgeConstants; import com.android.layoutlib.bridge.android.BridgeContext; import com.android.layoutlib.bridge.android.BridgeInflater; @@ -259,22 +260,32 @@ public class LayoutSceneImpl { int renderScreenWidth = mParams.getScreenWidth(); int renderScreenHeight = mParams.getScreenHeight(); - if (mParams.getRenderFullSize()) { + RenderingMode renderingMode = mParams.getRenderingMode(); + + if (renderingMode != RenderingMode.NORMAL) { // measure the full size needed by the layout. w_spec = MeasureSpec.makeMeasureSpec(renderScreenWidth, - MeasureSpec.UNSPECIFIED); // this lets us know the actual needed size + renderingMode.isHorizExpand() ? + MeasureSpec.UNSPECIFIED // this lets us know the actual needed size + : MeasureSpec.EXACTLY); h_spec = MeasureSpec.makeMeasureSpec(renderScreenHeight - mScreenOffset, - MeasureSpec.UNSPECIFIED); // this lets us know the actual needed size + renderingMode.isVertExpand() ? + MeasureSpec.UNSPECIFIED // this lets us know the actual needed size + : MeasureSpec.EXACTLY); mViewRoot.measure(w_spec, h_spec); - int neededWidth = mViewRoot.getChildAt(0).getMeasuredWidth(); - if (neededWidth > renderScreenWidth) { - renderScreenWidth = neededWidth; + if (renderingMode.isHorizExpand()) { + int neededWidth = mViewRoot.getChildAt(0).getMeasuredWidth(); + if (neededWidth > renderScreenWidth) { + renderScreenWidth = neededWidth; + } } - int neededHeight = mViewRoot.getChildAt(0).getMeasuredHeight(); - if (neededHeight > renderScreenHeight - mScreenOffset) { - renderScreenHeight = neededHeight + mScreenOffset; + if (renderingMode.isVertExpand()) { + int neededHeight = mViewRoot.getChildAt(0).getMeasuredHeight(); + if (neededHeight > renderScreenHeight - mScreenOffset) { + renderScreenHeight = neededHeight + mScreenOffset; + } } } diff --git a/tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/TestClassReplacement.java b/tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/TestClassReplacement.java index adb693de475c..a068ae273651 100644 --- a/tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/TestClassReplacement.java +++ b/tools/layoutlib/bridge/tests/com/android/layoutlib/bridge/TestClassReplacement.java @@ -25,11 +25,11 @@ import junit.framework.TestCase; public class TestClassReplacement extends TestCase { public void testClassReplacements() { - // TODO: we want to test all the classes. For now only Paint passes the tests. + // TODO: we want to test all the classes. For now only, no classes pass the test. // final String[] classes = CreateInfo.RENAMED_CLASSES; final String[] classes = new String[] { - "android.graphics.Paint", "android.graphics._Original_Paint", - "android.graphics.Canvas", "android.graphics._Original_Canvas", +// "android.graphics.Paint", "android.graphics._Original_Paint", +// "android.graphics.Canvas", "android.graphics._Original_Canvas", }; final int count = classes.length; for (int i = 0 ; i < count ; i += 2) { diff --git a/wifi/java/android/net/wifi/SupplicantStateTracker.java b/wifi/java/android/net/wifi/SupplicantStateTracker.java new file mode 100644 index 000000000000..a83a0ad44ce2 --- /dev/null +++ b/wifi/java/android/net/wifi/SupplicantStateTracker.java @@ -0,0 +1,333 @@ +/* + * 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 android.net.wifi; + +import com.android.internal.util.HierarchicalState; +import com.android.internal.util.HierarchicalStateMachine; + +import android.net.wifi.WifiStateMachine.StateChangeResult; +import android.content.Context; +import android.content.Intent; +import android.os.Handler; +import android.os.Message; +import android.os.Parcelable; +import android.util.Log; + +/** + * Tracks the state changes in supplicant and provides functionality + * that is based on these state changes: + * - detect a failed WPA handshake that loops indefinitely + * - password failure handling + * - Enable networks after a WPS success/failure + */ +class SupplicantStateTracker extends HierarchicalStateMachine { + + private static final String TAG = "SupplicantStateTracker"; + private static final boolean DBG = false; + + private WifiStateMachine mWifiStateMachine; + private int mPasswordFailuresCount = 0; + /* Indicates authentication failure in supplicant broadcast. + * TODO: enhance auth failure reporting to include notification + * for all type of failures: EAP, WPS & WPA networks */ + private boolean mAuthFailureInSupplicantBroadcast = false; + + /* Maximum retries on a password failure notification */ + private static final int MAX_RETRIES_ON_PASSWORD_FAILURE = 2; + + /* Track if WPS was started since we need to re-enable networks + * and load configuration afterwards */ + private boolean mWpsStarted = false; + + private Context mContext; + + private HierarchicalState mUninitializedState = new UninitializedState(); + private HierarchicalState mDefaultState = new DefaultState(); + private HierarchicalState mInactiveState = new InactiveState(); + private HierarchicalState mDisconnectState = new DisconnectedState(); + private HierarchicalState mScanState = new ScanState(); + private HierarchicalState mHandshakeState = new HandshakeState(); + private HierarchicalState mCompletedState = new CompletedState(); + private HierarchicalState mDormantState = new DormantState(); + + public SupplicantStateTracker(Context context, WifiStateMachine wsm, Handler target) { + super(TAG, target.getLooper()); + + mContext = context; + mWifiStateMachine = wsm; + addState(mDefaultState); + addState(mUninitializedState, mDefaultState); + addState(mInactiveState, mDefaultState); + addState(mDisconnectState, mDefaultState); + addState(mScanState, mDefaultState); + addState(mHandshakeState, mDefaultState); + addState(mCompletedState, mDefaultState); + addState(mDormantState, mDefaultState); + + setInitialState(mUninitializedState); + + //start the state machine + start(); + } + + public void resetSupplicantState() { + transitionTo(mUninitializedState); + } + + + private void transitionOnSupplicantStateChange(StateChangeResult stateChangeResult) { + SupplicantState supState = (SupplicantState) stateChangeResult.state; + + if (DBG) Log.d(TAG, "Supplicant state: " + supState.toString() + "\n"); + + switch (supState) { + case DISCONNECTED: + transitionTo(mDisconnectState); + break; + case SCANNING: + transitionTo(mScanState); + break; + case ASSOCIATING: + case ASSOCIATED: + case FOUR_WAY_HANDSHAKE: + case GROUP_HANDSHAKE: + transitionTo(mHandshakeState); + break; + case COMPLETED: + transitionTo(mCompletedState); + break; + case DORMANT: + transitionTo(mDormantState); + break; + case INACTIVE: + transitionTo(mInactiveState); + break; + case UNINITIALIZED: + case INVALID: + transitionTo(mUninitializedState); + break; + default: + Log.e(TAG, "Unknown supplicant state " + supState); + break; + } + } + + private void sendSupplicantStateChangedBroadcast(StateChangeResult sc, boolean failedAuth) { + Intent intent = new Intent(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT + | Intent.FLAG_RECEIVER_REPLACE_PENDING); + intent.putExtra(WifiManager.EXTRA_NEW_STATE, (Parcelable)sc.state); + if (failedAuth) { + intent.putExtra( + WifiManager.EXTRA_SUPPLICANT_ERROR, + WifiManager.ERROR_AUTHENTICATING); + } + mContext.sendStickyBroadcast(intent); + } + + /******************************************************** + * HSM states + *******************************************************/ + + class DefaultState extends HierarchicalState { + @Override + public void enter() { + if (DBG) Log.d(TAG, getName() + "\n"); + } + @Override + public boolean processMessage(Message message) { + if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); + switch (message.what) { + case WifiStateMachine.PASSWORD_MAY_BE_INCORRECT_EVENT: + mPasswordFailuresCount++; + mAuthFailureInSupplicantBroadcast = true; + break; + case WifiStateMachine.CMD_START_WPS_PBC: + case WifiStateMachine.CMD_START_WPS_PIN_FROM_AP: + case WifiStateMachine.CMD_START_WPS_PIN_FROM_DEVICE: + mWpsStarted = true; + break; + case WifiStateMachine.SUPPLICANT_STATE_CHANGE_EVENT: + StateChangeResult stateChangeResult = (StateChangeResult) message.obj; + sendSupplicantStateChangedBroadcast(stateChangeResult, + mAuthFailureInSupplicantBroadcast); + mAuthFailureInSupplicantBroadcast = false; + transitionOnSupplicantStateChange(stateChangeResult); + break; + default: + Log.e(TAG, "Ignoring " + message); + break; + } + return HANDLED; + } + } + + class UninitializedState extends HierarchicalState { + @Override + public void enter() { + if (DBG) Log.d(TAG, getName() + "\n"); + mWifiStateMachine.setNetworkAvailable(false); + } + @Override + public void exit() { + mWifiStateMachine.setNetworkAvailable(true); + } + } + + class InactiveState extends HierarchicalState { + @Override + public void enter() { + if (DBG) Log.d(TAG, getName() + "\n"); + /* A failed WPS connection */ + if (mWpsStarted) { + Log.e(TAG, "WPS set up failed, enabling other networks"); + WifiConfigStore.enableAllNetworks(); + mWpsStarted = false; + } + mWifiStateMachine.setNetworkAvailable(false); + } + @Override + public void exit() { + mWifiStateMachine.setNetworkAvailable(true); + } + } + + + class DisconnectedState extends HierarchicalState { + @Override + public void enter() { + if (DBG) Log.d(TAG, getName() + "\n"); + /* If a disconnect event happens after password key failure + * exceeds maximum retries, disable the network + */ + + Message message = getCurrentMessage(); + StateChangeResult stateChangeResult = (StateChangeResult) message.obj; + + if (mPasswordFailuresCount >= MAX_RETRIES_ON_PASSWORD_FAILURE) { + Log.d(TAG, "Failed to authenticate, disabling network " + + stateChangeResult.networkId); + WifiConfigStore.disableNetwork(stateChangeResult.networkId); + mPasswordFailuresCount = 0; + } + } + } + + class ScanState extends HierarchicalState { + @Override + public void enter() { + if (DBG) Log.d(TAG, getName() + "\n"); + } + } + + class HandshakeState extends HierarchicalState { + /** + * The max number of the WPA supplicant loop iterations before we + * decide that the loop should be terminated: + */ + private static final int MAX_SUPPLICANT_LOOP_ITERATIONS = 4; + private int mLoopDetectIndex; + private int mLoopDetectCount; + + @Override + public void enter() { + if (DBG) Log.d(TAG, getName() + "\n"); + mLoopDetectIndex = 0; + mLoopDetectCount = 0; + } + @Override + public boolean processMessage(Message message) { + if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); + switch (message.what) { + case WifiStateMachine.SUPPLICANT_STATE_CHANGE_EVENT: + StateChangeResult stateChangeResult = (StateChangeResult) message.obj; + SupplicantState state = (SupplicantState) stateChangeResult.state; + if (state == SupplicantState.ASSOCIATING || + state == SupplicantState.ASSOCIATED || + state == SupplicantState.FOUR_WAY_HANDSHAKE || + state == SupplicantState.GROUP_HANDSHAKE) { + if (mLoopDetectIndex > state.ordinal()) { + mLoopDetectCount++; + } + if (mLoopDetectCount > MAX_SUPPLICANT_LOOP_ITERATIONS) { + Log.d(TAG, "Supplicant loop detected, disabling network " + + stateChangeResult.networkId); + WifiConfigStore.disableNetwork(stateChangeResult.networkId); + } + mLoopDetectIndex = state.ordinal(); + sendSupplicantStateChangedBroadcast(stateChangeResult, + mAuthFailureInSupplicantBroadcast); + } else { + //Have the DefaultState handle the transition + return NOT_HANDLED; + } + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + } + + class CompletedState extends HierarchicalState { + @Override + public void enter() { + if (DBG) Log.d(TAG, getName() + "\n"); + /* Reset password failure count */ + mPasswordFailuresCount = 0; + + /* A successful WPS connection */ + if (mWpsStarted) { + WifiConfigStore.enableAllNetworks(); + WifiConfigStore.loadConfiguredNetworks(); + mWpsStarted = false; + } + } + @Override + public boolean processMessage(Message message) { + if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); + switch(message.what) { + case WifiStateMachine.SUPPLICANT_STATE_CHANGE_EVENT: + StateChangeResult stateChangeResult = (StateChangeResult) message.obj; + SupplicantState state = (SupplicantState) stateChangeResult.state; + sendSupplicantStateChangedBroadcast(stateChangeResult, + mAuthFailureInSupplicantBroadcast); + /* Ignore a re-auth in completed state */ + if (state == SupplicantState.ASSOCIATING || + state == SupplicantState.ASSOCIATED || + state == SupplicantState.FOUR_WAY_HANDSHAKE || + state == SupplicantState.GROUP_HANDSHAKE || + state == SupplicantState.COMPLETED) { + break; + } + transitionOnSupplicantStateChange(stateChangeResult); + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + } + + //TODO: remove after getting rid of the state in supplicant + class DormantState extends HierarchicalState { + @Override + public void enter() { + if (DBG) Log.d(TAG, getName() + "\n"); + } + } +}
\ No newline at end of file diff --git a/wifi/java/android/net/wifi/WifiConfigStore.java b/wifi/java/android/net/wifi/WifiConfigStore.java index 04b3891ef240..24f6f319f84a 100644 --- a/wifi/java/android/net/wifi/WifiConfigStore.java +++ b/wifi/java/android/net/wifi/WifiConfigStore.java @@ -595,9 +595,9 @@ class WifiConfigStore { out.writeUTF(PROXY_SETTINGS_KEY); out.writeUTF(config.proxySettings.toString()); out.writeUTF(PROXY_HOST_KEY); - out.writeUTF(proxyProperties.getSocketAddress().getHostName()); + out.writeUTF(proxyProperties.getHost()); out.writeUTF(PROXY_PORT_KEY); - out.writeInt(proxyProperties.getSocketAddress().getPort()); + out.writeInt(proxyProperties.getPort()); out.writeUTF(EXCLUSION_LIST_KEY); out.writeUTF(exclusionList); writeToFile = true; @@ -716,10 +716,8 @@ class WifiConfigStore { switch (proxySettings) { case STATIC: config.proxySettings = proxySettings; - ProxyProperties proxyProperties = new ProxyProperties(); - proxyProperties.setSocketAddress( - new InetSocketAddress(proxyHost, proxyPort)); - proxyProperties.setExclusionList(exclusionList); + ProxyProperties proxyProperties = + new ProxyProperties(proxyHost, proxyPort, exclusionList); linkProperties.setHttpProxy(proxyProperties); break; case NONE: @@ -1012,42 +1010,13 @@ class WifiConfigStore { switch (newConfig.proxySettings) { case STATIC: - InetSocketAddress newSockAddr = null; - String newExclusionList = null; - InetSocketAddress currentSockAddr = null; - String currentExclusionList = null; - ProxyProperties newHttpProxy = newConfig.linkProperties.getHttpProxy(); - if (newHttpProxy != null) { - newSockAddr = newHttpProxy.getSocketAddress(); - newExclusionList = newHttpProxy.getExclusionList(); - } - ProxyProperties currentHttpProxy = currentConfig.linkProperties.getHttpProxy(); - if (currentHttpProxy != null) { - currentSockAddr = currentHttpProxy.getSocketAddress(); - currentExclusionList = currentHttpProxy.getExclusionList(); - } - - boolean socketAddressDiffers = false; - boolean exclusionListDiffers = false; - if (newSockAddr != null && currentSockAddr != null ) { - socketAddressDiffers = !currentSockAddr.equals(newSockAddr); - } else if (newSockAddr != null || currentSockAddr != null) { - socketAddressDiffers = true; - } - - if (newExclusionList != null && currentExclusionList != null) { - exclusionListDiffers = !currentExclusionList.equals(newExclusionList); - } else if (newExclusionList != null || currentExclusionList != null) { - exclusionListDiffers = true; - } - - if ((currentConfig.proxySettings != newConfig.proxySettings) || - socketAddressDiffers || - exclusionListDiffers) { - proxyChanged = true; + if (newHttpProxy != null) { + proxyChanged = !newHttpProxy.equals(currentHttpProxy); + } else { + proxyChanged = (currentHttpProxy != null); } break; case NONE: diff --git a/wifi/java/android/net/wifi/WifiStateMachine.java b/wifi/java/android/net/wifi/WifiStateMachine.java index 95e2df33456c..90abd029442a 100644 --- a/wifi/java/android/net/wifi/WifiStateMachine.java +++ b/wifi/java/android/net/wifi/WifiStateMachine.java @@ -49,8 +49,6 @@ import android.net.NetworkInfo.DetailedState; import android.net.LinkProperties; import android.os.Binder; import android.os.Message; -import android.os.Parcelable; -import android.os.Handler; import android.os.IBinder; import android.os.INetworkManagementService; import android.os.PowerManager; @@ -93,8 +91,6 @@ import java.util.regex.Pattern; * * @hide */ -//TODO: we still need frequent scanning for the case when -// we issue disconnect but need scan results for open network notification public class WifiStateMachine extends HierarchicalStateMachine { private static final String TAG = "WifiStateMachine"; @@ -120,11 +116,17 @@ public class WifiStateMachine extends HierarchicalStateMachine { private String mLastBssid; private int mLastNetworkId; private boolean mEnableRssiPolling = false; - private boolean mPasswordKeyMayBeIncorrect = false; + private int mRssiPollToken = 0; private int mReconnectCount = 0; private boolean mIsScanMode = false; /** + * Interval in milliseconds between polling for RSSI + * and linkspeed information + */ + private static final int POLL_RSSI_INTERVAL_MSECS = 3000; + + /** * Instance of the bluetooth headset helper. This needs to be created * early because there is a delay before it actually 'connects', as * noted by its javadoc. If we check before it is connected, it will be @@ -148,9 +150,6 @@ public class WifiStateMachine extends HierarchicalStateMachine { /* Connection to a specific network involves disabling all networks, * this flag tracks if networks need to be re-enabled */ private boolean mEnableAllNetworks = false; - /* Track if WPS was started since we need to re-enable networks - * and load configuration afterwards */ - private boolean mWpsStarted = false; private AlarmManager mAlarmManager; private PendingIntent mScanIntent; @@ -166,95 +165,95 @@ public class WifiStateMachine extends HierarchicalStateMachine { private static final int EVENTLOG_SUPPLICANT_STATE_CHANGED = 50023; /* Load the driver */ - private static final int CMD_LOAD_DRIVER = 1; + static final int CMD_LOAD_DRIVER = 1; /* Unload the driver */ - private static final int CMD_UNLOAD_DRIVER = 2; + static final int CMD_UNLOAD_DRIVER = 2; /* Indicates driver load succeeded */ - private static final int CMD_LOAD_DRIVER_SUCCESS = 3; + static final int CMD_LOAD_DRIVER_SUCCESS = 3; /* Indicates driver load failed */ - private static final int CMD_LOAD_DRIVER_FAILURE = 4; + static final int CMD_LOAD_DRIVER_FAILURE = 4; /* Indicates driver unload succeeded */ - private static final int CMD_UNLOAD_DRIVER_SUCCESS = 5; + static final int CMD_UNLOAD_DRIVER_SUCCESS = 5; /* Indicates driver unload failed */ - private static final int CMD_UNLOAD_DRIVER_FAILURE = 6; + static final int CMD_UNLOAD_DRIVER_FAILURE = 6; /* Set bluetooth headset proxy */ - private static final int CMD_SET_BLUETOOTH_HEADSET_PROXY = 7; + static final int CMD_SET_BLUETOOTH_HEADSET_PROXY = 7; /* Set bluetooth A2dp proxy */ - private static final int CMD_SET_BLUETOOTH_A2DP_PROXY = 8; + static final int CMD_SET_BLUETOOTH_A2DP_PROXY = 8; /* Start the supplicant */ - private static final int CMD_START_SUPPLICANT = 11; + static final int CMD_START_SUPPLICANT = 11; /* Stop the supplicant */ - private static final int CMD_STOP_SUPPLICANT = 12; + static final int CMD_STOP_SUPPLICANT = 12; /* Start the driver */ - private static final int CMD_START_DRIVER = 13; + static final int CMD_START_DRIVER = 13; /* Start the driver */ - private static final int CMD_STOP_DRIVER = 14; + static final int CMD_STOP_DRIVER = 14; /* Indicates DHCP succeded */ - private static final int CMD_IP_CONFIG_SUCCESS = 15; + static final int CMD_IP_CONFIG_SUCCESS = 15; /* Indicates DHCP failed */ - private static final int CMD_IP_CONFIG_FAILURE = 16; + static final int CMD_IP_CONFIG_FAILURE = 16; /* Re-configure interface */ - private static final int CMD_RECONFIGURE_IP = 17; + static final int CMD_RECONFIGURE_IP = 17; /* Start the soft access point */ - private static final int CMD_START_AP = 21; + static final int CMD_START_AP = 21; /* Stop the soft access point */ - private static final int CMD_STOP_AP = 22; + static final int CMD_STOP_AP = 22; /* Supplicant events */ /* Connection to supplicant established */ - private static final int SUP_CONNECTION_EVENT = 31; + static final int SUP_CONNECTION_EVENT = 31; /* Connection to supplicant lost */ - private static final int SUP_DISCONNECTION_EVENT = 32; + static final int SUP_DISCONNECTION_EVENT = 32; /* Driver start completed */ - private static final int DRIVER_START_EVENT = 33; + static final int DRIVER_START_EVENT = 33; /* Driver stop completed */ - private static final int DRIVER_STOP_EVENT = 34; + static final int DRIVER_STOP_EVENT = 34; /* Network connection completed */ - private static final int NETWORK_CONNECTION_EVENT = 36; + static final int NETWORK_CONNECTION_EVENT = 36; /* Network disconnection completed */ - private static final int NETWORK_DISCONNECTION_EVENT = 37; + static final int NETWORK_DISCONNECTION_EVENT = 37; /* Scan results are available */ - private static final int SCAN_RESULTS_EVENT = 38; + static final int SCAN_RESULTS_EVENT = 38; /* Supplicate state changed */ - private static final int SUPPLICANT_STATE_CHANGE_EVENT = 39; + static final int SUPPLICANT_STATE_CHANGE_EVENT = 39; /* Password may be incorrect */ - private static final int PASSWORD_MAY_BE_INCORRECT_EVENT = 40; + static final int PASSWORD_MAY_BE_INCORRECT_EVENT = 40; /* Supplicant commands */ /* Is supplicant alive ? */ - private static final int CMD_PING_SUPPLICANT = 51; + static final int CMD_PING_SUPPLICANT = 51; /* Add/update a network configuration */ - private static final int CMD_ADD_OR_UPDATE_NETWORK = 52; + static final int CMD_ADD_OR_UPDATE_NETWORK = 52; /* Delete a network */ - private static final int CMD_REMOVE_NETWORK = 53; + static final int CMD_REMOVE_NETWORK = 53; /* Enable a network. The device will attempt a connection to the given network. */ - private static final int CMD_ENABLE_NETWORK = 54; + static final int CMD_ENABLE_NETWORK = 54; /* Disable a network. The device does not attempt a connection to the given network. */ - private static final int CMD_DISABLE_NETWORK = 55; + static final int CMD_DISABLE_NETWORK = 55; /* Blacklist network. De-prioritizes the given BSSID for connection. */ - private static final int CMD_BLACKLIST_NETWORK = 56; + static final int CMD_BLACKLIST_NETWORK = 56; /* Clear the blacklist network list */ - private static final int CMD_CLEAR_BLACKLIST = 57; + static final int CMD_CLEAR_BLACKLIST = 57; /* Save configuration */ - private static final int CMD_SAVE_CONFIG = 58; + static final int CMD_SAVE_CONFIG = 58; /* Supplicant commands after driver start*/ /* Initiate a scan */ - private static final int CMD_START_SCAN = 71; + static final int CMD_START_SCAN = 71; /* Set scan mode. CONNECT_MODE or SCAN_ONLY_MODE */ - private static final int CMD_SET_SCAN_MODE = 72; + static final int CMD_SET_SCAN_MODE = 72; /* Set scan type. SCAN_ACTIVE or SCAN_PASSIVE */ - private static final int CMD_SET_SCAN_TYPE = 73; + static final int CMD_SET_SCAN_TYPE = 73; /* Disconnect from a network */ - private static final int CMD_DISCONNECT = 74; + static final int CMD_DISCONNECT = 74; /* Reconnect to a network */ - private static final int CMD_RECONNECT = 75; + static final int CMD_RECONNECT = 75; /* Reassociate to a network */ - private static final int CMD_REASSOCIATE = 76; + static final int CMD_REASSOCIATE = 76; /* Controls power mode and suspend mode optimizations * * When high perf mode is enabled, power mode is set to @@ -268,30 +267,30 @@ public class WifiStateMachine extends HierarchicalStateMachine { * - turn off roaming * - DTIM wake up settings */ - private static final int CMD_SET_HIGH_PERF_MODE = 77; + static final int CMD_SET_HIGH_PERF_MODE = 77; /* Set bluetooth co-existence * BLUETOOTH_COEXISTENCE_MODE_ENABLED * BLUETOOTH_COEXISTENCE_MODE_DISABLED * BLUETOOTH_COEXISTENCE_MODE_SENSE */ - private static final int CMD_SET_BLUETOOTH_COEXISTENCE = 78; + static final int CMD_SET_BLUETOOTH_COEXISTENCE = 78; /* Enable/disable bluetooth scan mode * true(1) * false(0) */ - private static final int CMD_SET_BLUETOOTH_SCAN_MODE = 79; + static final int CMD_SET_BLUETOOTH_SCAN_MODE = 79; /* Set the country code */ - private static final int CMD_SET_COUNTRY_CODE = 80; + static final int CMD_SET_COUNTRY_CODE = 80; /* Request connectivity manager wake lock before driver stop */ - private static final int CMD_REQUEST_CM_WAKELOCK = 81; + static final int CMD_REQUEST_CM_WAKELOCK = 81; /* Enables RSSI poll */ - private static final int CMD_ENABLE_RSSI_POLL = 82; + static final int CMD_ENABLE_RSSI_POLL = 82; /* RSSI poll */ - private static final int CMD_RSSI_POLL = 83; + static final int CMD_RSSI_POLL = 83; /* Set up packet filtering */ - private static final int CMD_START_PACKET_FILTERING = 84; + static final int CMD_START_PACKET_FILTERING = 84; /* Clear packet filter */ - private static final int CMD_STOP_PACKET_FILTERING = 85; + static final int CMD_STOP_PACKET_FILTERING = 85; /* Connect to a specified network (network id * or WifiConfiguration) This involves increasing * the priority of the network, enabling the network @@ -300,32 +299,29 @@ public class WifiStateMachine extends HierarchicalStateMachine { * an existing network. All the networks get enabled * upon a successful connection or a failure. */ - private static final int CMD_CONNECT_NETWORK = 86; + static final int CMD_CONNECT_NETWORK = 86; /* Save the specified network. This involves adding * an enabled network (if new) and updating the * config and issuing a save on supplicant config. */ - private static final int CMD_SAVE_NETWORK = 87; + static final int CMD_SAVE_NETWORK = 87; /* Delete the specified network. This involves * removing the network and issuing a save on * supplicant config. */ - private static final int CMD_FORGET_NETWORK = 88; + static final int CMD_FORGET_NETWORK = 88; /* Start Wi-Fi protected setup push button configuration */ - private static final int CMD_START_WPS_PBC = 89; + static final int CMD_START_WPS_PBC = 89; /* Start Wi-Fi protected setup pin method configuration with pin obtained from AP */ - private static final int CMD_START_WPS_PIN_FROM_AP = 90; + static final int CMD_START_WPS_PIN_FROM_AP = 90; /* Start Wi-Fi protected setup pin method configuration with pin obtained from device */ - private static final int CMD_START_WPS_PIN_FROM_DEVICE = 91; + static final int CMD_START_WPS_PIN_FROM_DEVICE = 91; /* Set the frequency band */ - private static final int CMD_SET_FREQUENCY_BAND = 92; + static final int CMD_SET_FREQUENCY_BAND = 92; - /** - * Interval in milliseconds between polling for connection - * status items that are not sent via asynchronous events. - * An example is RSSI (signal strength). - */ - private static final int POLL_RSSI_INTERVAL_MSECS = 3000; + /* Commands from the SupplicantStateTracker */ + /* Indicates whether a wifi network is available for connection */ + static final int CMD_SET_NETWORK_AVAILABLE = 111; private static final int CONNECT_MODE = 1; private static final int SCAN_ONLY_MODE = 2; @@ -463,7 +459,7 @@ public class WifiStateMachine extends HierarchicalStateMachine { mDhcpInfo = new DhcpInfo(); mWifiInfo = new WifiInfo(); mInterfaceName = SystemProperties.get("wifi.interface", "tiwlan0"); - mSupplicantStateTracker = new SupplicantStateTracker(context, getHandler()); + mSupplicantStateTracker = new SupplicantStateTracker(context, this, getHandler()); mLinkProperties = new LinkProperties(); BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); @@ -985,8 +981,6 @@ public class WifiStateMachine extends HierarchicalStateMachine { sb.append("mLastBssid ").append(mLastBssid).append(LS); sb.append("mLastNetworkId ").append(mLastNetworkId).append(LS); sb.append("mEnableAllNetworks ").append(mEnableAllNetworks).append(LS); - sb.append("mEnableRssiPolling ").append(mEnableRssiPolling).append(LS); - sb.append("mPasswordKeyMayBeIncorrect ").append(mPasswordKeyMayBeIncorrect).append(LS); sb.append("mReconnectCount ").append(mReconnectCount).append(LS); sb.append("mIsScanMode ").append(mIsScanMode).append(LS); sb.append("Supplicant status").append(LS) @@ -1215,6 +1209,44 @@ public class WifiStateMachine extends HierarchicalStateMachine { return null; } + /* + * Fetch RSSI and linkspeed on current connection + */ + private void fetchRssiAndLinkSpeedNative() { + int newRssi = WifiNative.getRssiCommand(); + if (newRssi != -1 && -200 < newRssi && newRssi < 256) { // screen out invalid values + /* some implementations avoid negative values by adding 256 + * so we need to adjust for that here. + */ + if (newRssi > 0) newRssi -= 256; + mWifiInfo.setRssi(newRssi); + /* + * Rather then sending the raw RSSI out every time it + * changes, we precalculate the signal level that would + * be displayed in the status bar, and only send the + * broadcast if that much more coarse-grained number + * changes. This cuts down greatly on the number of + * broadcasts, at the cost of not mWifiInforming others + * interested in RSSI of all the changes in signal + * level. + */ + // TODO: The second arg to the call below needs to be a symbol somewhere, but + // it's actually the size of an array of icons that's private + // to StatusBar Policy. + int newSignalLevel = WifiManager.calculateSignalLevel(newRssi, 4); + if (newSignalLevel != mLastSignalLevel) { + sendRssiChangeBroadcast(newRssi); + } + mLastSignalLevel = newSignalLevel; + } else { + mWifiInfo.setRssi(-200); + } + int newLinkSpeed = WifiNative.getLinkSpeedCommand(); + if (newLinkSpeed != -1) { + mWifiInfo.setLinkSpeed(newLinkSpeed); + } + } + private void setHighPerfModeEnabledNative(boolean enable) { if(!WifiNative.setSuspendOptimizationsCommand(!enable)) { Log.e(TAG, "set suspend optimizations failed!"); @@ -1336,19 +1368,6 @@ public class WifiStateMachine extends HierarchicalStateMachine { mContext.sendBroadcast(intent); } - private void sendSupplicantStateChangedBroadcast(StateChangeResult sc, boolean failedAuth) { - Intent intent = new Intent(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION); - intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT - | Intent.FLAG_RECEIVER_REPLACE_PENDING); - intent.putExtra(WifiManager.EXTRA_NEW_STATE, (Parcelable)sc.state); - if (failedAuth) { - intent.putExtra( - WifiManager.EXTRA_SUPPLICANT_ERROR, - WifiManager.ERROR_AUTHENTICATING); - } - mContext.sendStickyBroadcast(intent); - } - private void sendSupplicantConnectionChangedBroadcast(boolean connected) { if (!ActivityManagerNative.isSystemReady()) return; @@ -1370,45 +1389,6 @@ public class WifiStateMachine extends HierarchicalStateMachine { } /** - * Poll for info not reported via events - * RSSI & Linkspeed - */ - private void requestPolledInfo() { - int newRssi = WifiNative.getRssiCommand(); - if (newRssi != -1 && -200 < newRssi && newRssi < 256) { // screen out invalid values - /* some implementations avoid negative values by adding 256 - * so we need to adjust for that here. - */ - if (newRssi > 0) newRssi -= 256; - mWifiInfo.setRssi(newRssi); - /* - * Rather then sending the raw RSSI out every time it - * changes, we precalculate the signal level that would - * be displayed in the status bar, and only send the - * broadcast if that much more coarse-grained number - * changes. This cuts down greatly on the number of - * broadcasts, at the cost of not mWifiInforming others - * interested in RSSI of all the changes in signal - * level. - */ - // TODO: The second arg to the call below needs to be a symbol somewhere, but - // it's actually the size of an array of icons that's private - // to StatusBar Policy. - int newSignalLevel = WifiManager.calculateSignalLevel(newRssi, 4); - if (newSignalLevel != mLastSignalLevel) { - sendRssiChangeBroadcast(newRssi); - } - mLastSignalLevel = newSignalLevel; - } else { - mWifiInfo.setRssi(-200); - } - int newLinkSpeed = WifiNative.getLinkSpeedCommand(); - if (newLinkSpeed != -1) { - mWifiInfo.setLinkSpeed(newLinkSpeed); - } - } - - /** * Resets the Wi-Fi Connections by clearing any state, resetting any sockets * using the interface, stopping DHCP & disabling interface */ @@ -1456,7 +1436,7 @@ public class WifiStateMachine extends HierarchicalStateMachine { * WifiMonitor * thread. */ - private static class StateChangeResult { + static class StateChangeResult { StateChangeResult(int networkId, String BSSID, Object state) { this.state = state; this.BSSID = BSSID; @@ -1548,6 +1528,9 @@ public class WifiStateMachine extends HierarchicalStateMachine { setWifiEnabled(true); } + void setNetworkAvailable(boolean available) { + sendMessage(CMD_SET_NETWORK_AVAILABLE, available ? 1 : 0); + } /******************************************************** * HSM states @@ -1576,7 +1559,9 @@ public class WifiStateMachine extends HierarchicalStateMachine { break; case CMD_ENABLE_RSSI_POLL: mEnableRssiPolling = (message.arg1 == 1); - mSupplicantStateTracker.sendMessage(CMD_ENABLE_RSSI_POLL); + break; + case CMD_SET_NETWORK_AVAILABLE: + mNetworkInfo.setIsAvailable(message.arg1 == 1); break; /* Discard */ case CMD_LOAD_DRIVER: @@ -1616,6 +1601,7 @@ public class WifiStateMachine extends HierarchicalStateMachine { case CMD_FORGET_NETWORK: case CMD_START_WPS_PBC: case CMD_START_WPS_PIN_FROM_AP: + case CMD_RSSI_POLL: break; default: Log.e(TAG, "Error! unhandled message" + message); @@ -2136,6 +2122,9 @@ public class WifiStateMachine extends HierarchicalStateMachine { WifiNative.setScanModeCommand(false); } break; + case CMD_START_SCAN: + WifiNative.scanCommand(message.arg1 == SCAN_ACTIVE); + break; case CMD_SET_HIGH_PERF_MODE: setHighPerfModeEnabledNative(message.arg1 == 1); break; @@ -2278,9 +2267,6 @@ public class WifiStateMachine extends HierarchicalStateMachine { transitionTo(mDisconnectedState); } break; - case CMD_START_SCAN: - WifiNative.scanCommand(message.arg1 == SCAN_ACTIVE); - break; /* Ignore */ case CMD_DISCONNECT: case CMD_RECONNECT: @@ -2309,14 +2295,23 @@ public class WifiStateMachine extends HierarchicalStateMachine { StateChangeResult stateChangeResult; switch(message.what) { case PASSWORD_MAY_BE_INCORRECT_EVENT: - mPasswordKeyMayBeIncorrect = true; + mSupplicantStateTracker.sendMessage(PASSWORD_MAY_BE_INCORRECT_EVENT); break; case SUPPLICANT_STATE_CHANGE_EVENT: stateChangeResult = (StateChangeResult) message.obj; - mSupplicantStateTracker.handleEvent(stateChangeResult); - break; - case CMD_START_SCAN: - /* We need to set scan type in completed state */ + SupplicantState state = (SupplicantState) stateChangeResult.state; + // Supplicant state change + // [31-13] Reserved for future use + // [8 - 0] Supplicant state (as defined in SupplicantState.java) + // 50023 supplicant_state_changed (custom|1|5) + EventLog.writeEvent(EVENTLOG_SUPPLICANT_STATE_CHANGED, state.ordinal()); + mWifiInfo.setSupplicantState(state); + mWifiInfo.setNetworkId(stateChangeResult.networkId); + if (state == SupplicantState.ASSOCIATING) { + /* BSSID is valid only in ASSOCIATING state */ + mWifiInfo.setBSSID(stateChangeResult.BSSID); + } + Message newMsg = obtainMessage(); newMsg.copyFrom(message); mSupplicantStateTracker.sendMessage(newMsg); @@ -2375,7 +2370,7 @@ public class WifiStateMachine extends HierarchicalStateMachine { * Upon success, the configuration list needs to be reloaded */ if (success) { - mWpsStarted = true; + mSupplicantStateTracker.sendMessage(message.what); /* Expect a disconnection from the old connection */ transitionTo(mDisconnectingState); } @@ -2388,7 +2383,7 @@ public class WifiStateMachine extends HierarchicalStateMachine { success = WifiConfigStore.startWpsWithPinFromAccessPoint(bssid, apPin); if (success) { - mWpsStarted = true; + mSupplicantStateTracker.sendMessage(message.what); /* Expect a disconnection from the old connection */ transitionTo(mDisconnectingState); } @@ -2400,7 +2395,7 @@ public class WifiStateMachine extends HierarchicalStateMachine { mReplyChannel.replyToMessage(message, CMD_START_WPS_PIN_FROM_DEVICE, pin); if (success) { - mWpsStarted = true; + mSupplicantStateTracker.sendMessage(message.what); /* Expect a disconnection from the old connection */ transitionTo(mDisconnectingState); } @@ -2583,6 +2578,10 @@ public class WifiStateMachine extends HierarchicalStateMachine { deferMessage(message); } break; + /* Defer scan when IP is being fetched */ + case CMD_START_SCAN: + deferMessage(message); + break; case CMD_RECONFIGURE_IP: deferMessage(message); break; @@ -2619,13 +2618,11 @@ public class WifiStateMachine extends HierarchicalStateMachine { @Override public void enter() { if (DBG) Log.d(TAG, getName() + "\n"); - /* A successful WPS connection */ - if (mWpsStarted) { - WifiConfigStore.enableAllNetworks(); - WifiConfigStore.loadConfiguredNetworks(); - mWpsStarted = false; - } EventLog.writeEvent(EVENTLOG_WIFI_STATE_CHANGED, getName()); + mRssiPollToken++; + if (mEnableRssiPolling) { + sendMessage(obtainMessage(WifiStateMachine.CMD_RSSI_POLL, mRssiPollToken, 0)); + } } @Override public boolean processMessage(Message message) { @@ -2650,6 +2647,15 @@ public class WifiStateMachine extends HierarchicalStateMachine { deferMessage(message); } break; + case CMD_START_SCAN: + /* When the network is connected, re-scanning can trigger + * a reconnection. Put it in scan-only mode during scan. + * When scan results are received, the mode is switched + * back to CONNECT_MODE. + */ + WifiNative.setScanResultHandlingCommand(SCAN_ONLY_MODE); + WifiNative.scanCommand(message.arg1 == SCAN_ACTIVE); + break; /* Ignore connection to same network */ case CMD_CONNECT_NETWORK: int netId = message.arg1; @@ -2660,6 +2666,26 @@ public class WifiStateMachine extends HierarchicalStateMachine { /* Ignore */ case NETWORK_CONNECTION_EVENT: break; + case CMD_RSSI_POLL: + if (message.arg1 == mRssiPollToken) { + // Get Info and continue polling + fetchRssiAndLinkSpeedNative(); + sendMessageDelayed(obtainMessage(WifiStateMachine.CMD_RSSI_POLL, + mRssiPollToken, 0), POLL_RSSI_INTERVAL_MSECS); + } else { + // Polling has completed + } + break; + case CMD_ENABLE_RSSI_POLL: + mEnableRssiPolling = (message.arg1 == 1); + mRssiPollToken++; + if (mEnableRssiPolling) { + // first poll + fetchRssiAndLinkSpeedNative(); + sendMessageDelayed(obtainMessage(WifiStateMachine.CMD_RSSI_POLL, + mRssiPollToken, 0), POLL_RSSI_INTERVAL_MSECS); + } + break; default: return NOT_HANDLED; } @@ -2686,6 +2712,10 @@ public class WifiStateMachine extends HierarchicalStateMachine { deferMessage(message); } break; + /* Handle in DisconnectedState */ + case SUPPLICANT_STATE_CHANGE_EVENT: + deferMessage(message); + break; default: return NOT_HANDLED; } @@ -2734,6 +2764,12 @@ public class WifiStateMachine extends HierarchicalStateMachine { /* Ignore network disconnect */ case NETWORK_DISCONNECTION_EVENT: break; + case SUPPLICANT_STATE_CHANGE_EVENT: + StateChangeResult stateChangeResult = (StateChangeResult) message.obj; + SupplicantState state = (SupplicantState) stateChangeResult.state; + setDetailedState(WifiInfo.getDetailedStateOf(state)); + /* DriverStartedState does the rest of the handling */ + return NOT_HANDLED; default: return NOT_HANDLED; } @@ -2795,375 +2831,4 @@ public class WifiStateMachine extends HierarchicalStateMachine { return HANDLED; } } - - - class SupplicantStateTracker extends HierarchicalStateMachine { - - private int mRssiPollToken = 0; - - /** - * The max number of the WPA supplicant loop iterations before we - * decide that the loop should be terminated: - */ - private static final int MAX_SUPPLICANT_LOOP_ITERATIONS = 4; - private int mLoopDetectIndex = 0; - private int mLoopDetectCount = 0; - - /** - * Supplicant state change commands follow - * the ordinal values defined in SupplicantState.java - */ - private static final int DISCONNECTED = 0; - private static final int INACTIVE = 1; - private static final int SCANNING = 2; - private static final int ASSOCIATING = 3; - private static final int ASSOCIATED = 4; - private static final int FOUR_WAY_HANDSHAKE = 5; - private static final int GROUP_HANDSHAKE = 6; - private static final int COMPLETED = 7; - private static final int DORMANT = 8; - private static final int UNINITIALIZED = 9; - private static final int INVALID = 10; - - private HierarchicalState mUninitializedState = new UninitializedState(); - private HierarchicalState mInitializedState = new InitializedState();; - private HierarchicalState mInactiveState = new InactiveState(); - private HierarchicalState mDisconnectState = new DisconnectedState(); - private HierarchicalState mScanState = new ScanState(); - private HierarchicalState mConnectState = new ConnectState(); - private HierarchicalState mHandshakeState = new HandshakeState(); - private HierarchicalState mCompletedState = new CompletedState(); - private HierarchicalState mDormantState = new DormantState(); - - public SupplicantStateTracker(Context context, Handler target) { - super(TAG, target.getLooper()); - - addState(mUninitializedState); - addState(mInitializedState); - addState(mInactiveState, mInitializedState); - addState(mDisconnectState, mInitializedState); - addState(mScanState, mInitializedState); - addState(mConnectState, mInitializedState); - addState(mHandshakeState, mConnectState); - addState(mCompletedState, mConnectState); - addState(mDormantState, mInitializedState); - - setInitialState(mUninitializedState); - - //start the state machine - start(); - } - - public void handleEvent(StateChangeResult stateChangeResult) { - SupplicantState newState = (SupplicantState) stateChangeResult.state; - - // Supplicant state change - // [31-13] Reserved for future use - // [8 - 0] Supplicant state (as defined in SupplicantState.java) - // 50023 supplicant_state_changed (custom|1|5) - EventLog.writeEvent(EVENTLOG_SUPPLICANT_STATE_CHANGED, newState.ordinal()); - - sendMessage(obtainMessage(newState.ordinal(), stateChangeResult)); - } - - public void resetSupplicantState() { - transitionTo(mUninitializedState); - } - - private void resetLoopDetection() { - mLoopDetectCount = 0; - mLoopDetectIndex = 0; - } - - private boolean handleTransition(Message msg) { - if (DBG) Log.d(TAG, getName() + msg.toString() + "\n"); - switch (msg.what) { - case DISCONNECTED: - transitionTo(mDisconnectState); - break; - case SCANNING: - transitionTo(mScanState); - break; - case ASSOCIATING: - StateChangeResult stateChangeResult = (StateChangeResult) msg.obj; - /* BSSID is valid only in ASSOCIATING state */ - mWifiInfo.setBSSID(stateChangeResult.BSSID); - //$FALL-THROUGH$ - case ASSOCIATED: - case FOUR_WAY_HANDSHAKE: - case GROUP_HANDSHAKE: - transitionTo(mHandshakeState); - break; - case COMPLETED: - transitionTo(mCompletedState); - break; - case DORMANT: - transitionTo(mDormantState); - break; - case INACTIVE: - transitionTo(mInactiveState); - break; - case UNINITIALIZED: - case INVALID: - transitionTo(mUninitializedState); - break; - default: - return NOT_HANDLED; - } - StateChangeResult stateChangeResult = (StateChangeResult) msg.obj; - SupplicantState supState = (SupplicantState) stateChangeResult.state; - setDetailedState(WifiInfo.getDetailedStateOf(supState)); - mWifiInfo.setSupplicantState(supState); - mWifiInfo.setNetworkId(stateChangeResult.networkId); - return HANDLED; - } - - /******************************************************** - * HSM states - *******************************************************/ - - class InitializedState extends HierarchicalState { - @Override - public void enter() { - if (DBG) Log.d(TAG, getName() + "\n"); - } - @Override - public boolean processMessage(Message message) { - if (DBG) Log.d(TAG, getName() + message.toString() + "\n"); - switch (message.what) { - case CMD_START_SCAN: - WifiNative.scanCommand(message.arg1 == SCAN_ACTIVE); - break; - default: - if (DBG) Log.w(TAG, "Ignoring " + message); - break; - } - return HANDLED; - } - } - - class UninitializedState extends HierarchicalState { - @Override - public void enter() { - if (DBG) Log.d(TAG, getName() + "\n"); - mNetworkInfo.setIsAvailable(false); - resetLoopDetection(); - mPasswordKeyMayBeIncorrect = false; - } - @Override - public boolean processMessage(Message message) { - switch(message.what) { - default: - if (!handleTransition(message)) { - if (DBG) Log.w(TAG, "Ignoring " + message); - } - break; - } - return HANDLED; - } - @Override - public void exit() { - mNetworkInfo.setIsAvailable(true); - } - } - - class InactiveState extends HierarchicalState { - @Override - public void enter() { - if (DBG) Log.d(TAG, getName() + "\n"); - - /* A failed WPS connection */ - if (mWpsStarted) { - Log.e(TAG, "WPS set up failed, enabling other networks"); - WifiConfigStore.enableAllNetworks(); - mWpsStarted = false; - } - - Message message = getCurrentMessage(); - StateChangeResult stateChangeResult = (StateChangeResult) message.obj; - - mNetworkInfo.setIsAvailable(false); - resetLoopDetection(); - mPasswordKeyMayBeIncorrect = false; - - sendSupplicantStateChangedBroadcast(stateChangeResult, false); - } - @Override - public boolean processMessage(Message message) { - return handleTransition(message); - } - @Override - public void exit() { - mNetworkInfo.setIsAvailable(true); - } - } - - - class DisconnectedState extends HierarchicalState { - @Override - public void enter() { - if (DBG) Log.d(TAG, getName() + "\n"); - Message message = getCurrentMessage(); - StateChangeResult stateChangeResult = (StateChangeResult) message.obj; - - resetLoopDetection(); - - /* If a disconnect event happens after a password key failure - * event, disable the network - */ - if (mPasswordKeyMayBeIncorrect) { - Log.d(TAG, "Failed to authenticate, disabling network " + - mWifiInfo.getNetworkId()); - WifiConfigStore.disableNetwork(mWifiInfo.getNetworkId()); - mPasswordKeyMayBeIncorrect = false; - sendSupplicantStateChangedBroadcast(stateChangeResult, true); - } - else { - sendSupplicantStateChangedBroadcast(stateChangeResult, false); - } - } - @Override - public boolean processMessage(Message message) { - return handleTransition(message); - } - } - - class ScanState extends HierarchicalState { - @Override - public void enter() { - if (DBG) Log.d(TAG, getName() + "\n"); - Message message = getCurrentMessage(); - StateChangeResult stateChangeResult = (StateChangeResult) message.obj; - - mPasswordKeyMayBeIncorrect = false; - resetLoopDetection(); - sendSupplicantStateChangedBroadcast(stateChangeResult, false); - } - @Override - public boolean processMessage(Message message) { - return handleTransition(message); - } - } - - class ConnectState extends HierarchicalState { - @Override - public void enter() { - if (DBG) Log.d(TAG, getName() + "\n"); - } - @Override - public boolean processMessage(Message message) { - switch (message.what) { - case CMD_START_SCAN: - WifiNative.setScanResultHandlingCommand(SCAN_ONLY_MODE); - WifiNative.scanCommand(message.arg1 == SCAN_ACTIVE); - break; - default: - return NOT_HANDLED; - } - return HANDLED; - } - } - - class HandshakeState extends HierarchicalState { - @Override - public void enter() { - if (DBG) Log.d(TAG, getName() + "\n"); - final Message message = getCurrentMessage(); - StateChangeResult stateChangeResult = (StateChangeResult) message.obj; - - if (mLoopDetectIndex > message.what) { - mLoopDetectCount++; - } - if (mLoopDetectCount > MAX_SUPPLICANT_LOOP_ITERATIONS) { - WifiConfigStore.disableNetwork(stateChangeResult.networkId); - mLoopDetectCount = 0; - } - - mLoopDetectIndex = message.what; - - mPasswordKeyMayBeIncorrect = false; - sendSupplicantStateChangedBroadcast(stateChangeResult, false); - } - @Override - public boolean processMessage(Message message) { - return handleTransition(message); - } - } - - class CompletedState extends HierarchicalState { - @Override - public void enter() { - if (DBG) Log.d(TAG, getName() + "\n"); - Message message = getCurrentMessage(); - StateChangeResult stateChangeResult = (StateChangeResult) message.obj; - - mRssiPollToken++; - if (mEnableRssiPolling) { - sendMessageDelayed(obtainMessage(CMD_RSSI_POLL, mRssiPollToken, 0), - POLL_RSSI_INTERVAL_MSECS); - } - - resetLoopDetection(); - - mPasswordKeyMayBeIncorrect = false; - sendSupplicantStateChangedBroadcast(stateChangeResult, false); - } - @Override - public boolean processMessage(Message message) { - switch(message.what) { - case ASSOCIATING: - case ASSOCIATED: - case FOUR_WAY_HANDSHAKE: - case GROUP_HANDSHAKE: - case COMPLETED: - break; - case CMD_RSSI_POLL: - if (message.arg1 == mRssiPollToken) { - // Get Info and continue polling - requestPolledInfo(); - sendMessageDelayed(obtainMessage(CMD_RSSI_POLL, mRssiPollToken, 0), - POLL_RSSI_INTERVAL_MSECS); - } else { - // Polling has completed - } - break; - case CMD_ENABLE_RSSI_POLL: - mRssiPollToken++; - if (mEnableRssiPolling) { - // first poll - requestPolledInfo(); - sendMessageDelayed(obtainMessage(CMD_RSSI_POLL, mRssiPollToken, 0), - POLL_RSSI_INTERVAL_MSECS); - } - break; - default: - return handleTransition(message); - } - return HANDLED; - } - } - - class DormantState extends HierarchicalState { - @Override - public void enter() { - if (DBG) Log.d(TAG, getName() + "\n"); - Message message = getCurrentMessage(); - StateChangeResult stateChangeResult = (StateChangeResult) message.obj; - - resetLoopDetection(); - mPasswordKeyMayBeIncorrect = false; - - sendSupplicantStateChangedBroadcast(stateChangeResult, false); - - /* TODO: reconnect is now being handled at DHCP failure handling - * If we run into issues with staying in Dormant state, might - * need a reconnect here - */ - } - @Override - public boolean processMessage(Message message) { - return handleTransition(message); - } - } - } } |