diff options
99 files changed, 3719 insertions, 1008 deletions
diff --git a/api/current.txt b/api/current.txt index 3132a9188f5d..16681702b928 100644 --- a/api/current.txt +++ b/api/current.txt @@ -5919,7 +5919,6 @@ package android.content { field public static final java.lang.String ACTION_USER_INITIALIZE = "android.intent.action.USER_INITIALIZE"; field public static final java.lang.String ACTION_USER_PRESENT = "android.intent.action.USER_PRESENT"; field public static final java.lang.String ACTION_VIEW = "android.intent.action.VIEW"; - field public static final java.lang.String ACTION_VOICE_ASSIST = "android.intent.action.VOICE_ASSIST"; field public static final java.lang.String ACTION_VOICE_COMMAND = "android.intent.action.VOICE_COMMAND"; field public static final deprecated java.lang.String ACTION_WALLPAPER_CHANGED = "android.intent.action.WALLPAPER_CHANGED"; field public static final java.lang.String ACTION_WEB_SEARCH = "android.intent.action.WEB_SEARCH"; diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java index 9fa7dbb7e91d..1c02960deeca 100644 --- a/cmds/am/src/com/android/commands/am/Am.java +++ b/cmds/am/src/com/android/commands/am/Am.java @@ -63,6 +63,7 @@ public class Am { private int mRepeat = 0; private int mUserId; + private String mReceiverPermission; private String mProfileFile; @@ -332,6 +333,8 @@ public class Am { mStartFlags |= ActivityManager.START_FLAG_OPENGL_TRACES; } else if (opt.equals("--user")) { mUserId = parseUserArg(nextArgRequired()); + } else if (opt.equals("--receiver-permission")) { + mReceiverPermission = nextArgRequired(); } else { System.err.println("Error: Unknown option: " + opt); return null; @@ -608,7 +611,7 @@ public class Am { Intent intent = makeIntent(UserHandle.USER_ALL); IntentReceiver receiver = new IntentReceiver(); System.out.println("Broadcasting: " + intent); - mAm.broadcastIntent(null, intent, null, receiver, 0, null, null, null, + mAm.broadcastIntent(null, intent, null, receiver, 0, null, null, mReceiverPermission, android.app.AppOpsManager.OP_NONE, true, false, mUserId); receiver.waitForFinish(); } @@ -1408,6 +1411,7 @@ public class Am { "am broadcast: send a broadcast Intent. Options are:\n" + " --user <USER_ID> | all | current: Specify which user to send to; if not\n" + " specified then send to all users.\n" + + " --receiver-permission <PERMISSION>: Require receiver to hold permission.\n" + "\n" + "am instrument: start an Instrumentation. Typically this target <COMPONENT>\n" + " is the form <TEST_PACKAGE>/<RUNNER_CLASS>. Options are:\n" + diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java index 6aac72332b1f..f8b7a0c949be 100644 --- a/core/java/android/accounts/AccountManager.java +++ b/core/java/android/accounts/AccountManager.java @@ -18,9 +18,11 @@ package android.accounts; import android.app.Activity; import android.content.Intent; +import android.content.ComponentName; import android.content.Context; import android.content.IntentFilter; import android.content.BroadcastReceiver; +import android.content.res.Resources; import android.database.SQLException; import android.os.Bundle; import android.os.Handler; @@ -44,6 +46,7 @@ import java.util.concurrent.TimeUnit; import java.util.HashMap; import java.util.Map; +import com.android.internal.R; import com.google.android.collect.Maps; /** @@ -1777,8 +1780,11 @@ public class AccountManager { }; // have many accounts, launch the chooser Intent intent = new Intent(); - intent.setClassName("android", - "android.accounts.ChooseAccountActivity"); + ComponentName componentName = ComponentName.unflattenFromString( + Resources.getSystem().getString( + R.string.config_chooseAccountActivity)); + intent.setClassName(componentName.getPackageName(), + componentName.getClassName()); intent.putExtra(KEY_ACCOUNTS, accounts); intent.putExtra(KEY_ACCOUNT_MANAGER_RESPONSE, new AccountManagerResponse(chooseResponse)); @@ -1934,7 +1940,10 @@ public class AccountManager { String[] addAccountRequiredFeatures, Bundle addAccountOptions) { Intent intent = new Intent(); - intent.setClassName("android", "android.accounts.ChooseTypeAndAccountActivity"); + ComponentName componentName = ComponentName.unflattenFromString( + Resources.getSystem().getString(R.string.config_chooseTypeAndAccountActivity)); + intent.setClassName(componentName.getPackageName(), + componentName.getClassName()); intent.putExtra(ChooseTypeAndAccountActivity.EXTRA_ALLOWABLE_ACCOUNTS_ARRAYLIST, allowableAccounts); intent.putExtra(ChooseTypeAndAccountActivity.EXTRA_ALLOWABLE_ACCOUNT_TYPES_STRING_ARRAY, diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java index 37804e9103a5..20114cc52802 100644 --- a/core/java/android/app/PendingIntent.java +++ b/core/java/android/app/PendingIntent.java @@ -261,6 +261,7 @@ public final class PendingIntent implements Parcelable { context.getContentResolver()) : null; try { intent.setAllowFds(false); + intent.migrateExtraStreamToClipData(); IIntentSender target = ActivityManagerNative.getDefault().getIntentSender( ActivityManager.INTENT_SENDER_ACTIVITY, packageName, @@ -285,6 +286,7 @@ public final class PendingIntent implements Parcelable { context.getContentResolver()) : null; try { intent.setAllowFds(false); + intent.migrateExtraStreamToClipData(); IIntentSender target = ActivityManagerNative.getDefault().getIntentSender( ActivityManager.INTENT_SENDER_ACTIVITY, packageName, diff --git a/core/java/android/bluetooth/BluetoothGattCharacteristic.java b/core/java/android/bluetooth/BluetoothGattCharacteristic.java index 18492ab8ff6c..f44dc5c08bb9 100644 --- a/core/java/android/bluetooth/BluetoothGattCharacteristic.java +++ b/core/java/android/bluetooth/BluetoothGattCharacteristic.java @@ -529,7 +529,8 @@ public class BluetoothGattCharacteristic { case FORMAT_UINT32: mValue[offset++] = (byte)(value & 0xFF); mValue[offset++] = (byte)((value >> 8) & 0xFF); - mValue[offset] = (byte)((value >> 16) & 0xFF); + mValue[offset++] = (byte)((value >> 16) & 0xFF); + mValue[offset] = (byte)((value >> 24) & 0xFF); break; default: diff --git a/core/java/android/bluetooth/BluetoothGattServer.java b/core/java/android/bluetooth/BluetoothGattServer.java index 91a1a9443f8f..6b693779153e 100644 --- a/core/java/android/bluetooth/BluetoothGattServer.java +++ b/core/java/android/bluetooth/BluetoothGattServer.java @@ -534,7 +534,7 @@ public final class BluetoothGattServer implements BluetoothProfile { * * <p>The connection may not be established right away, but will be * completed when the remote device is available. A - * {@link BluetoothGattCallback#onConnectionStateChange} callback will be + * {@link BluetoothGattServerCallback#onConnectionStateChange} callback will be * invoked when the connection state changes as a result of this function. * * <p>The autoConnect paramter determines whether to actively connect to @@ -553,7 +553,7 @@ public final class BluetoothGattServer implements BluetoothProfile { * @return true, if the connection attempt was initiated successfully */ public boolean connect(BluetoothDevice device, boolean autoConnect) { - if (DBG) Log.d(TAG, "connect: " + device.getAddress() + ", auto: " + autoConnect); + if (DBG) Log.d(TAG, "connect() - device: " + device.getAddress() + ", auto: " + autoConnect); if (mService == null || mServerIf == 0) return false; try { diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java index 8aef4051cfba..496826888fad 100644 --- a/core/java/android/content/ContentProvider.java +++ b/core/java/android/content/ContentProvider.java @@ -1256,6 +1256,13 @@ public abstract class ContentProvider implements ComponentCallbacks2 { * interfaces that are cheaper and/or unnatural for a table-like * model. * + * <p class="note"><strong>WARNING:</strong> The framework does no permission checking + * on this entry into the content provider besides the basic ability for the application + * to get access to the provider at all. For example, it has no idea whether the call + * being executed may read or write data in the provider, so can't enforce those + * individual permissions. Any implementation of this method <strong>must</strong> + * do its own permission checks on incoming calls to make sure they are allowed.</p> + * * @param method method name to call. Opaque to framework, but should not be {@code null}. * @param arg provider-defined String argument. May be {@code null}. * @param extras provider-defined Bundle argument. May be {@code null}. diff --git a/core/java/android/content/CursorLoader.java b/core/java/android/content/CursorLoader.java index 4e89dec9f02c..5d7d67743a7d 100644 --- a/core/java/android/content/CursorLoader.java +++ b/core/java/android/content/CursorLoader.java @@ -68,7 +68,7 @@ public class CursorLoader extends AsyncTaskLoader<Cursor> { try { // Ensure the cursor window is filled. cursor.getCount(); - registerContentObserver(cursor, mObserver); + cursor.registerContentObserver(mObserver); } catch (RuntimeException ex) { cursor.close(); throw ex; @@ -93,14 +93,6 @@ public class CursorLoader extends AsyncTaskLoader<Cursor> { } } - /** - * Registers an observer to get notifications from the content provider - * when the cursor needs to be refreshed. - */ - void registerContentObserver(Cursor cursor, ContentObserver observer) { - cursor.registerContentObserver(mObserver); - } - /* Runs on the UI thread */ @Override public void deliverResult(Cursor cursor) { diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 60e9f58b171b..53c47d2abe9d 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -1164,12 +1164,13 @@ public class Intent implements Parcelable, Cloneable { * additional optional contextual information about where the user was when they requested * the voice assist. * Output: nothing. + * @hide */ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String ACTION_VOICE_ASSIST = "android.intent.action.VOICE_ASSIST"; /** - * An optional field on {@link #ACTION_ASSIST} and {@link #ACTION_VOICE_ASSIST} + * An optional field on {@link #ACTION_ASSIST} * containing the name of the current foreground application package at the time * the assist was invoked. */ @@ -1177,7 +1178,7 @@ public class Intent implements Parcelable, Cloneable { = "android.intent.extra.ASSIST_PACKAGE"; /** - * An optional field on {@link #ACTION_ASSIST} and {@link #ACTION_VOICE_ASSIST} + * An optional field on {@link #ACTION_ASSIST} * containing additional contextual information supplied by the current * foreground app at the time of the assist request. This is a {@link Bundle} of * additional data. diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index c50724505927..0d463ee6d3ec 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -691,6 +691,17 @@ public abstract class PackageManager { public static final int DELETE_ALL_USERS = 0x00000002; /** + * Flag parameter for {@link #deletePackage} to indicate that, if you are calling + * uninstall on a system that has been updated, then don't do the normal process + * of uninstalling the update and rolling back to the older system version (which + * needs to happen for all users); instead, just mark the app as uninstalled for + * the current user. + * + * @hide + */ + public static final int DELETE_SYSTEM_APP = 0x00000004; + + /** * Return code for when package deletion succeeds. This is passed to the * {@link IPackageDeleteObserver} by {@link #deletePackage()} if the system * succeeded in deleting the package. diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index e1887bc64452..5eac90373d17 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -1941,6 +1941,28 @@ public class PackageParser { return false; } + } else if (tagName.equals("library")) { + sa = res.obtainAttributes(attrs, + com.android.internal.R.styleable.AndroidManifestLibrary); + + // Note: don't allow this value to be a reference to a resource + // that may change. + String lname = sa.getNonResourceString( + com.android.internal.R.styleable.AndroidManifestLibrary_name); + + sa.recycle(); + + if (lname != null) { + if (owner.libraryNames == null) { + owner.libraryNames = new ArrayList<String>(); + } + if (!owner.libraryNames.contains(lname)) { + owner.libraryNames.add(lname.intern()); + } + } + + XmlUtils.skipCurrentTag(parser); + } else if (tagName.equals("uses-library")) { sa = res.obtainAttributes(attrs, com.android.internal.R.styleable.AndroidManifestUsesLibrary); @@ -3182,7 +3204,8 @@ public class PackageParser { public final ArrayList<Boolean> requestedPermissionsRequired = new ArrayList<Boolean>(); public ArrayList<String> protectedBroadcasts; - + + public ArrayList<String> libraryNames = null; public ArrayList<String> usesLibraries = null; public ArrayList<String> usesOptionalLibraries = null; public String[] usesLibraryFiles = null; diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java index 2d67875c6084..d59c7b819df3 100644 --- a/core/java/android/inputmethodservice/IInputMethodWrapper.java +++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java @@ -284,6 +284,12 @@ class IInputMethodWrapper extends IInputMethod.Stub flags, resultReceiver)); } + @Override + public void removeSoftInputMessages() { + mCaller.removeMessages(DO_SHOW_SOFT_INPUT); + mCaller.removeMessages(DO_HIDE_SOFT_INPUT); + } + public void changeInputMethodSubtype(InputMethodSubtype subtype) { mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_CHANGE_INPUTMETHOD_SUBTYPE, subtype)); diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index 99624cc817fc..288cefff55df 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -39,6 +39,7 @@ import android.text.method.MovementMethod; import android.util.Log; import android.util.PrintWriterPrinter; import android.util.Printer; +import android.util.Slog; import android.view.KeyCharacterMap; import android.view.KeyEvent; import android.view.LayoutInflater; @@ -351,6 +352,7 @@ public class InputMethodService extends AbstractInputMethodService { * Take care of attaching the given window token provided by the system. */ public void attachToken(IBinder token) { + Slog.i(TAG, "attachToken: Existing token=" + mToken + " new token=" + token); if (mToken == null) { mToken = token; mWindow.setToken(token); @@ -417,7 +419,7 @@ public class InputMethodService extends AbstractInputMethodService { * Handle a request by the system to show the soft input area. */ public void showSoftInput(int flags, ResultReceiver resultReceiver) { - if (DEBUG) Log.v(TAG, "showSoftInput()"); + if (true || DEBUG) Slog.v(TAG, "showSoftInput()"); boolean wasVis = isInputViewShown(); mShowInputFlags = 0; if (onShowInputRequested(flags, false)) { @@ -1388,7 +1390,7 @@ public class InputMethodService extends AbstractInputMethodService { } public void showWindow(boolean showInput) { - if (DEBUG) Log.v(TAG, "Showing window: showInput=" + showInput + if (true || DEBUG) Slog.v(TAG, "Showing window: showInput=" + showInput + " mShowInputRequested=" + mShowInputRequested + " mWindowAdded=" + mWindowAdded + " mWindowCreated=" + mWindowCreated diff --git a/core/java/android/net/DhcpStateMachine.java b/core/java/android/net/DhcpStateMachine.java index fd22b10d3e4f..518dd4b7bcca 100644 --- a/core/java/android/net/DhcpStateMachine.java +++ b/core/java/android/net/DhcpStateMachine.java @@ -50,7 +50,7 @@ import android.util.Log; public class DhcpStateMachine extends StateMachine { private static final String TAG = "DhcpStateMachine"; - private static final boolean DBG = false; + private static final boolean DBG = true; /* A StateMachine that controls the DhcpStateMachine */ @@ -77,7 +77,7 @@ public class DhcpStateMachine extends StateMachine { RENEW }; - private String mInterfaceName; + private final String mInterfaceName; private boolean mRegisteredForPreDhcpNotification = false; private static final int BASE = Protocol.BASE_DHCP; @@ -349,6 +349,7 @@ public class DhcpStateMachine extends StateMachine { private boolean runDhcp(DhcpAction dhcpAction) { boolean success = false; DhcpResults dhcpResults = new DhcpResults(); + dhcpResults.linkProperties.mLogMe = true; if (dhcpAction == DhcpAction.START) { /* Stop any existing DHCP daemon before starting new */ diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java index ec8d77e75788..9292e5f63888 100644 --- a/core/java/android/net/LinkProperties.java +++ b/core/java/android/net/LinkProperties.java @@ -20,6 +20,7 @@ import android.net.ProxyProperties; import android.os.Parcelable; import android.os.Parcel; import android.text.TextUtils; +import android.util.Log; import java.net.InetAddress; import java.net.UnknownHostException; @@ -57,6 +58,7 @@ public class LinkProperties implements Parcelable { private String mDomains; private Collection<RouteInfo> mRoutes = new ArrayList<RouteInfo>(); private ProxyProperties mHttpProxy; + public boolean mLogMe; public static class CompareResult<T> { public Collection<T> removed = new ArrayList<T>(); @@ -75,6 +77,7 @@ public class LinkProperties implements Parcelable { public LinkProperties() { clear(); + mLogMe = false; } // copy constructor instead of clone @@ -91,6 +94,14 @@ public class LinkProperties implements Parcelable { } public void setInterfaceName(String iface) { + if (mLogMe) { + Log.d("LinkProperties", "setInterfaceName from " + mIfaceName + + " to " + iface); + for (StackTraceElement e : Thread.currentThread().getStackTrace()) { + Log.d("LinkProperties", " " + e.toString()); + } + } + mIfaceName = iface; ArrayList<RouteInfo> newRoutes = new ArrayList<RouteInfo>(mRoutes.size()); for (RouteInfo route : mRoutes) { @@ -146,9 +157,9 @@ public class LinkProperties implements Parcelable { if (route != null) { String routeIface = route.getInterface(); if (routeIface != null && !routeIface.equals(mIfaceName)) { - throw new IllegalStateException( + throw new IllegalArgumentException( "Route added with non-matching interface: " + routeIface + - " vs. mIfaceName"); + " vs. " + mIfaceName); } mRoutes.add(routeWithInterface(route)); } @@ -166,6 +177,13 @@ public class LinkProperties implements Parcelable { } public void clear() { + if (mLogMe) { + Log.d("LinkProperties", "clear from " + mIfaceName); + for (StackTraceElement e : Thread.currentThread().getStackTrace()) { + Log.d("LinkProperties", " " + e.toString()); + } + } + mIfaceName = null; mLinkAddresses.clear(); mDnses.clear(); @@ -370,7 +388,7 @@ public class LinkProperties implements Parcelable { public CompareResult<RouteInfo> compareRoutes(LinkProperties target) { /* * Duplicate the RouteInfos into removed, we will be removing - * routes which are common between mDnses and target + * routes which are common between mRoutes and target * leaving the routes that are different. And route address which * are in target but not in mRoutes are placed in added. */ diff --git a/core/java/android/net/SSLCertificateSocketFactory.java b/core/java/android/net/SSLCertificateSocketFactory.java index c0a894b6e66e..2a2f7cf81f84 100644 --- a/core/java/android/net/SSLCertificateSocketFactory.java +++ b/core/java/android/net/SSLCertificateSocketFactory.java @@ -23,8 +23,8 @@ import java.net.InetAddress; import java.net.Socket; import java.net.SocketException; import java.security.KeyManagementException; +import java.security.PrivateKey; import java.security.cert.X509Certificate; -import java.security.interfaces.ECPrivateKey; import javax.net.SocketFactory; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HttpsURLConnection; @@ -89,7 +89,7 @@ public class SSLCertificateSocketFactory extends SSLSocketFactory { private TrustManager[] mTrustManagers = null; private KeyManager[] mKeyManagers = null; private byte[] mNpnProtocols = null; - private ECPrivateKey mChannelIdPrivateKey = null; + private PrivateKey mChannelIdPrivateKey = null; private final int mHandshakeTimeoutMillis; private final SSLClientSessionCache mSessionCache; @@ -321,7 +321,7 @@ public class SSLCertificateSocketFactory extends SSLSocketFactory { } /** - * Sets the {@link ECPrivateKey} to be used for TLS Channel ID by connections made by this + * Sets the private key to be used for TLS Channel ID by connections made by this * factory. * * @param privateKey private key (enables TLS Channel ID) or {@code null} for no key (disables @@ -330,7 +330,7 @@ public class SSLCertificateSocketFactory extends SSLSocketFactory { * * @hide */ - public void setChannelIdPrivateKey(ECPrivateKey privateKey) { + public void setChannelIdPrivateKey(PrivateKey privateKey) { mChannelIdPrivateKey = privateKey; } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 266d0d3e1d2a..d251ca25e068 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -4784,6 +4784,13 @@ public final class Settings { public static final String WIFI_ON = "wifi_on"; /** + * Setting to allow scans to be enabled even wifi is turned off for connectivity. + * @hide + */ + public static final String WIFI_SCAN_ALWAYS_AVAILABLE = + "wifi_scan_always_enabled"; + + /** * Used to save the Wifi_ON state prior to tethering. * This state will be checked to restore Wifi after * the user turns off tethering. @@ -5345,6 +5352,7 @@ public final class Settings { WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, WIFI_NETWORKS_AVAILABLE_REPEAT_DELAY, WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED, + WIFI_SCAN_ALWAYS_AVAILABLE, WIFI_NUM_OPEN_NETWORKS_KEPT, EMERGENCY_TONE, CALL_AUTO_RETRY, diff --git a/core/java/android/speech/tts/TextToSpeechService.java b/core/java/android/speech/tts/TextToSpeechService.java index 4379418d00a7..703dcff1688c 100644 --- a/core/java/android/speech/tts/TextToSpeechService.java +++ b/core/java/android/speech/tts/TextToSpeechService.java @@ -810,9 +810,15 @@ public abstract class TextToSpeechService extends Service { return TextToSpeech.ERROR; } + // In test env, ParcelFileDescriptor instance may be EXACTLY the same + // one that is used by client. And it will be closed by a client, thus + // preventing us from writing anything to it. + final ParcelFileDescriptor sameFileDescriptor = ParcelFileDescriptor.adoptFd( + fileDescriptor.detachFd()); + SpeechItem item = new SynthesisToFileOutputStreamSpeechItem(caller, Binder.getCallingUid(), Binder.getCallingPid(), params, text, - new ParcelFileDescriptor.AutoCloseOutputStream(fileDescriptor)); + new ParcelFileDescriptor.AutoCloseOutputStream(sameFileDescriptor)); return mSynthHandler.enqueueSpeechItem(TextToSpeech.QUEUE_ADD, item); } diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java index 9955bc18bc21..0492d2969551 100644 --- a/core/java/android/view/Surface.java +++ b/core/java/android/view/Surface.java @@ -39,7 +39,6 @@ public class Surface implements Parcelable { private native void nativeUnlockCanvasAndPost(int nativeObject, Canvas canvas); private static native void nativeRelease(int nativeObject); - private static native void nativeDestroy(int nativeObject); private static native boolean nativeIsValid(int nativeObject); private static native boolean nativeIsConsumerRunningBehind(int nativeObject); private static native int nativeCopyFrom(int nativeObject, int surfaceControlNativeObject); @@ -106,7 +105,6 @@ public class Surface implements Parcelable { * @hide */ public Surface() { - mCloseGuard.open("release"); } /** @@ -135,6 +133,7 @@ public class Surface implements Parcelable { mCloseGuard.open("release"); } + /* called from android_view_Surface_createFromIGraphicBufferProducer() */ private Surface(int nativeObject) { mNativeObject = nativeObject; mCloseGuard.open("release"); @@ -146,9 +145,7 @@ public class Surface implements Parcelable { if (mCloseGuard != null) { mCloseGuard.warnIfOpen(); } - if (mNativeObject != 0) { - nativeRelease(mNativeObject); - } + release(); } finally { super.finalize(); } @@ -175,12 +172,7 @@ public class Surface implements Parcelable { * @hide */ public void destroy() { - if (mNativeObject != 0) { - nativeDestroy(mNativeObject); - mNativeObject = 0; - mGenerationId++; - } - mCloseGuard.close(); + release(); } /** @@ -287,6 +279,10 @@ public class Surface implements Parcelable { "SurfaceControl native object is null. Are you using a released SurfaceControl?"); } mNativeObject = nativeCopyFrom(mNativeObject, other.mNativeObject); + if (mNativeObject == 0) { + // nativeCopyFrom released our reference + mCloseGuard.close(); + } mGenerationId++; } @@ -308,11 +304,15 @@ public class Surface implements Parcelable { nativeRelease(mNativeObject); } // transfer the reference from other to us + if (other.mNativeObject != 0 && mNativeObject == 0) { + mCloseGuard.open("release"); + } mNativeObject = other.mNativeObject; mGenerationId++; other.mNativeObject = 0; other.mGenerationId++; + other.mCloseGuard.close(); } } @@ -326,7 +326,11 @@ public class Surface implements Parcelable { throw new IllegalArgumentException("source must not be null"); } mName = source.readString(); - mNativeObject = nativeReadFromParcel(mNativeObject, source); + int nativeObject = nativeReadFromParcel(mNativeObject, source); + if (nativeObject !=0 && mNativeObject == 0) { + mCloseGuard.open("release"); + } + mNativeObject = nativeObject; mGenerationId++; } diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 792188bb501a..96ef0b41ef69 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -272,7 +272,7 @@ public interface WindowManager extends ViewManager { public static final int TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW; /** - * Window type: window for showing media (e.g. video). These windows + * Window type: window for showing media (such as video). These windows * are displayed behind their attached window. */ public static final int TYPE_APPLICATION_MEDIA = FIRST_SUB_WINDOW+1; @@ -584,14 +584,14 @@ public interface WindowManager extends ViewManager { /** Window flag: this window can never receive touch events. */ public static final int FLAG_NOT_TOUCHABLE = 0x00000010; - /** Window flag: Even when this window is focusable (its - * {@link #FLAG_NOT_FOCUSABLE is not set), allow any pointer events + /** Window flag: even when this window is focusable (its + * {@link #FLAG_NOT_FOCUSABLE} is not set), allow any pointer events * outside of the window to be sent to the windows behind it. Otherwise * it will consume all pointer events itself, regardless of whether they * are inside of the window. */ public static final int FLAG_NOT_TOUCH_MODAL = 0x00000020; - /** Window flag: When set, if the device is asleep when the touch + /** Window flag: when set, if the device is asleep when the touch * screen is pressed, you will receive this first touch event. Usually * the first touch event is consumed by the system since the user can * not see what they are pressing on. @@ -603,7 +603,7 @@ public interface WindowManager extends ViewManager { public static final int FLAG_KEEP_SCREEN_ON = 0x00000080; /** Window flag: place the window within the entire screen, ignoring - * decorations around the border (a.k.a. the status bar). The + * decorations around the border (such as the status bar). The * window must correctly position its contents to take the screen * decoration into account. This flag is normally set for you * by Window as described in {@link Window#setFlags}. */ @@ -613,7 +613,7 @@ public interface WindowManager extends ViewManager { public static final int FLAG_LAYOUT_NO_LIMITS = 0x00000200; /** - * Window flag: Hide all screen decorations (e.g. status bar) while + * Window flag: hide all screen decorations (such as the status bar) while * this window is displayed. This allows the window to use the entire * display space for itself -- the status bar will be hidden when * an app window with this flag set is on the top layer. @@ -631,8 +631,8 @@ public interface WindowManager extends ViewManager { */ public static final int FLAG_FULLSCREEN = 0x00000400; - /** Window flag: Override {@link #FLAG_FULLSCREEN and force the - * screen decorations (such as status bar) to be shown. */ + /** Window flag: override {@link #FLAG_FULLSCREEN} and force the + * screen decorations (such as the status bar) to be shown. */ public static final int FLAG_FORCE_NOT_FULLSCREEN = 0x00000800; /** Window flag: turn on dithering when compositing this window to @@ -641,7 +641,7 @@ public interface WindowManager extends ViewManager { @Deprecated public static final int FLAG_DITHER = 0x00001000; - /** Window flag: Treat the content of the window as secure, preventing + /** Window flag: treat the content of the window as secure, preventing * it from appearing in screenshots or from being viewed on non-secure * displays. * diff --git a/core/java/android/webkit/WebViewClassic.java b/core/java/android/webkit/WebViewClassic.java index 67041ac06a92..c7dacf336b1c 100644 --- a/core/java/android/webkit/WebViewClassic.java +++ b/core/java/android/webkit/WebViewClassic.java @@ -5419,7 +5419,7 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc ClipData clipData = cm.getPrimaryClip(); if (clipData != null) { ClipData.Item clipItem = clipData.getItemAt(0); - CharSequence pasteText = clipItem.getText(); + CharSequence pasteText = clipItem.coerceToText(mContext); if (mInputConnection != null) { mInputConnection.replaceSelection(pasteText); } diff --git a/core/java/com/android/internal/util/Protocol.java b/core/java/com/android/internal/util/Protocol.java index 7c2b1b56be05..91b109e94d48 100644 --- a/core/java/com/android/internal/util/Protocol.java +++ b/core/java/com/android/internal/util/Protocol.java @@ -45,6 +45,7 @@ public class Protocol { public static final int BASE_WIFI_P2P_SERVICE = 0x00023000; public static final int BASE_WIFI_MONITOR = 0x00024000; public static final int BASE_WIFI_MANAGER = 0x00025000; + public static final int BASE_WIFI_CONTROLLER = 0x00026000; public static final int BASE_DHCP = 0x00030000; public static final int BASE_DATA_CONNECTION = 0x00040000; public static final int BASE_DATA_CONNECTION_AC = 0x00041000; diff --git a/core/java/com/android/internal/util/StateMachine.java b/core/java/com/android/internal/util/StateMachine.java index e547f238b770..2cdd5798d4f2 100644 --- a/core/java/com/android/internal/util/StateMachine.java +++ b/core/java/com/android/internal/util/StateMachine.java @@ -1549,6 +1549,24 @@ public class StateMachine { * * @param what is assigned to Message.what * @param arg1 is assigned to Message.arg1 + * @return A Message object from the global pool + */ + public final Message obtainMessage(int what, int arg1) { + // use this obtain so we don't match the obtain(h, what, Object) method + return Message.obtain(mSmHandler, what, arg1, 0); + } + + /** + * Get a message and set Message.target state machine handler, + * what, arg1 and arg2 + * + * Note: The handler can be null if the state machine has quit, + * which means target will be null and may cause a AndroidRuntimeException + * in MessageQueue#enqueMessage if sent directly or if sent using + * StateMachine#sendMessage the message will just be ignored. + * + * @param what is assigned to Message.what + * @param arg1 is assigned to Message.arg1 * @param arg2 is assigned to Message.arg2 * @return A Message object from the global pool */ @@ -1606,6 +1624,32 @@ public class StateMachine { * * Message is ignored if state machine has quit. */ + public final void sendMessage(int what, int arg1) { + // mSmHandler can be null if the state machine has quit. + SmHandler smh = mSmHandler; + if (smh == null) return; + + smh.sendMessage(obtainMessage(what, arg1)); + } + + /** + * Enqueue a message to this state machine. + * + * Message is ignored if state machine has quit. + */ + public final void sendMessage(int what, int arg1, int arg2) { + // mSmHandler can be null if the state machine has quit. + SmHandler smh = mSmHandler; + if (smh == null) return; + + smh.sendMessage(obtainMessage(what, arg1, arg2)); + } + + /** + * Enqueue a message to this state machine. + * + * Message is ignored if state machine has quit. + */ public final void sendMessage(int what, int arg1, int arg2, Object obj) { // mSmHandler can be null if the state machine has quit. SmHandler smh = mSmHandler; @@ -1658,6 +1702,32 @@ public class StateMachine { * * Message is ignored if state machine has quit. */ + public final void sendMessageDelayed(int what, int arg1, long delayMillis) { + // mSmHandler can be null if the state machine has quit. + SmHandler smh = mSmHandler; + if (smh == null) return; + + smh.sendMessageDelayed(obtainMessage(what, arg1), delayMillis); + } + + /** + * Enqueue a message to this state machine after a delay. + * + * Message is ignored if state machine has quit. + */ + public final void sendMessageDelayed(int what, int arg1, int arg2, long delayMillis) { + // mSmHandler can be null if the state machine has quit. + SmHandler smh = mSmHandler; + if (smh == null) return; + + smh.sendMessageDelayed(obtainMessage(what, arg1, arg2), delayMillis); + } + + /** + * Enqueue a message to this state machine after a delay. + * + * Message is ignored if state machine has quit. + */ public final void sendMessageDelayed(int what, int arg1, int arg2, Object obj, long delayMillis) { // mSmHandler can be null if the state machine has quit. @@ -1686,6 +1756,20 @@ public class StateMachine { * * Message is ignored if state machine has quit. */ + protected final void sendMessageAtFrontOfQueue(int what) { + // mSmHandler can be null if the state machine has quit. + SmHandler smh = mSmHandler; + if (smh == null) return; + + smh.sendMessageAtFrontOfQueue(obtainMessage(what)); + } + + /** + * Enqueue a message to the front of the queue for this state machine. + * Protected, may only be called by instances of StateMachine. + * + * Message is ignored if state machine has quit. + */ protected final void sendMessageAtFrontOfQueue(int what, Object obj) { // mSmHandler can be null if the state machine has quit. SmHandler smh = mSmHandler; @@ -1700,12 +1784,27 @@ public class StateMachine { * * Message is ignored if state machine has quit. */ - protected final void sendMessageAtFrontOfQueue(int what) { + protected final void sendMessageAtFrontOfQueue(int what, int arg1) { // mSmHandler can be null if the state machine has quit. SmHandler smh = mSmHandler; if (smh == null) return; - smh.sendMessageAtFrontOfQueue(obtainMessage(what)); + smh.sendMessageAtFrontOfQueue(obtainMessage(what, arg1)); + } + + + /** + * Enqueue a message to the front of the queue for this state machine. + * Protected, may only be called by instances of StateMachine. + * + * Message is ignored if state machine has quit. + */ + protected final void sendMessageAtFrontOfQueue(int what, int arg1, int arg2) { + // mSmHandler can be null if the state machine has quit. + SmHandler smh = mSmHandler; + if (smh == null) return; + + smh.sendMessageAtFrontOfQueue(obtainMessage(what, arg1, arg2)); } /** diff --git a/core/java/com/android/internal/view/IInputMethod.aidl b/core/java/com/android/internal/view/IInputMethod.aidl index c7fcab8ecd29..c2a7fc73b0c3 100644 --- a/core/java/com/android/internal/view/IInputMethod.aidl +++ b/core/java/com/android/internal/view/IInputMethod.aidl @@ -33,26 +33,28 @@ import com.android.internal.view.IInputMethodSession; * Service). * {@hide} */ -oneway interface IInputMethod { - void attachToken(IBinder token); +interface IInputMethod { + oneway void attachToken(IBinder token); - void bindInput(in InputBinding binding); + oneway void bindInput(in InputBinding binding); - void unbindInput(); + oneway void unbindInput(); - void startInput(in IInputContext inputContext, in EditorInfo attribute); + oneway void startInput(in IInputContext inputContext, in EditorInfo attribute); - void restartInput(in IInputContext inputContext, in EditorInfo attribute); + oneway void restartInput(in IInputContext inputContext, in EditorInfo attribute); - void createSession(IInputMethodCallback callback); + oneway void createSession(IInputMethodCallback callback); - void setSessionEnabled(IInputMethodSession session, boolean enabled); + oneway void setSessionEnabled(IInputMethodSession session, boolean enabled); - void revokeSession(IInputMethodSession session); + oneway void revokeSession(IInputMethodSession session); - void showSoftInput(int flags, in ResultReceiver resultReceiver); + oneway void showSoftInput(int flags, in ResultReceiver resultReceiver); - void hideSoftInput(int flags, in ResultReceiver resultReceiver); + oneway void hideSoftInput(int flags, in ResultReceiver resultReceiver); - void changeInputMethodSubtype(in InputMethodSubtype subtype); + void removeSoftInputMessages(); + + oneway void changeInputMethodSubtype(in InputMethodSubtype subtype); } diff --git a/core/jni/android_hardware_Camera.cpp b/core/jni/android_hardware_Camera.cpp index 6fbaaf2735e9..686e4e311e9e 100644 --- a/core/jni/android_hardware_Camera.cpp +++ b/core/jni/android_hardware_Camera.cpp @@ -544,13 +544,17 @@ static void android_hardware_Camera_setPreviewDisplay(JNIEnv *env, jobject thiz, sp<Camera> camera = get_native_camera(env, thiz, NULL); if (camera == 0) return; + sp<IGraphicBufferProducer> gbp; sp<Surface> surface; if (jSurface) { surface = android_view_Surface_getSurface(env, jSurface); + if (surface != NULL) { + gbp = surface->getIGraphicBufferProducer(); + } } - if (camera->setPreviewDisplay(surface) != NO_ERROR) { - jniThrowException(env, "java/io/IOException", "setPreviewDisplay failed"); + if (camera->setPreviewTexture(gbp) != NO_ERROR) { + jniThrowException(env, "java/io/IOException", "setPreviewTexture failed"); } } diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp index 0104f4bfce50..4671282de0cf 100644 --- a/core/jni/android_view_Surface.cpp +++ b/core/jni/android_view_Surface.cpp @@ -27,6 +27,8 @@ #include <android_runtime/android_view_Surface.h> #include <android_runtime/android_graphics_SurfaceTexture.h> +#include <binder/Parcel.h> + #include <gui/Surface.h> #include <gui/SurfaceControl.h> #include <gui/GLConsumer.h> @@ -149,11 +151,6 @@ static void nativeRelease(JNIEnv* env, jclass clazz, jint nativeObject) { sur->decStrong(&sRefBaseOwner); } -static void nativeDestroy(JNIEnv* env, jclass clazz, jint nativeObject) { - sp<Surface> sur(reinterpret_cast<Surface *>(nativeObject)); - sur->decStrong(&sRefBaseOwner); -} - static jboolean nativeIsValid(JNIEnv* env, jclass clazz, jint nativeObject) { sp<Surface> sur(reinterpret_cast<Surface *>(nativeObject)); return isSurfaceValid(sur) ? JNI_TRUE : JNI_FALSE; @@ -330,14 +327,32 @@ static jint nativeReadFromParcel(JNIEnv* env, jclass clazz, doThrowNPE(env); return 0; } + sp<Surface> self(reinterpret_cast<Surface *>(nativeObject)); - if (self != NULL) { - self->decStrong(&sRefBaseOwner); + sp<IBinder> binder(parcel->readStrongBinder()); + + // update the Surface only if the underlying IGraphicBufferProducer + // has changed. + if (self != NULL + && (self->getIGraphicBufferProducer()->asBinder() == binder)) { + // same IGraphicBufferProducer, return ourselves + return int(self.get()); } - sp<Surface> sur(Surface::readFromParcel(*parcel)); - if (sur != NULL) { + + sp<Surface> sur; + sp<IGraphicBufferProducer> gbp(interface_cast<IGraphicBufferProducer>(binder)); + if (gbp != NULL) { + // we have a new IGraphicBufferProducer, create a new Surface for it + sur = new Surface(gbp); + // and keep a reference before passing to java sur->incStrong(&sRefBaseOwner); } + + if (self != NULL) { + // and loose the java reference to ourselves + self->decStrong(&sRefBaseOwner); + } + return int(sur.get()); } @@ -349,7 +364,7 @@ static void nativeWriteToParcel(JNIEnv* env, jclass clazz, return; } sp<Surface> self(reinterpret_cast<Surface *>(nativeObject)); - Surface::writeToParcel(self, parcel); + parcel->writeStrongBinder( self != 0 ? self->getIGraphicBufferProducer()->asBinder() : NULL); } // ---------------------------------------------------------------------------- @@ -359,8 +374,6 @@ static JNINativeMethod gSurfaceMethods[] = { (void*)nativeCreateFromSurfaceTexture }, {"nativeRelease", "(I)V", (void*)nativeRelease }, - {"nativeDestroy", "(I)V", - (void*)nativeDestroy }, {"nativeIsValid", "(I)Z", (void*)nativeIsValid }, {"nativeIsConsumerRunningBehind", "(I)Z", diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 8a53cc35ca7d..5a1c0f89fd5d 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -169,6 +169,7 @@ <protected-broadcast android:name="android.intent.action.EXTERNAL_APPLICATIONS_UNAVAILABLE" /> <protected-broadcast android:name="android.intent.action.AIRPLANE_MODE" /> <protected-broadcast android:name="android.intent.action.ADVANCED_SETTINGS" /> + <protected-broadcast android:name="android.intent.action.BUGREPORT_FINISHED" /> <protected-broadcast android:name="android.intent.action.ACTION_IDLE_MAINTENANCE_START" /> <protected-broadcast android:name="android.intent.action.ACTION_IDLE_MAINTENANCE_END" /> diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml index 95e0b5b1ca6e..2fc7f4a0bf28 100644 --- a/core/res/res/values-de/strings.xml +++ b/core/res/res/values-de/strings.xml @@ -329,7 +329,7 @@ <string name="permdesc_confirm_full_backup" msgid="1748762171637699562">"Ermöglicht der App, die Benutzeroberfläche zur Bestätigung der vollständigen Sicherung zu starten. Kann nicht von jeder App verwendet werden."</string> <string name="permlab_internalSystemWindow" msgid="2148563628140193231">"Nicht autorisierte Fenster anzeigen"</string> <string name="permdesc_internalSystemWindow" msgid="7458387759461466397">"Ermöglicht der App die Erstellung von Fenstern, die von der Benutzeroberfläche des internen Systems verwendet werden. Nicht für normale Apps vorgesehen."</string> - <string name="permlab_systemAlertWindow" msgid="3543347980839518613">"Über andere Apps zeichnen"</string> + <string name="permlab_systemAlertWindow" msgid="3543347980839518613">"Über anderen Apps einblenden"</string> <string name="permdesc_systemAlertWindow" msgid="8584678381972820118">"Ermöglicht der App, über andere Apps oder Teile der Benutzeroberfläche zu zeichnen. Dies kann sich auf die Oberfläche in jeder App auswirken oder die erwartete Darstellung in anderen Apps verändern."</string> <string name="permlab_setAnimationScale" msgid="2805103241153907174">"Allgemeine Animationsgeschwindigkeit einstellen"</string> <string name="permdesc_setAnimationScale" msgid="7690063428924343571">"Ermöglicht der App, die allgemeine Animationsgeschwindigkeit (langsamere oder schnellere Animationen) jederzeit anzupassen."</string> diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml index 985b088f63c1..1fc59b6534d8 100644 --- a/core/res/res/values-es-rUS/strings.xml +++ b/core/res/res/values-es-rUS/strings.xml @@ -182,7 +182,7 @@ <string name="permgroupdesc_bluetoothNetwork" msgid="5625288577164282391">"Acceder a dispositivos y redes a través de Bluetooth"</string> <string name="permgrouplab_audioSettings" msgid="8329261670151871235">"Configuración de audio"</string> <string name="permgroupdesc_audioSettings" msgid="2641515403347568130">"Cambiar la configuración de audio"</string> - <string name="permgrouplab_affectsBattery" msgid="6209246653424798033">"Afecta la batería."</string> + <string name="permgrouplab_affectsBattery" msgid="6209246653424798033">"Afecta la batería"</string> <string name="permgroupdesc_affectsBattery" msgid="6441275320638916947">"Uso de las características que se pueden agotar rápidamente la batería"</string> <string name="permgrouplab_calendar" msgid="5863508437783683902">"Calendario"</string> <string name="permgroupdesc_calendar" msgid="5777534316982184416">"Acceso directo a calendario y eventos"</string> diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml index 5a701a052615..86ead94651d9 100644 --- a/core/res/res/values-pt-rPT/strings.xml +++ b/core/res/res/values-pt-rPT/strings.xml @@ -329,7 +329,7 @@ <string name="permdesc_confirm_full_backup" msgid="1748762171637699562">"Permite que a aplicação inicie a IU de confirmação de cópia de segurança completa. Não deve ser utilizado por qualquer aplicação."</string> <string name="permlab_internalSystemWindow" msgid="2148563628140193231">"apresentar janelas não autorizadas"</string> <string name="permdesc_internalSystemWindow" msgid="7458387759461466397">"Permite que a aplicação crie janelas que se destinam a ser utilizadas pela interface de utilizador do sistema interno. Nunca é necessário para aplicações normais."</string> - <string name="permlab_systemAlertWindow" msgid="3543347980839518613">"desenhar sobre outras aplicações"</string> + <string name="permlab_systemAlertWindow" msgid="3543347980839518613">"mostrar sobre outras aplicações"</string> <string name="permdesc_systemAlertWindow" msgid="8584678381972820118">"Permite que a aplicação se sobreponha a outras aplicações ou partes da interface de utilizador. Poderá interferir na utilização da interface de qualquer aplicação ou alterar o que pensa estar a ver noutras aplicações."</string> <string name="permlab_setAnimationScale" msgid="2805103241153907174">"modificar velocidade global da animação"</string> <string name="permdesc_setAnimationScale" msgid="7690063428924343571">"Permite que a aplicação altere a velocidade global da animação (animações mais rápidas ou mais lentas) em qualquer altura."</string> diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml index 9eb11b226a3c..4650d55a3e13 100644 --- a/core/res/res/values-pt/strings.xml +++ b/core/res/res/values-pt/strings.xml @@ -329,7 +329,7 @@ <string name="permdesc_confirm_full_backup" msgid="1748762171637699562">"Permite que o aplicativo lance a interface de usuário de confirmação de backup completo. Não deve ser usado por qualquer aplicativo."</string> <string name="permlab_internalSystemWindow" msgid="2148563628140193231">"exibir janelas não autorizadas"</string> <string name="permdesc_internalSystemWindow" msgid="7458387759461466397">"Permite que o aplicativo crie janelas destinadas ao uso pela interface interna do sistema. Não deve ser usado em aplicativos normais."</string> - <string name="permlab_systemAlertWindow" msgid="3543347980839518613">"induzir outros aplicativos"</string> + <string name="permlab_systemAlertWindow" msgid="3543347980839518613">"sobrepor outros aplicativos"</string> <string name="permdesc_systemAlertWindow" msgid="8584678381972820118">"Permite que o aplicativo se sobreponha visualmente a outros aplicativos ou a partes da interface do usuário. Podem interferir com o uso da interface de qualquer aplicativo ou alterar o que você acha que está vendo em outros aplicativos."</string> <string name="permlab_setAnimationScale" msgid="2805103241153907174">"modificar velocidade de animação global"</string> <string name="permdesc_setAnimationScale" msgid="7690063428924343571">"Permite que o aplicativo altere a velocidade de animação global (animação mais rápida ou mais lenta) a qualquer momento."</string> diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml index eb7185fcb799..88c56bc906a1 100644 --- a/core/res/res/values-ro/strings.xml +++ b/core/res/res/values-ro/strings.xml @@ -1200,8 +1200,8 @@ <string name="extmedia_format_button_format" msgid="4131064560127478695">"Formataţi"</string> <string name="adb_active_notification_title" msgid="6729044778949189918">"Depanarea USB este conectată"</string> <string name="adb_active_notification_message" msgid="1016654627626476142">"Atingeţi pentru a dezactiva depanarea USB."</string> - <string name="select_input_method" msgid="4653387336791222978">"Alegeţi metoda de introducere"</string> - <string name="configure_input_methods" msgid="9091652157722495116">"Configurare metode introducere"</string> + <string name="select_input_method" msgid="4653387336791222978">"Alegeți metoda de introducere de text"</string> + <string name="configure_input_methods" msgid="9091652157722495116">"Configurați metode introducere"</string> <string name="use_physical_keyboard" msgid="6203112478095117625">"Tastatură fizică"</string> <string name="hardware" msgid="7517821086888990278">"Hardware"</string> <string name="select_keyboard_layout_notification_title" msgid="1407367017263030773">"Selectaţi aspectul tastaturii"</string> diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml index b8b1c29c7831..94754721847a 100644 --- a/core/res/res/values-ru/strings.xml +++ b/core/res/res/values-ru/strings.xml @@ -329,7 +329,7 @@ <string name="permdesc_confirm_full_backup" msgid="1748762171637699562">"Приложение сможет отображать окно подтверждения полного резервного копирования. Это разрешение не предназначено для всех приложений."</string> <string name="permlab_internalSystemWindow" msgid="2148563628140193231">"показывать неавторизованные окна"</string> <string name="permdesc_internalSystemWindow" msgid="7458387759461466397">"Приложение сможет создавать окна для интерфейса внутренней системы. Это разрешение не используется обычными приложениями."</string> - <string name="permlab_systemAlertWindow" msgid="3543347980839518613">"Показ сообщений поверх других окон"</string> + <string name="permlab_systemAlertWindow" msgid="3543347980839518613">"Показ элементов интерфейса поверх других окон"</string> <string name="permdesc_systemAlertWindow" msgid="8584678381972820118">"Разрешает приложению отображать элементы своего интерфейса поверх окон других программ. Это может мешать вашему взаимодействию с другими приложениями и вести к недоразумениям."</string> <string name="permlab_setAnimationScale" msgid="2805103241153907174">"изменять глобальную скорость анимации"</string> <string name="permdesc_setAnimationScale" msgid="7690063428924343571">"Приложение сможет в любой момент изменить общую скорость анимации."</string> diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml index adfc22a3d824..370c7d639a4c 100644 --- a/core/res/res/values-sv/strings.xml +++ b/core/res/res/values-sv/strings.xml @@ -751,7 +751,7 @@ <string name="relationTypeSister" msgid="1735983554479076481">"Syster"</string> <string name="relationTypeSpouse" msgid="394136939428698117">"Make/maka"</string> <string name="sipAddressTypeCustom" msgid="2473580593111590945">"Anpassad"</string> - <string name="sipAddressTypeHome" msgid="6093598181069359295">"Startsida"</string> + <string name="sipAddressTypeHome" msgid="6093598181069359295">"Hem"</string> <string name="sipAddressTypeWork" msgid="6920725730797099047">"Arbete"</string> <string name="sipAddressTypeOther" msgid="4408436162950119849">"Övrigt"</string> <string name="keyguard_password_enter_pin_code" msgid="3037685796058495017">"Ange PIN-kod"</string> diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml index 554c1aa30124..b952bd4187ed 100644 --- a/core/res/res/values-zh-rCN/strings.xml +++ b/core/res/res/values-zh-rCN/strings.xml @@ -1347,8 +1347,8 @@ <string name="keyboardview_keycode_shift" msgid="2270748814315147690">"Shift"</string> <string name="keyboardview_keycode_enter" msgid="2985864015076059467">"Enter"</string> <string name="activitychooserview_choose_application" msgid="2125168057199941199">"选择应用"</string> - <string name="shareactionprovider_share_with" msgid="806688056141131819">"共享对象"</string> - <string name="shareactionprovider_share_with_application" msgid="5627411384638389738">"与“<xliff:g id="APPLICATION_NAME">%s</xliff:g>”共享"</string> + <string name="shareactionprovider_share_with" msgid="806688056141131819">"分享方式"</string> + <string name="shareactionprovider_share_with_application" msgid="5627411384638389738">"使用<xliff:g id="APPLICATION_NAME">%s</xliff:g>分享"</string> <string name="content_description_sliding_handle" msgid="415975056159262248">"滑动手柄。触摸并按住。"</string> <string name="description_direction_up" msgid="7169032478259485180">"向上滑动以<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>。"</string> <string name="description_direction_down" msgid="5087739728639014595">"向下滑动以<xliff:g id="TARGET_DESCRIPTION">%s</xliff:g>。"</string> @@ -1399,7 +1399,7 @@ <string name="sha1_fingerprint" msgid="7930330235269404581">"SHA-1 指纹:"</string> <string name="activity_chooser_view_see_all" msgid="4292569383976636200">"查看全部"</string> <string name="activity_chooser_view_dialog_title_default" msgid="4710013864974040615">"选择活动"</string> - <string name="share_action_provider_share_with" msgid="5247684435979149216">"分享对象"</string> + <string name="share_action_provider_share_with" msgid="5247684435979149216">"分享方式"</string> <string name="status_bar_device_locked" msgid="3092703448690669768">"设备已锁定。"</string> <string name="list_delimeter" msgid="3975117572185494152">"、 "</string> <string name="sending" msgid="3245653681008218030">"正在发送..."</string> diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index d899e9dc61b6..f1d8c03c5e58 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -1065,6 +1065,21 @@ <attr name="maxSdkVersion" format="integer" /> </declare-styleable> + <!-- The <code>library</code> tag declares that this apk is providing itself + as a shared library for other applications to use. It can only be used + with apks that are built in to the system image. Other apks can link to + it with the {@link #AndroidManifestUsesLibrary uses-library} tag. + + <p>This appears as a child tag of the + {@link #AndroidManifestApplication application} tag. --> + <declare-styleable name="AndroidManifestLibrary" parent="AndroidManifest"> + <!-- Required public name of the library, which other components and + packages will use when referring to this library. This is a string using + Java-style scoping to ensure it is unique. The name should typically + be the same as the apk's package name. --> + <attr name="name" /> + </declare-styleable> + <!-- The <code>uses-libraries</code> specifies a shared library that this package requires to be linked against. Specifying this flag tells the system to include this library's code in your class loader. diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index ccdddd8a0dbe..6a8407f9d43b 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -1031,4 +1031,14 @@ <!-- Flag indicating if the speed up audio on mt call code should be executed --> <bool name="config_speed_up_audio_on_mt_calls">false</bool> + + <!-- Class name of the framework account picker activity. + Can be customized for other product types --> + <string name="config_chooseAccountActivity" + >android/android.accounts.ChooseAccountActivity</string> + <!-- Class name of the account type and account picker activity. + Can be customized for other product types --> + <string name="config_chooseTypeAndAccountActivity" + >android/android.accounts.ChooseTypeAndAccountActivity</string> + </resources> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 140ff704f277..d57d56a0e939 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -863,6 +863,9 @@ <java-symbol type="string" name="media_route_status_available" /> <java-symbol type="string" name="media_route_status_not_available" /> <java-symbol type="string" name="owner_name" /> + <java-symbol type="string" name="config_chooseAccountActivity" /> + <java-symbol type="string" name="config_chooseTypeAndAccountActivity" /> + <java-symbol type="plurals" name="abbrev_in_num_days" /> <java-symbol type="plurals" name="abbrev_in_num_hours" /> diff --git a/core/tests/coretests/src/android/net/LinkPropertiesTest.java b/core/tests/coretests/src/android/net/LinkPropertiesTest.java index e3b6b5f95050..fffaa005ef82 100644 --- a/core/tests/coretests/src/android/net/LinkPropertiesTest.java +++ b/core/tests/coretests/src/android/net/LinkPropertiesTest.java @@ -197,4 +197,63 @@ public class LinkPropertiesTest extends TestCase { } } + private void assertAllRoutesHaveInterface(String iface, LinkProperties lp) { + for (RouteInfo r : lp.getRoutes()) { + assertEquals(iface, r.getInterface()); + } + } + + @SmallTest + public void testRouteInterfaces() { + LinkAddress prefix = new LinkAddress( + NetworkUtils.numericToInetAddress("2001:db8::"), 32); + InetAddress address = NetworkUtils.numericToInetAddress(ADDRV6); + + // Add a route with no interface to a LinkProperties with no interface. No errors. + LinkProperties lp = new LinkProperties(); + RouteInfo r = new RouteInfo(prefix, address, null); + lp.addRoute(r); + assertEquals(1, lp.getRoutes().size()); + assertAllRoutesHaveInterface(null, lp); + + // Add a route with an interface. Except an exception. + r = new RouteInfo(prefix, address, "wlan0"); + try { + lp.addRoute(r); + fail("Adding wlan0 route to LP with no interface, expect exception"); + } catch (IllegalArgumentException expected) {} + + // Change the interface name. All the routes should change their interface name too. + lp.setInterfaceName("rmnet0"); + assertAllRoutesHaveInterface("rmnet0", lp); + + // Now add a route with the wrong interface. This causes an exception too. + try { + lp.addRoute(r); + fail("Adding wlan0 route to rmnet0 LP, expect exception"); + } catch (IllegalArgumentException expected) {} + + // If the interface name matches, the route is added. + lp.setInterfaceName("wlan0"); + lp.addRoute(r); + assertEquals(2, lp.getRoutes().size()); + assertAllRoutesHaveInterface("wlan0", lp); + + // Routes with null interfaces are converted to wlan0. + r = RouteInfo.makeHostRoute(NetworkUtils.numericToInetAddress(ADDRV6), null); + lp.addRoute(r); + assertEquals(3, lp.getRoutes().size()); + assertAllRoutesHaveInterface("wlan0", lp); + + // Check comparisons work. + LinkProperties lp2 = new LinkProperties(lp); + assertAllRoutesHaveInterface("wlan0", lp); + assertEquals(0, lp.compareRoutes(lp2).added.size()); + assertEquals(0, lp.compareRoutes(lp2).removed.size()); + + lp2.setInterfaceName("p2p0"); + assertAllRoutesHaveInterface("p2p0", lp2); + assertEquals(3, lp.compareRoutes(lp2).added.size()); + assertEquals(3, lp.compareRoutes(lp2).removed.size()); + } } diff --git a/docs/downloads/training/InteractiveChart.zip b/docs/downloads/training/InteractiveChart.zip Binary files differnew file mode 100644 index 000000000000..95248ada943b --- /dev/null +++ b/docs/downloads/training/InteractiveChart.zip diff --git a/docs/html/index.jd b/docs/html/index.jd index f2df7beccc90..ec0469c51de5 100644 --- a/docs/html/index.jd +++ b/docs/html/index.jd @@ -19,12 +19,11 @@ page.metaDescription=The official site for Android developers. Provides the Andr <div class="content-right col-5"> <h1>Google I/O 2013</h1> <p>Android will be at Google I/O on May 15-17, 2013, with sessions covering a variety of topics - such as design, performance, and how to extend your app with the latest Android features. - Registration opens on March 13, 2013 at 7:00 AM PDT (GMT-7).</p> + such as design, performance, and how to extend your app with the latest Android features.</p> <p>For more information about event details and planned sessions, stay tuned to <a href="http://google.com/+GoogleDevelopers">+Google Developers</a>.</p> - <p><a href="https://developers.google.com/events/io/register" class="button">Register here</a></p> + <p><a href="https://developers.google.com/events/io/" class="button">Learn more</a></p> </div> </li> <li class="item carousel-home"> diff --git a/docs/html/training/gestures/detector.jd b/docs/html/training/gestures/detector.jd index 06d0e983f5cc..65ddb1baee3a 100644 --- a/docs/html/training/gestures/detector.jd +++ b/docs/html/training/gestures/detector.jd @@ -25,12 +25,18 @@ next.link=movement.html <li><a href="http://developer.android.com/guide/topics/ui/ui-events.html">Input Events</a> API Guide </li> <li><a href="{@docRoot}guide/topics/sensors/sensors_overview.html">Sensors Overview</a></li> - <li><a href="http://android-developers.blogspot.com/2010/06/making-sense-of-multitouch.html">Making Sense of Multitouch</a> blog post</li> <li><a href="{@docRoot}training/custom-views/making-interactive.html">Making the View Interactive</a> </li> <li>Design Guide for <a href="{@docRoot}design/patterns/gestures.html">Gestures</a></li> <li>Design Guide for <a href="{@docRoot}design/style/touch-feedback.html">Touch Feedback</a></li> </ul> +<h2>Try it out</h2> + +<div class="download-box"> + <a href="{@docRoot}shareables/training/InteractiveChart.zip" +class="button">Download the sample</a> + <p class="filename">InteractiveChart.zip</p> +</div> </div> </div> diff --git a/docs/html/training/gestures/index.jd b/docs/html/training/gestures/index.jd index 0191450f29cc..16ca7b08e3af 100644 --- a/docs/html/training/gestures/index.jd +++ b/docs/html/training/gestures/index.jd @@ -20,12 +20,18 @@ next.link=detector.html <li><a href="http://developer.android.com/guide/topics/ui/ui-events.html">Input Events</a> API Guide </li> <li><a href="{@docRoot}guide/topics/sensors/sensors_overview.html">Sensors Overview</a></li> - <li><a href="http://android-developers.blogspot.com/2010/06/making-sense-of-multitouch.html">Making Sense of Multitouch</a> blog post</li> <li><a href="{@docRoot}training/custom-views/making-interactive.html">Making the View Interactive</a> </li> <li>Design Guide for <a href="{@docRoot}design/patterns/gestures.html">Gestures</a></li> <li>Design Guide for <a href="{@docRoot}design/style/touch-feedback.html">Touch Feedback</a></li> </ul> +<h2>Try it out</h2> + +<div class="download-box"> + <a href="{@docRoot}shareables/training/InteractiveChart.zip" +class="button">Download the sample</a> + <p class="filename">InteractiveChart.zip</p> +</div> </div> </div> diff --git a/docs/html/training/gestures/movement.jd b/docs/html/training/gestures/movement.jd index f2c49d726c31..fdc1ea4ef4ff 100644 --- a/docs/html/training/gestures/movement.jd +++ b/docs/html/training/gestures/movement.jd @@ -24,12 +24,18 @@ next.link=scroll.html <li><a href="http://developer.android.com/guide/topics/ui/ui-events.html">Input Events</a> API Guide </li> <li><a href="{@docRoot}guide/topics/sensors/sensors_overview.html">Sensors Overview</a></li> - <li><a href="http://android-developers.blogspot.com/2010/06/making-sense-of-multitouch.html">Making Sense of Multitouch</a> blog post</li> <li><a href="{@docRoot}training/custom-views/making-interactive.html">Making the View Interactive</a> </li> <li>Design Guide for <a href="{@docRoot}design/patterns/gestures.html">Gestures</a></li> <li>Design Guide for <a href="{@docRoot}design/style/touch-feedback.html">Touch Feedback</a></li> </ul> +<h2>Try it out</h2> + +<div class="download-box"> + <a href="{@docRoot}shareables/training/InteractiveChart.zip" +class="button">Download the sample</a> + <p class="filename">InteractiveChart.zip</p> +</div> </div> </div> diff --git a/docs/html/training/gestures/multi.jd b/docs/html/training/gestures/multi.jd index d4c5b1d07df9..6a0df11f4c5c 100644 --- a/docs/html/training/gestures/multi.jd +++ b/docs/html/training/gestures/multi.jd @@ -25,12 +25,18 @@ next.link=scale.html <li><a href="http://developer.android.com/guide/topics/ui/ui-events.html">Input Events</a> API Guide </li> <li><a href="{@docRoot}guide/topics/sensors/sensors_overview.html">Sensors Overview</a></li> - <li><a href="http://android-developers.blogspot.com/2010/06/making-sense-of-multitouch.html">Making Sense of Multitouch</a> blog post</li> <li><a href="{@docRoot}training/custom-views/making-interactive.html">Making the View Interactive</a> </li> <li>Design Guide for <a href="{@docRoot}design/patterns/gestures.html">Gestures</a></li> <li>Design Guide for <a href="{@docRoot}design/style/touch-feedback.html">Touch Feedback</a></li> </ul> +<h2>Try it out</h2> + +<div class="download-box"> + <a href="{@docRoot}shareables/training/InteractiveChart.zip" +class="button">Download the sample</a> + <p class="filename">InteractiveChart.zip</p> +</div> </div> </div> diff --git a/docs/html/training/gestures/scale.jd b/docs/html/training/gestures/scale.jd index 17e40858588a..f2e4eb82d122 100644 --- a/docs/html/training/gestures/scale.jd +++ b/docs/html/training/gestures/scale.jd @@ -15,6 +15,7 @@ next.link=viewgroup.html <h2>This lesson teaches you to</h2> <ol> <li><a href="#drag">Drag an Object</a></li> + <li><a href="#pan">Drag to Pan</a></li> <li><a href="#scale">Use Touch to Perform Scaling</a></li> </ol> @@ -25,20 +26,25 @@ next.link=viewgroup.html <li><a href="http://developer.android.com/guide/topics/ui/ui-events.html">Input Events</a> API Guide </li> <li><a href="{@docRoot}guide/topics/sensors/sensors_overview.html">Sensors Overview</a></li> - <li><a href="http://android-developers.blogspot.com/2010/06/making-sense-of-multitouch.html">Making Sense of Multitouch</a> blog post</li> <li><a href="{@docRoot}training/custom-views/making-interactive.html">Making the View Interactive</a> </li> <li>Design Guide for <a href="{@docRoot}design/patterns/gestures.html">Gestures</a></li> <li>Design Guide for <a href="{@docRoot}design/style/touch-feedback.html">Touch Feedback</a></li> </ul> +<h2>Try it out</h2> + +<div class="download-box"> + <a href="{@docRoot}shareables/training/InteractiveChart.zip" +class="button">Download the sample</a> + <p class="filename">InteractiveChart.zip</p> +</div> </div> </div> + <p>This lesson describes how to use touch gestures to drag and scale on-screen objects, using {@link android.view.View#onTouchEvent onTouchEvent()} to intercept -touch events. Here is the original <a -href="http://code.google.com/p/android-touchexample/">source code</a> -for the examples used in this lesson. +touch events. </p> <h2 id="drag">Drag an Object</h2> @@ -128,17 +134,15 @@ public boolean onTouchEvent(MotionEvent ev) { final float x = MotionEventCompat.getX(ev, pointerIndex); final float y = MotionEventCompat.getY(ev, pointerIndex); - // Only move if the ScaleGestureDetector isn't processing a gesture. - if (!mScaleDetector.isInProgress()) { - // Calculate the distance moved - final float dx = x - mLastTouchX; - final float dy = y - mLastTouchY; + // Calculate the distance moved + final float dx = x - mLastTouchX; + final float dy = y - mLastTouchY; - mPosX += dx; - mPosY += dy; + mPosX += dx; + mPosY += dy; + + invalidate(); - invalidate(); - } // Remember this touch position for the next move event mLastTouchX = x; mLastTouchY = y; @@ -175,6 +179,88 @@ public boolean onTouchEvent(MotionEvent ev) { return true; }</pre> +<h2 id="pan">Drag to Pan</h2> + +<p>The previous section showed an example of dragging an object around the screen. Another +common scenario is <em>panning</em>, which is when a user's dragging motion causes scrolling +in both the x and y axes. The above snippet directly intercepted the {@link android.view.MotionEvent} +actions to implement dragging. The snippet in this section takes advantage of the platform's +built-in support for common gestures. It overrides +{@link android.view.GestureDetector.OnGestureListener#onScroll onScroll()} in +{@link android.view.GestureDetector.SimpleOnGestureListener}.</p> + +<p>To provide a little more context, {@link android.view.GestureDetector.OnGestureListener#onScroll onScroll()} +is called when a user is dragging his finger to pan the content. +{@link android.view.GestureDetector.OnGestureListener#onScroll onScroll()} is only called when +a finger is down; as soon as the finger is lifted from the screen, the gesture either ends, +or a fling gesture is started (if the finger was moving with some speed just before it was lifted). +For more discussion of scrolling vs. flinging, see <a href="scroll.html">Animating a Scroll Gesture</a>.</p> + +<p>Here is the snippet for {@link android.view.GestureDetector.OnGestureListener#onScroll onScroll()}: + + +<pre>// The current viewport. This rectangle represents the currently visible +// chart domain and range. +private RectF mCurrentViewport = + new RectF(AXIS_X_MIN, AXIS_Y_MIN, AXIS_X_MAX, AXIS_Y_MAX); + +// The current destination rectangle (in pixel coordinates) into which the +// chart data should be drawn. +private Rect mContentRect; + +private final GestureDetector.SimpleOnGestureListener mGestureListener + = new GestureDetector.SimpleOnGestureListener() { +... + +@Override +public boolean onScroll(MotionEvent e1, MotionEvent e2, + float distanceX, float distanceY) { + // Scrolling uses math based on the viewport (as opposed to math using pixels). + + // Pixel offset is the offset in screen pixels, while viewport offset is the + // offset within the current viewport. + float viewportOffsetX = distanceX * mCurrentViewport.width() + / mContentRect.width(); + float viewportOffsetY = -distanceY * mCurrentViewport.height() + / mContentRect.height(); + ... + // Updates the viewport, refreshes the display. + setViewportBottomLeft( + mCurrentViewport.left + viewportOffsetX, + mCurrentViewport.bottom + viewportOffsetY); + ... + return true; +}</pre> + +<p>The implementation of {@link android.view.GestureDetector.OnGestureListener#onScroll onScroll()} +scrolls the viewport in response to the touch gesture:</p> + +<pre> +/** + * Sets the current viewport (defined by mCurrentViewport) to the given + * X and Y positions. Note that the Y value represents the topmost pixel position, + * and thus the bottom of the mCurrentViewport rectangle. + */ +private void setViewportBottomLeft(float x, float y) { + /* + * Constrains within the scroll range. The scroll range is simply the viewport + * extremes (AXIS_X_MAX, etc.) minus the viewport size. For example, if the + * extremes were 0 and 10, and the viewport size was 2, the scroll range would + * be 0 to 8. + */ + + float curWidth = mCurrentViewport.width(); + float curHeight = mCurrentViewport.height(); + x = Math.max(AXIS_X_MIN, Math.min(x, AXIS_X_MAX - curWidth)); + y = Math.max(AXIS_Y_MIN + curHeight, Math.min(y, AXIS_Y_MAX)); + + mCurrentViewport.set(x, y - curHeight, x + curWidth, y); + + // Invalidates the View to update the display. + ViewCompat.postInvalidateOnAnimation(this); +} +</pre> + <h2 id="scale">Use Touch to Perform Scaling</h2> <p>As discussed in <a href="detector.html">Detecting Common Gestures</a>, @@ -191,10 +277,10 @@ Android provides {@link android.view.ScaleGestureDetector.SimpleOnScaleGestureListener} as a helper class that you can extend if you don’t care about all of the reported events.</p> -<p>Here is a snippet that gives you the basic idea of how to perform scaling. -Here is the original <a -href="http://code.google.com/p/android-touchexample/">source code</a> -for the examples.</p> + +<h3>Basic scaling example</h3> + +<p>Here is a snippet that illustrates the basic ingredients involved in scaling.</p> <pre>private ScaleGestureDetector mScaleDetector; private float mScaleFactor = 1.f; @@ -238,3 +324,88 @@ private class ScaleListener return true; } }</pre> + + + + +<h3>More complex scaling example</h3> +<p>Here is a more complex example from the {@code InteractiveChart} sample provided with this class. +The {@code InteractiveChart} sample supports both scrolling (panning) and scaling with multiple fingers, +using the {@link android.view.ScaleGestureDetector} "span" +({@link android.view.ScaleGestureDetector#getCurrentSpanX getCurrentSpanX/Y}) and +"focus" ({@link android.view.ScaleGestureDetector#getFocusX getFocusX/Y}) features:</p> + +<pre>@Override +private RectF mCurrentViewport = + new RectF(AXIS_X_MIN, AXIS_Y_MIN, AXIS_X_MAX, AXIS_Y_MAX); +private Rect mContentRect; +private ScaleGestureDetector mScaleGestureDetector; +... +public boolean onTouchEvent(MotionEvent event) { + boolean retVal = mScaleGestureDetector.onTouchEvent(event); + retVal = mGestureDetector.onTouchEvent(event) || retVal; + return retVal || super.onTouchEvent(event); +} + +/** + * The scale listener, used for handling multi-finger scale gestures. + */ +private final ScaleGestureDetector.OnScaleGestureListener mScaleGestureListener + = new ScaleGestureDetector.SimpleOnScaleGestureListener() { + /** + * This is the active focal point in terms of the viewport. Could be a local + * variable but kept here to minimize per-frame allocations. + */ + private PointF viewportFocus = new PointF(); + private float lastSpanX; + private float lastSpanY; + + // Detects that new pointers are going down. + @Override + public boolean onScaleBegin(ScaleGestureDetector scaleGestureDetector) { + lastSpanX = ScaleGestureDetectorCompat. + getCurrentSpanX(scaleGestureDetector); + lastSpanY = ScaleGestureDetectorCompat. + getCurrentSpanY(scaleGestureDetector); + return true; + } + + @Override + public boolean onScale(ScaleGestureDetector scaleGestureDetector) { + + float spanX = ScaleGestureDetectorCompat. + getCurrentSpanX(scaleGestureDetector); + float spanY = ScaleGestureDetectorCompat. + getCurrentSpanY(scaleGestureDetector); + + float newWidth = lastSpanX / spanX * mCurrentViewport.width(); + float newHeight = lastSpanY / spanY * mCurrentViewport.height(); + + float focusX = scaleGestureDetector.getFocusX(); + float focusY = scaleGestureDetector.getFocusY(); + // Makes sure that the chart point is within the chart region. + // See the sample for the implementation of hitTest(). + hitTest(scaleGestureDetector.getFocusX(), + scaleGestureDetector.getFocusY(), + viewportFocus); + + mCurrentViewport.set( + viewportFocus.x + - newWidth * (focusX - mContentRect.left) + / mContentRect.width(), + viewportFocus.y + - newHeight * (mContentRect.bottom - focusY) + / mContentRect.height(), + 0, + 0); + mCurrentViewport.right = mCurrentViewport.left + newWidth; + mCurrentViewport.bottom = mCurrentViewport.top + newHeight; + ... + // Invalidates the View to update the display. + ViewCompat.postInvalidateOnAnimation(InteractiveLineGraphView.this); + + lastSpanX = spanX; + lastSpanY = spanY; + return true; + } +};</pre> diff --git a/docs/html/training/gestures/scroll.jd b/docs/html/training/gestures/scroll.jd index 8576948b5a23..bd1537a65a67 100644 --- a/docs/html/training/gestures/scroll.jd +++ b/docs/html/training/gestures/scroll.jd @@ -14,6 +14,7 @@ next.link=multi.html <!-- table of contents --> <h2>This lesson teaches you to</h2> <ol> + <li><a href="#term">Understand Scrolling Terminology</a></li> <li><a href="#scroll">Implement Touch-Based Scrolling</a></li> </ol> @@ -24,12 +25,18 @@ next.link=multi.html <li><a href="http://developer.android.com/guide/topics/ui/ui-events.html">Input Events</a> API Guide </li> <li><a href="{@docRoot}guide/topics/sensors/sensors_overview.html">Sensors Overview</a></li> - <li><a href="http://android-developers.blogspot.com/2010/06/making-sense-of-multitouch.html">Making Sense of Multitouch</a> blog post</li> <li><a href="{@docRoot}training/custom-views/making-interactive.html">Making the View Interactive</a> </li> <li>Design Guide for <a href="{@docRoot}design/patterns/gestures.html">Gestures</a></li> <li>Design Guide for <a href="{@docRoot}design/style/touch-feedback.html">Touch Feedback</a></li> </ul> +<h2>Try it out</h2> + +<div class="download-box"> + <a href="{@docRoot}shareables/training/InteractiveChart.zip" +class="button">Download the sample</a> + <p class="filename">InteractiveChart.zip</p> +</div> </div> </div> @@ -45,7 +52,26 @@ a scrolling effect in response to touch gestures using <em>scrollers</em>. <p>You can use scrollers ({@link android.widget.Scroller} or {@link android.widget.OverScroller}) to collect the data you need to produce a -scrolling animation in response to a touch event.</p> +scrolling animation in response to a touch event. They are similar, but +{@link android.widget.OverScroller} +includes methods for indicating to users that they've reached the content edges +after a pan or fling gesture. The {@code InteractiveChart} sample +uses the the {@link android.widget.EdgeEffect} class +(actually the {@link android.support.v4.widget.EdgeEffectCompat} class) +to display a "glow" effect when users reach the content edges.</p> + +<p class="note"><strong>Note:</strong> We recommend that you +use {@link android.widget.OverScroller} rather than {@link +android.widget.Scroller} for scrolling animations. +{@link android.widget.OverScroller} provides the best backward +compatibility with older devices. +<br /> +Also note that you generally only need to use scrollers +when implementing scrolling yourself. {@link android.widget.ScrollView} and +{@link android.widget.HorizontalScrollView} do all of this for you if you nest your +layout within them. +</p> + <p>A scroller is used to animate scrolling over time, using platform-standard scrolling physics (friction, velocity, etc.). The scroller itself doesn't @@ -54,101 +80,280 @@ they don't automatically apply those positions to your view. It's your responsibility to get and apply new coordinates at a rate that will make the scrolling animation look smooth.</p> -<p class="note"><strong>Note:</strong> You generally only need to use scrollers -when implementing scrolling yourself. {@link android.widget.ScrollView} and -{@link android.widget.HorizontalScrollView} do all this for you do all of this for you if you nest your layout within them.</p> -<h2 id = "scroll">Implement Touch-Based Scrolling</h2> +<h2 id="term">Understand Scrolling Terminology</h2> -<p>This snippet illustrates the basics of using a scroller. It uses a -{@link android.view.GestureDetector}, and overrides the -{@link android.view.GestureDetector.SimpleOnGestureListener} methods -{@link android.view.GestureDetector.OnGestureListener#onDown onDown()} and -{@link android.view.GestureDetector.OnGestureListener#onFling onFling()}. It also -overrides {@link android.view.GestureDetector.OnGestureListener#onScroll onScroll()} -to return {@code false} since you don't need to animate a scroll.</p> +<p>"Scrolling" is a word that can take on different meanings in Android, depending on the context.</p> + +<p><strong>Scrolling</strong> is the general process of moving the viewport (that is, the 'window' +of content you're looking at). When scrolling is in both the x and y axes, it's called +<em>panning</em>. The sample application provided with this class, {@code InteractiveChart}, illustrates +two different types of scrolling, dragging and flinging:</p> +<ul> + <li><strong>Dragging</strong> is the type of scrolling that occurs when a user drags her +finger across the touch screen. Simple dragging is often implemented by overriding +{@link android.view.GestureDetector.OnGestureListener#onScroll onScroll()} in +{@link android.view.GestureDetector.OnGestureListener}. For more discussion of dragging, see +<a href="dragging.jd">Dragging and Scaling</a>.</li> + <li><strong>Flinging</strong> is the type of scrolling that occurs when a user +drags and lifts her finger quickly. After the user lifts her finger, you generally +want to keep scrolling (moving the viewport), but decelerate until the viewport stops moving. +Flinging can be implemented by overriding +{@link android.view.GestureDetector.OnGestureListener#onFling onFling()} +in {@link android.view.GestureDetector.OnGestureListener}, and by using +a scroller object. This is the use +case that is the topic of this lesson.</li> +</ul> -<p>It's common to use scrollers in conjunction with a fling gesture, but they +<p>It's common to use scroller objects +in conjunction with a fling gesture, but they can be used in pretty much any context where you want the UI to display -scrolling in response to a touch event. For example, you could override {@link -android.view.View#onTouchEvent onTouchEvent()} to process touch events directly, -and produce a scrolling effect in response to those touch events.</p> +scrolling in response to a touch event. For example, you could override +{@link android.view.View#onTouchEvent onTouchEvent()} to process touch +events directly, and produce a scrolling effect or a "snapping to page" animation +in response to those touch events.</p> + + +<h2 id="#scroll">Implement Touch-Based Scrolling</h2> + +<p>This section describes how to use a scroller. +The snippet shown below comes from the {@code InteractiveChart} sample +provided with this class. +It uses a +{@link android.view.GestureDetector}, and overrides the +{@link android.view.GestureDetector.SimpleOnGestureListener} method +{@link android.view.GestureDetector.OnGestureListener#onFling onFling()}. +It uses {@link android.widget.OverScroller} to track the fling gesture. +If the user reaches the content edges +after the fling gesture, the app displays a "glow" effect. +</p> -<pre> -private OverScroller mScroller = new OverScroller(context); +<p class="note"><strong>Note:</strong> The {@code InteractiveChart} sample app displays a +chart that you can zoom, pan, scroll, and so on. In the following snippet, +{@code mContentRect} represents the rectangle coordinates within the view that the chart +will be drawn into. At any given time, a subset of the total chart domain and range are drawn +into this rectangular area. +{@code mCurrentViewport} represents the portion of the chart that is currently +visible in the screen. Because pixel offsets are generally treated as integers, +{@code mContentRect} is of the type {@link android.graphics.Rect}. Because the +graph domain and range are decimal/float values, {@code mCurrentViewport} is of +the type {@link android.graphics.RectF}.</p> -private GestureDetector.SimpleOnGestureListener mGestureListener +<p>The first part of the snippet shows the implementation of +{@link android.view.GestureDetector.OnGestureListener#onFling onFling()}:</p> + +<pre>// The current viewport. This rectangle represents the currently visible +// chart domain and range. The viewport is the part of the app that the +// user manipulates via touch gestures. +private RectF mCurrentViewport = + new RectF(AXIS_X_MIN, AXIS_Y_MIN, AXIS_X_MAX, AXIS_Y_MAX); + +// The current destination rectangle (in pixel coordinates) into which the +// chart data should be drawn. +private Rect mContentRect; + +private OverScroller mScroller; +private RectF mScrollerStartViewport; +... +private final GestureDetector.SimpleOnGestureListener mGestureListener = new GestureDetector.SimpleOnGestureListener() { @Override public boolean onDown(MotionEvent e) { - // Abort any active scroll animations and invalidate. + // Initiates the decay phase of any active edge effects. + releaseEdgeEffects(); + mScrollerStartViewport.set(mCurrentViewport); + // Aborts any active scroll animations and invalidates. mScroller.forceFinished(true); - // There is also a compatibility version: - // ViewCompat.postInvalidateOnAnimation - postInvalidateOnAnimation(); + ViewCompat.postInvalidateOnAnimation(InteractiveLineGraphView.this); return true; } - - @Override - public boolean onScroll(MotionEvent e1, MotionEvent e2, - float distanceX, float distanceY) { - // You don't use a scroller in onScroll because you don't need to animate - // a scroll. The scroll occurs instantly in response to touch feedback. - return false; - } - + ... @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { - // Before flinging, abort the current animation. - mScroller.forceFinished(true); - // Begin the scroll animation - mScroller.fling( - // Current scroll position - startX, - startY, - // Velocities, negated for natural touch response - (int) -velocityX, - (int) -velocityY, - // Minimum and maximum scroll positions. The minimum scroll - // position is generally zero and the maximum scroll position - // is generally the content size less the screen size. So if the - // content width is 1000 pixels and the screen width is 200 - // pixels, the maximum scroll offset should be 800 pixels. - minX, maxX, - minY, maxY, - // The maximum overscroll bounds. This is useful when using - // the EdgeEffect class to draw overscroll "glow" overlays. - mContentRect.width() / 2, - mContentRect.height() / 2); - // Invalidate to trigger computeScroll() - postInvalidateOnAnimation(); + fling((int) -velocityX, (int) -velocityY); return true; } }; +private void fling(int velocityX, int velocityY) { + // Initiates the decay phase of any active edge effects. + releaseEdgeEffects(); + // Flings use math in pixels (as opposed to math based on the viewport). + Point surfaceSize = computeScrollSurfaceSize(); + mScrollerStartViewport.set(mCurrentViewport); + int startX = (int) (surfaceSize.x * (mScrollerStartViewport.left - + AXIS_X_MIN) / ( + AXIS_X_MAX - AXIS_X_MIN)); + int startY = (int) (surfaceSize.y * (AXIS_Y_MAX - + mScrollerStartViewport.bottom) / ( + AXIS_Y_MAX - AXIS_Y_MIN)); + // Before flinging, aborts the current animation. + mScroller.forceFinished(true); + // Begins the animation + mScroller.fling( + // Current scroll position + startX, + startY, + velocityX, + velocityY, + /* + * Minimum and maximum scroll positions. The minimum scroll + * position is generally zero and the maximum scroll position + * is generally the content size less the screen size. So if the + * content width is 1000 pixels and the screen width is 200 + * pixels, the maximum scroll offset should be 800 pixels. + */ + 0, surfaceSize.x - mContentRect.width(), + 0, surfaceSize.y - mContentRect.height(), + // The edges of the content. This comes into play when using + // the EdgeEffect class to draw "glow" overlays. + mContentRect.width() / 2, + mContentRect.height() / 2); + // Invalidates to trigger computeScroll() + ViewCompat.postInvalidateOnAnimation(this); +}</pre> + +<p>When {@link android.view.GestureDetector.OnGestureListener#onFling onFling()} calls +{@link android.support.v4.view.ViewCompat#postInvalidateOnAnimation postInvalidateOnAnimation()}, +it triggers +{@link android.view.View#computeScroll computeScroll()} to update the values for x and y. +This is typically be done when a view child is animating a scroll using a scroller object, as in this example. </p> + +<p>Most views pass the scroller object's x and y position directly to +{@link android.view.View#scrollTo scrollTo()}. +The following implementation of {@link android.view.View#computeScroll computeScroll()} +takes a different approach—it calls +{@link android.widget.OverScroller#computeScrollOffset computeScrollOffset()} to get the current +location of x and y. When the criteria for displaying an overscroll "glow" edge effect are met +(the display is zoomed in, x or y is out of bounds, and the app isn't already showing an overscroll), +the code sets up the overscroll glow effect and calls +{@link android.support.v4.view.ViewCompat#postInvalidateOnAnimation postInvalidateOnAnimation()} +to trigger an invalidate on the view:</p> + +<pre>// Edge effect / overscroll tracking objects. +private EdgeEffectCompat mEdgeEffectTop; +private EdgeEffectCompat mEdgeEffectBottom; +private EdgeEffectCompat mEdgeEffectLeft; +private EdgeEffectCompat mEdgeEffectRight; + +private boolean mEdgeEffectTopActive; +private boolean mEdgeEffectBottomActive; +private boolean mEdgeEffectLeftActive; +private boolean mEdgeEffectRightActive; + @Override public void computeScroll() { super.computeScroll(); - // Compute the current scroll offsets. If this returns true, then the - // scroll has not yet finished. + boolean needsInvalidate = false; + + // The scroller isn't finished, meaning a fling or programmatic pan + // operation is currently active. if (mScroller.computeScrollOffset()) { + Point surfaceSize = computeScrollSurfaceSize(); int currX = mScroller.getCurrX(); int currY = mScroller.getCurrY(); - // Actually render the scrolled viewport, or actually scroll the - // view using View.scrollTo. + boolean canScrollX = (mCurrentViewport.left > AXIS_X_MIN + || mCurrentViewport.right < AXIS_X_MAX); + boolean canScrollY = (mCurrentViewport.top > AXIS_Y_MIN + || mCurrentViewport.bottom < AXIS_Y_MAX); - // If currX or currY are outside the bounds, render the overscroll - // glow using EdgeEffect. + /* + * If you are zoomed in and currX or currY is + * outside of bounds and you're not already + * showing overscroll, then render the overscroll + * glow edge effect. + */ + if (canScrollX + && currX < 0 + && mEdgeEffectLeft.isFinished() + && !mEdgeEffectLeftActive) { + mEdgeEffectLeft.onAbsorb((int) + OverScrollerCompat.getCurrVelocity(mScroller)); + mEdgeEffectLeftActive = true; + needsInvalidate = true; + } else if (canScrollX + && currX > (surfaceSize.x - mContentRect.width()) + && mEdgeEffectRight.isFinished() + && !mEdgeEffectRightActive) { + mEdgeEffectRight.onAbsorb((int) + OverScrollerCompat.getCurrVelocity(mScroller)); + mEdgeEffectRightActive = true; + needsInvalidate = true; + } - } else { - // The scroll has finished. - } + if (canScrollY + && currY < 0 + && mEdgeEffectTop.isFinished() + && !mEdgeEffectTopActive) { + mEdgeEffectTop.onAbsorb((int) + OverScrollerCompat.getCurrVelocity(mScroller)); + mEdgeEffectTopActive = true; + needsInvalidate = true; + } else if (canScrollY + && currY > (surfaceSize.y - mContentRect.height()) + && mEdgeEffectBottom.isFinished() + && !mEdgeEffectBottomActive) { + mEdgeEffectBottom.onAbsorb((int) + OverScrollerCompat.getCurrVelocity(mScroller)); + mEdgeEffectBottomActive = true; + needsInvalidate = true; + } + ... + }</pre> + +<p>Here is the section of the code that performs the actual zoom:</p> + +<pre>// Custom object that is functionally similar to Scroller +Zoomer mZoomer; +private PointF mZoomFocalPoint = new PointF(); +... + +// If a zoom is in progress (either programmatically or via double +// touch), performs the zoom. +if (mZoomer.computeZoom()) { + float newWidth = (1f - mZoomer.getCurrZoom()) * + mScrollerStartViewport.width(); + float newHeight = (1f - mZoomer.getCurrZoom()) * + mScrollerStartViewport.height(); + float pointWithinViewportX = (mZoomFocalPoint.x - + mScrollerStartViewport.left) + / mScrollerStartViewport.width(); + float pointWithinViewportY = (mZoomFocalPoint.y - + mScrollerStartViewport.top) + / mScrollerStartViewport.height(); + mCurrentViewport.set( + mZoomFocalPoint.x - newWidth * pointWithinViewportX, + mZoomFocalPoint.y - newHeight * pointWithinViewportY, + mZoomFocalPoint.x + newWidth * (1 - pointWithinViewportX), + mZoomFocalPoint.y + newHeight * (1 - pointWithinViewportY)); + constrainViewport(); + needsInvalidate = true; +} +if (needsInvalidate) { + ViewCompat.postInvalidateOnAnimation(this); +} +</pre> + +<p>This is the {@code computeScrollSurfaceSize()} method that's called in the above snippet. It +computes the current scrollable surface size, in pixels. For example, if the entire chart area is visible, +this is simply the current size of {@code mContentRect}. If the chart is zoomed in 200% in both directions, +the returned size will be twice as large horizontally and vertically.</p> + +<pre>private Point computeScrollSurfaceSize() { + return new Point( + (int) (mContentRect.width() * (AXIS_X_MAX - AXIS_X_MIN) + / mCurrentViewport.width()), + (int) (mContentRect.height() * (AXIS_Y_MAX - AXIS_Y_MIN) + / mCurrentViewport.height())); }</pre> -<p>For another example of scroller usage, see the <a href="http://github.com/android/platform_frameworks_support/blob/master/v4/java/android/support/v4/view/ViewPager.java">source code</a> for the -{@link android.support.v4.view.ViewPager} class.</p> +<p>For another example of scroller usage, see the +<a href="http://github.com/android/platform_frameworks_support/blob/master/v4/java/android/support/v4/view/ViewPager.java">source code</a> for the +{@link android.support.v4.view.ViewPager} class. It scrolls in response to flings, +and uses scrolling to implement the "snapping to page" animation.</p> + diff --git a/docs/html/training/gestures/viewgroup.jd b/docs/html/training/gestures/viewgroup.jd index 257a5d816e56..5b32300b99b0 100644 --- a/docs/html/training/gestures/viewgroup.jd +++ b/docs/html/training/gestures/viewgroup.jd @@ -26,12 +26,19 @@ next.link= <li><a href="http://developer.android.com/guide/topics/ui/ui-events.html">Input Events</a> API Guide </li> <li><a href="{@docRoot}guide/topics/sensors/sensors_overview.html">Sensors Overview</a></li> - <li><a href="http://android-developers.blogspot.com/2010/06/making-sense-of-multitouch.html">Making Sense of Multitouch</a> blog post</li> <li><a href="{@docRoot}training/custom-views/making-interactive.html">Making the View Interactive</a> </li> <li>Design Guide for <a href="{@docRoot}design/patterns/gestures.html">Gestures</a></li> <li>Design Guide for <a href="{@docRoot}design/style/touch-feedback.html">Touch Feedback</a></li> </ul> +<h2>Try it out</h2> + +<div class="download-box"> + <a href="{@docRoot}shareables/training/InteractiveChart.zip" +class="button">Download the sample</a> + <p class="filename">InteractiveChart.zip</p> +</div> + </div> </div> diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk index 85b2052ec795..161811070e75 100644 --- a/libs/hwui/Android.mk +++ b/libs/hwui/Android.mk @@ -7,6 +7,7 @@ ifeq ($(USE_OPENGL_RENDERER),true) LOCAL_SRC_FILES:= \ utils/Blur.cpp \ utils/SortedListImpl.cpp \ + thread/TaskManager.cpp \ font/CacheTexture.cpp \ font/Font.cpp \ FontRenderer.cpp \ diff --git a/libs/hwui/Caches.h b/libs/hwui/Caches.h index ca699d566881..dc32a7ec63fa 100644 --- a/libs/hwui/Caches.h +++ b/libs/hwui/Caches.h @@ -25,6 +25,9 @@ #include <cutils/compiler.h> +#include "thread/TaskProcessor.h" +#include "thread/TaskManager.h" + #include "FontRenderer.h" #include "GammaFontRenderer.h" #include "TextureCache.h" @@ -278,6 +281,8 @@ public: GammaFontRenderer* fontRenderer; + TaskManager tasks; + Dither dither; Stencil stencil; diff --git a/libs/hwui/DisplayListOp.h b/libs/hwui/DisplayListOp.h index 105f45f7f27b..4e6b5525a914 100644 --- a/libs/hwui/DisplayListOp.h +++ b/libs/hwui/DisplayListOp.h @@ -1169,6 +1169,7 @@ public: break; } mLocalBounds.set(x, mY + metrics.fTop, x + length, mY + metrics.fBottom); + memset(&mPrecacheTransform.data[0], 0xff, 16 * sizeof(float)); } /* @@ -1179,9 +1180,11 @@ public: virtual void onDrawOpDeferred(OpenGLRenderer& renderer) { SkPaint* paint = getPaint(renderer); FontRenderer& fontRenderer = renderer.getCaches().fontRenderer->getFontRenderer(paint); - const bool pureTranslate = state.mMatrix.isPureTranslate(); - const mat4 transform = renderer.findBestFontTransform(state.mMatrix); - fontRenderer.precache(paint, mText, mCount, transform); + const mat4& transform = renderer.findBestFontTransform(state.mMatrix); + if (mPrecacheTransform != transform) { + fontRenderer.precache(paint, mText, mCount, transform); + mPrecacheTransform = transform; + } } virtual status_t applyDraw(OpenGLRenderer& renderer, Rect& dirty, uint32_t level, @@ -1210,6 +1213,7 @@ private: float mY; const float* mPositions; float mLength; + mat4 mPrecacheTransform; }; /////////////////////////////////////////////////////////////////////////////// diff --git a/libs/hwui/Matrix.h b/libs/hwui/Matrix.h index 7b7357edf963..75e280cf502b 100644 --- a/libs/hwui/Matrix.h +++ b/libs/hwui/Matrix.h @@ -93,6 +93,14 @@ public: return *this; } + friend bool operator==(const Matrix4& a, const Matrix4& b) { + return !memcmp(&a.data[0], &b.data[0], 16 * sizeof(float)); + } + + friend bool operator!=(const Matrix4& a, const Matrix4& b) { + return !(a == b); + } + void loadIdentity(); void load(const float* v); diff --git a/libs/hwui/PathCache.cpp b/libs/hwui/PathCache.cpp index 9e6ec8488ebf..fb687cd16ff5 100644 --- a/libs/hwui/PathCache.cpp +++ b/libs/hwui/PathCache.cpp @@ -31,69 +31,34 @@ namespace uirenderer { // Path precaching /////////////////////////////////////////////////////////////////////////////// -bool PathCache::PrecacheThread::threadLoop() { - mSignal.wait(); - Vector<Task> tasks; - { - Mutex::Autolock l(mLock); - tasks = mTasks; - mTasks.clear(); - } - - Caches& caches = Caches::getInstance(); - uint32_t maxSize = caches.maxTextureSize; - - ATRACE_BEGIN("pathPrecache"); - for (size_t i = 0; i < tasks.size(); i++) { - const Task& task = tasks.itemAt(i); - - float left, top, offset; - uint32_t width, height; - PathCache::computePathBounds(task.path, task.paint, left, top, offset, width, height); - - if (width <= maxSize && height <= maxSize) { - SkBitmap* bitmap = new SkBitmap(); - - PathTexture* texture = task.texture; - texture->left = left; - texture->top = top; - texture->offset = offset; - texture->width = width; - texture->height = height; - - PathCache::drawPath(task.path, task.paint, *bitmap, left, top, offset, width, height); - - texture->future()->produce(bitmap); - } else { - task.texture->future()->produce(NULL); - } - } - ATRACE_END(); - return true; +PathCache::PathProcessor::PathProcessor(Caches& caches): + TaskProcessor<SkBitmap*>(&caches.tasks), mMaxTextureSize(caches.maxTextureSize) { } -void PathCache::PrecacheThread::addTask(PathTexture* texture, SkPath* path, SkPaint* paint) { - if (!isRunning()) { - run("libhwui:pathPrecache", PRIORITY_DEFAULT); - } - - Task task; - task.texture = texture; - task.path = path; - task.paint = paint; - - Mutex::Autolock l(mLock); - mTasks.add(task); - mSignal.signal(); -} - -void PathCache::PrecacheThread::exit() { - { - Mutex::Autolock l(mLock); - mTasks.clear(); +void PathCache::PathProcessor::onProcess(const sp<Task<SkBitmap*> >& task) { + sp<PathTask> t = static_cast<PathTask* >(task.get()); + ATRACE_NAME("pathPrecache"); + + float left, top, offset; + uint32_t width, height; + PathCache::computePathBounds(t->path, t->paint, left, top, offset, width, height); + + PathTexture* texture = t->texture; + texture->left = left; + texture->top = top; + texture->offset = offset; + texture->width = width; + texture->height = height; + + if (width <= mMaxTextureSize && height <= mMaxTextureSize) { + SkBitmap* bitmap = new SkBitmap(); + PathCache::drawPath(t->path, t->paint, *bitmap, left, top, offset, width, height); + t->setResult(bitmap); + } else { + texture->width = 0; + texture->height = 0; + t->setResult(NULL); } - requestExit(); - mSignal.signal(); } /////////////////////////////////////////////////////////////////////////////// @@ -101,11 +66,10 @@ void PathCache::PrecacheThread::exit() { /////////////////////////////////////////////////////////////////////////////// PathCache::PathCache(): ShapeCache<PathCacheEntry>("path", - PROPERTY_PATH_CACHE_SIZE, DEFAULT_PATH_CACHE_SIZE), mThread(new PrecacheThread()) { + PROPERTY_PATH_CACHE_SIZE, DEFAULT_PATH_CACHE_SIZE) { } PathCache::~PathCache() { - mThread->exit(); } void PathCache::remove(SkPath* path) { @@ -165,17 +129,17 @@ PathTexture* PathCache::get(SkPath* path, SkPaint* paint) { } else { // A bitmap is attached to the texture, this means we need to // upload it as a GL texture - if (texture->future() != NULL) { + const sp<Task<SkBitmap*> >& task = texture->task(); + if (task != NULL) { // But we must first wait for the worker thread to be done // producing the bitmap, so let's wait - SkBitmap* bitmap = texture->future()->get(); + SkBitmap* bitmap = task->getResult(); if (bitmap) { addTexture(entry, bitmap, texture); - texture->clearFuture(); + texture->clearTask(); } else { - ALOGW("Path too large to be rendered into a texture (%dx%d)", - texture->width, texture->height); - texture->clearFuture(); + ALOGW("Path too large to be rendered into a texture"); + texture->clearTask(); texture = NULL; mCache.remove(entry); } @@ -189,6 +153,10 @@ PathTexture* PathCache::get(SkPath* path, SkPaint* paint) { } void PathCache::precache(SkPath* path, SkPaint* paint) { + if (!Caches::getInstance().tasks.canRunTasks()) { + return; + } + path = getSourcePath(path); PathCacheEntry entry(path, paint); @@ -205,7 +173,9 @@ void PathCache::precache(SkPath* path, SkPaint* paint) { if (generate) { // It is important to specify the generation ID so we do not // attempt to precache the same path several times - texture = createTexture(0.0f, 0.0f, 0.0f, 0, 0, path->getGenerationID(), true); + texture = createTexture(0.0f, 0.0f, 0.0f, 0, 0, path->getGenerationID()); + sp<PathTask> task = new PathTask(path, paint, texture); + texture->setTask(task); // During the precaching phase we insert path texture objects into // the cache that do not point to any GL texture. They are instead @@ -215,7 +185,11 @@ void PathCache::precache(SkPath* path, SkPaint* paint) { // asks for a path texture. This is also when the cache limit will // be enforced. mCache.put(entry, texture); - mThread->addTask(texture, path, paint); + + if (mProcessor == NULL) { + mProcessor = new PathProcessor(Caches::getInstance()); + } + mProcessor->add(task); } } diff --git a/libs/hwui/PathCache.h b/libs/hwui/PathCache.h index 1d28ecbf6a00..27031a5e04a7 100644 --- a/libs/hwui/PathCache.h +++ b/libs/hwui/PathCache.h @@ -23,6 +23,8 @@ #include "Debug.h" #include "ShapeCache.h" #include "thread/Signal.h" +#include "thread/Task.h" +#include "thread/TaskProcessor.h" class SkPaint; class SkPath; @@ -100,32 +102,33 @@ public: void precache(SkPath* path, SkPaint* paint); private: - class PrecacheThread: public Thread { + class PathTask: public Task<SkBitmap*> { public: - PrecacheThread(): mSignal(Condition::WAKE_UP_ONE) { } + PathTask(SkPath* path, SkPaint* paint, PathTexture* texture): + path(path), paint(paint), texture(texture) { + } - void addTask(PathTexture* texture, SkPath* path, SkPaint* paint); - void exit(); + ~PathTask() { + delete future()->get(); + } - private: - struct Task { - PathTexture* texture; - SkPath* path; - SkPaint* paint; - }; + SkPath* path; + SkPaint* paint; + PathTexture* texture; + }; - virtual bool threadLoop(); + class PathProcessor: public TaskProcessor<SkBitmap*> { + public: + PathProcessor(Caches& caches); + ~PathProcessor() { } - // Lock for the list of tasks - Mutex mLock; - Vector<Task> mTasks; + virtual void onProcess(const sp<Task<SkBitmap*> >& task); - // Signal used to wake up the thread when a new - // task is available in the list - mutable Signal mSignal; + private: + uint32_t mMaxTextureSize; }; - sp<PrecacheThread> mThread; + sp<PathProcessor> mProcessor; Vector<SkPath*> mGarbage; mutable Mutex mLock; }; // class PathCache diff --git a/libs/hwui/ShapeCache.h b/libs/hwui/ShapeCache.h index 67ae85b459ce..92314b029520 100644 --- a/libs/hwui/ShapeCache.h +++ b/libs/hwui/ShapeCache.h @@ -30,12 +30,11 @@ #include <utils/JenkinsHash.h> #include <utils/LruCache.h> #include <utils/Trace.h> -#include <utils/CallStack.h> #include "Debug.h" #include "Properties.h" #include "Texture.h" -#include "thread/Future.h" +#include "thread/Task.h" namespace android { namespace uirenderer { @@ -62,14 +61,8 @@ struct PathTexture: public Texture { PathTexture(): Texture() { } - PathTexture(bool hasFuture): Texture() { - if (hasFuture) { - mFuture = new Future<SkBitmap*>(); - } - } - ~PathTexture() { - clearFuture(); + clearTask(); } /** @@ -85,19 +78,22 @@ struct PathTexture: public Texture { */ float offset; - sp<Future<SkBitmap*> > future() const { - return mFuture; + sp<Task<SkBitmap*> > task() const { + return mTask; } - void clearFuture() { - if (mFuture != NULL) { - delete mFuture->get(); - mFuture.clear(); + void setTask(const sp<Task<SkBitmap*> >& task) { + mTask = task; + } + + void clearTask() { + if (mTask != NULL) { + mTask.clear(); } } private: - sp<Future<SkBitmap*> > mFuture; + sp<Task<SkBitmap*> > mTask; }; // struct PathTexture /** @@ -551,8 +547,8 @@ protected: } static PathTexture* createTexture(float left, float top, float offset, - uint32_t width, uint32_t height, uint32_t id, bool hasFuture = false) { - PathTexture* texture = new PathTexture(hasFuture); + uint32_t width, uint32_t height, uint32_t id) { + PathTexture* texture = new PathTexture(); texture->left = left; texture->top = top; texture->offset = offset; @@ -721,7 +717,9 @@ void ShapeCache<Entry>::removeTexture(PathTexture* texture) { ALOGD("Shape %s deleted, size = %d", mName, size); } - glDeleteTextures(1, &texture->id); + if (texture->id) { + glDeleteTextures(1, &texture->id); + } delete texture; } } diff --git a/libs/hwui/font/Font.cpp b/libs/hwui/font/Font.cpp index c932087fe4c6..02c1aa191e5c 100644 --- a/libs/hwui/font/Font.cpp +++ b/libs/hwui/font/Font.cpp @@ -15,10 +15,12 @@ */ #define LOG_TAG "OpenGLRenderer" +#define ATRACE_TAG ATRACE_TAG_VIEW #include <cutils/compiler.h> #include <utils/JenkinsHash.h> +#include <utils/Trace.h> #include <SkGlyph.h> #include <SkUtils.h> @@ -261,11 +263,8 @@ void Font::drawCachedGlyph(CachedGlyphInfo* glyph, float x, float hOffset, float } CachedGlyphInfo* Font::getCachedGlyph(SkPaint* paint, glyph_t textUnit, bool precaching) { - CachedGlyphInfo* cachedGlyph = NULL; - ssize_t index = mCachedGlyphs.indexOfKey(textUnit); - if (index >= 0) { - cachedGlyph = mCachedGlyphs.valueAt(index); - + CachedGlyphInfo* cachedGlyph = mCachedGlyphs.valueFor(textUnit); + if (cachedGlyph) { // Is the glyph still in texture cache? if (!cachedGlyph->mIsValid) { const SkGlyph& skiaGlyph = GET_METRICS(paint, textUnit, @@ -346,11 +345,13 @@ void Font::measure(SkPaint* paint, const char* text, uint32_t start, uint32_t le } void Font::precache(SkPaint* paint, const char* text, int numGlyphs) { + ATRACE_NAME("precacheText"); + if (numGlyphs == 0 || text == NULL) { return; } - int glyphsCount = 0; + int glyphsCount = 0; while (glyphsCount < numGlyphs) { glyph_t glyph = GET_GLYPH(text); @@ -360,7 +361,6 @@ void Font::precache(SkPaint* paint, const char* text, int numGlyphs) { } CachedGlyphInfo* cachedGlyph = getCachedGlyph(paint, glyph, true); - glyphsCount++; } } diff --git a/libs/hwui/thread/Future.h b/libs/hwui/thread/Future.h index 340fec79cabb..a3ff3bcc3698 100644 --- a/libs/hwui/thread/Future.h +++ b/libs/hwui/thread/Future.h @@ -24,7 +24,7 @@ namespace android { namespace uirenderer { -template<class T> +template<typename T> class Future: public LightRefBase<Future<T> > { public: Future(Condition::WakeUpType type = Condition::WAKE_UP_ONE): mBarrier(type), mResult() { } diff --git a/libs/hwui/thread/Task.h b/libs/hwui/thread/Task.h new file mode 100644 index 000000000000..9a211a238bbc --- /dev/null +++ b/libs/hwui/thread/Task.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_HWUI_TASK_H +#define ANDROID_HWUI_TASK_H + +#define ATRACE_TAG ATRACE_TAG_VIEW + +#include <utils/RefBase.h> +#include <utils/Trace.h> + +#include "Future.h" + +namespace android { +namespace uirenderer { + +class TaskBase: public RefBase { +public: + TaskBase() { } + virtual ~TaskBase() { } +}; + +template<typename T> +class Task: public TaskBase { +public: + Task(): mFuture(new Future<T>()) { } + virtual ~Task() { } + + T getResult() const { + ATRACE_NAME("waitForTask"); + return mFuture->get(); + } + + void setResult(T result) { + mFuture->produce(result); + } + +protected: + const sp<Future<T> >& future() const { + return mFuture; + } + +private: + sp<Future<T> > mFuture; +}; + +}; // namespace uirenderer +}; // namespace android + +#endif // ANDROID_HWUI_TASK_H diff --git a/libs/hwui/thread/TaskManager.cpp b/libs/hwui/thread/TaskManager.cpp new file mode 100644 index 000000000000..ce6c8c04483d --- /dev/null +++ b/libs/hwui/thread/TaskManager.cpp @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <sys/sysinfo.h> + +#include "Task.h" +#include "TaskProcessor.h" +#include "TaskManager.h" + +namespace android { +namespace uirenderer { + +/////////////////////////////////////////////////////////////////////////////// +// Manager +/////////////////////////////////////////////////////////////////////////////// + +TaskManager::TaskManager() { + // Get the number of available CPUs. This value does not change over time. + int cpuCount = sysconf(_SC_NPROCESSORS_ONLN); + + for (int i = 0; i < cpuCount / 2; i++) { + String8 name; + name.appendFormat("hwuiTask%d", i + 1); + mThreads.add(new WorkerThread(name)); + } +} + +TaskManager::~TaskManager() { + for (size_t i = 0; i < mThreads.size(); i++) { + mThreads[i]->exit(); + } +} + +bool TaskManager::canRunTasks() const { + return mThreads.size() > 0; +} + +bool TaskManager::addTaskBase(const sp<TaskBase>& task, const sp<TaskProcessorBase>& processor) { + if (mThreads.size() > 0) { + TaskWrapper wrapper(task, processor); + + size_t minQueueSize = INT_MAX; + sp<WorkerThread> thread; + + for (size_t i = 0; i < mThreads.size(); i++) { + if (mThreads[i]->getTaskCount() < minQueueSize) { + thread = mThreads[i]; + minQueueSize = mThreads[i]->getTaskCount(); + } + } + + return thread->addTask(wrapper); + } + return false; +} + +/////////////////////////////////////////////////////////////////////////////// +// Thread +/////////////////////////////////////////////////////////////////////////////// + +bool TaskManager::WorkerThread::threadLoop() { + mSignal.wait(); + Vector<TaskWrapper> tasks; + { + Mutex::Autolock l(mLock); + tasks = mTasks; + mTasks.clear(); + } + + for (size_t i = 0; i < tasks.size(); i++) { + const TaskWrapper& task = tasks.itemAt(i); + task.mProcessor->process(task.mTask); + } + + return true; +} + +bool TaskManager::WorkerThread::addTask(TaskWrapper task) { + if (!isRunning()) { + run(mName.string(), PRIORITY_DEFAULT); + } + + Mutex::Autolock l(mLock); + ssize_t index = mTasks.add(task); + mSignal.signal(); + + return index >= 0; +} + +size_t TaskManager::WorkerThread::getTaskCount() const { + Mutex::Autolock l(mLock); + return mTasks.size(); +} + +void TaskManager::WorkerThread::exit() { + { + Mutex::Autolock l(mLock); + mTasks.clear(); + } + requestExit(); + mSignal.signal(); +} + +}; // namespace uirenderer +}; // namespace android diff --git a/libs/hwui/thread/TaskManager.h b/libs/hwui/thread/TaskManager.h new file mode 100644 index 000000000000..bc86062819cc --- /dev/null +++ b/libs/hwui/thread/TaskManager.h @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_HWUI_TASK_MANAGER_H +#define ANDROID_HWUI_TASK_MANAGER_H + +#include <utils/Mutex.h> +#include <utils/String8.h> +#include <utils/Thread.h> +#include <utils/Vector.h> + +#include "Signal.h" + +namespace android { +namespace uirenderer { + +template <typename T> +class Task; +class TaskBase; + +template <typename T> +class TaskProcessor; +class TaskProcessorBase; + +class TaskManager { +public: + TaskManager(); + ~TaskManager(); + + /** + * Returns true if this task manager can run tasks, + * false otherwise. This method will typically return + * true on a single CPU core device. + */ + bool canRunTasks() const; + +private: + template <typename T> + friend class TaskProcessor; + + template<typename T> + bool addTask(const sp<Task<T> >& task, const sp<TaskProcessor<T> >& processor) { + return addTaskBase(sp<TaskBase>(task), sp<TaskProcessorBase>(processor)); + } + + bool addTaskBase(const sp<TaskBase>& task, const sp<TaskProcessorBase>& processor); + + struct TaskWrapper { + TaskWrapper(): mTask(), mProcessor() { } + + TaskWrapper(const sp<TaskBase>& task, const sp<TaskProcessorBase>& processor): + mTask(task), mProcessor(processor) { + } + + sp<TaskBase> mTask; + sp<TaskProcessorBase> mProcessor; + }; + + class WorkerThread: public Thread { + public: + WorkerThread(const String8 name): mSignal(Condition::WAKE_UP_ONE), mName(name) { } + + bool addTask(TaskWrapper task); + size_t getTaskCount() const; + void exit(); + + private: + virtual bool threadLoop(); + + // Lock for the list of tasks + mutable Mutex mLock; + Vector<TaskWrapper> mTasks; + + // Signal used to wake up the thread when a new + // task is available in the list + mutable Signal mSignal; + + const String8 mName; + }; + + Vector<sp<WorkerThread> > mThreads; +}; + +}; // namespace uirenderer +}; // namespace android + +#endif // ANDROID_HWUI_TASK_MANAGER_H diff --git a/libs/hwui/thread/TaskProcessor.h b/libs/hwui/thread/TaskProcessor.h new file mode 100644 index 000000000000..d1269f0bd55a --- /dev/null +++ b/libs/hwui/thread/TaskProcessor.h @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_HWUI_TASK_PROCESSOR_H +#define ANDROID_HWUI_TASK_PROCESSOR_H + +#include <utils/RefBase.h> + +#include "Task.h" +#include "TaskManager.h" + +namespace android { +namespace uirenderer { + +class TaskProcessorBase: public RefBase { +public: + TaskProcessorBase() { } + virtual ~TaskProcessorBase() { }; + +private: + friend class TaskManager; + + virtual void process(const sp<TaskBase>& task) = 0; +}; + +template<typename T> +class TaskProcessor: public TaskProcessorBase { +public: + TaskProcessor(TaskManager* manager): mManager(manager) { } + virtual ~TaskProcessor() { } + + bool add(const sp<Task<T> >& task); + + virtual void onProcess(const sp<Task<T> >& task) = 0; + +private: + virtual void process(const sp<TaskBase>& task) { + sp<Task<T> > realTask = static_cast<Task<T>* >(task.get()); + // This is the right way to do it but sp<> doesn't play nice + // sp<Task<T> > realTask = static_cast<sp<Task<T> > >(task); + onProcess(realTask); + } + + TaskManager* mManager; +}; + +template<typename T> +bool TaskProcessor<T>::add(const sp<Task<T> >& task) { + if (mManager) { + sp<TaskProcessor<T> > self(this); + return mManager->addTask(task, self); + } + return false; +} + +}; // namespace uirenderer +}; // namespace android + +#endif // ANDROID_HWUI_TASK_PROCESSOR_H diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java index 882a6350ae87..f0a5c28e8cfb 100644 --- a/media/java/android/media/AudioService.java +++ b/media/java/android/media/AudioService.java @@ -1948,8 +1948,13 @@ public class AudioService extends IAudioService.Stub implements OnFinished { !mBootCompleted) { return; } - final long ident = Binder.clearCallingIdentity(); ScoClient client = getScoClient(cb, true); + // The calling identity must be cleared before calling ScoClient.incCount(). + // inCount() calls requestScoState() which in turn can call BluetoothHeadset APIs + // and this must be done on behalf of system server to make sure permissions are granted. + // The caller identity must be cleared after getScoClient() because it is needed if a new + // client is created. + final long ident = Binder.clearCallingIdentity(); client.incCount(); Binder.restoreCallingIdentity(ident); } @@ -1960,8 +1965,11 @@ public class AudioService extends IAudioService.Stub implements OnFinished { !mBootCompleted) { return; } - final long ident = Binder.clearCallingIdentity(); ScoClient client = getScoClient(cb, false); + // The calling identity must be cleared before calling ScoClient.decCount(). + // decCount() calls requestScoState() which in turn can call BluetoothHeadset APIs + // and this must be done on behalf of system server to make sure permissions are granted. + final long ident = Binder.clearCallingIdentity(); if (client != null) { client.decCount(); } diff --git a/media/jni/android_media_MediaRecorder.cpp b/media/jni/android_media_MediaRecorder.cpp index 4ebbbde1fd72..98885911b86f 100644 --- a/media/jni/android_media_MediaRecorder.cpp +++ b/media/jni/android_media_MediaRecorder.cpp @@ -327,7 +327,7 @@ android_media_MediaRecorder_prepare(JNIEnv *env, jobject thiz) } ALOGI("prepare: surface=%p", native_surface.get()); - if (process_media_recorder_call(env, mr->setPreviewSurface(native_surface), "java/lang/RuntimeException", "setPreviewSurface failed.")) { + if (process_media_recorder_call(env, mr->setPreviewSurface(native_surface->getIGraphicBufferProducer()), "java/lang/RuntimeException", "setPreviewSurface failed.")) { return; } } diff --git a/packages/Shell/Android.mk b/packages/Shell/Android.mk index f993ab542fa1..fc4c0f57475d 100644 --- a/packages/Shell/Android.mk +++ b/packages/Shell/Android.mk @@ -5,6 +5,8 @@ LOCAL_MODULE_TAGS := optional LOCAL_SRC_FILES := $(call all-subdir-java-files) +LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4 + LOCAL_PACKAGE_NAME := Shell LOCAL_CERTIFICATE := platform diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index b42db453e69e..ffb4c2026291 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -68,7 +68,32 @@ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" /> <uses-permission android:name="android.permission.MANAGE_USERS" /> <uses-permission android:name="android.permission.BLUETOOTH_STACK" /> - - <application android:hasCode="false" android:label="@string/app_label"> + <uses-permission android:name="android.permission.GET_ACCOUNTS" /> + + <application android:label="@string/app_label"> + <provider + android:name="android.support.v4.content.FileProvider" + android:authorities="com.android.shell" + android:grantUriPermissions="true" + android:exported="false"> + <meta-data + android:name="android.support.FILE_PROVIDER_PATHS" + android:resource="@xml/file_provider_paths" /> + </provider> + + <activity + android:name=".BugreportWarningActivity" + android:theme="@*android:style/Theme.Holo.Dialog.Alert" + android:finishOnCloseSystemDialogs="true" + android:excludeFromRecents="true" + android:exported="false" /> + + <receiver + android:name=".BugreportReceiver" + android:permission="android.permission.DUMP"> + <intent-filter> + <action android:name="android.intent.action.BUGREPORT_FINISHED" /> + </intent-filter> + </receiver> </application> </manifest> diff --git a/packages/Shell/res/layout/confirm_repeat.xml b/packages/Shell/res/layout/confirm_repeat.xml new file mode 100644 index 000000000000..dc250d68754d --- /dev/null +++ b/packages/Shell/res/layout/confirm_repeat.xml @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2013 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingStart="16dip" + android:paddingEnd="16dip" + android:paddingTop="8dip" + android:paddingBottom="16dip" + android:orientation="vertical"> + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/bugreport_confirm" + android:paddingBottom="16dip" + style="?android:attr/textAppearanceMedium" /> + <CheckBox + android:id="@android:id/checkbox" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/bugreport_confirm_repeat" /> +</LinearLayout> diff --git a/packages/Shell/res/values/strings.xml b/packages/Shell/res/values/strings.xml index 50610d586187..e5606c78ec06 100644 --- a/packages/Shell/res/values/strings.xml +++ b/packages/Shell/res/values/strings.xml @@ -16,4 +16,14 @@ <resources> <string name="app_label">Shell</string> + + <!-- Title of notification indicating a bugreport has been successfully captured. [CHAR LIMIT=50] --> + <string name="bugreport_finished_title">Bug report captured</string> + <!-- Text of notification indicating that touching will share the captured bugreport. [CHAR LIMIT=100] --> + <string name="bugreport_finished_text">Touch to share your bug report</string> + + <!-- Body of dialog informing user about contents of a bugreport. [CHAR LIMIT=NONE] --> + <string name="bugreport_confirm">Bug reports contain data from the system\'s various log files, including personal and private information. Only share bug reports with apps and people you trust.</string> + <!-- Checkbox that indicates this dialog should be shown again when the next bugreport is taken. [CHAR LIMIT=50] --> + <string name="bugreport_confirm_repeat">Show this message next time</string> </resources> diff --git a/packages/Shell/res/xml/file_provider_paths.xml b/packages/Shell/res/xml/file_provider_paths.xml new file mode 100644 index 000000000000..225c7571e7e2 --- /dev/null +++ b/packages/Shell/res/xml/file_provider_paths.xml @@ -0,0 +1,3 @@ +<paths xmlns:android="http://schemas.android.com/apk/res/android"> + <files-path name="bugreports" path="bugreports/" /> +</paths> diff --git a/packages/Shell/src/com/android/shell/BugreportPrefs.java b/packages/Shell/src/com/android/shell/BugreportPrefs.java new file mode 100644 index 000000000000..3748e89c0598 --- /dev/null +++ b/packages/Shell/src/com/android/shell/BugreportPrefs.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.shell; + +import android.content.Context; +import android.content.SharedPreferences; + +/** + * Preferences related to bug reports. + */ +public class BugreportPrefs { + private static final String PREFS_BUGREPORT = "bugreports"; + + private static final String KEY_WARNING_STATE = "warning-state"; + + public static final int STATE_UNKNOWN = 0; + public static final int STATE_SHOW = 1; + public static final int STATE_HIDE = 2; + + public static int getWarningState(Context context, int def) { + final SharedPreferences prefs = context.getSharedPreferences( + PREFS_BUGREPORT, Context.MODE_PRIVATE); + return prefs.getInt(KEY_WARNING_STATE, def); + } + + public static void setWarningState(Context context, int value) { + final SharedPreferences prefs = context.getSharedPreferences( + PREFS_BUGREPORT, Context.MODE_PRIVATE); + prefs.edit().putInt(KEY_WARNING_STATE, value).apply(); + } +} diff --git a/packages/Shell/src/com/android/shell/BugreportReceiver.java b/packages/Shell/src/com/android/shell/BugreportReceiver.java new file mode 100644 index 000000000000..3b1ebf49490b --- /dev/null +++ b/packages/Shell/src/com/android/shell/BugreportReceiver.java @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.shell; + +import static com.android.shell.BugreportPrefs.STATE_SHOW; +import static com.android.shell.BugreportPrefs.getWarningState; + +import android.accounts.Account; +import android.accounts.AccountManager; +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.SystemProperties; +import android.support.v4.content.FileProvider; +import android.util.Log; +import android.util.Patterns; + +import com.google.android.collect.Lists; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; + +/** + * Receiver that handles finished bugreports, usually by attaching them to an + * {@link Intent#ACTION_SEND}. + */ +public class BugreportReceiver extends BroadcastReceiver { + private static final String TAG = "Shell"; + + private static final String AUTHORITY = "com.android.shell"; + + private static final String EXTRA_BUGREPORT = "android.intent.extra.BUGREPORT"; + private static final String EXTRA_SCREENSHOT = "android.intent.extra.SCREENSHOT"; + + /** + * Number of bugreports to retain before deleting the oldest; 4 reports and + * 4 screenshots are roughly 17MB of disk space. + */ + private static final int NUM_OLD_FILES = 8; + + @Override + public void onReceive(Context context, Intent intent) { + final File bugreportFile = getFileExtra(intent, EXTRA_BUGREPORT); + final File screenshotFile = getFileExtra(intent, EXTRA_SCREENSHOT); + + // Files are kept on private storage, so turn into Uris that we can + // grant temporary permissions for. + final Uri bugreportUri = FileProvider.getUriForFile(context, AUTHORITY, bugreportFile); + final Uri screenshotUri = FileProvider.getUriForFile(context, AUTHORITY, screenshotFile); + + Intent sendIntent = buildSendIntent(context, bugreportUri, screenshotUri); + Intent notifIntent; + + // Send through warning dialog by default + if (getWarningState(context, STATE_SHOW) == STATE_SHOW) { + notifIntent = buildWarningIntent(context, sendIntent); + } else { + notifIntent = sendIntent; + } + notifIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + + final Notification.Builder builder = new Notification.Builder(context); + builder.setSmallIcon(com.android.internal.R.drawable.stat_sys_adb); + builder.setContentTitle(context.getString(R.string.bugreport_finished_title)); + builder.setContentText(context.getString(R.string.bugreport_finished_text)); + builder.setContentIntent(PendingIntent.getActivity( + context, 0, notifIntent, PendingIntent.FLAG_CANCEL_CURRENT)); + builder.setAutoCancel(true); + NotificationManager.from(context).notify(TAG, 0, builder.build()); + + // Clean up older bugreports in background + final PendingResult result = goAsync(); + new AsyncTask<Void, Void, Void>() { + @Override + protected Void doInBackground(Void... params) { + deleteOlderFiles(bugreportFile.getParentFile(), NUM_OLD_FILES); + result.finish(); + return null; + } + }.execute(); + } + + private static Intent buildWarningIntent(Context context, Intent sendIntent) { + final Intent intent = new Intent(context, BugreportWarningActivity.class); + intent.putExtra(Intent.EXTRA_INTENT, sendIntent); + return intent; + } + + /** + * Build {@link Intent} that can be used to share the given bugreport. + */ + private static Intent buildSendIntent(Context context, Uri bugreportUri, Uri screenshotUri) { + final Intent intent = new Intent(Intent.ACTION_SEND_MULTIPLE); + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + intent.addCategory(Intent.CATEGORY_DEFAULT); + intent.setType("application/vnd.android.bugreport"); + + intent.putExtra(Intent.EXTRA_SUBJECT, bugreportUri.getLastPathSegment()); + intent.putExtra(Intent.EXTRA_TEXT, SystemProperties.get("ro.build.description")); + + final ArrayList<Uri> attachments = Lists.newArrayList(bugreportUri, screenshotUri); + intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, attachments); + + final Account sendToAccount = findSendToAccount(context); + if (sendToAccount != null) { + intent.putExtra(Intent.EXTRA_EMAIL, new String[] { sendToAccount.name }); + } + + return intent; + } + + /** + * Find the best matching {@link Account} based on build properties. + */ + private static Account findSendToAccount(Context context) { + final AccountManager am = (AccountManager) context.getSystemService( + Context.ACCOUNT_SERVICE); + + String preferredDomain = SystemProperties.get("sendbug.preferred.domain"); + if (!preferredDomain.startsWith("@")) { + preferredDomain = "@" + preferredDomain; + } + + final Account[] accounts = am.getAccounts(); + Account foundAccount = null; + for (Account account : accounts) { + if (Patterns.EMAIL_ADDRESS.matcher(account.name).matches()) { + if (!preferredDomain.isEmpty()) { + // if we have a preferred domain and it matches, return; otherwise keep + // looking + if (account.name.endsWith(preferredDomain)) { + return account; + } else { + foundAccount = account; + } + // if we don't have a preferred domain, just return since it looks like + // an email address + } else { + return account; + } + } + } + return foundAccount; + } + + /** + * Delete the oldest files in given directory until only the requested + * number remain. + */ + private static void deleteOlderFiles(File dir, int retainNum) { + final File[] files = dir.listFiles(); + if (files == null) return; + + Arrays.sort(files, new ModifiedComparator()); + for (int i = retainNum; i < files.length; i++) { + Log.d(TAG, "Deleting old file " + files[i]); + files[i].delete(); + } + } + + private static class ModifiedComparator implements Comparator<File> { + @Override + public int compare(File lhs, File rhs) { + return (int) (rhs.lastModified() - lhs.lastModified()); + } + } + + private static File getFileExtra(Intent intent, String key) { + final String path = intent.getStringExtra(key); + if (path != null) { + return new File(path); + } else { + return null; + } + } + +} diff --git a/packages/Shell/src/com/android/shell/BugreportWarningActivity.java b/packages/Shell/src/com/android/shell/BugreportWarningActivity.java new file mode 100644 index 000000000000..a1d879af2025 --- /dev/null +++ b/packages/Shell/src/com/android/shell/BugreportWarningActivity.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.shell; + +import static com.android.shell.BugreportPrefs.STATE_HIDE; +import static com.android.shell.BugreportPrefs.STATE_SHOW; +import static com.android.shell.BugreportPrefs.STATE_UNKNOWN; +import static com.android.shell.BugreportPrefs.getWarningState; +import static com.android.shell.BugreportPrefs.setWarningState; + +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.content.Intent; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.widget.CheckBox; + +import com.android.internal.app.AlertActivity; +import com.android.internal.app.AlertController; + +/** + * Dialog that warns about contents of a bugreport. + */ +public class BugreportWarningActivity extends AlertActivity + implements DialogInterface.OnClickListener { + + private Intent mSendIntent; + private CheckBox mConfirmRepeat; + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + + mSendIntent = getIntent().getParcelableExtra(Intent.EXTRA_INTENT); + + // We need to touch the extras to unpack them so they get migrated to + // ClipData correctly. + mSendIntent.hasExtra(Intent.EXTRA_STREAM); + + final AlertController.AlertParams ap = mAlertParams; + ap.mView = LayoutInflater.from(this).inflate(R.layout.confirm_repeat, null); + ap.mPositiveButtonText = getString(android.R.string.ok); + ap.mNegativeButtonText = getString(android.R.string.cancel); + ap.mPositiveButtonListener = this; + ap.mNegativeButtonListener = this; + + mConfirmRepeat = (CheckBox) ap.mView.findViewById(android.R.id.checkbox); + mConfirmRepeat.setChecked(getWarningState(this, STATE_UNKNOWN) == STATE_SHOW); + + setupAlert(); + } + + @Override + public void onClick(DialogInterface dialog, int which) { + if (which == AlertDialog.BUTTON_POSITIVE) { + // Remember confirm state, and launch target + setWarningState(this, mConfirmRepeat.isChecked() ? STATE_SHOW : STATE_HIDE); + startActivity(mSendIntent); + } + + finish(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java index f3eecf2dda99..627235f1e03b 100644 --- a/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java +++ b/packages/SystemUI/src/com/android/systemui/ImageWallpaper.java @@ -627,10 +627,26 @@ public class ImageWallpaper extends WallpaperService { } mEglContext = createContext(mEgl, mEglDisplay, mEglConfig); + if (mEglContext == EGL_NO_CONTEXT) { + throw new RuntimeException("createContext failed " + + GLUtils.getEGLErrorString(mEgl.eglGetError())); + } + + int attribs[] = { + EGL_WIDTH, 1, + EGL_HEIGHT, 1, + EGL_NONE + }; + EGLSurface tmpSurface = mEgl.eglCreatePbufferSurface(mEglDisplay, mEglConfig, attribs); + mEgl.eglMakeCurrent(mEglDisplay, tmpSurface, tmpSurface, mEglContext); int[] maxSize = new int[1]; Rect frame = surfaceHolder.getSurfaceFrame(); glGetIntegerv(GL_MAX_TEXTURE_SIZE, maxSize, 0); + + mEgl.eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + mEgl.eglDestroySurface(mEglDisplay, tmpSurface); + if(frame.width() > maxSize[0] || frame.height() > maxSize[0]) { mEgl.eglDestroyContext(mEglDisplay, mEglContext); mEgl.eglTerminate(mEglDisplay); @@ -639,9 +655,8 @@ public class ImageWallpaper extends WallpaperService { maxSize[0] + "x" + maxSize[0]); return false; } - + mEglSurface = mEgl.eglCreateWindowSurface(mEglDisplay, mEglConfig, surfaceHolder, null); - if (mEglSurface == null || mEglSurface == EGL_NO_SURFACE) { int error = mEgl.eglGetError(); if (error == EGL_BAD_NATIVE_WINDOW || error == EGL_BAD_ALLOC) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java index f0b1d7eac152..9f5457339e88 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -407,6 +407,7 @@ public class PhoneStatusBar extends BaseStatusBar { mSystemIconArea = (LinearLayout) mStatusBarView.findViewById(R.id.system_icon_area); mStatusIcons = (LinearLayout)mStatusBarView.findViewById(R.id.statusIcons); mNotificationIcons = (IconMerger)mStatusBarView.findViewById(R.id.notificationIcons); + mMoreIcon = mStatusBarView.findViewById(R.id.moreIcon); mNotificationIcons.setOverflowIndicator(mMoreIcon); mStatusBarContents = (LinearLayout)mStatusBarView.findViewById(R.id.status_bar_contents); mTickerView = mStatusBarView.findViewById(R.id.ticker); diff --git a/policy/src/com/android/internal/policy/impl/keyguard/EmergencyButton.java b/policy/src/com/android/internal/policy/impl/keyguard/EmergencyButton.java index cd7324c79cfa..c68bab53e0d5 100644 --- a/policy/src/com/android/internal/policy/impl/keyguard/EmergencyButton.java +++ b/policy/src/com/android/internal/policy/impl/keyguard/EmergencyButton.java @@ -20,6 +20,7 @@ import android.content.Context; import android.content.Intent; import android.os.PowerManager; import android.os.SystemClock; +import android.os.UserHandle; import android.telephony.TelephonyManager; import android.util.AttributeSet; import android.view.View; @@ -104,7 +105,8 @@ public class EmergencyButton extends Button { Intent intent = new Intent(ACTION_EMERGENCY_DIAL); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); - getContext().startActivity(intent); + getContext().startActivityAsUser(intent, + new UserHandle(mLockPatternUtils.getCurrentUser())); } } diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardHostView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardHostView.java index 06f06b52639a..78d7caa409c6 100644 --- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardHostView.java +++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardHostView.java @@ -310,9 +310,7 @@ public class KeyguardHostView extends KeyguardViewBase { mKeyguardSelectorView = (KeyguardSelectorView) findViewById(R.id.keyguard_selector_view); mViewStateManager.setSecurityViewContainer(mSecurityViewContainer); - if (!(mContext instanceof Activity)) { - setSystemUiVisibility(getSystemUiVisibility() | View.STATUS_BAR_DISABLE_BACK); - } + setBackButtonEnabled(false); addDefaultWidgets(); @@ -329,6 +327,13 @@ public class KeyguardHostView extends KeyguardViewBase { updateSecurityViews(); } + private void setBackButtonEnabled(boolean enabled) { + if (mContext instanceof Activity) return; // always enabled in activity mode + setSystemUiVisibility(enabled ? + getSystemUiVisibility() & ~View.STATUS_BAR_DISABLE_BACK : + getSystemUiVisibility() | View.STATUS_BAR_DISABLE_BACK); + } + private boolean shouldEnableAddWidget() { return numWidgets() < MAX_WIDGETS && mUserSetupCompleted; } @@ -907,6 +912,10 @@ public class KeyguardHostView extends KeyguardViewBase { // Discard current runnable if we're switching back to the selector view setOnDismissAction(null); } + if (securityMode == SecurityMode.Account && !mLockPatternUtils.isPermanentlyLocked()) { + // we're showing account as a backup, provide a way to get back to primary + setBackButtonEnabled(true); + } mCurrentSecuritySelection = securityMode; } @@ -1579,6 +1588,12 @@ public class KeyguardHostView extends KeyguardViewBase { } public boolean handleBackKey() { + if (mCurrentSecuritySelection == SecurityMode.Account) { + // go back to primary screen and re-disable back + setBackButtonEnabled(false); + showPrimarySecurityScreen(false /*turningOff*/); + return true; + } if (mCurrentSecuritySelection != SecurityMode.None) { mCallback.dismiss(false); return true; diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewManager.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewManager.java index 8562f0c20a5c..30c95fba71b5 100644 --- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewManager.java +++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewManager.java @@ -200,6 +200,9 @@ public class KeyguardViewManager { stretch, stretch, type, flags, PixelFormat.TRANSLUCENT); lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; lp.windowAnimations = com.android.internal.R.style.Animation_LockScreen; + lp.screenOrientation = enableScreenRotation ? + ActivityInfo.SCREEN_ORIENTATION_USER : ActivityInfo.SCREEN_ORIENTATION_NOSENSOR; + if (ActivityManager.isHighEndGfx()) { lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; lp.privateFlags |= diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java index 6ba5cffcaa66..14841af70f43 100644 --- a/services/java/com/android/server/InputMethodManagerService.java +++ b/services/java/com/android/server/InputMethodManagerService.java @@ -1199,7 +1199,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mCurId = info.getId(); mCurToken = new Binder(); try { - if (DEBUG) Slog.v(TAG, "Adding window token: " + mCurToken); + if (true || DEBUG) Slog.v(TAG, "Adding window token: " + mCurToken); mIWindowManager.addWindowToken(mCurToken, WindowManager.LayoutParams.TYPE_INPUT_METHOD); } catch (RemoteException e) { @@ -1237,13 +1237,24 @@ public class InputMethodManagerService extends IInputMethodManager.Stub public void onServiceConnected(ComponentName name, IBinder service) { synchronized (mMethodMap) { if (mCurIntent != null && name.equals(mCurIntent.getComponent())) { + IInputMethod prevMethod = mCurMethod; mCurMethod = IInputMethod.Stub.asInterface(service); if (mCurToken == null) { Slog.w(TAG, "Service connected without a token!"); unbindCurrentMethodLocked(false, false); return; } - if (DEBUG) Slog.v(TAG, "Initiating attach with token: " + mCurToken); + // Remove messages relating to the previous service. Otherwise WindowManagerService + // will throw a BadTokenException because the old token is being removed. + if (prevMethod != null) { + try { + prevMethod.removeSoftInputMessages(); + } catch (RemoteException e) { + } + } + mCaller.removeMessages(MSG_SHOW_SOFT_INPUT); + mCaller.removeMessages(MSG_HIDE_SOFT_INPUT); + if (true || DEBUG) Slog.v(TAG, "Initiating attach with token: " + mCurToken); executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO( MSG_ATTACH_TOKEN, mCurMethod, mCurToken)); if (mCurClient != null) { @@ -1689,7 +1700,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } - if (DEBUG) Slog.v(TAG, "Client requesting input be shown"); + if (true || DEBUG) Slog.v(TAG, "Client requesting input be shown"); return showCurrentInputLocked(flags, resultReceiver); } } finally { @@ -1713,6 +1724,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub boolean res = false; if (mCurMethod != null) { + if (true ||DEBUG) Slog.d(TAG, "showCurrentInputLocked: mCurToken=" + mCurToken, + new RuntimeException("here").fillInStackTrace()); executeOrSendMessage(mCurMethod, mCaller.obtainMessageIOO( MSG_SHOW_SOFT_INPUT, getImeShowFlags(), mCurMethod, resultReceiver)); @@ -1784,12 +1797,12 @@ public class InputMethodManagerService extends IInputMethodManager.Stub boolean hideCurrentInputLocked(int flags, ResultReceiver resultReceiver) { if ((flags&InputMethodManager.HIDE_IMPLICIT_ONLY) != 0 && (mShowExplicitlyRequested || mShowForced)) { - if (DEBUG) Slog.v(TAG, + if (true ||DEBUG) Slog.v(TAG, "Not hiding: explicit show not cancelled by non-explicit hide"); return false; } if (mShowForced && (flags&InputMethodManager.HIDE_NOT_ALWAYS) != 0) { - if (DEBUG) Slog.v(TAG, + if (true ||DEBUG) Slog.v(TAG, "Not hiding: forced show not cancelled by not-always hide"); return false; } @@ -2301,8 +2314,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub case MSG_SHOW_SOFT_INPUT: args = (SomeArgs)msg.obj; try { - ((IInputMethod)args.arg1).showSoftInput(msg.arg1, - (ResultReceiver)args.arg2); + if (true || DEBUG) Slog.v(TAG, "Calling " + args.arg1 + ".showSoftInput(" + + msg.arg1 + ", " + args.arg2 + ")"); + ((IInputMethod)args.arg1).showSoftInput(msg.arg1, (ResultReceiver)args.arg2); } catch (RemoteException e) { } args.recycle(); @@ -2310,8 +2324,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub case MSG_HIDE_SOFT_INPUT: args = (SomeArgs)msg.obj; try { - ((IInputMethod)args.arg1).hideSoftInput(0, - (ResultReceiver)args.arg2); + if (true || DEBUG) Slog.v(TAG, "Calling " + args.arg1 + ".hideSoftInput(0, " + + args.arg2 + ")"); + ((IInputMethod)args.arg1).hideSoftInput(0, (ResultReceiver)args.arg2); } catch (RemoteException e) { } args.recycle(); @@ -2319,7 +2334,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub case MSG_ATTACH_TOKEN: args = (SomeArgs)msg.obj; try { - if (DEBUG) Slog.v(TAG, "Sending attach of token: " + args.arg2); + if (true || DEBUG) Slog.v(TAG, "Sending attach of token: " + args.arg2); ((IInputMethod)args.arg1).attachToken((IBinder)args.arg2); } catch (RemoteException e) { } diff --git a/services/java/com/android/server/pm/PackageManagerService.java b/services/java/com/android/server/pm/PackageManagerService.java index 51f001f655a7..2d12a774bdaf 100644 --- a/services/java/com/android/server/pm/PackageManagerService.java +++ b/services/java/com/android/server/pm/PackageManagerService.java @@ -170,7 +170,7 @@ adb shell am instrument -w -e class com.android.unit_tests.PackageManagerTests c public class PackageManagerService extends IPackageManager.Stub { static final String TAG = "PackageManager"; static final boolean DEBUG_SETTINGS = false; - static final boolean DEBUG_PREFERRED = true; + static final boolean DEBUG_PREFERRED = false; static final boolean DEBUG_UPGRADE = false; private static final boolean DEBUG_INSTALL = false; private static final boolean DEBUG_REMOVE = false; @@ -339,9 +339,20 @@ public class PackageManagerService extends IPackageManager.Stub { final SparseArray<HashSet<String>> mSystemPermissions = new SparseArray<HashSet<String>>(); + static final class SharedLibraryEntry { + final String path; + final String apk; + + SharedLibraryEntry(String _path, String _apk) { + path = _path; + apk = _apk; + } + } + // These are the built-in shared libraries that were read from the // etc/permissions.xml file. - final HashMap<String, String> mSharedLibraries = new HashMap<String, String>(); + final HashMap<String, SharedLibraryEntry> mSharedLibraries + = new HashMap<String, SharedLibraryEntry>(); // Temporary for building the final shared libraries for an .apk. String[] mTmpSharedLibraries = null; @@ -390,8 +401,7 @@ public class PackageManagerService extends IPackageManager.Stub { final SparseArray<PackageVerificationState> mPendingVerification = new SparseArray<PackageVerificationState>(); - final ArrayList<PackageParser.Package> mDeferredDexOpt = - new ArrayList<PackageParser.Package>(); + HashSet<PackageParser.Package> mDeferredDexOpt = null; /** Token for keys in mPendingVerification. */ private int mPendingVerificationToken = 0; @@ -514,10 +524,9 @@ public class PackageManagerService extends IPackageManager.Stub { void doHandleMessage(Message msg) { switch (msg.what) { case INIT_COPY: { - if (DEBUG_INSTALL) Slog.i(TAG, "init_copy"); HandlerParams params = (HandlerParams) msg.obj; int idx = mPendingInstalls.size(); - if (DEBUG_INSTALL) Slog.i(TAG, "idx=" + idx); + if (DEBUG_INSTALL) Slog.i(TAG, "init_copy idx=" + idx + ": " + params); // If a bind was already initiated we dont really // need to do anything. The pending install // will be processed later on. @@ -1071,9 +1080,12 @@ public class PackageManagerService extends IPackageManager.Stub { * Also ensure all external libraries have had dexopt run on them. */ if (mSharedLibraries.size() > 0) { - Iterator<String> libs = mSharedLibraries.values().iterator(); + Iterator<SharedLibraryEntry> libs = mSharedLibraries.values().iterator(); while (libs.hasNext()) { - String lib = libs.next(); + String lib = libs.next().path; + if (lib == null) { + continue; + } try { if (dalvik.system.DexFile.isDexOptNeeded(lib)) { libFiles.add(lib); @@ -1277,6 +1289,10 @@ public class PackageManagerService extends IPackageManager.Stub { mDrmAppInstallObserver = null; } + // Now that we know all of the shared libraries, update all clients to have + // the correct library paths. + updateAllSharedLibrariesLPw(); + EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_SCAN_END, SystemClock.uptimeMillis()); Slog.i(TAG, "Time to scan packages: " @@ -1517,7 +1533,7 @@ public class PackageManagerService extends IPackageManager.Stub { + parser.getPositionDescription()); } else { //Log.i(TAG, "Got library " + lname + " in " + lfile); - mSharedLibraries.put(lname, lfile); + mSharedLibraries.put(lname, new SharedLibraryEntry(lfile, null)); } XmlUtils.skipCurrentTag(parser); continue; @@ -3249,6 +3265,7 @@ public class PackageManagerService extends IPackageManager.Stub { int parseFlags, int scanMode, long currentTime, UserHandle user) { mLastScanError = PackageManager.INSTALL_SUCCEEDED; String scanPath = scanFile.getPath(); + if (DEBUG_INSTALL) Slog.d(TAG, "Parsing: " + scanPath); parseFlags |= mDefParseFlags; PackageParser pp = new PackageParser(scanPath); pp.setSeparateProcesses(mSeparateProcesses); @@ -3278,6 +3295,7 @@ public class PackageManagerService extends IPackageManager.Stub { // package. Must look for it either under the original or real // package name depending on our state. updatedPkg = mSettings.getDisabledSystemPkgLPr(ps != null ? ps.name : pkg.packageName); + if (DEBUG_INSTALL && updatedPkg != null) Slog.d(TAG, "updatedPkg = " + updatedPkg); } // First check if this is a system package that may involve an update if (updatedPkg != null && (parseFlags&PackageParser.PARSE_IS_SYSTEM) != 0) { @@ -3285,6 +3303,7 @@ public class PackageManagerService extends IPackageManager.Stub { // The path has changed from what was last scanned... check the // version of the new path against what we have stored to determine // what to do. + if (DEBUG_INSTALL) Slog.d(TAG, "Path changing from " + ps.codePath); if (pkg.mVersionCode < ps.versionCode) { // The system package has been updated and the code path does not match // Ignore entry. Skip it. @@ -3298,6 +3317,7 @@ public class PackageManagerService extends IPackageManager.Stub { updatedPkg.codePath = scanFile; updatedPkg.codePathString = scanFile.toString(); } + updatedPkg.pkg = pkg; mLastScanError = PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE; return null; } else { @@ -3353,6 +3373,7 @@ public class PackageManagerService extends IPackageManager.Stub { */ if (compareSignatures(ps.signatures.mSignatures, pkg.mSignatures) != PackageManager.SIGNATURE_MATCH) { + if (DEBUG_INSTALL) Slog.d(TAG, "Signature mismatch!"); deletePackageLI(pkg.packageName, null, true, 0, null, false); ps = null; } else { @@ -3484,28 +3505,28 @@ public class PackageManagerService extends IPackageManager.Stub { } public void performBootDexOpt() { - ArrayList<PackageParser.Package> pkgs = null; + HashSet<PackageParser.Package> pkgs = null; synchronized (mPackages) { - if (mDeferredDexOpt.size() > 0) { - pkgs = new ArrayList<PackageParser.Package>(mDeferredDexOpt); - mDeferredDexOpt.clear(); - } + pkgs = mDeferredDexOpt; + mDeferredDexOpt = null; } if (pkgs != null) { - for (int i=0; i<pkgs.size(); i++) { + int i = 0; + for (PackageParser.Package pkg : pkgs) { if (!isFirstBoot()) { + i++; try { ActivityManagerNative.getDefault().showBootMessage( mContext.getResources().getString( com.android.internal.R.string.android_upgrading_apk, - i+1, pkgs.size()), true); + i, pkgs.size()), true); } catch (RemoteException e) { } } - PackageParser.Package p = pkgs.get(i); + PackageParser.Package p = pkg; synchronized (mInstallLock) { if (!p.mDidDexOpt) { - performDexOptLI(p, false, false); + performDexOptLI(p, false, false, true); } } } @@ -3527,7 +3548,27 @@ public class PackageManagerService extends IPackageManager.Stub { } } synchronized (mInstallLock) { - return performDexOptLI(p, false, false) == DEX_OPT_PERFORMED; + return performDexOptLI(p, false, false, true) == DEX_OPT_PERFORMED; + } + } + + private void performDexOptLibsLI(ArrayList<String> libs, boolean forceDex, boolean defer, + HashSet<String> done) { + for (int i=0; i<libs.size(); i++) { + PackageParser.Package libPkg; + String libName; + synchronized (mPackages) { + libName = libs.get(i); + SharedLibraryEntry lib = mSharedLibraries.get(libName); + if (lib != null && lib.apk != null) { + libPkg = mPackages.get(lib.apk); + } else { + libPkg = null; + } + } + if (libPkg != null && !done.contains(libName)) { + performDexOptLI(libPkg, forceDex, defer, done); + } } } @@ -3536,14 +3577,27 @@ public class PackageManagerService extends IPackageManager.Stub { static final int DEX_OPT_DEFERRED = 2; static final int DEX_OPT_FAILED = -1; - private int performDexOptLI(PackageParser.Package pkg, boolean forceDex, boolean defer) { + private int performDexOptLI(PackageParser.Package pkg, boolean forceDex, boolean defer, + HashSet<String> done) { boolean performed = false; + if (done != null) { + done.add(pkg.packageName); + if (pkg.usesLibraries != null) { + performDexOptLibsLI(pkg.usesLibraries, forceDex, defer, done); + } + if (pkg.usesOptionalLibraries != null) { + performDexOptLibsLI(pkg.usesOptionalLibraries, forceDex, defer, done); + } + } if ((pkg.applicationInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0) { String path = pkg.mScanPath; int ret = 0; try { if (forceDex || dalvik.system.DexFile.isDexOptNeeded(path)) { if (!forceDex && defer) { + if (mDeferredDexOpt == null) { + mDeferredDexOpt = new HashSet<PackageParser.Package>(); + } mDeferredDexOpt.add(pkg); return DEX_OPT_DEFERRED; } else { @@ -3576,6 +3630,19 @@ public class PackageManagerService extends IPackageManager.Stub { return performed ? DEX_OPT_PERFORMED : DEX_OPT_SKIPPED; } + private int performDexOptLI(PackageParser.Package pkg, boolean forceDex, boolean defer, + boolean inclDependencies) { + HashSet<String> done; + boolean performed = false; + if (inclDependencies && (pkg.usesLibraries != null || pkg.usesOptionalLibraries != null)) { + done = new HashSet<String>(); + done.add(pkg.packageName); + } else { + done = null; + } + return performDexOptLI(pkg, forceDex, defer, done); + } + private boolean verifyPackageUpdateLPr(PackageSetting oldPkg, PackageParser.Package newPkg) { if ((oldPkg.pkgFlags&ApplicationInfo.FLAG_SYSTEM) == 0) { Slog.w(TAG, "Unable to update from " + oldPkg.name @@ -3646,6 +3713,113 @@ public class PackageManagerService extends IPackageManager.Stub { return res; } + private int addSharedLibraryLPw(final SharedLibraryEntry file, int num, + PackageParser.Package changingLib) { + if (file.path != null) { + mTmpSharedLibraries[num] = file.path; + return num+1; + } + PackageParser.Package p = mPackages.get(file.apk); + if (changingLib != null && changingLib.packageName.equals(file.apk)) { + // If we are doing this while in the middle of updating a library apk, + // then we need to make sure to use that new apk for determining the + // dependencies here. (We haven't yet finished committing the new apk + // to the package manager state.) + if (p == null || p.packageName.equals(changingLib.packageName)) { + p = changingLib; + } + } + if (p != null) { + String path = p.mPath; + for (int i=0; i<num; i++) { + if (mTmpSharedLibraries[i].equals(path)) { + return num; + } + } + mTmpSharedLibraries[num] = p.mPath; + return num+1; + } + return num; + } + + private boolean updateSharedLibrariesLPw(PackageParser.Package pkg, + PackageParser.Package changingLib) { + if (pkg.usesLibraries != null || pkg.usesOptionalLibraries != null) { + if (mTmpSharedLibraries == null || + mTmpSharedLibraries.length < mSharedLibraries.size()) { + mTmpSharedLibraries = new String[mSharedLibraries.size()]; + } + int num = 0; + int N = pkg.usesLibraries != null ? pkg.usesLibraries.size() : 0; + for (int i=0; i<N; i++) { + final SharedLibraryEntry file = mSharedLibraries.get(pkg.usesLibraries.get(i)); + if (file == null) { + Slog.e(TAG, "Package " + pkg.packageName + + " requires unavailable shared library " + + pkg.usesLibraries.get(i) + "; failing!"); + mLastScanError = PackageManager.INSTALL_FAILED_MISSING_SHARED_LIBRARY; + return false; + } + num = addSharedLibraryLPw(file, num, changingLib); + } + N = pkg.usesOptionalLibraries != null ? pkg.usesOptionalLibraries.size() : 0; + for (int i=0; i<N; i++) { + final SharedLibraryEntry file = mSharedLibraries.get(pkg.usesOptionalLibraries.get(i)); + if (file == null) { + Slog.w(TAG, "Package " + pkg.packageName + + " desires unavailable shared library " + + pkg.usesOptionalLibraries.get(i) + "; ignoring!"); + } else { + num = addSharedLibraryLPw(file, num, changingLib); + } + } + if (num > 0) { + pkg.usesLibraryFiles = new String[num]; + System.arraycopy(mTmpSharedLibraries, 0, + pkg.usesLibraryFiles, 0, num); + } else { + pkg.usesLibraryFiles = null; + } + } + return true; + } + + private static boolean hasString(List<String> list, List<String> which) { + if (list == null) { + return false; + } + for (int i=list.size()-1; i>=0; i--) { + for (int j=which.size()-1; j>=0; j--) { + if (which.get(j).equals(list.get(i))) { + return true; + } + } + } + return false; + } + + private void updateAllSharedLibrariesLPw() { + for (PackageParser.Package pkg : mPackages.values()) { + updateSharedLibrariesLPw(pkg, null); + } + } + + private ArrayList<PackageParser.Package> updateAllSharedLibrariesLPw( + PackageParser.Package changingPkg) { + ArrayList<PackageParser.Package> res = null; + for (PackageParser.Package pkg : mPackages.values()) { + if (hasString(pkg.usesLibraries, changingPkg.libraryNames) + || hasString(pkg.usesOptionalLibraries, changingPkg.libraryNames)) { + if (res == null) { + res = new ArrayList<PackageParser.Package>(); + } + res.add(pkg); + updateSharedLibrariesLPw(pkg, changingPkg); + } + } + return res; + } + private PackageParser.Package scanPackageLI(PackageParser.Package pkg, int parseFlags, int scanMode, long currentTime, UserHandle user) { File scanFile = new File(pkg.mScanPath); @@ -3725,42 +3899,14 @@ public class PackageManagerService extends IPackageManager.Stub { // writer synchronized (mPackages) { - // Check all shared libraries and map to their actual file path. - if (pkg.usesLibraries != null || pkg.usesOptionalLibraries != null) { - if (mTmpSharedLibraries == null || - mTmpSharedLibraries.length < mSharedLibraries.size()) { - mTmpSharedLibraries = new String[mSharedLibraries.size()]; - } - int num = 0; - int N = pkg.usesLibraries != null ? pkg.usesLibraries.size() : 0; - for (int i=0; i<N; i++) { - final String file = mSharedLibraries.get(pkg.usesLibraries.get(i)); - if (file == null) { - Slog.e(TAG, "Package " + pkg.packageName - + " requires unavailable shared library " - + pkg.usesLibraries.get(i) + "; failing!"); - mLastScanError = PackageManager.INSTALL_FAILED_MISSING_SHARED_LIBRARY; - return null; - } - mTmpSharedLibraries[num] = file; - num++; - } - N = pkg.usesOptionalLibraries != null ? pkg.usesOptionalLibraries.size() : 0; - for (int i=0; i<N; i++) { - final String file = mSharedLibraries.get(pkg.usesOptionalLibraries.get(i)); - if (file == null) { - Slog.w(TAG, "Package " + pkg.packageName - + " desires unavailable shared library " - + pkg.usesOptionalLibraries.get(i) + "; ignoring!"); - } else { - mTmpSharedLibraries[num] = file; - num++; - } - } - if (num > 0) { - pkg.usesLibraryFiles = new String[num]; - System.arraycopy(mTmpSharedLibraries, 0, - pkg.usesLibraryFiles, 0, num); + if ((parseFlags&PackageParser.PARSE_IS_SYSTEM_DIR) == 0) { + // Check all shared libraries and map to their actual file path. + // We only do this here for apps not on a system dir, because those + // are the only ones that can fail an install due to this. We + // will take care of the system apps by updating all of their + // library paths after the scan is done. + if (!updateSharedLibrariesLPw(pkg, null)) { + return null; } } @@ -4166,7 +4312,7 @@ public class PackageManagerService extends IPackageManager.Stub { pkg.mScanPath = path; if ((scanMode&SCAN_NO_DEX) == 0) { - if (performDexOptLI(pkg, forceDex, (scanMode&SCAN_DEFER_DEX) != 0) + if (performDexOptLI(pkg, forceDex, (scanMode&SCAN_DEFER_DEX) != 0, false) == DEX_OPT_FAILED) { mLastScanError = PackageManager.INSTALL_FAILED_DEXOPT; return null; @@ -4178,6 +4324,80 @@ public class PackageManagerService extends IPackageManager.Stub { pkg.applicationInfo.flags |= ApplicationInfo.FLAG_FACTORY_TEST; } + ArrayList<PackageParser.Package> clientLibPkgs = null; + + // writer + synchronized (mPackages) { + if ((pkg.applicationInfo.flags&ApplicationInfo.FLAG_SYSTEM) != 0) { + // Only system apps can add new shared libraries. + if (pkg.libraryNames != null) { + for (int i=0; i<pkg.libraryNames.size(); i++) { + String name = pkg.libraryNames.get(i); + boolean allowed = false; + if (isUpdatedSystemApp(pkg)) { + // New library entries can only be added through the + // system image. This is important to get rid of a lot + // of nasty edge cases: for example if we allowed a non- + // system update of the app to add a library, then uninstalling + // the update would make the library go away, and assumptions + // we made such as through app install filtering would now + // have allowed apps on the device which aren't compatible + // with it. Better to just have the restriction here, be + // conservative, and create many fewer cases that can negatively + // impact the user experience. + final PackageSetting sysPs = mSettings + .getDisabledSystemPkgLPr(pkg.packageName); + if (sysPs.pkg != null && sysPs.pkg.libraryNames != null) { + for (int j=0; j<sysPs.pkg.libraryNames.size(); j++) { + if (name.equals(sysPs.pkg.libraryNames.get(j))) { + allowed = true; + allowed = true; + break; + } + } + } + } else { + allowed = true; + } + if (allowed) { + if (!mSharedLibraries.containsKey(name)) { + mSharedLibraries.put(name, new SharedLibraryEntry(null, + pkg.packageName)); + } else if (!name.equals(pkg.packageName)) { + Slog.w(TAG, "Package " + pkg.packageName + " library " + + name + " already exists; skipping"); + } + } else { + Slog.w(TAG, "Package " + pkg.packageName + " declares lib " + + name + " that is not declared on system image; skipping"); + } + } + if ((scanMode&SCAN_BOOTING) == 0) { + // If we are not booting, we need to update any applications + // that are clients of our shared library. If we are booting, + // this will all be done once the scan is complete. + clientLibPkgs = updateAllSharedLibrariesLPw(pkg); + } + } + } + } + + // We also need to dexopt any apps that are dependent on this library. Note that + // if these fail, we should abort the install since installing the library will + // result in some apps being broken. + if (clientLibPkgs != null) { + if ((scanMode&SCAN_NO_DEX) == 0) { + for (int i=0; i<clientLibPkgs.size(); i++) { + PackageParser.Package clientPkg = clientLibPkgs.get(i); + if (performDexOptLI(clientPkg, forceDex, (scanMode&SCAN_DEFER_DEX) != 0, false) + == DEX_OPT_FAILED) { + mLastScanError = PackageManager.INSTALL_FAILED_DEXOPT; + return null; + } + } + } + } + // Request the ActivityManager to kill the process(only for existing packages) // so that we do not end up in a confused state while the user is still using the older // version of the application while the new one gets installed. @@ -4186,6 +4406,15 @@ public class PackageManagerService extends IPackageManager.Stub { pkg.applicationInfo.uid); } + // Also need to kill any apps that are dependent on the library. + if (clientLibPkgs != null) { + for (int i=0; i<clientLibPkgs.size(); i++) { + PackageParser.Package clientPkg = clientLibPkgs.get(i); + killApplication(clientPkg.applicationInfo.packageName, + clientPkg.applicationInfo.uid); + } + } + // writer synchronized (mPackages) { // We don't expect installation to fail beyond this point, @@ -4591,7 +4820,7 @@ public class PackageManagerService extends IPackageManager.Stub { } } } - if (chatty) { + if (DEBUG_REMOVE && chatty) { if (r == null) { r = new StringBuilder(256); } else { @@ -4627,7 +4856,7 @@ public class PackageManagerService extends IPackageManager.Stub { for (i=0; i<N; i++) { PackageParser.Activity a = pkg.receivers.get(i); mReceivers.removeActivity(a, "receiver"); - if (chatty) { + if (DEBUG_REMOVE && chatty) { if (r == null) { r = new StringBuilder(256); } else { @@ -4645,7 +4874,7 @@ public class PackageManagerService extends IPackageManager.Stub { for (i=0; i<N; i++) { PackageParser.Activity a = pkg.activities.get(i); mActivities.removeActivity(a, "activity"); - if (chatty) { + if (DEBUG_REMOVE && chatty) { if (r == null) { r = new StringBuilder(256); } else { @@ -4668,7 +4897,7 @@ public class PackageManagerService extends IPackageManager.Stub { } if (bp != null && bp.perm == p) { bp.perm = null; - if (chatty) { + if (DEBUG_REMOVE && chatty) { if (r == null) { r = new StringBuilder(256); } else { @@ -4687,7 +4916,7 @@ public class PackageManagerService extends IPackageManager.Stub { for (i=0; i<N; i++) { PackageParser.Instrumentation a = pkg.instrumentation.get(i); mInstrumentation.remove(a.getComponentName()); - if (chatty) { + if (DEBUG_REMOVE && chatty) { if (r == null) { r = new StringBuilder(256); } else { @@ -4699,6 +4928,31 @@ public class PackageManagerService extends IPackageManager.Stub { if (r != null) { if (DEBUG_REMOVE) Log.d(TAG, " Instrumentation: " + r); } + + r = null; + if ((pkg.applicationInfo.flags&ApplicationInfo.FLAG_SYSTEM) != 0) { + // Only system apps can hold shared libraries. + if (pkg.libraryNames != null) { + for (i=0; i<pkg.libraryNames.size(); i++) { + String name = pkg.libraryNames.get(i); + SharedLibraryEntry cur = mSharedLibraries.get(name); + if (cur != null && cur.apk != null && cur.apk.equals(pkg.packageName)) { + mSharedLibraries.remove(name); + if (DEBUG_REMOVE && chatty) { + if (r == null) { + r = new StringBuilder(256); + } else { + r.append(' '); + } + r.append(name); + } + } + } + } + } + if (r != null) { + if (DEBUG_REMOVE) Log.d(TAG, " Libraries: " + r); + } } private static final boolean isPackageFilename(String name) { @@ -4860,7 +5114,23 @@ public class PackageManagerService extends IPackageManager.Stub { if (origGp.grantedPermissions.contains(perm)) { allowed = true; } else { + // The system apk may have been updated with an older + // version of the one on the data partition, but which + // granted a new system permission that it didn't have + // before. In this case we do want to allow the app to + // now get the new permission, because it is allowed by + // the system image. allowed = false; + if (sysPs.pkg != null) { + for (int j=0; + j<sysPs.pkg.requestedPermissions.size(); j++) { + if (perm.equals( + sysPs.pkg.requestedPermissions.get(j))) { + allowed = true; + break; + } + } + } } } else { allowed = true; @@ -5557,6 +5827,7 @@ public class PackageManagerService extends IPackageManager.Stub { } if ((event&REMOVE_EVENTS) != 0) { if (ps != null) { + if (DEBUG_REMOVE) Slog.d(TAG, "Package disappeared: " + ps); removePackageLI(ps, true); removedPackage = ps.name; removedAppId = ps.appId; @@ -5565,6 +5836,7 @@ public class PackageManagerService extends IPackageManager.Stub { if ((event&ADD_EVENTS) != 0) { if (p == null) { + if (DEBUG_INSTALL) Slog.d(TAG, "New file appeared: " + fullPath); p = scanPackageLI(fullPath, (mIsRom ? PackageParser.PARSE_IS_SYSTEM | PackageParser.PARSE_IS_SYSTEM_DIR: 0) | @@ -6126,7 +6398,7 @@ public class PackageManagerService extends IPackageManager.Stub { final boolean startCopy() { boolean res; try { - if (DEBUG_INSTALL) Slog.i(TAG, "startCopy"); + if (DEBUG_INSTALL) Slog.i(TAG, "startCopy " + mUser + ": " + this); if (++mRetries > MAX_RETRIES) { Slog.w(TAG, "Failed to invoke remote methods on default container service. Giving up"); @@ -6170,6 +6442,13 @@ public class PackageManagerService extends IPackageManager.Stub { } @Override + public String toString() { + return "MeasureParams{" + + Integer.toHexString(System.identityHashCode(this)) + + " " + mStats.packageName + "}"; + } + + @Override void handleStartCopy() throws RemoteException { synchronized (mInstallLock) { mSuccess = getPackageSizeInfoLI(mStats.packageName, mStats.userHandle, mStats); @@ -6258,6 +6537,13 @@ public class PackageManagerService extends IPackageManager.Stub { this.encryptionParams = encryptionParams; } + @Override + public String toString() { + return "InstallParams{" + + Integer.toHexString(System.identityHashCode(this)) + + " " + mPackageURI + "}"; + } + public ManifestDigest getManifestDigest() { if (verificationParams == null) { return null; @@ -6671,6 +6957,13 @@ public class PackageManagerService extends IPackageManager.Stub { } } + @Override + public String toString() { + return "MoveParams{" + + Integer.toHexString(System.identityHashCode(this)) + + " " + packageName + "}"; + } + public void handleStartCopy() throws RemoteException { mRet = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; // Check for storage space on target medium @@ -7582,6 +7875,7 @@ public class PackageManagerService extends IPackageManager.Stub { // Remember this for later, in case we need to rollback this install String pkgName = pkg.packageName; + if (DEBUG_INSTALL) Slog.d(TAG, "installNewPackageLI: " + pkg); boolean dataDirExists = getDataPathForPackage(pkg.packageName, 0).exists(); synchronized(mPackages) { if (mSettings.mRenamedPackages.containsKey(pkgName)) { @@ -7638,6 +7932,7 @@ public class PackageManagerService extends IPackageManager.Stub { // First find the old package info and check signatures synchronized(mPackages) { oldPackage = mPackages.get(pkgName); + if (DEBUG_INSTALL) Slog.d(TAG, "replacePackageLI: new=" + pkg + ", old=" + oldPackage); if (compareSignatures(oldPackage.mSignatures, pkg.mSignatures) != PackageManager.SIGNATURE_MATCH) { Slog.w(TAG, "New package has a different signature: " + pkgName); @@ -7663,6 +7958,8 @@ public class PackageManagerService extends IPackageManager.Stub { boolean deletedPkg = true; boolean updatedSettings = false; + if (DEBUG_INSTALL) Slog.d(TAG, "replaceNonSystemPackageLI: new=" + pkg + ", old=" + + deletedPackage); long origUpdateTime; if (pkg.mExtras != null) { origUpdateTime = ((PackageSetting)pkg.mExtras).lastUpdateTime; @@ -7700,6 +7997,7 @@ public class PackageManagerService extends IPackageManager.Stub { // scanPackageLocked, unless those directories existed before we even tried to // install. if(updatedSettings) { + if (DEBUG_INSTALL) Slog.d(TAG, "Install failed, rolling pack: " + pkgName); deletePackageLI( pkgName, null, true, PackageManager.DELETE_KEEP_DATA, @@ -7708,6 +8006,7 @@ public class PackageManagerService extends IPackageManager.Stub { // Since we failed to install the new package we need to restore the old // package that we deleted. if(deletedPkg) { + if (DEBUG_INSTALL) Slog.d(TAG, "Install failed, reinstalling: " + deletedPackage); File restoreFile = new File(deletedPackage.mPath); // Parse old package boolean oldOnSd = isExternal(deletedPackage); @@ -7737,6 +8036,8 @@ public class PackageManagerService extends IPackageManager.Stub { private void replaceSystemPackageLI(PackageParser.Package deletedPackage, PackageParser.Package pkg, int parseFlags, int scanMode, UserHandle user, String installerPackageName, PackageInstalledInfo res) { + if (DEBUG_INSTALL) Slog.d(TAG, "replaceSystemPackageLI: new=" + pkg + + ", old=" + deletedPackage); PackageParser.Package newPackage = null; boolean updatedSettings = false; parseFlags |= PackageManager.INSTALL_REPLACE_EXISTING | @@ -7859,7 +8160,7 @@ public class PackageManagerService extends IPackageManager.Stub { return; } - Log.d(TAG, "New package installed in " + newPackage.mPath); + if (DEBUG_INSTALL) Slog.d(TAG, "New package installed in " + newPackage.mPath); synchronized (mPackages) { updatePermissionsLPw(newPackage.packageName, newPackage, @@ -7889,6 +8190,7 @@ public class PackageManagerService extends IPackageManager.Stub { // Result object to be returned res.returnCode = PackageManager.INSTALL_SUCCEEDED; + if (DEBUG_INSTALL) Slog.d(TAG, "installPackageLI: path=" + tmpPackageFile); // Retrieve PackageSettings and parse package int parseFlags = mDefParseFlags | PackageParser.PARSE_CHATTY | (forwardLocked ? PackageParser.PARSE_FORWARD_LOCK : 0) @@ -7950,14 +8252,18 @@ public class PackageManagerService extends IPackageManager.Stub { pkg.setPackageName(oldName); pkgName = pkg.packageName; replace = true; + if (DEBUG_INSTALL) Slog.d(TAG, "Replacing existing renamed package: oldName=" + + oldName + " pkgName=" + pkgName); } else if (mPackages.containsKey(pkgName)) { // This package, under its official name, already exists // on the device; we should replace it. replace = true; + if (DEBUG_INSTALL) Slog.d(TAG, "Replace existing pacakge: " + pkgName); } } PackageSetting ps = mSettings.mPackages.get(pkgName); if (ps != null) { + if (DEBUG_INSTALL) Slog.d(TAG, "Existing package: " + ps); oldCodePath = mSettings.mPackages.get(pkgName).codePathString; if (ps.pkg != null && ps.pkg.applicationInfo != null) { systemApp = (ps.pkg.applicationInfo.flags & @@ -8104,6 +8410,7 @@ public class PackageManagerService extends IPackageManager.Stub { return; } + if (DEBUG_REMOVE) Slog.d(TAG, "deletePackageAsUser: pkg=" + packageName + " user=" + userId); // Queue up an async operation since the package deletion may take a little while. mHandler.post(new Runnable() { public void run() { @@ -8151,6 +8458,7 @@ public class PackageManagerService extends IPackageManager.Stub { boolean removedForAllUsers = false; boolean systemUpdate = false; synchronized (mInstallLock) { + if (DEBUG_REMOVE) Slog.d(TAG, "deletePackageX: pkg=" + packageName + " user=" + userId); res = deletePackageLI(packageName, (flags & PackageManager.DELETE_ALL_USERS) != 0 ? UserHandle.ALL : new UserHandle(userId), @@ -8159,6 +8467,8 @@ public class PackageManagerService extends IPackageManager.Stub { if (res && !systemUpdate && mPackages.get(packageName) == null) { removedForAllUsers = true; } + if (DEBUG_REMOVE) Slog.d(TAG, "delete res: systemUpdate=" + systemUpdate + + " removedForAllUsers=" + removedForAllUsers); } if (res) { @@ -8234,6 +8544,7 @@ public class PackageManagerService extends IPackageManager.Stub { private void removePackageDataLI(PackageSetting ps, PackageRemovedInfo outInfo, int flags, boolean writeSettings) { String packageName = ps.name; + if (DEBUG_REMOVE) Slog.d(TAG, "removePackageDataLI: " + ps); removePackageLI(ps, (flags&REMOVE_CHATTY) != 0); // Retrieve object to delete permissions for shared user later on final PackageSetting deletedPs; @@ -8289,11 +8600,13 @@ public class PackageManagerService extends IPackageManager.Stub { synchronized (mPackages) { disabledPs = mSettings.getDisabledSystemPkgLPr(newPs.name); } + if (DEBUG_REMOVE) Slog.d(TAG, "deleteSystemPackageLI: newPs=" + newPs + + " disabledPs=" + disabledPs); if (disabledPs == null) { Slog.w(TAG, "Attempt to delete unknown system package "+ newPs.name); return false; - } else { - Log.i(TAG, "Deleting system pkg from data partition"); + } else if (DEBUG_REMOVE) { + Slog.d(TAG, "Deleting system pkg from data partition"); } // Delete the updated package outInfo.isRemovedPackageSystemUpdate = true; @@ -8317,6 +8630,7 @@ public class PackageManagerService extends IPackageManager.Stub { NativeLibraryHelper.removeNativeBinariesLI(newPs.nativeLibraryPathString); } // Install the system package + if (DEBUG_REMOVE) Slog.d(TAG, "Re-installing system package: " + disabledPs); PackageParser.Package newPkg = scanPackageLI(disabledPs.codePath, PackageParser.PARSE_MUST_BE_APK | PackageParser.PARSE_IS_SYSTEM, SCAN_MONITOR | SCAN_NO_PATHS, 0, null); @@ -8366,6 +8680,7 @@ public class PackageManagerService extends IPackageManager.Stub { Slog.w(TAG, "Attempt to delete null packageName."); return false; } + if (DEBUG_REMOVE) Slog.d(TAG, "deletePackageLI: " + packageName + " user " + user); PackageSetting ps; boolean dataOnly = false; int removeUser = -1; @@ -8376,11 +8691,14 @@ public class PackageManagerService extends IPackageManager.Stub { Slog.w(TAG, "Package named '" + packageName + "' doesn't exist."); return false; } - if (user != null + if ((!isSystemApp(ps) || (flags&PackageManager.DELETE_SYSTEM_APP) != 0) && user != null && user.getIdentifier() != UserHandle.USER_ALL) { // The caller is asking that the package only be deleted for a single // user. To do this, we just mark its uninstalled state and delete - // its data. + // its data. If this is a system app, we only allow this to happen if + // they have set the special DELETE_SYSTEM_APP which requests different + // semantics than normal for uninstalling system apps. + if (DEBUG_REMOVE) Slog.d(TAG, "Only deleting for single user"); ps.setUserState(user.getIdentifier(), COMPONENT_ENABLED_STATE_DEFAULT, false, //installed @@ -8392,12 +8710,14 @@ public class PackageManagerService extends IPackageManager.Stub { // Other user still have this package installed, so all // we need to do is clear this user's data and save that // it is uninstalled. + if (DEBUG_REMOVE) Slog.d(TAG, "Still installed by other users"); removeUser = user.getIdentifier(); appId = ps.appId; mSettings.writePackageRestrictionsLPr(removeUser); } else { // We need to set it back to 'installed' so the uninstall // broadcasts will be sent correctly. + if (DEBUG_REMOVE) Slog.d(TAG, "Not installed by other users, full delete"); ps.setInstalled(true, user.getIdentifier()); } } else { @@ -8405,6 +8725,7 @@ public class PackageManagerService extends IPackageManager.Stub { // other users still have this package installed, so all // we need to do is clear this user's data and save that // it is uninstalled. + if (DEBUG_REMOVE) Slog.d(TAG, "Deleting system app"); removeUser = user.getIdentifier(); appId = ps.appId; mSettings.writePackageRestrictionsLPr(removeUser); @@ -8415,6 +8736,7 @@ public class PackageManagerService extends IPackageManager.Stub { if (removeUser >= 0) { // From above, we determined that we are deleting this only // for a single user. Continue the work here. + if (DEBUG_REMOVE) Slog.d(TAG, "Updating install state for user: " + removeUser); if (outInfo != null) { outInfo.removedPackage = packageName; outInfo.removedAppId = appId; @@ -8427,17 +8749,18 @@ public class PackageManagerService extends IPackageManager.Stub { if (dataOnly) { // Delete application data first + if (DEBUG_REMOVE) Slog.d(TAG, "Removing package data only"); removePackageDataLI(ps, outInfo, flags, writeSettings); return true; } boolean ret = false; if (isSystemApp(ps)) { - Log.i(TAG, "Removing system package:" + ps.name); + if (DEBUG_REMOVE) Slog.d(TAG, "Removing system package:" + ps.name); // When an updated system application is deleted we delete the existing resources as well and // fall back to existing code in system partition ret = deleteSystemPackageLI(ps, flags, outInfo, writeSettings); } else { - Log.i(TAG, "Removing non-system package:" + ps.name); + if (DEBUG_REMOVE) Slog.d(TAG, "Removing non-system package:" + ps.name); // Kill application pre-emptively especially for apps on sd. killApplication(packageName, ps.appId); ret = deleteInstalledPackageLI(ps, deleteCodeAndResources, flags, outInfo, @@ -9408,7 +9731,15 @@ public class PackageManagerService extends IPackageManager.Stub { pw.print(" "); pw.print(name); pw.print(" -> "); - pw.println(mSharedLibraries.get(name)); + SharedLibraryEntry ent = mSharedLibraries.get(name); + if (ent.path != null) { + pw.print("(jar) "); + pw.print(ent.path); + } else { + pw.print("(apk) "); + pw.print(ent.apk); + } + pw.println(); } } diff --git a/services/java/com/android/server/pm/Settings.java b/services/java/com/android/server/pm/Settings.java index 13f514b26073..e6450787a9b6 100644 --- a/services/java/com/android/server/pm/Settings.java +++ b/services/java/com/android/server/pm/Settings.java @@ -32,7 +32,6 @@ import android.util.LogPrinter; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.JournaledFile; import com.android.internal.util.XmlUtils; -import com.android.server.IntentResolver; import com.android.server.pm.PackageManagerService.DumpState; import org.xmlpull.v1.XmlPullParser; @@ -2657,6 +2656,162 @@ final class Settings { ApplicationInfo.FLAG_CANT_SAVE_STATE, "CANT_SAVE_STATE", }; + void dumpPackageLPr(PrintWriter pw, String prefix, PackageSetting ps, SimpleDateFormat sdf, + Date date, List<UserInfo> users) { + pw.print(prefix); pw.print("Package ["); + pw.print(ps.realName != null ? ps.realName : ps.name); + pw.print("] ("); + pw.print(Integer.toHexString(System.identityHashCode(ps))); + pw.println("):"); + + if (ps.realName != null) { + pw.print(prefix); pw.print(" compat name="); + pw.println(ps.name); + } + + pw.print(prefix); pw.print(" userId="); pw.print(ps.appId); + pw.print(" gids="); pw.println(PackageManagerService.arrayToString(ps.gids)); + if (ps.sharedUser != null) { + pw.print(prefix); pw.print(" sharedUser="); pw.println(ps.sharedUser); + } + pw.print(prefix); pw.print(" pkg="); pw.println(ps.pkg); + pw.print(prefix); pw.print(" codePath="); pw.println(ps.codePathString); + pw.print(prefix); pw.print(" resourcePath="); pw.println(ps.resourcePathString); + pw.print(prefix); pw.print(" nativeLibraryPath="); pw.println(ps.nativeLibraryPathString); + pw.print(prefix); pw.print(" versionCode="); pw.print(ps.versionCode); + if (ps.pkg != null) { + pw.print(" targetSdk="); pw.print(ps.pkg.applicationInfo.targetSdkVersion); + } + pw.println(); + if (ps.pkg != null) { + pw.print(prefix); pw.print(" versionName="); pw.println(ps.pkg.mVersionName); + pw.print(prefix); pw.print(" applicationInfo="); + pw.println(ps.pkg.applicationInfo.toString()); + pw.print(prefix); pw.print(" flags="); printFlags(pw, ps.pkg.applicationInfo.flags, + FLAG_DUMP_SPEC); pw.println(); + pw.print(prefix); pw.print(" dataDir="); pw.println(ps.pkg.applicationInfo.dataDir); + if (ps.pkg.mOperationPending) { + pw.print(prefix); pw.println(" mOperationPending=true"); + } + pw.print(prefix); pw.print(" supportsScreens=["); + boolean first = true; + if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SUPPORTS_SMALL_SCREENS) != 0) { + if (!first) + pw.print(", "); + first = false; + pw.print("small"); + } + if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SUPPORTS_NORMAL_SCREENS) != 0) { + if (!first) + pw.print(", "); + first = false; + pw.print("medium"); + } + if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS) != 0) { + if (!first) + pw.print(", "); + first = false; + pw.print("large"); + } + if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SUPPORTS_XLARGE_SCREENS) != 0) { + if (!first) + pw.print(", "); + first = false; + pw.print("xlarge"); + } + if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_RESIZEABLE_FOR_SCREENS) != 0) { + if (!first) + pw.print(", "); + first = false; + pw.print("resizeable"); + } + if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES) != 0) { + if (!first) + pw.print(", "); + first = false; + pw.print("anyDensity"); + } + pw.println("]"); + if (ps.pkg.libraryNames != null && ps.pkg.libraryNames.size() > 0) { + pw.print(prefix); pw.println(" libraries:"); + for (int i=0; i<ps.pkg.libraryNames.size(); i++) { + pw.print(prefix); pw.print(" "); pw.println(ps.pkg.libraryNames.get(i)); + } + } + if (ps.pkg.usesLibraries != null && ps.pkg.usesLibraries.size() > 0) { + pw.print(prefix); pw.println(" usesLibraries:"); + for (int i=0; i<ps.pkg.usesLibraries.size(); i++) { + pw.print(prefix); pw.print(" "); pw.println(ps.pkg.usesLibraries.get(i)); + } + } + if (ps.pkg.usesOptionalLibraries != null + && ps.pkg.usesOptionalLibraries.size() > 0) { + pw.print(prefix); pw.println(" usesOptionalLibraries:"); + for (int i=0; i<ps.pkg.usesOptionalLibraries.size(); i++) { + pw.print(prefix); pw.print(" "); + pw.println(ps.pkg.usesOptionalLibraries.get(i)); + } + } + if (ps.pkg.usesLibraryFiles != null + && ps.pkg.usesLibraryFiles.length > 0) { + pw.print(prefix); pw.println(" usesLibraryFiles:"); + for (int i=0; i<ps.pkg.usesLibraryFiles.length; i++) { + pw.print(prefix); pw.print(" "); pw.println(ps.pkg.usesLibraryFiles[i]); + } + } + } + pw.print(prefix); pw.print(" timeStamp="); + date.setTime(ps.timeStamp); + pw.println(sdf.format(date)); + pw.print(prefix); pw.print(" firstInstallTime="); + date.setTime(ps.firstInstallTime); + pw.println(sdf.format(date)); + pw.print(prefix); pw.print(" lastUpdateTime="); + date.setTime(ps.lastUpdateTime); + pw.println(sdf.format(date)); + if (ps.installerPackageName != null) { + pw.print(prefix); pw.print(" installerPackageName="); + pw.println(ps.installerPackageName); + } + pw.print(prefix); pw.print(" signatures="); pw.println(ps.signatures); + pw.print(prefix); pw.print(" permissionsFixed="); pw.print(ps.permissionsFixed); + pw.print(" haveGids="); pw.print(ps.haveGids); + pw.print(" installStatus="); pw.println(ps.installStatus); + pw.print(prefix); pw.print(" pkgFlags="); printFlags(pw, ps.pkgFlags, FLAG_DUMP_SPEC); + pw.println(); + for (UserInfo user : users) { + pw.print(prefix); pw.print(" User "); pw.print(user.id); pw.print(": "); + pw.print(" installed="); + pw.print(ps.getInstalled(user.id)); + pw.print(" stopped="); + pw.print(ps.getStopped(user.id)); + pw.print(" notLaunched="); + pw.print(ps.getNotLaunched(user.id)); + pw.print(" enabled="); + pw.println(ps.getEnabled(user.id)); + HashSet<String> cmp = ps.getDisabledComponents(user.id); + if (cmp != null && cmp.size() > 0) { + pw.print(prefix); pw.println(" disabledComponents:"); + for (String s : cmp) { + pw.print(prefix); pw.print(" "); pw.println(s); + } + } + cmp = ps.getEnabledComponents(user.id); + if (cmp != null && cmp.size() > 0) { + pw.print(prefix); pw.println(" enabledComponents:"); + for (String s : cmp) { + pw.print(prefix); pw.print(" "); pw.println(s); + } + } + } + if (ps.grantedPermissions.size() > 0) { + pw.print(prefix); pw.println(" grantedPermissions:"); + for (String s : ps.grantedPermissions) { + pw.print(prefix); pw.print(" "); pw.println(s); + } + } + } + void dumpPackagesLPr(PrintWriter pw, String packageName, DumpState dumpState) { final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); final Date date = new Date(); @@ -2678,123 +2833,7 @@ final class Settings { pw.println("Packages:"); printedSomething = true; } - pw.print(" Package ["); - pw.print(ps.realName != null ? ps.realName : ps.name); - pw.print("] ("); - pw.print(Integer.toHexString(System.identityHashCode(ps))); - pw.println("):"); - - if (ps.realName != null) { - pw.print(" compat name="); - pw.println(ps.name); - } - - pw.print(" userId="); pw.print(ps.appId); - pw.print(" gids="); pw.println(PackageManagerService.arrayToString(ps.gids)); - pw.print(" sharedUser="); pw.println(ps.sharedUser); - pw.print(" pkg="); pw.println(ps.pkg); - pw.print(" codePath="); pw.println(ps.codePathString); - pw.print(" resourcePath="); pw.println(ps.resourcePathString); - pw.print(" nativeLibraryPath="); pw.println(ps.nativeLibraryPathString); - pw.print(" versionCode="); pw.println(ps.versionCode); - if (ps.pkg != null) { - pw.print(" applicationInfo="); pw.println(ps.pkg.applicationInfo.toString()); - pw.print(" flags="); printFlags(pw, ps.pkg.applicationInfo.flags, FLAG_DUMP_SPEC); pw.println(); - pw.print(" versionName="); pw.println(ps.pkg.mVersionName); - pw.print(" dataDir="); pw.println(ps.pkg.applicationInfo.dataDir); - pw.print(" targetSdk="); pw.println(ps.pkg.applicationInfo.targetSdkVersion); - if (ps.pkg.mOperationPending) { - pw.println(" mOperationPending=true"); - } - pw.print(" supportsScreens=["); - boolean first = true; - if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SUPPORTS_SMALL_SCREENS) != 0) { - if (!first) - pw.print(", "); - first = false; - pw.print("small"); - } - if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SUPPORTS_NORMAL_SCREENS) != 0) { - if (!first) - pw.print(", "); - first = false; - pw.print("medium"); - } - if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS) != 0) { - if (!first) - pw.print(", "); - first = false; - pw.print("large"); - } - if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SUPPORTS_XLARGE_SCREENS) != 0) { - if (!first) - pw.print(", "); - first = false; - pw.print("xlarge"); - } - if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_RESIZEABLE_FOR_SCREENS) != 0) { - if (!first) - pw.print(", "); - first = false; - pw.print("resizeable"); - } - if ((ps.pkg.applicationInfo.flags & ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES) != 0) { - if (!first) - pw.print(", "); - first = false; - pw.print("anyDensity"); - } - pw.println("]"); - } - pw.print(" timeStamp="); - date.setTime(ps.timeStamp); - pw.println(sdf.format(date)); - pw.print(" firstInstallTime="); - date.setTime(ps.firstInstallTime); - pw.println(sdf.format(date)); - pw.print(" lastUpdateTime="); - date.setTime(ps.lastUpdateTime); - pw.println(sdf.format(date)); - if (ps.installerPackageName != null) { - pw.print(" installerPackageName="); pw.println(ps.installerPackageName); - } - pw.print(" signatures="); pw.println(ps.signatures); - pw.print(" permissionsFixed="); pw.print(ps.permissionsFixed); - pw.print(" haveGids="); pw.print(ps.haveGids); - pw.print(" installStatus="); pw.println(ps.installStatus); - pw.print(" pkgFlags="); printFlags(pw, ps.pkgFlags, FLAG_DUMP_SPEC); - pw.println(); - for (UserInfo user : users) { - pw.print(" User "); pw.print(user.id); pw.print(": "); - pw.print(" installed="); - pw.print(ps.getInstalled(user.id)); - pw.print(" stopped="); - pw.print(ps.getStopped(user.id)); - pw.print(" notLaunched="); - pw.print(ps.getNotLaunched(user.id)); - pw.print(" enabled="); - pw.println(ps.getEnabled(user.id)); - HashSet<String> cmp = ps.getDisabledComponents(user.id); - if (cmp != null && cmp.size() > 0) { - pw.println(" disabledComponents:"); - for (String s : cmp) { - pw.print(" "); pw.println(s); - } - } - cmp = ps.getEnabledComponents(user.id); - if (cmp != null && cmp.size() > 0) { - pw.println(" enabledComponents:"); - for (String s : cmp) { - pw.print(" "); pw.println(s); - } - } - } - if (ps.grantedPermissions.size() > 0) { - pw.println(" grantedPermissions:"); - for (String s : ps.grantedPermissions) { - pw.print(" "); pw.println(s); - } - } + dumpPackageLPr(pw, " ", ps, sdf, date, users); } printedSomething = false; @@ -2830,27 +2869,7 @@ final class Settings { pw.println("Hidden system packages:"); printedSomething = true; } - pw.print(" Package ["); - pw.print(ps.realName != null ? ps.realName : ps.name); - pw.print("] ("); - pw.print(Integer.toHexString(System.identityHashCode(ps))); - pw.println("):"); - if (ps.realName != null) { - pw.print(" compat name="); - pw.println(ps.name); - } - if (ps.pkg != null && ps.pkg.applicationInfo != null) { - pw.print(" applicationInfo="); - pw.println(ps.pkg.applicationInfo.toString()); - } - pw.print(" userId="); - pw.println(ps.appId); - pw.print(" sharedUser="); - pw.println(ps.sharedUser); - pw.print(" codePath="); - pw.println(ps.codePathString); - pw.print(" resourcePath="); - pw.println(ps.resourcePathString); + dumpPackageLPr(pw, " ", ps, sdf, date, users); } } } diff --git a/services/java/com/android/server/power/ElectronBeam.java b/services/java/com/android/server/power/ElectronBeam.java index 457e92d06f71..4a74149cdcf4 100644 --- a/services/java/com/android/server/power/ElectronBeam.java +++ b/services/java/com/android/server/power/ElectronBeam.java @@ -82,7 +82,7 @@ final class ElectronBeam { private int mDisplayHeight; // real height, not rotated private SurfaceSession mSurfaceSession; private SurfaceControl mSurfaceControl; - private final Surface mSurface = new Surface(); + private Surface mSurface; private NaturalSurfaceLayout mSurfaceLayout; private EGLDisplay mEglDisplay; private EGLConfig mEglConfig; @@ -519,6 +519,7 @@ final class ElectronBeam { mSurfaceControl.setLayerStack(mDisplayLayerStack); mSurfaceControl.setSize(mDisplayWidth, mDisplayHeight); + mSurface = new Surface(); mSurface.copyFrom(mSurfaceControl); mSurfaceLayout = new NaturalSurfaceLayout(mDisplayManager, mSurfaceControl); diff --git a/services/java/com/android/server/wifi/README.txt b/services/java/com/android/server/wifi/README.txt new file mode 100644 index 000000000000..c03bff5a1ef6 --- /dev/null +++ b/services/java/com/android/server/wifi/README.txt @@ -0,0 +1,12 @@ +WifiService: Implements the IWifiManager 3rd party API. The API and the device state information (screen on/off, battery state, sleep policy) go as input into the WifiController which tracks high level states as to whether STA or AP mode is operational and controls the WifiStateMachine to handle bringup and shut down. + +WifiController: Acts as a controller to the WifiStateMachine based on various inputs (API and device state). Runs on the same thread created in WifiService. + +WifiSettingsStore: Tracks the various settings (wifi toggle, airplane toggle, tethering toggle, scan mode toggle) and provides API to figure if wifi should be turned on or off. + +WifiTrafficPoller: Polls traffic on wifi and notifies apps listening on it. + +WifiNotificationController: Controls whether the open network notification is displayed or not based on the scan results. + +WifiStateMachine: Tracks the various states on STA and AP connectivity and handles bring up and shut down. + diff --git a/services/java/com/android/server/wifi/WifiController.java b/services/java/com/android/server/wifi/WifiController.java new file mode 100644 index 000000000000..4d7c4345837c --- /dev/null +++ b/services/java/com/android/server/wifi/WifiController.java @@ -0,0 +1,626 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wifi; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.database.ContentObserver; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.net.wifi.WifiConfiguration; +import android.net.wifi.WifiManager; +import static android.net.wifi.WifiManager.WIFI_MODE_FULL; +import static android.net.wifi.WifiManager.WIFI_MODE_FULL_HIGH_PERF; +import static android.net.wifi.WifiManager.WIFI_MODE_SCAN_ONLY; +import android.net.wifi.WifiStateMachine; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.WorkSource; +import android.provider.Settings; +import android.util.Slog; + +import com.android.internal.util.Protocol; +import com.android.internal.util.State; +import com.android.internal.util.StateMachine; +import com.android.server.wifi.WifiService.LockList; + +import java.io.FileDescriptor; +import java.io.PrintWriter; + +class WifiController extends StateMachine { + private static final String TAG = "WifiController"; + private static final boolean DBG = false; + private Context mContext; + private boolean mScreenOff; + private boolean mDeviceIdle; + private int mPluggedType; + private int mStayAwakeConditions; + private long mIdleMillis; + private int mSleepPolicy; + + private AlarmManager mAlarmManager; + private PendingIntent mIdleIntent; + private static final int IDLE_REQUEST = 0; + + /** + * See {@link Settings.Global#WIFI_IDLE_MS}. This is the default value if a + * Settings.Global value is not present. This timeout value is chosen as + * the approximate point at which the battery drain caused by Wi-Fi + * being enabled but not active exceeds the battery drain caused by + * re-establishing a connection to the mobile data network. + */ + private static final long DEFAULT_IDLE_MS = 15 * 60 * 1000; /* 15 minutes */ + + NetworkInfo mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_WIFI, 0, "WIFI", ""); + + private static final String ACTION_DEVICE_IDLE = + "com.android.server.WifiManager.action.DEVICE_IDLE"; + + /* References to values tracked in WifiService */ + final WifiStateMachine mWifiStateMachine; + final WifiSettingsStore mSettingsStore; + final LockList mLocks; + + /** + * Temporary for computing UIDS that are responsible for starting WIFI. + * Protected by mWifiStateTracker lock. + */ + private final WorkSource mTmpWorkSource = new WorkSource(); + + private static final int BASE = Protocol.BASE_WIFI_CONTROLLER; + + static final int CMD_EMERGENCY_MODE_CHANGED = BASE + 1; + static final int CMD_SCREEN_ON = BASE + 2; + static final int CMD_SCREEN_OFF = BASE + 3; + static final int CMD_BATTERY_CHANGED = BASE + 4; + static final int CMD_DEVICE_IDLE = BASE + 5; + static final int CMD_LOCKS_CHANGED = BASE + 6; + static final int CMD_SCAN_ALWAYS_MODE_CHANGED = BASE + 7; + static final int CMD_WIFI_TOGGLED = BASE + 8; + static final int CMD_AIRPLANE_TOGGLED = BASE + 9; + static final int CMD_SET_AP = BASE + 10; + + private DefaultState mDefaultState = new DefaultState(); + private StaEnabledState mStaEnabledState = new StaEnabledState(); + private ApStaDisabledState mApStaDisabledState = new ApStaDisabledState(); + private StaDisabledWithScanState mStaDisabledWithScanState = new StaDisabledWithScanState(); + private ApEnabledState mApEnabledState = new ApEnabledState(); + private DeviceActiveState mDeviceActiveState = new DeviceActiveState(); + private DeviceInactiveState mDeviceInactiveState = new DeviceInactiveState(); + private ScanOnlyLockHeldState mScanOnlyLockHeldState = new ScanOnlyLockHeldState(); + private FullLockHeldState mFullLockHeldState = new FullLockHeldState(); + private FullHighPerfLockHeldState mFullHighPerfLockHeldState = new FullHighPerfLockHeldState(); + private NoLockHeldState mNoLockHeldState = new NoLockHeldState(); + private EcmState mEcmState = new EcmState(); + + WifiController(Context context, WifiService service, Looper looper) { + super(TAG, looper); + mContext = context; + mWifiStateMachine = service.mWifiStateMachine; + mSettingsStore = service.mSettingsStore; + mLocks = service.mLocks; + + mAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE); + Intent idleIntent = new Intent(ACTION_DEVICE_IDLE, null); + mIdleIntent = PendingIntent.getBroadcast(mContext, IDLE_REQUEST, idleIntent, 0); + + addState(mDefaultState); + addState(mApStaDisabledState, mDefaultState); + addState(mStaEnabledState, mDefaultState); + addState(mDeviceActiveState, mStaEnabledState); + addState(mDeviceInactiveState, mStaEnabledState); + addState(mScanOnlyLockHeldState, mDeviceInactiveState); + addState(mFullLockHeldState, mDeviceInactiveState); + addState(mFullHighPerfLockHeldState, mDeviceInactiveState); + addState(mNoLockHeldState, mDeviceInactiveState); + addState(mStaDisabledWithScanState, mDefaultState); + addState(mApEnabledState, mDefaultState); + addState(mEcmState, mDefaultState); + setInitialState(mApStaDisabledState); + setLogRecSize(25); + setLogOnlyTransitions(true); + + IntentFilter filter = new IntentFilter(); + filter.addAction(ACTION_DEVICE_IDLE); + filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); + mContext.registerReceiver( + new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action.equals(ACTION_DEVICE_IDLE)) { + sendMessage(CMD_DEVICE_IDLE); + } else if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) { + mNetworkInfo = (NetworkInfo) intent.getParcelableExtra( + WifiManager.EXTRA_NETWORK_INFO); + } + } + }, + new IntentFilter(filter)); + + initializeAndRegisterForSettingsChange(looper); + } + + private void initializeAndRegisterForSettingsChange(Looper looper) { + Handler handler = new Handler(looper); + readStayAwakeConditions(); + registerForStayAwakeModeChange(handler); + readWifiIdleTime(); + registerForWifiIdleTimeChange(handler); + readStayAwakeConditions(); + registerForWifiSleepPolicyChange(handler); + } + + private void readStayAwakeConditions() { + mStayAwakeConditions = Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0); + } + + private void readWifiIdleTime() { + mIdleMillis = Settings.Global.getLong(mContext.getContentResolver(), + Settings.Global.WIFI_IDLE_MS, DEFAULT_IDLE_MS); + } + + private void readWifiSleepPolicy() { + mSleepPolicy = Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.WIFI_SLEEP_POLICY, + Settings.Global.WIFI_SLEEP_POLICY_NEVER); + } + + /** + * Observes settings changes to scan always mode. + */ + private void registerForStayAwakeModeChange(Handler handler) { + ContentObserver contentObserver = new ContentObserver(handler) { + @Override + public void onChange(boolean selfChange) { + readStayAwakeConditions(); + } + }; + + mContext.getContentResolver().registerContentObserver( + Settings.Global.getUriFor(Settings.Global.STAY_ON_WHILE_PLUGGED_IN), + false, contentObserver); + } + + /** + * Observes settings changes to scan always mode. + */ + private void registerForWifiIdleTimeChange(Handler handler) { + ContentObserver contentObserver = new ContentObserver(handler) { + @Override + public void onChange(boolean selfChange) { + readWifiIdleTime(); + } + }; + + mContext.getContentResolver().registerContentObserver( + Settings.Global.getUriFor(Settings.Global.WIFI_IDLE_MS), + false, contentObserver); + } + + /** + * Observes changes to wifi sleep policy + */ + private void registerForWifiSleepPolicyChange(Handler handler) { + ContentObserver contentObserver = new ContentObserver(handler) { + @Override + public void onChange(boolean selfChange) { + readWifiSleepPolicy(); + } + }; + mContext.getContentResolver().registerContentObserver( + Settings.Global.getUriFor(Settings.Global.WIFI_SLEEP_POLICY), + false, contentObserver); + } + + /** + * Determines whether the Wi-Fi chipset should stay awake or be put to + * sleep. Looks at the setting for the sleep policy and the current + * conditions. + * + * @see #shouldDeviceStayAwake(int) + */ + private boolean shouldWifiStayAwake(int pluggedType) { + if (mSleepPolicy == Settings.Global.WIFI_SLEEP_POLICY_NEVER) { + // Never sleep + return true; + } else if ((mSleepPolicy == Settings.Global.WIFI_SLEEP_POLICY_NEVER_WHILE_PLUGGED) && + (pluggedType != 0)) { + // Never sleep while plugged, and we're plugged + return true; + } else { + // Default + return shouldDeviceStayAwake(pluggedType); + } + } + + /** + * Determine whether the bit value corresponding to {@code pluggedType} is set in + * the bit string mStayAwakeConditions. This determines whether the device should + * stay awake based on the current plugged type. + * + * @param pluggedType the type of plug (USB, AC, or none) for which the check is + * being made + * @return {@code true} if {@code pluggedType} indicates that the device is + * supposed to stay awake, {@code false} otherwise. + */ + private boolean shouldDeviceStayAwake(int pluggedType) { + return (mStayAwakeConditions & pluggedType) != 0; + } + + private void updateBatteryWorkSource() { + mTmpWorkSource.clear(); + if (mDeviceIdle) { + mLocks.updateWorkSource(mTmpWorkSource); + } + mWifiStateMachine.updateBatteryWorkSource(mTmpWorkSource); + } + + class DefaultState extends State { + @Override + public boolean processMessage(Message msg) { + switch (msg.what) { + case CMD_SCREEN_ON: + mAlarmManager.cancel(mIdleIntent); + mScreenOff = false; + mDeviceIdle = false; + updateBatteryWorkSource(); + break; + case CMD_SCREEN_OFF: + mScreenOff = true; + /* + * Set a timer to put Wi-Fi to sleep, but only if the screen is off + * AND the "stay on while plugged in" setting doesn't match the + * current power conditions (i.e, not plugged in, plugged in to USB, + * or plugged in to AC). + */ + if (!shouldWifiStayAwake(mPluggedType)) { + //Delayed shutdown if wifi is connected + if (mNetworkInfo.getDetailedState() == + NetworkInfo.DetailedState.CONNECTED) { + if (DBG) Slog.d(TAG, "set idle timer: " + mIdleMillis + " ms"); + mAlarmManager.set(AlarmManager.RTC_WAKEUP, + System.currentTimeMillis() + mIdleMillis, mIdleIntent); + } else { + sendMessage(CMD_DEVICE_IDLE); + } + } + break; + case CMD_DEVICE_IDLE: + mDeviceIdle = true; + updateBatteryWorkSource(); + break; + case CMD_BATTERY_CHANGED: + /* + * Set a timer to put Wi-Fi to sleep, but only if the screen is off + * AND we are transitioning from a state in which the device was supposed + * to stay awake to a state in which it is not supposed to stay awake. + * If "stay awake" state is not changing, we do nothing, to avoid resetting + * the already-set timer. + */ + int pluggedType = msg.arg1; + if (DBG) Slog.d(TAG, "battery changed pluggedType: " + pluggedType); + if (mScreenOff && shouldWifiStayAwake(mPluggedType) && + !shouldWifiStayAwake(pluggedType)) { + long triggerTime = System.currentTimeMillis() + mIdleMillis; + if (DBG) Slog.d(TAG, "set idle timer for " + mIdleMillis + "ms"); + mAlarmManager.set(AlarmManager.RTC_WAKEUP, triggerTime, mIdleIntent); + } + + mPluggedType = pluggedType; + break; + case CMD_SET_AP: + case CMD_SCAN_ALWAYS_MODE_CHANGED: + case CMD_LOCKS_CHANGED: + case CMD_WIFI_TOGGLED: + case CMD_AIRPLANE_TOGGLED: + case CMD_EMERGENCY_MODE_CHANGED: + break; + default: + throw new RuntimeException("WifiController.handleMessage " + msg.what); + } + return HANDLED; + } + + } + + class ApStaDisabledState extends State { + @Override + public void enter() { + mWifiStateMachine.setSupplicantRunning(false); + } + @Override + public boolean processMessage(Message msg) { + switch (msg.what) { + case CMD_WIFI_TOGGLED: + case CMD_AIRPLANE_TOGGLED: + if (mSettingsStore.isWifiToggleEnabled()) { + if (mDeviceIdle == false) { + transitionTo(mDeviceActiveState); + } else { + checkLocksAndTransitionWhenDeviceIdle(); + } + } + break; + case CMD_SCAN_ALWAYS_MODE_CHANGED: + if (mSettingsStore.isScanAlwaysAvailable()) { + transitionTo(mStaDisabledWithScanState); + } + break; + case CMD_SET_AP: + if (msg.arg1 == 1) { + mWifiStateMachine.setHostApRunning((WifiConfiguration) msg.obj, + true); + transitionTo(mApEnabledState); + } + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + + } + + class StaEnabledState extends State { + @Override + public void enter() { + mWifiStateMachine.setSupplicantRunning(true); + } + @Override + public boolean processMessage(Message msg) { + switch (msg.what) { + case CMD_WIFI_TOGGLED: + if (! mSettingsStore.isWifiToggleEnabled()) { + if (mSettingsStore.isScanAlwaysAvailable()) { + transitionTo(mStaDisabledWithScanState); + } else { + transitionTo(mApStaDisabledState); + } + } + break; + case CMD_AIRPLANE_TOGGLED: + /* When wi-fi is turned off due to airplane, + * disable entirely (including scan) + */ + if (! mSettingsStore.isWifiToggleEnabled()) { + transitionTo(mApStaDisabledState); + } + break; + case CMD_EMERGENCY_MODE_CHANGED: + if (msg.arg1 == 1) { + transitionTo(mEcmState); + break; + } + default: + return NOT_HANDLED; + + } + return HANDLED; + } + } + + class StaDisabledWithScanState extends State { + @Override + public void enter() { + mWifiStateMachine.setSupplicantRunning(true); + mWifiStateMachine.setOperationalMode(WifiStateMachine.SCAN_ONLY_WITH_WIFI_OFF_MODE); + mWifiStateMachine.setDriverStart(true); + } + + @Override + public boolean processMessage(Message msg) { + switch (msg.what) { + case CMD_WIFI_TOGGLED: + if (mSettingsStore.isWifiToggleEnabled()) { + if (mDeviceIdle == false) { + transitionTo(mDeviceActiveState); + } else { + checkLocksAndTransitionWhenDeviceIdle(); + } + } + break; + case CMD_AIRPLANE_TOGGLED: + if (mSettingsStore.isAirplaneModeOn() && + ! mSettingsStore.isWifiToggleEnabled()) { + transitionTo(mApStaDisabledState); + } + case CMD_SCAN_ALWAYS_MODE_CHANGED: + if (! mSettingsStore.isScanAlwaysAvailable()) { + transitionTo(mApStaDisabledState); + } + break; + case CMD_SET_AP: + // Before starting tethering, turn off supplicant for scan mode + if (msg.arg1 == 1) { + deferMessage(msg); + transitionTo(mApStaDisabledState); + } + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + } + + class ApEnabledState extends State { + @Override + public boolean processMessage(Message msg) { + switch (msg.what) { + case CMD_AIRPLANE_TOGGLED: + if (mSettingsStore.isAirplaneModeOn()) { + mWifiStateMachine.setHostApRunning(null, false); + transitionTo(mApStaDisabledState); + } + break; + case CMD_SET_AP: + if (msg.arg1 == 0) { + mWifiStateMachine.setHostApRunning(null, false); + transitionTo(mApStaDisabledState); + } + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + } + + class EcmState extends State { + @Override + public void enter() { + mWifiStateMachine.setSupplicantRunning(false); + } + + @Override + public boolean processMessage(Message msg) { + if (msg.what == CMD_EMERGENCY_MODE_CHANGED && msg.arg1 == 0) { + if (mSettingsStore.isWifiToggleEnabled()) { + if (mDeviceIdle == false) { + transitionTo(mDeviceActiveState); + } else { + checkLocksAndTransitionWhenDeviceIdle(); + } + } else if (mSettingsStore.isScanAlwaysAvailable()) { + transitionTo(mStaDisabledWithScanState); + } else { + transitionTo(mApStaDisabledState); + } + return HANDLED; + } else { + return NOT_HANDLED; + } + } + } + + /* Parent: StaEnabledState */ + class DeviceActiveState extends State { + @Override + public void enter() { + mWifiStateMachine.setOperationalMode(WifiStateMachine.CONNECT_MODE); + mWifiStateMachine.setDriverStart(true); + mWifiStateMachine.setHighPerfModeEnabled(false); + } + + @Override + public boolean processMessage(Message msg) { + if (msg.what == CMD_DEVICE_IDLE) { + checkLocksAndTransitionWhenDeviceIdle(); + // We let default state handle the rest of work + } + return NOT_HANDLED; + } + } + + /* Parent: StaEnabledState */ + class DeviceInactiveState extends State { + @Override + public boolean processMessage(Message msg) { + switch (msg.what) { + case CMD_LOCKS_CHANGED: + checkLocksAndTransitionWhenDeviceIdle(); + updateBatteryWorkSource(); + return HANDLED; + case CMD_SCREEN_ON: + transitionTo(mDeviceActiveState); + // More work in default state + return NOT_HANDLED; + default: + return NOT_HANDLED; + } + } + } + + /* Parent: DeviceInactiveState. Device is inactive, but an app is holding a scan only lock. */ + class ScanOnlyLockHeldState extends State { + @Override + public void enter() { + mWifiStateMachine.setOperationalMode(WifiStateMachine.SCAN_ONLY_MODE); + mWifiStateMachine.setDriverStart(true); + } + } + + /* Parent: DeviceInactiveState. Device is inactive, but an app is holding a full lock. */ + class FullLockHeldState extends State { + @Override + public void enter() { + mWifiStateMachine.setOperationalMode(WifiStateMachine.CONNECT_MODE); + mWifiStateMachine.setDriverStart(true); + mWifiStateMachine.setHighPerfModeEnabled(false); + } + } + + /* Parent: DeviceInactiveState. Device is inactive, but an app is holding a high perf lock. */ + class FullHighPerfLockHeldState extends State { + @Override + public void enter() { + mWifiStateMachine.setOperationalMode(WifiStateMachine.CONNECT_MODE); + mWifiStateMachine.setDriverStart(true); + mWifiStateMachine.setHighPerfModeEnabled(true); + } + } + + /* Parent: DeviceInactiveState. Device is inactive and no app is holding a wifi lock. */ + class NoLockHeldState extends State { + @Override + public void enter() { + mWifiStateMachine.setDriverStart(false); + } + } + + private void checkLocksAndTransitionWhenDeviceIdle() { + if (mLocks.hasLocks()) { + switch (mLocks.getStrongestLockMode()) { + case WIFI_MODE_FULL: + transitionTo(mFullLockHeldState); + break; + case WIFI_MODE_FULL_HIGH_PERF: + transitionTo(mFullHighPerfLockHeldState); + break; + case WIFI_MODE_SCAN_ONLY: + transitionTo(mScanOnlyLockHeldState); + break; + default: + loge("Illegal lock " + mLocks.getStrongestLockMode()); + } + } else { + if (mSettingsStore.isScanAlwaysAvailable()) { + transitionTo(mScanOnlyLockHeldState); + } else { + transitionTo(mNoLockHeldState); + } + } + } + + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + super.dump(fd, pw, args); + + pw.println("mScreenOff " + mScreenOff); + pw.println("mDeviceIdle " + mDeviceIdle); + pw.println("mPluggedType " + mPluggedType); + pw.println("mIdleMillis " + mIdleMillis); + pw.println("mSleepPolicy " + mSleepPolicy); + } +} diff --git a/services/java/com/android/server/wifi/WifiService.java b/services/java/com/android/server/wifi/WifiService.java index d675822ea10f..bc6bdaf903ac 100644 --- a/services/java/com/android/server/wifi/WifiService.java +++ b/services/java/com/android/server/wifi/WifiService.java @@ -17,15 +17,14 @@ package com.android.server.wifi; import android.app.ActivityManager; -import android.app.AlarmManager; import android.app.AppOpsManager; -import android.app.PendingIntent; import android.bluetooth.BluetoothAdapter; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; +import android.database.ContentObserver; import android.net.wifi.IWifiManager; import android.net.wifi.ScanResult; import android.net.wifi.WifiInfo; @@ -33,12 +32,9 @@ import android.net.wifi.WifiManager; import android.net.wifi.WifiStateMachine; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiWatchdogStateMachine; -import android.net.ConnectivityManager; import android.net.DhcpInfo; import android.net.DhcpResults; import android.net.LinkAddress; -import android.net.NetworkInfo; -import android.net.NetworkInfo.DetailedState; import android.net.NetworkUtils; import android.net.RouteInfo; import android.os.Binder; @@ -63,39 +59,35 @@ import java.net.Inet4Address; import java.util.ArrayList; import java.util.List; +import com.android.internal.R; import com.android.internal.app.IBatteryStats; import com.android.internal.telephony.TelephonyIntents; import com.android.internal.util.AsyncChannel; import com.android.server.am.BatteryStatsService; -import com.android.internal.R; - +import static com.android.server.wifi.WifiController.CMD_AIRPLANE_TOGGLED; +import static com.android.server.wifi.WifiController.CMD_BATTERY_CHANGED; +import static com.android.server.wifi.WifiController.CMD_EMERGENCY_MODE_CHANGED; +import static com.android.server.wifi.WifiController.CMD_LOCKS_CHANGED; +import static com.android.server.wifi.WifiController.CMD_SCAN_ALWAYS_MODE_CHANGED; +import static com.android.server.wifi.WifiController.CMD_SCREEN_OFF; +import static com.android.server.wifi.WifiController.CMD_SCREEN_ON; +import static com.android.server.wifi.WifiController.CMD_SET_AP; +import static com.android.server.wifi.WifiController.CMD_WIFI_TOGGLED; /** * WifiService handles remote WiFi operation requests by implementing * the IWifiManager interface. * * @hide */ -//TODO: Clean up multiple locks and implement WifiService -// as a SM to track soft AP/client/adhoc bring up based -// on device idle state, airplane mode and boot. - public final class WifiService extends IWifiManager.Stub { private static final String TAG = "WifiService"; private static final boolean DBG = false; - private final WifiStateMachine mWifiStateMachine; + final WifiStateMachine mWifiStateMachine; private final Context mContext; - private AlarmManager mAlarmManager; - private PendingIntent mIdleIntent; - private static final int IDLE_REQUEST = 0; - private boolean mScreenOff; - private boolean mDeviceIdle; - private boolean mEmergencyCallbackMode = false; - private int mPluggedType; - - private final LockList mLocks = new LockList(); + final LockList mLocks = new LockList(); // some wifi lock statistics private int mFullHighPerfLocksAcquired; private int mFullHighPerfLocksReleased; @@ -119,19 +111,7 @@ public final class WifiService extends IWifiManager.Stub { /* Polls traffic stats and notifies clients */ private WifiTrafficPoller mTrafficPoller; /* Tracks the persisted states for wi-fi & airplane mode */ - private WifiSettingsStore mSettingsStore; - - /** - * See {@link Settings.Global#WIFI_IDLE_MS}. This is the default value if a - * Settings.Global value is not present. This timeout value is chosen as - * the approximate point at which the battery drain caused by Wi-Fi - * being enabled but not active exceeds the battery drain caused by - * re-establishing a connection to the mobile data network. - */ - private static final long DEFAULT_IDLE_MS = 15 * 60 * 1000; /* 15 minutes */ - - private static final String ACTION_DEVICE_IDLE = - "com.android.server.WifiManager.action.DEVICE_IDLE"; + final WifiSettingsStore mSettingsStore; /* The work source (UID) that triggered the current WIFI scan, synchronized * on this */ @@ -139,8 +119,6 @@ public final class WifiService extends IWifiManager.Stub { private boolean mIsReceiverRegistered = false; - NetworkInfo mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_WIFI, 0, "WIFI", ""); - /** * Asynchronous channel to WifiStateMachine */ @@ -195,7 +173,7 @@ public final class WifiService extends IWifiManager.Stub { break; } default: { - Slog.d(TAG, "WifiServicehandler.handleMessage ignoring msg=" + msg); + Slog.d(TAG, "ClientHandler.handleMessage ignoring msg=" + msg); break; } } @@ -243,11 +221,6 @@ public final class WifiService extends IWifiManager.Stub { } WifiStateMachineHandler mWifiStateMachineHandler; - /** - * Temporary for computing UIDS that are responsible for starting WIFI. - * Protected by mWifiStateTracker lock. - */ - private final WorkSource mTmpWorkSource = new WorkSource(); private WifiWatchdogStateMachine mWifiWatchdogStateMachine; public WifiService(Context context) { @@ -260,20 +233,24 @@ public final class WifiService extends IWifiManager.Stub { mBatteryStats = BatteryStatsService.getService(); mAppOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE); - mAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE); - Intent idleIntent = new Intent(ACTION_DEVICE_IDLE, null); - mIdleIntent = PendingIntent.getBroadcast(mContext, IDLE_REQUEST, idleIntent, 0); - mNotificationController = new WifiNotificationController(mContext, mWifiStateMachine); mTrafficPoller = new WifiTrafficPoller(mContext, mInterfaceName); mSettingsStore = new WifiSettingsStore(mContext); + HandlerThread wifiThread = new HandlerThread("WifiService"); + wifiThread.start(); + mClientHandler = new ClientHandler(wifiThread.getLooper()); + mWifiStateMachineHandler = new WifiStateMachineHandler(wifiThread.getLooper()); + mWifiController = new WifiController(mContext, this, wifiThread.getLooper()); + mWifiController.start(); + + registerForScanModeChange(); mContext.registerReceiver( new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (mSettingsStore.handleAirplaneModeToggled()) { - updateWifiState(); + mWifiController.sendMessage(CMD_AIRPLANE_TOGGLED); } } }, @@ -289,13 +266,10 @@ public final class WifiService extends IWifiManager.Stub { } } }, new IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)); - - HandlerThread wifiThread = new HandlerThread("WifiService"); - wifiThread.start(); - mClientHandler = new ClientHandler(wifiThread.getLooper()); - mWifiStateMachineHandler = new WifiStateMachineHandler(wifiThread.getLooper()); } + private WifiController mWifiController; + /** Tell battery stats about a new WIFI scan */ private void noteScanStart() { WorkSource scanWorkSource = null; @@ -342,7 +316,7 @@ public final class WifiService extends IWifiManager.Stub { */ public void checkAndStartWifi() { /* Check if wi-fi needs to be enabled */ - boolean wifiEnabled = mSettingsStore.shouldWifiBeEnabled(); + boolean wifiEnabled = mSettingsStore.isWifiToggleEnabled(); Slog.i(TAG, "WifiService starting up with Wi-Fi " + (wifiEnabled ? "enabled" : "disabled")); @@ -430,11 +404,7 @@ public final class WifiService extends IWifiManager.Stub { Binder.restoreCallingIdentity(ident); } - if (enable) { - reportStartWorkSource(); - } - - mWifiStateMachine.setWifiEnabled(enable); + mWifiController.sendMessage(CMD_WIFI_TOGGLED); if (enable) { if (!mIsReceiverRegistered) { @@ -470,7 +440,7 @@ public final class WifiService extends IWifiManager.Stub { */ public void setWifiApEnabled(WifiConfiguration wifiConfig, boolean enabled) { enforceChangePermission(); - mWifiStateMachine.setWifiApEnabled(wifiConfig, enabled); + mWifiController.obtainMessage(CMD_SET_AP, enabled ? 1 : 0, 0, wifiConfig).sendToTarget(); } /** @@ -507,6 +477,26 @@ public final class WifiService extends IWifiManager.Stub { } /** + * @param enable {@code true} to enable, {@code false} to disable. + * @return {@code true} if the enable/disable operation was + * started or is already in the queue. + */ + public boolean isScanningAlwaysAvailable() { + // TODO: implement + return true; + } + + /** + * @param enable {@code true} to enable, {@code false} to disable. + * @return {@code true} if the enable/disable operation was + * started or is already in the queue. + */ + public void setScanningAlwaysAvailable(boolean enable) { + // TODO: implement + } + + + /** * see {@link android.net.wifi.WifiManager#disconnect()} */ public void disconnect() { @@ -777,7 +767,7 @@ public final class WifiService extends IWifiManager.Stub { * of WifiLock & device idle status unless wifi enabled status is toggled */ - mWifiStateMachine.setDriverStart(true, mEmergencyCallbackMode); + mWifiStateMachine.setDriverStart(true); mWifiStateMachine.reconnectCommand(); } @@ -796,7 +786,7 @@ public final class WifiService extends IWifiManager.Stub { * TODO: if a stop is issued, wifi is brought up only by startWifi * unless wifi enabled status is toggled */ - mWifiStateMachine.setDriverStart(false, mEmergencyCallbackMode); + mWifiStateMachine.setDriverStart(false); } /** @@ -848,175 +838,39 @@ public final class WifiService extends IWifiManager.Stub { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); - - long idleMillis = - Settings.Global.getLong(mContext.getContentResolver(), - Settings.Global.WIFI_IDLE_MS, DEFAULT_IDLE_MS); - int stayAwakeConditions = - Settings.Global.getInt(mContext.getContentResolver(), - Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0); if (action.equals(Intent.ACTION_SCREEN_ON)) { - if (DBG) { - Slog.d(TAG, "ACTION_SCREEN_ON"); - } - mAlarmManager.cancel(mIdleIntent); - mScreenOff = false; - setDeviceIdleAndUpdateWifi(false); + mWifiController.sendMessage(CMD_SCREEN_ON); } else if (action.equals(Intent.ACTION_SCREEN_OFF)) { - if (DBG) { - Slog.d(TAG, "ACTION_SCREEN_OFF"); - } - mScreenOff = true; - /* - * Set a timer to put Wi-Fi to sleep, but only if the screen is off - * AND the "stay on while plugged in" setting doesn't match the - * current power conditions (i.e, not plugged in, plugged in to USB, - * or plugged in to AC). - */ - if (!shouldWifiStayAwake(stayAwakeConditions, mPluggedType)) { - //Delayed shutdown if wifi is connected - if (mNetworkInfo.getDetailedState() == DetailedState.CONNECTED) { - if (DBG) Slog.d(TAG, "setting ACTION_DEVICE_IDLE: " + idleMillis + " ms"); - mAlarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() - + idleMillis, mIdleIntent); - } else { - setDeviceIdleAndUpdateWifi(true); - } - } - } else if (action.equals(ACTION_DEVICE_IDLE)) { - setDeviceIdleAndUpdateWifi(true); + mWifiController.sendMessage(CMD_SCREEN_OFF); } else if (action.equals(Intent.ACTION_BATTERY_CHANGED)) { - /* - * Set a timer to put Wi-Fi to sleep, but only if the screen is off - * AND we are transitioning from a state in which the device was supposed - * to stay awake to a state in which it is not supposed to stay awake. - * If "stay awake" state is not changing, we do nothing, to avoid resetting - * the already-set timer. - */ int pluggedType = intent.getIntExtra("plugged", 0); - if (DBG) { - Slog.d(TAG, "ACTION_BATTERY_CHANGED pluggedType: " + pluggedType); - } - if (mScreenOff && shouldWifiStayAwake(stayAwakeConditions, mPluggedType) && - !shouldWifiStayAwake(stayAwakeConditions, pluggedType)) { - long triggerTime = System.currentTimeMillis() + idleMillis; - if (DBG) { - Slog.d(TAG, "setting ACTION_DEVICE_IDLE timer for " + idleMillis + "ms"); - } - mAlarmManager.set(AlarmManager.RTC_WAKEUP, triggerTime, mIdleIntent); - } - - mPluggedType = pluggedType; + mWifiController.sendMessage(CMD_BATTERY_CHANGED, pluggedType, 0, null); } else if (action.equals(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)) { int state = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE, BluetoothAdapter.STATE_DISCONNECTED); mWifiStateMachine.sendBluetoothAdapterStateChange(state); } else if (action.equals(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED)) { - mEmergencyCallbackMode = intent.getBooleanExtra("phoneinECMState", false); - updateWifiState(); + boolean emergencyMode = intent.getBooleanExtra("phoneinECMState", false); + mWifiController.sendMessage(CMD_EMERGENCY_MODE_CHANGED, emergencyMode ? 1 : 0, 0); } } - - /** - * Determines whether the Wi-Fi chipset should stay awake or be put to - * sleep. Looks at the setting for the sleep policy and the current - * conditions. - * - * @see #shouldDeviceStayAwake(int, int) - */ - private boolean shouldWifiStayAwake(int stayAwakeConditions, int pluggedType) { - //Never sleep as long as the user has not changed the settings - int wifiSleepPolicy = Settings.Global.getInt(mContext.getContentResolver(), - Settings.Global.WIFI_SLEEP_POLICY, - Settings.Global.WIFI_SLEEP_POLICY_NEVER); - - if (wifiSleepPolicy == Settings.Global.WIFI_SLEEP_POLICY_NEVER) { - // Never sleep - return true; - } else if ((wifiSleepPolicy == Settings.Global.WIFI_SLEEP_POLICY_NEVER_WHILE_PLUGGED) && - (pluggedType != 0)) { - // Never sleep while plugged, and we're plugged - return true; - } else { - // Default - return shouldDeviceStayAwake(stayAwakeConditions, pluggedType); - } - } - - /** - * Determine whether the bit value corresponding to {@code pluggedType} is set in - * the bit string {@code stayAwakeConditions}. Because a {@code pluggedType} value - * of {@code 0} isn't really a plugged type, but rather an indication that the - * device isn't plugged in at all, there is no bit value corresponding to a - * {@code pluggedType} value of {@code 0}. That is why we shift by - * {@code pluggedType - 1} instead of by {@code pluggedType}. - * @param stayAwakeConditions a bit string specifying which "plugged types" should - * keep the device (and hence Wi-Fi) awake. - * @param pluggedType the type of plug (USB, AC, or none) for which the check is - * being made - * @return {@code true} if {@code pluggedType} indicates that the device is - * supposed to stay awake, {@code false} otherwise. - */ - private boolean shouldDeviceStayAwake(int stayAwakeConditions, int pluggedType) { - return (stayAwakeConditions & pluggedType) != 0; - } }; - private void setDeviceIdleAndUpdateWifi(boolean deviceIdle) { - mDeviceIdle = deviceIdle; - reportStartWorkSource(); - updateWifiState(); - } - - private synchronized void reportStartWorkSource() { - mTmpWorkSource.clear(); - if (mDeviceIdle) { - for (int i=0; i<mLocks.mList.size(); i++) { - mTmpWorkSource.add(mLocks.mList.get(i).mWorkSource); + /** + * Observes settings changes to scan always mode. + */ + private void registerForScanModeChange() { + ContentObserver contentObserver = new ContentObserver(null) { + @Override + public void onChange(boolean selfChange) { + mSettingsStore.handleWifiScanAlwaysAvailableToggled(); + mWifiController.sendMessage(CMD_SCAN_ALWAYS_MODE_CHANGED); } - } - mWifiStateMachine.updateBatteryWorkSource(mTmpWorkSource); - } - - private void updateWifiState() { - boolean lockHeld = mLocks.hasLocks(); - int strongestLockMode = WifiManager.WIFI_MODE_FULL; - boolean wifiShouldBeStarted; - - if (mEmergencyCallbackMode) { - wifiShouldBeStarted = false; - } else { - wifiShouldBeStarted = !mDeviceIdle || lockHeld; - } - - if (lockHeld) { - strongestLockMode = mLocks.getStrongestLockMode(); - } - /* If device is not idle, lockmode cannot be scan only */ - if (!mDeviceIdle && strongestLockMode == WifiManager.WIFI_MODE_SCAN_ONLY) { - strongestLockMode = WifiManager.WIFI_MODE_FULL; - } - - /* Disable tethering when airplane mode is enabled */ - if (mSettingsStore.isAirplaneModeOn()) { - mWifiStateMachine.setWifiApEnabled(null, false); - } + }; - if (mSettingsStore.shouldWifiBeEnabled()) { - if (wifiShouldBeStarted) { - reportStartWorkSource(); - mWifiStateMachine.setWifiEnabled(true); - mWifiStateMachine.setScanOnlyMode( - strongestLockMode == WifiManager.WIFI_MODE_SCAN_ONLY); - mWifiStateMachine.setDriverStart(true, mEmergencyCallbackMode); - mWifiStateMachine.setHighPerfModeEnabled(strongestLockMode - == WifiManager.WIFI_MODE_FULL_HIGH_PERF); - } else { - mWifiStateMachine.setDriverStart(false, mEmergencyCallbackMode); - } - } else { - mWifiStateMachine.setWifiEnabled(false); - } + mContext.getContentResolver().registerContentObserver( + Settings.Global.getUriFor(Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE), + false, contentObserver); } private void registerForBroadcasts() { @@ -1024,7 +878,7 @@ public final class WifiService extends IWifiManager.Stub { intentFilter.addAction(Intent.ACTION_SCREEN_ON); intentFilter.addAction(Intent.ACTION_SCREEN_OFF); intentFilter.addAction(Intent.ACTION_BATTERY_CHANGED); - intentFilter.addAction(ACTION_DEVICE_IDLE); + intentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); intentFilter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED); intentFilter.addAction(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED); mContext.registerReceiver(mReceiver, intentFilter); @@ -1043,12 +897,9 @@ public final class WifiService extends IWifiManager.Stub { pw.println("Stay-awake conditions: " + Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0)); - pw.println("mScreenOff " + mScreenOff); - pw.println("mDeviceIdle " + mDeviceIdle); - pw.println("mPluggedType " + mPluggedType); - pw.println("mEmergencyCallbackMode " + mEmergencyCallbackMode); pw.println("mMulticastEnabled " + mMulticastEnabled); pw.println("mMulticastDisabled " + mMulticastDisabled); + mWifiController.dump(fd, pw, args); mSettingsStore.dump(fd, pw, args); mNotificationController.dump(fd, pw, args); mTrafficPoller.dump(fd, pw, args); @@ -1099,18 +950,18 @@ public final class WifiService extends IWifiManager.Stub { } } - private class LockList { + class LockList { private List<WifiLock> mList; private LockList() { mList = new ArrayList<WifiLock>(); } - private synchronized boolean hasLocks() { + synchronized boolean hasLocks() { return !mList.isEmpty(); } - private synchronized int getStrongestLockMode() { + synchronized int getStrongestLockMode() { if (mList.isEmpty()) { return WifiManager.WIFI_MODE_FULL; } @@ -1126,6 +977,12 @@ public final class WifiService extends IWifiManager.Stub { return WifiManager.WIFI_MODE_SCAN_ONLY; } + synchronized void updateWorkSource(WorkSource ws) { + for (int i = 0; i < mLocks.mList.size(); i++) { + ws.add(mLocks.mList.get(i).mWorkSource); + } + } + private void addLock(WifiLock lock) { if (findLockByBinder(lock.mBinder) < 0) { mList.add(lock); @@ -1145,9 +1002,10 @@ public final class WifiService extends IWifiManager.Stub { private int findLockByBinder(IBinder binder) { int size = mList.size(); - for (int i = size - 1; i >= 0; i--) + for (int i = size - 1; i >= 0; i--) { if (mList.get(i).mBinder == binder) return i; + } return -1; } @@ -1231,12 +1089,7 @@ public final class WifiService extends IWifiManager.Stub { ++mScanLocksAcquired; break; } - - // Be aggressive about adding new locks into the accounted state... - // we want to over-report rather than under-report. - reportStartWorkSource(); - - updateWifiState(); + mWifiController.sendMessage(CMD_LOCKS_CHANGED); return true; } catch (RemoteException e) { return false; @@ -1303,11 +1156,8 @@ public final class WifiService extends IWifiManager.Stub { ++mScanLocksReleased; break; } + mWifiController.sendMessage(CMD_LOCKS_CHANGED); } - - // TODO - should this only happen if you hadLock? - updateWifiState(); - } catch (RemoteException e) { } finally { Binder.restoreCallingIdentity(ident); diff --git a/services/java/com/android/server/wifi/WifiSettingsStore.java b/services/java/com/android/server/wifi/WifiSettingsStore.java index d7c87521d00c..3ff80616142c 100644 --- a/services/java/com/android/server/wifi/WifiSettingsStore.java +++ b/services/java/com/android/server/wifi/WifiSettingsStore.java @@ -37,7 +37,11 @@ final class WifiSettingsStore { private int mPersistWifiState = WIFI_DISABLED; /* Tracks current airplane mode state */ private boolean mAirplaneModeOn = false; - /* Tracks whether wifi is enabled from WifiStateMachine's perspective */ + + /* Tracks the setting of scan being available even when wi-fi is turned off + */ + private boolean mScanAlwaysAvailable; + private final Context mContext; /* Tracks if we have checked the saved wi-fi state after boot */ @@ -47,9 +51,10 @@ final class WifiSettingsStore { mContext = context; mAirplaneModeOn = getPersistedAirplaneModeOn(); mPersistWifiState = getPersistedWifiState(); + mScanAlwaysAvailable = getPersistedScanAlwaysAvailable(); } - synchronized boolean shouldWifiBeEnabled() { + synchronized boolean isWifiToggleEnabled() { if (!mCheckSavedStateAtBoot) { mCheckSavedStateAtBoot = true; if (testAndClearWifiSavedState()) return true; @@ -70,6 +75,10 @@ final class WifiSettingsStore { return mAirplaneModeOn; } + synchronized boolean isScanAlwaysAvailable() { + return mScanAlwaysAvailable; + } + synchronized boolean handleWifiToggled(boolean wifiEnabled) { // Can Wi-Fi be toggled in airplane mode ? if (mAirplaneModeOn && !isAirplaneToggleable()) { @@ -114,6 +123,10 @@ final class WifiSettingsStore { return true; } + synchronized void handleWifiScanAlwaysAvailableToggled() { + mScanAlwaysAvailable = getPersistedScanAlwaysAvailable(); + } + void dump(FileDescriptor fd, PrintWriter pw, String[] args) { pw.println("mPersistWifiState " + mPersistWifiState); pw.println("mAirplaneModeOn " + mAirplaneModeOn); @@ -175,4 +188,10 @@ final class WifiSettingsStore { return Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 0) == 1; } + + private boolean getPersistedScanAlwaysAvailable() { + return Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, + 0) == 1; + } } diff --git a/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java b/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java index fbdd333cca18..dffb617680e6 100644 --- a/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java +++ b/tests/AppLaunch/src/com/android/tests/applaunch/AppLaunch.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012 The Android Open Source Project + * Copyright (C) 2013 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,6 +32,7 @@ import android.test.InstrumentationTestCase; import android.test.InstrumentationTestRunner; import android.util.Log; +import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -48,16 +49,22 @@ import java.util.Map; public class AppLaunch extends InstrumentationTestCase { private static final int JOIN_TIMEOUT = 10000; - private static final String TAG = "AppLaunch"; + private static final String TAG = AppLaunch.class.getSimpleName(); private static final String KEY_APPS = "apps"; + private static final String KEY_LAUNCH_ITERATIONS = "launch_iterations"; + private static final int INITIAL_LAUNCH_IDLE_TIMEOUT = 7500; //7.5s to allow app to idle + private static final int POST_LAUNCH_IDLE_TIMEOUT = 750; //750ms idle for non initial launches + private static final int BETWEEN_LAUNCH_SLEEP_TIMEOUT = 2000; //2s between launching apps private Map<String, Intent> mNameToIntent; private Map<String, String> mNameToProcess; private Map<String, String> mNameToResultKey; - + private Map<String, Long> mNameToLaunchTime; private IActivityManager mAm; + private int mLaunchIterations = 10; + private Bundle mResult = new Bundle(); - public void testMeasureStartUpTime() throws RemoteException { + public void testMeasureStartUpTime() throws RemoteException, NameNotFoundException { InstrumentationTestRunner instrumentation = (InstrumentationTestRunner)getInstrumentation(); Bundle args = instrumentation.getArguments(); @@ -66,25 +73,59 @@ public class AppLaunch extends InstrumentationTestCase { createMappings(); parseArgs(args); - Bundle results = new Bundle(); + // do initial app launch, without force stopping for (String app : mNameToResultKey.keySet()) { - try { - startApp(app, results); - sleep(750); - closeApp(app); - sleep(2000); - } catch (NameNotFoundException e) { - Log.i(TAG, "Application " + app + " not found"); + long launchTime = startApp(app, false); + if (launchTime <=0 ) { + mNameToLaunchTime.put(app, -1L); + // simply pass the app if launch isn't successful + // error should have already been logged by startApp + continue; + } + sleep(INITIAL_LAUNCH_IDLE_TIMEOUT); + closeApp(app, false); + sleep(BETWEEN_LAUNCH_SLEEP_TIMEOUT); + } + // do the real app launch now + for (int i = 0; i < mLaunchIterations; i++) { + for (String app : mNameToResultKey.keySet()) { + long totalLaunchTime = mNameToLaunchTime.get(app); + long launchTime = 0; + if (totalLaunchTime < 0) { + // skip if the app has previous failures + continue; + } + launchTime = startApp(app, true); + if (launchTime <= 0) { + // if it fails once, skip the rest of the launches + mNameToLaunchTime.put(app, -1L); + continue; + } + totalLaunchTime += launchTime; + mNameToLaunchTime.put(app, totalLaunchTime); + sleep(POST_LAUNCH_IDLE_TIMEOUT); + closeApp(app, true); + sleep(BETWEEN_LAUNCH_SLEEP_TIMEOUT); + } + } + for (String app : mNameToResultKey.keySet()) { + long totalLaunchTime = mNameToLaunchTime.get(app); + if (totalLaunchTime != -1) { + mResult.putDouble(mNameToResultKey.get(app), + ((double) totalLaunchTime) / mLaunchIterations); } - } - instrumentation.sendStatus(0, results); + instrumentation.sendStatus(0, mResult); } private void parseArgs(Bundle args) { mNameToResultKey = new LinkedHashMap<String, String>(); + mNameToLaunchTime = new HashMap<String, Long>(); + String launchIterations = args.getString(KEY_LAUNCH_ITERATIONS); + if (launchIterations != null) { + mLaunchIterations = Integer.parseInt(launchIterations); + } String appList = args.getString(KEY_APPS); - if (appList == null) return; @@ -97,6 +138,7 @@ public class AppLaunch extends InstrumentationTestCase { } mNameToResultKey.put(parts[0], parts[1]); + mNameToLaunchTime.put(parts[0], 0L); } } @@ -118,23 +160,26 @@ public class AppLaunch extends InstrumentationTestCase { | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); startIntent.setClassName(ri.activityInfo.packageName, ri.activityInfo.name); - mNameToIntent.put(ri.loadLabel(pm).toString(), startIntent); - mNameToProcess.put(ri.loadLabel(pm).toString(), - ri.activityInfo.processName); + String appName = ri.loadLabel(pm).toString(); + if (appName != null) { + mNameToIntent.put(appName, startIntent); + mNameToProcess.put(appName, ri.activityInfo.processName); + } } } } - private void startApp(String appName, Bundle results) + private long startApp(String appName, boolean forceStopBeforeLaunch) throws NameNotFoundException, RemoteException { Log.i(TAG, "Starting " + appName); Intent startIntent = mNameToIntent.get(appName); if (startIntent == null) { Log.w(TAG, "App does not exist: " + appName); - return; + mResult.putString(mNameToResultKey.get(appName), "App does not exist"); + return -1; } - AppLaunchRunnable runnable = new AppLaunchRunnable(startIntent); + AppLaunchRunnable runnable = new AppLaunchRunnable(startIntent, forceStopBeforeLaunch); Thread t = new Thread(runnable); t.start(); try { @@ -143,27 +188,38 @@ public class AppLaunch extends InstrumentationTestCase { // ignore } WaitResult result = runnable.getResult(); - if(t.isAlive() || (result != null && result.result != ActivityManager.START_SUCCESS)) { + // report error if any of the following is true: + // * launch thread is alive + // * result is not null, but: + // * result is not START_SUCESS + // * or in case of no force stop, result is not TASK_TO_FRONT either + if (t.isAlive() || (result != null + && ((result.result != ActivityManager.START_SUCCESS) + && (!forceStopBeforeLaunch + && result.result != ActivityManager.START_TASK_TO_FRONT)))) { Log.w(TAG, "Assuming app " + appName + " crashed."); - reportError(appName, mNameToProcess.get(appName), results); - return; + reportError(appName, mNameToProcess.get(appName)); + return -1; } - results.putString(mNameToResultKey.get(appName), String.valueOf(result.thisTime)); + return result.thisTime; } - private void closeApp(String appName) { + private void closeApp(String appName, boolean forceStopApp) { Intent homeIntent = new Intent(Intent.ACTION_MAIN); homeIntent.addCategory(Intent.CATEGORY_HOME); homeIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); getInstrumentation().getContext().startActivity(homeIntent); - Intent startIntent = mNameToIntent.get(appName); - if (startIntent != null) { - String packageName = startIntent.getComponent().getPackageName(); - try { - mAm.forceStopPackage(packageName, UserHandle.USER_CURRENT); - } catch (RemoteException e) { - Log.w(TAG, "Error closing app", e); + sleep(POST_LAUNCH_IDLE_TIMEOUT); + if (forceStopApp) { + Intent startIntent = mNameToIntent.get(appName); + if (startIntent != null) { + String packageName = startIntent.getComponent().getPackageName(); + try { + mAm.forceStopPackage(packageName, UserHandle.USER_CURRENT); + } catch (RemoteException e) { + Log.w(TAG, "Error closing app", e); + } } } } @@ -176,7 +232,7 @@ public class AppLaunch extends InstrumentationTestCase { } } - private void reportError(String appName, String processName, Bundle results) { + private void reportError(String appName, String processName) { ActivityManager am = (ActivityManager) getInstrumentation() .getContext().getSystemService(Context.ACTIVITY_SERVICE); List<ProcessErrorStateInfo> crashes = am.getProcessesInErrorState(); @@ -186,12 +242,12 @@ public class AppLaunch extends InstrumentationTestCase { continue; Log.w(TAG, appName + " crashed: " + crash.shortMsg); - results.putString(mNameToResultKey.get(appName), crash.shortMsg); + mResult.putString(mNameToResultKey.get(appName), crash.shortMsg); return; } } - results.putString(mNameToResultKey.get(appName), + mResult.putString(mNameToResultKey.get(appName), "Crashed for unknown reason"); Log.w(TAG, appName + " not found in process list, most likely it is crashed"); @@ -200,8 +256,11 @@ public class AppLaunch extends InstrumentationTestCase { private class AppLaunchRunnable implements Runnable { private Intent mLaunchIntent; private IActivityManager.WaitResult mResult; - public AppLaunchRunnable(Intent intent) { + private boolean mForceStopBeforeLaunch; + + public AppLaunchRunnable(Intent intent, boolean forceStopBeforeLaunch) { mLaunchIntent = intent; + mForceStopBeforeLaunch = forceStopBeforeLaunch; } public IActivityManager.WaitResult getResult() { @@ -211,7 +270,9 @@ public class AppLaunch extends InstrumentationTestCase { public void run() { try { String packageName = mLaunchIntent.getComponent().getPackageName(); - mAm.forceStopPackage(packageName, UserHandle.USER_CURRENT); + if (mForceStopBeforeLaunch) { + mAm.forceStopPackage(packageName, UserHandle.USER_CURRENT); + } String mimeType = mLaunchIntent.getType(); if (mimeType == null && mLaunchIntent.getData() != null && "content".equals(mLaunchIntent.getData().getScheme())) { diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/PathsCacheActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/PathsCacheActivity.java index ac8ab1fdf34f..9f973117f4df 100644 --- a/tests/HwAccelerationTest/src/com/android/test/hwui/PathsCacheActivity.java +++ b/tests/HwAccelerationTest/src/com/android/test/hwui/PathsCacheActivity.java @@ -33,6 +33,7 @@ public class PathsCacheActivity extends Activity { private Path mPath; private final Random mRandom = new Random(); + @SuppressWarnings("MismatchedQueryAndUpdateOfCollection") private final ArrayList<Path> mPathList = new ArrayList<Path>(); @Override @@ -58,6 +59,19 @@ public class PathsCacheActivity extends Activity { path.cubicTo(-80.0f, 200.0f, 100.0f, 200.0f, 200.0f, 0.0f); } + private static Path makeLargePath() { + Path path = new Path(); + buildLargePath(path); + return path; + } + + private static void buildLargePath(Path path) { + path.moveTo(0.0f, 0.0f); + path.cubicTo(0.0f, 0.0f, 10000.0f, 15000.0f, 10000.0f, 20000.0f); + path.cubicTo(10000.0f, 20000.0f, 5000.0f, 30000.0f, -8000.0f, 20000.0f); + path.cubicTo(-8000.0f, 20000.0f, 10000.0f, 20000.0f, 20000.0f, 0.0f); + } + public class PathsView extends View { private final Paint mMediumPaint; @@ -97,6 +111,9 @@ public class PathsCacheActivity extends Activity { int r = mRandom.nextInt(10); if (r == 5 || r == 3) { mPathList.add(path); + } else if (r == 7) { + path = makeLargePath(); + mPathList.add(path); } canvas.save(); diff --git a/tests/SharedLibrary/client/Android.mk b/tests/SharedLibrary/client/Android.mk new file mode 100644 index 000000000000..60ef92af55e5 --- /dev/null +++ b/tests/SharedLibrary/client/Android.mk @@ -0,0 +1,12 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := $(call all-subdir-java-files) + +LOCAL_APK_LIBRARIES := SharedLibrary + +LOCAL_PACKAGE_NAME := SharedLibraryClient + +LOCAL_MODULE_TAGS := tests + +include $(BUILD_PACKAGE) diff --git a/tests/SharedLibrary/client/AndroidManifest.xml b/tests/SharedLibrary/client/AndroidManifest.xml new file mode 100644 index 000000000000..c6a43c0fcd93 --- /dev/null +++ b/tests/SharedLibrary/client/AndroidManifest.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2013 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.google.android.test.lib_client"> + <application android:label="@string/app_title"> + <uses-library android:name="android.test.runner" /> + <uses-library android:name="com.google.android.test.shared_library" /> + <activity android:name="ActivityMain"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + </application> +</manifest> diff --git a/tests/SharedLibrary/client/res/values/strings.xml b/tests/SharedLibrary/client/res/values/strings.xml new file mode 100644 index 000000000000..3757a2554f8b --- /dev/null +++ b/tests/SharedLibrary/client/res/values/strings.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2013 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<resources> + <string name="app_title">SharedLibrary client</string> +</resources> diff --git a/tests/SharedLibrary/client/src/com/google/android/test/lib_client/ActivityMain.java b/tests/SharedLibrary/client/src/com/google/android/test/lib_client/ActivityMain.java new file mode 100644 index 000000000000..d6121a503780 --- /dev/null +++ b/tests/SharedLibrary/client/src/com/google/android/test/lib_client/ActivityMain.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.test.lib_client; + +import android.app.Activity; +import android.os.Bundle; +import android.widget.TextView; +import com.google.android.test.shared_library.SharedLibraryMain; + +public class ActivityMain extends Activity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + TextView content = new TextView(this); + content.setText("Library version: " + SharedLibraryMain.getVersion(this) + "!"); + + SharedLibraryMain.ensureVersion(this, SharedLibraryMain.VERSION_BASE); + setContentView(content); + } +} diff --git a/tests/SharedLibrary/lib/Android.mk b/tests/SharedLibrary/lib/Android.mk new file mode 100644 index 000000000000..c19e23adcbee --- /dev/null +++ b/tests/SharedLibrary/lib/Android.mk @@ -0,0 +1,10 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := $(call all-subdir-java-files) + +LOCAL_PACKAGE_NAME := SharedLibrary + +LOCAL_MODULE_TAGS := tests + +include $(BUILD_PACKAGE) diff --git a/tests/SharedLibrary/lib/AndroidManifest.xml b/tests/SharedLibrary/lib/AndroidManifest.xml new file mode 100644 index 000000000000..31fac20e4163 --- /dev/null +++ b/tests/SharedLibrary/lib/AndroidManifest.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2013 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.google.android.test.shared_library" + android:versionCode="2"> + <application android:label="SharedLibrary"> + <library android:name="com.google.android.test.shared_library" /> + <activity android:name="ActivityMain"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + </application> +</manifest> diff --git a/tests/SharedLibrary/lib/res/values/strings.xml b/tests/SharedLibrary/lib/res/values/strings.xml new file mode 100644 index 000000000000..bbfb0b4d81d5 --- /dev/null +++ b/tests/SharedLibrary/lib/res/values/strings.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2013 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <string name="upgrade_title">Upgrade required</string> + <string name="upgrade_body"><xliff:g id="app">%1$s</xliff:g> requires a newer version + of <xliff:g id="lib">%2$s</xliff:g> to run.</string> + <string name="upgrade_button">Upgrade</string> +</resources> diff --git a/core/java/com/android/internal/view/IInputConnectionCallback.aidl b/tests/SharedLibrary/lib/src/com/google/android/test/shared_library/ActivityMain.java index 5b5b3dfbb4a5..895acedb1e7f 100644 --- a/core/java/com/android/internal/view/IInputConnectionCallback.aidl +++ b/tests/SharedLibrary/lib/src/com/google/android/test/shared_library/ActivityMain.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008 The Android Open Source Project + * Copyright (C) 2013 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,18 +14,19 @@ * limitations under the License. */ -package com.android.internal.view; +package com.google.android.test.shared_library; -import android.graphics.Rect; -import android.view.KeyEvent; -import android.view.MotionEvent; -import android.view.inputmethod.TextBoxAttribute; -import com.android.internal.view.IInputContext; -import android.os.IBinder; +import android.app.Activity; +import android.os.Bundle; +import android.widget.TextView; -/** - * {@hide} - */ -oneway interface IInputMethodCallback { - void finishedEvent(int seq, boolean handled); +public class ActivityMain extends Activity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + TextView content = new TextView(this); + content.setText("Dummy main entry for this apk; not really needed..."); + setContentView(content); + } } diff --git a/tests/SharedLibrary/lib/src/com/google/android/test/shared_library/SharedLibraryMain.java b/tests/SharedLibrary/lib/src/com/google/android/test/shared_library/SharedLibraryMain.java new file mode 100644 index 000000000000..c1cd9254a30d --- /dev/null +++ b/tests/SharedLibrary/lib/src/com/google/android/test/shared_library/SharedLibraryMain.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.test.shared_library; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.res.Resources; + +public class SharedLibraryMain { + private static String LIBRARY_PACKAGE = "com.google.android.test.shared_library"; + + /** + * Base version of the library. + */ + public static int VERSION_BASE = 1; + + /** + * The second version of the library. + */ + public static int VERSION_SECOND = 2; + + public static int getVersion(Context context) { + PackageInfo pi = null; + try { + pi = context.getPackageManager().getPackageInfo(LIBRARY_PACKAGE, 0); + return pi.versionCode; + } catch (PackageManager.NameNotFoundException e) { + throw new IllegalStateException("Can't find my package!", e); + } + } + + public static void ensureVersion(Activity activity, int minVersion) { + if (getVersion(activity) >= minVersion) { + return; + } + + // The current version of the library does not meet the required version. Show + // a dialog to inform the user and have them update to the current version. + // Note that updating the library will be necessity mean killing the current + // application (so it can be re-started with the new version, so there is no + // reason to return a result here. + final Context context; + try { + context = activity.createPackageContext(LIBRARY_PACKAGE, 0); + } catch (PackageManager.NameNotFoundException e) { + throw new IllegalStateException("Can't find my package!", e); + } + + // Display the dialog. Note that we don't need to deal with activity lifecycle + // stuff because if the activity gets recreated, it will first call through to + // ensureVersion(), causing us to either re-display the dialog if needed or let + // it now proceed. + final Resources res = context.getResources(); + AlertDialog.Builder builder = new AlertDialog.Builder(activity); + builder.setTitle(res.getText(R.string.upgrade_title)); + builder.setMessage(res.getString(R.string.upgrade_body, + activity.getApplicationInfo().loadLabel(activity.getPackageManager()), + context.getApplicationInfo().loadLabel(context.getPackageManager()))); + builder.setPositiveButton(res.getText(R.string.upgrade_button), + new Dialog.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + // Launch play store. + } + }); + builder.show(); + } +} diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl index bef58242696d..e0684fbe86c8 100644 --- a/wifi/java/android/net/wifi/IWifiManager.aidl +++ b/wifi/java/android/net/wifi/IWifiManager.aidl @@ -71,6 +71,10 @@ interface IWifiManager DhcpInfo getDhcpInfo(); + boolean isScanningAlwaysAvailable(); + + void setScanningAlwaysAvailable(boolean enable); + boolean acquireWifiLock(IBinder lock, int lockType, String tag, in WorkSource ws); void updateWifiLockWorkSource(IBinder lock, in WorkSource ws); diff --git a/wifi/java/android/net/wifi/WifiConfigStore.java b/wifi/java/android/net/wifi/WifiConfigStore.java index eb2f74c0d1b6..2385c24a0a3a 100644 --- a/wifi/java/android/net/wifi/WifiConfigStore.java +++ b/wifi/java/android/net/wifi/WifiConfigStore.java @@ -395,6 +395,23 @@ class WifiConfigStore { return ret; } + void disableAllNetworks() { + boolean networkDisabled = false; + for(WifiConfiguration config : mConfiguredNetworks.values()) { + if(config != null && config.status != Status.DISABLED) { + if(mWifiNative.disableNetwork(config.networkId)) { + networkDisabled = true; + config.status = Status.DISABLED; + } else { + loge("Disable network failed on " + config.networkId); + } + } + } + + if (networkDisabled) { + sendConfiguredNetworksChangedBroadcast(); + } + } /** * Disable a network. Note that there is no saveConfig operation. * @param netId network to be disabled diff --git a/wifi/java/android/net/wifi/WifiStateMachine.java b/wifi/java/android/net/wifi/WifiStateMachine.java index ed5d22c8e746..8cdfe032d0c7 100644 --- a/wifi/java/android/net/wifi/WifiStateMachine.java +++ b/wifi/java/android/net/wifi/WifiStateMachine.java @@ -134,7 +134,12 @@ public class WifiStateMachine extends StateMachine { private boolean mEnableBackgroundScan = false; private int mRssiPollToken = 0; private int mReconnectCount = 0; - private boolean mIsScanMode = false; + /* 3 operational states for STA operation: CONNECT_MODE, SCAN_ONLY_MODE, SCAN_ONLY_WIFI_OFF_MODE + * In CONNECT_MODE, the STA can scan and connect to an access point + * In SCAN_ONLY_MODE, the STA can only scan for access points + * In SCAN_ONLY_WIFI_OFF_MODE, the STA can only scan for access points with wifi toggle being off + */ + private int mOperationalMode = CONNECT_MODE; private boolean mScanResultIsPending = false; /* Tracks if state machine has received any screen state change broadcast yet. * We can miss one of these at boot. @@ -285,8 +290,8 @@ public class WifiStateMachine extends StateMachine { /* Supplicant commands after driver start*/ /* Initiate a scan */ static final int CMD_START_SCAN = BASE + 71; - /* Set scan mode. CONNECT_MODE or SCAN_ONLY_MODE */ - static final int CMD_SET_SCAN_MODE = BASE + 72; + /* Set operational mode. CONNECT, SCAN ONLY, SCAN_ONLY with Wi-Fi off mode */ + static final int CMD_SET_OPERATIONAL_MODE = BASE + 72; /* Disconnect from a network */ static final int CMD_DISCONNECT = BASE + 73; /* Reconnect to a network */ @@ -342,16 +347,13 @@ public class WifiStateMachine extends StateMachine { public static final int CMD_DISABLE_P2P_REQ = BASE + 132; public static final int CMD_DISABLE_P2P_RSP = BASE + 133; - private static final int CONNECT_MODE = 1; - private static final int SCAN_ONLY_MODE = 2; + public static final int CONNECT_MODE = 1; + public static final int SCAN_ONLY_MODE = 2; + public static final int SCAN_ONLY_WITH_WIFI_OFF_MODE = 3; private static final int SUCCESS = 1; private static final int FAILURE = -1; - /* Phone in emergency call back mode */ - private static final int IN_ECM_STATE = 1; - private static final int NOT_IN_ECM_STATE = 0; - /** * The maximum number of times we will retry a connection to an access point * for which we have failed in acquiring an IP address from DHCP. A value of @@ -496,9 +498,6 @@ public class WifiStateMachine extends StateMachine { */ private final AtomicInteger mWifiApState = new AtomicInteger(WIFI_AP_STATE_DISABLED); - private final AtomicInteger mLastEnableUid = new AtomicInteger(Process.myUid()); - private final AtomicInteger mLastApEnableUid = new AtomicInteger(Process.myUid()); - private static final int SCAN_REQUEST = 0; private static final String ACTION_START_SCAN = "com.android.server.WifiManager.action.START_SCAN"; @@ -623,7 +622,7 @@ public class WifiStateMachine extends StateMachine { @Override public void onReceive(Context context, Intent intent) { int counter = intent.getIntExtra(DELAYED_STOP_COUNTER, 0); - sendMessage(obtainMessage(CMD_DELAYED_STOP_DRIVER, counter, 0)); + sendMessage(CMD_DELAYED_STOP_DRIVER, counter, 0); } }, new IntentFilter(ACTION_DELAYED_DRIVER_STOP)); @@ -714,8 +713,7 @@ public class WifiStateMachine extends StateMachine { /** * TODO: doc */ - public void setWifiEnabled(boolean enable) { - mLastEnableUid.set(Binder.getCallingUid()); + public void setSupplicantRunning(boolean enable) { if (enable) { sendMessage(CMD_START_SUPPLICANT); } else { @@ -726,10 +724,9 @@ public class WifiStateMachine extends StateMachine { /** * TODO: doc */ - public void setWifiApEnabled(WifiConfiguration wifiConfig, boolean enable) { - mLastApEnableUid.set(Binder.getCallingUid()); + public void setHostApRunning(WifiConfiguration wifiConfig, boolean enable) { if (enable) { - sendMessage(obtainMessage(CMD_START_AP, wifiConfig)); + sendMessage(CMD_START_AP, wifiConfig); } else { sendMessage(CMD_STOP_AP); } @@ -818,27 +815,23 @@ public class WifiStateMachine extends StateMachine { /** * TODO: doc */ - public void setDriverStart(boolean enable, boolean ecm) { + public void setDriverStart(boolean enable) { if (enable) { sendMessage(CMD_START_DRIVER); } else { - sendMessage(obtainMessage(CMD_STOP_DRIVER, ecm ? IN_ECM_STATE : NOT_IN_ECM_STATE, 0)); + sendMessage(CMD_STOP_DRIVER); } } public void captivePortalCheckComplete() { - sendMessage(obtainMessage(CMD_CAPTIVE_CHECK_COMPLETE)); + sendMessage(CMD_CAPTIVE_CHECK_COMPLETE); } /** * TODO: doc */ - public void setScanOnlyMode(boolean enable) { - if (enable) { - sendMessage(obtainMessage(CMD_SET_SCAN_MODE, SCAN_ONLY_MODE, 0)); - } else { - sendMessage(obtainMessage(CMD_SET_SCAN_MODE, CONNECT_MODE, 0)); - } + public void setOperationalMode(int mode) { + sendMessage(CMD_SET_OPERATIONAL_MODE, mode, 0); } /** @@ -941,7 +934,7 @@ public class WifiStateMachine extends StateMachine { * @param bssid BSSID of the network */ public void addToBlacklist(String bssid) { - sendMessage(obtainMessage(CMD_BLACKLIST_NETWORK, bssid)); + sendMessage(CMD_BLACKLIST_NETWORK, bssid); } /** @@ -949,15 +942,15 @@ public class WifiStateMachine extends StateMachine { * */ public void clearBlacklist() { - sendMessage(obtainMessage(CMD_CLEAR_BLACKLIST)); + sendMessage(CMD_CLEAR_BLACKLIST); } public void enableRssiPolling(boolean enabled) { - sendMessage(obtainMessage(CMD_ENABLE_RSSI_POLL, enabled ? 1 : 0, 0)); + sendMessage(CMD_ENABLE_RSSI_POLL, enabled ? 1 : 0, 0); } public void enableBackgroundScanCommand(boolean enabled) { - sendMessage(obtainMessage(CMD_ENABLE_BACKGROUND_SCAN, enabled ? 1 : 0, 0)); + sendMessage(CMD_ENABLE_BACKGROUND_SCAN, enabled ? 1 : 0, 0); } public void enableAllNetworks() { @@ -969,7 +962,7 @@ public class WifiStateMachine extends StateMachine { */ public void startFilteringMulticastV4Packets() { mFilteringMulticastV4Packets.set(true); - sendMessage(obtainMessage(CMD_START_PACKET_FILTERING, MULTICAST_V4, 0)); + sendMessage(CMD_START_PACKET_FILTERING, MULTICAST_V4, 0); } /** @@ -977,21 +970,21 @@ public class WifiStateMachine extends StateMachine { */ public void stopFilteringMulticastV4Packets() { mFilteringMulticastV4Packets.set(false); - sendMessage(obtainMessage(CMD_STOP_PACKET_FILTERING, MULTICAST_V4, 0)); + sendMessage(CMD_STOP_PACKET_FILTERING, MULTICAST_V4, 0); } /** * Start filtering Multicast v4 packets */ public void startFilteringMulticastV6Packets() { - sendMessage(obtainMessage(CMD_START_PACKET_FILTERING, MULTICAST_V6, 0)); + sendMessage(CMD_START_PACKET_FILTERING, MULTICAST_V6, 0); } /** * Stop filtering Multicast v4 packets */ public void stopFilteringMulticastV6Packets() { - sendMessage(obtainMessage(CMD_STOP_PACKET_FILTERING, MULTICAST_V6, 0)); + sendMessage(CMD_STOP_PACKET_FILTERING, MULTICAST_V6, 0); } /** @@ -1001,7 +994,7 @@ public class WifiStateMachine extends StateMachine { * @param enable true if enable, false otherwise */ public void setHighPerfModeEnabled(boolean enable) { - sendMessage(obtainMessage(CMD_SET_HIGH_PERF_MODE, enable ? 1 : 0, 0)); + sendMessage(CMD_SET_HIGH_PERF_MODE, enable ? 1 : 0, 0); } /** @@ -1015,7 +1008,7 @@ public class WifiStateMachine extends StateMachine { Settings.Global.WIFI_COUNTRY_CODE, countryCode); } - sendMessage(obtainMessage(CMD_SET_COUNTRY_CODE, countryCode)); + sendMessage(CMD_SET_COUNTRY_CODE, countryCode); } /** @@ -1029,7 +1022,7 @@ public class WifiStateMachine extends StateMachine { Settings.Global.WIFI_FREQUENCY_BAND, band); } - sendMessage(obtainMessage(CMD_SET_FREQUENCY_BAND, band, 0)); + sendMessage(CMD_SET_FREQUENCY_BAND, band, 0); } /** @@ -1050,7 +1043,7 @@ public class WifiStateMachine extends StateMachine { * Send a message indicating bluetooth adapter connection state changed */ public void sendBluetoothAdapterStateChange(int state) { - sendMessage(obtainMessage(CMD_BLUETOOTH_ADAPTER_STATE_CHANGE, state, 0)); + sendMessage(CMD_BLUETOOTH_ADAPTER_STATE_CHANGE, state, 0); } /** @@ -1114,7 +1107,7 @@ public class WifiStateMachine extends StateMachine { pw.println("mLastBssid " + mLastBssid); pw.println("mLastNetworkId " + mLastNetworkId); pw.println("mReconnectCount " + mReconnectCount); - pw.println("mIsScanMode " + mIsScanMode); + pw.println("mOperationalMode " + mOperationalMode); pw.println("mUserWantsSuspendOpt " + mUserWantsSuspendOpt); pw.println("mSuspendOptNeedsDisabled " + mSuspendOptNeedsDisabled); pw.println("Supplicant status " + mWifiNative.status()); @@ -1136,11 +1129,11 @@ public class WifiStateMachine extends StateMachine { if (screenOn) enableAllNetworks(); if (mUserWantsSuspendOpt.get()) { if (screenOn) { - sendMessage(obtainMessage(CMD_SET_SUSPEND_OPT_ENABLED, 0, 0)); + sendMessage(CMD_SET_SUSPEND_OPT_ENABLED, 0, 0); } else { //Allow 2s for suspend optimizations to be set mSuspendWakeLock.acquire(2000); - sendMessage(obtainMessage(CMD_SET_SUSPEND_OPT_ENABLED, 1, 0)); + sendMessage(CMD_SET_SUSPEND_OPT_ENABLED, 1, 0); } } mScreenBroadcastReceived.set(true); @@ -1914,7 +1907,7 @@ public class WifiStateMachine extends StateMachine { case WifiMonitor.WPS_OVERLAP_EVENT: case CMD_BLACKLIST_NETWORK: case CMD_CLEAR_BLACKLIST: - case CMD_SET_SCAN_MODE: + case CMD_SET_OPERATIONAL_MODE: case CMD_SET_COUNTRY_CODE: case CMD_SET_FREQUENCY_BAND: case CMD_RSSI_POLL: @@ -1929,6 +1922,7 @@ public class WifiStateMachine extends StateMachine { case WifiWatchdogStateMachine.POOR_LINK_DETECTED: case WifiWatchdogStateMachine.GOOD_LINK_DETECTED: case CMD_NO_NETWORKS_PERIODIC_SCAN: + case CMD_DISABLE_P2P_RSP: break; case DhcpStateMachine.CMD_ON_QUIT: mDhcpStateMachine = null; @@ -1942,8 +1936,8 @@ public class WifiStateMachine extends StateMachine { } break; case WifiMonitor.DRIVER_HUNG_EVENT: - setWifiEnabled(false); - setWifiEnabled(true); + setSupplicantRunning(false); + setSupplicantRunning(true); break; case WifiManager.CONNECT_NETWORK: replyToMessage(message, WifiManager.CONNECT_NETWORK_FAILED, @@ -2143,7 +2137,7 @@ public class WifiStateMachine extends StateMachine { case CMD_STOP_AP: case CMD_START_DRIVER: case CMD_STOP_DRIVER: - case CMD_SET_SCAN_MODE: + case CMD_SET_OPERATIONAL_MODE: case CMD_SET_COUNTRY_CODE: case CMD_SET_FREQUENCY_BAND: case CMD_START_PACKET_FILTERING: @@ -2160,8 +2154,6 @@ public class WifiStateMachine extends StateMachine { class SupplicantStartedState extends State { @Override public void enter() { - /* Initialize for connect mode operation at start */ - mIsScanMode = false; /* Wifi is available as long as we have a connection to supplicant */ mNetworkInfo.setIsAvailable(true); @@ -2176,7 +2168,6 @@ public class WifiStateMachine extends StateMachine { } @Override public boolean processMessage(Message message) { - WifiConfiguration config; switch(message.what) { case CMD_STOP_SUPPLICANT: /* Supplicant stopped by user */ if (mP2pSupported) { @@ -2206,87 +2197,13 @@ public class WifiStateMachine extends StateMachine { boolean ok = mWifiNative.ping(); replyToMessage(message, message.what, ok ? SUCCESS : FAILURE); break; - case CMD_ADD_OR_UPDATE_NETWORK: - config = (WifiConfiguration) message.obj; - replyToMessage(message, CMD_ADD_OR_UPDATE_NETWORK, - mWifiConfigStore.addOrUpdateNetwork(config)); - break; - case CMD_REMOVE_NETWORK: - ok = mWifiConfigStore.removeNetwork(message.arg1); - replyToMessage(message, message.what, ok ? SUCCESS : FAILURE); - break; - case CMD_ENABLE_NETWORK: - ok = mWifiConfigStore.enableNetwork(message.arg1, message.arg2 == 1); - replyToMessage(message, message.what, ok ? SUCCESS : FAILURE); - break; - case CMD_ENABLE_ALL_NETWORKS: - long time = android.os.SystemClock.elapsedRealtime(); - if (time - mLastEnableAllNetworksTime > MIN_INTERVAL_ENABLE_ALL_NETWORKS_MS) { - mWifiConfigStore.enableAllNetworks(); - mLastEnableAllNetworksTime = time; - } - break; - case WifiManager.DISABLE_NETWORK: - if (mWifiConfigStore.disableNetwork(message.arg1, - WifiConfiguration.DISABLED_UNKNOWN_REASON) == true) { - replyToMessage(message, WifiManager.DISABLE_NETWORK_SUCCEEDED); - } else { - replyToMessage(message, WifiManager.DISABLE_NETWORK_FAILED, - WifiManager.ERROR); - } - break; - case CMD_BLACKLIST_NETWORK: - mWifiNative.addToBlacklist((String)message.obj); - break; - case CMD_CLEAR_BLACKLIST: - mWifiNative.clearBlacklist(); - break; - case CMD_SAVE_CONFIG: - ok = mWifiConfigStore.saveConfig(); - replyToMessage(message, CMD_SAVE_CONFIG, ok ? SUCCESS : FAILURE); - - // Inform the backup manager about a data change - IBackupManager ibm = IBackupManager.Stub.asInterface( - ServiceManager.getService(Context.BACKUP_SERVICE)); - if (ibm != null) { - try { - ibm.dataChanged("com.android.providers.settings"); - } catch (Exception e) { - // Try again later - } - } - break; - case CMD_GET_CONFIGURED_NETWORKS: - replyToMessage(message, message.what, - mWifiConfigStore.getConfiguredNetworks()); - break; /* Cannot start soft AP while in client mode */ case CMD_START_AP: loge("Failed to start soft AP with a running supplicant"); setWifiApState(WIFI_AP_STATE_FAILED); break; - case CMD_SET_SCAN_MODE: - mIsScanMode = (message.arg1 == SCAN_ONLY_MODE); - break; - case WifiManager.SAVE_NETWORK: - config = (WifiConfiguration) message.obj; - NetworkUpdateResult result = mWifiConfigStore.saveNetwork(config); - if (result.getNetworkId() != WifiConfiguration.INVALID_NETWORK_ID) { - replyToMessage(message, WifiManager.SAVE_NETWORK_SUCCEEDED); - } else { - loge("Failed to save network"); - replyToMessage(message, WifiManager.SAVE_NETWORK_FAILED, - WifiManager.ERROR); - } - break; - case WifiManager.FORGET_NETWORK: - if (mWifiConfigStore.forgetNetwork(message.arg1)) { - replyToMessage(message, WifiManager.FORGET_NETWORK_SUCCEEDED); - } else { - loge("Failed to forget network"); - replyToMessage(message, WifiManager.FORGET_NETWORK_FAILED, - WifiManager.ERROR); - } + case CMD_SET_OPERATIONAL_MODE: + mOperationalMode = message.arg1; break; default: return NOT_HANDLED; @@ -2344,7 +2261,7 @@ public class WifiStateMachine extends StateMachine { case CMD_STOP_AP: case CMD_START_DRIVER: case CMD_STOP_DRIVER: - case CMD_SET_SCAN_MODE: + case CMD_SET_OPERATIONAL_MODE: case CMD_SET_COUNTRY_CODE: case CMD_SET_FREQUENCY_BAND: case CMD_START_PACKET_FILTERING: @@ -2452,7 +2369,7 @@ public class WifiStateMachine extends StateMachine { mWifiNative.stopFilteringMulticastV4Packets(); } - if (mIsScanMode) { + if (mOperationalMode != CONNECT_MODE) { mWifiNative.disconnect(); transitionTo(mScanModeState); } else { @@ -2510,8 +2427,8 @@ public class WifiStateMachine extends StateMachine { case CMD_STOP_DRIVER: int mode = message.arg1; - /* Already doing a delayed stop && not in ecm state */ - if (mInDelayedStop && mode != IN_ECM_STATE) { + /* Already doing a delayed stop */ + if (mInDelayedStop) { if (DBG) log("Already in delayed stop"); break; } @@ -2519,20 +2436,15 @@ public class WifiStateMachine extends StateMachine { mDelayedStopCounter++; if (DBG) log("Delayed stop message " + mDelayedStopCounter); - if (mode == IN_ECM_STATE) { - /* send a shut down immediately */ - sendMessage(obtainMessage(CMD_DELAYED_STOP_DRIVER, mDelayedStopCounter, 0)); - } else { - /* send regular delayed shut down */ - Intent driverStopIntent = new Intent(ACTION_DELAYED_DRIVER_STOP, null); - driverStopIntent.putExtra(DELAYED_STOP_COUNTER, mDelayedStopCounter); - mDriverStopIntent = PendingIntent.getBroadcast(mContext, - DRIVER_STOP_REQUEST, driverStopIntent, - PendingIntent.FLAG_UPDATE_CURRENT); + /* send regular delayed shut down */ + Intent driverStopIntent = new Intent(ACTION_DELAYED_DRIVER_STOP, null); + driverStopIntent.putExtra(DELAYED_STOP_COUNTER, mDelayedStopCounter); + mDriverStopIntent = PendingIntent.getBroadcast(mContext, + DRIVER_STOP_REQUEST, driverStopIntent, + PendingIntent.FLAG_UPDATE_CURRENT); - mAlarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() - + mDriverStopDelayMs, mDriverStopIntent); - } + mAlarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + + mDriverStopDelayMs, mDriverStopIntent); break; case CMD_START_DRIVER: if (mInDelayedStop) { @@ -2638,7 +2550,7 @@ public class WifiStateMachine extends StateMachine { case CMD_STOP_AP: case CMD_START_DRIVER: case CMD_STOP_DRIVER: - case CMD_SET_SCAN_MODE: + case CMD_SET_OPERATIONAL_MODE: case CMD_SET_COUNTRY_CODE: case CMD_SET_FREQUENCY_BAND: case CMD_START_PACKET_FILTERING: @@ -2713,30 +2625,42 @@ public class WifiStateMachine extends StateMachine { } class ScanModeState extends State { + private int mLastOperationMode; + @Override + public void enter() { + mWifiConfigStore.disableAllNetworks(); + mLastOperationMode = mOperationalMode; + if (mLastOperationMode == SCAN_ONLY_WITH_WIFI_OFF_MODE) { + mWifiP2pChannel.sendMessage(CMD_DISABLE_P2P_REQ); + setWifiState(WIFI_STATE_DISABLED); + } + } + @Override + public void exit() { + if (mLastOperationMode == SCAN_ONLY_WITH_WIFI_OFF_MODE) { + setWifiState(WIFI_STATE_ENABLED); + mWifiP2pChannel.sendMessage(CMD_ENABLE_P2P); + } + mWifiConfigStore.enableAllNetworks(); + mWifiNative.reconnect(); + } @Override public boolean processMessage(Message message) { switch(message.what) { - case CMD_SET_SCAN_MODE: - if (message.arg1 == SCAN_ONLY_MODE) { - /* Ignore */ - return HANDLED; - } else { - mWifiNative.reconnect(); - mIsScanMode = false; + case CMD_SET_OPERATIONAL_MODE: + if (message.arg1 == CONNECT_MODE) { + mOperationalMode = CONNECT_MODE; transitionTo(mDisconnectedState); + } else { + // Nothing to do + return HANDLED; } break; + // Handle scan. All the connection related commands are + // handled only in ConnectModeState case CMD_START_SCAN: startScanNative(WifiNative.SCAN_WITHOUT_CONNECTION_SETUP); break; - /* Ignore */ - case CMD_DISCONNECT: - case CMD_RECONNECT: - case CMD_REASSOCIATE: - case WifiMonitor.SUPPLICANT_STATE_CHANGE_EVENT: - case WifiMonitor.NETWORK_CONNECTION_EVENT: - case WifiMonitor.NETWORK_DISCONNECTION_EVENT: - break; default: return NOT_HANDLED; } @@ -2747,7 +2671,8 @@ public class WifiStateMachine extends StateMachine { class ConnectModeState extends State { @Override public boolean processMessage(Message message) { - StateChangeResult stateChangeResult; + WifiConfiguration config; + boolean ok; switch(message.what) { case WifiMonitor.AUTHENTICATION_FAILURE_EVENT: mSupplicantStateTracker.sendMessage(WifiMonitor.AUTHENTICATION_FAILURE_EVENT); @@ -2787,6 +2712,60 @@ public class WifiStateMachine extends StateMachine { mTemporarilyDisconnectWifi = false; } break; + case CMD_ADD_OR_UPDATE_NETWORK: + config = (WifiConfiguration) message.obj; + replyToMessage(message, CMD_ADD_OR_UPDATE_NETWORK, + mWifiConfigStore.addOrUpdateNetwork(config)); + break; + case CMD_REMOVE_NETWORK: + ok = mWifiConfigStore.removeNetwork(message.arg1); + replyToMessage(message, message.what, ok ? SUCCESS : FAILURE); + break; + case CMD_ENABLE_NETWORK: + ok = mWifiConfigStore.enableNetwork(message.arg1, message.arg2 == 1); + replyToMessage(message, message.what, ok ? SUCCESS : FAILURE); + break; + case CMD_ENABLE_ALL_NETWORKS: + long time = android.os.SystemClock.elapsedRealtime(); + if (time - mLastEnableAllNetworksTime > MIN_INTERVAL_ENABLE_ALL_NETWORKS_MS) { + mWifiConfigStore.enableAllNetworks(); + mLastEnableAllNetworksTime = time; + } + break; + case WifiManager.DISABLE_NETWORK: + if (mWifiConfigStore.disableNetwork(message.arg1, + WifiConfiguration.DISABLED_UNKNOWN_REASON) == true) { + replyToMessage(message, WifiManager.DISABLE_NETWORK_SUCCEEDED); + } else { + replyToMessage(message, WifiManager.DISABLE_NETWORK_FAILED, + WifiManager.ERROR); + } + break; + case CMD_BLACKLIST_NETWORK: + mWifiNative.addToBlacklist((String)message.obj); + break; + case CMD_CLEAR_BLACKLIST: + mWifiNative.clearBlacklist(); + break; + case CMD_SAVE_CONFIG: + ok = mWifiConfigStore.saveConfig(); + replyToMessage(message, CMD_SAVE_CONFIG, ok ? SUCCESS : FAILURE); + + // Inform the backup manager about a data change + IBackupManager ibm = IBackupManager.Stub.asInterface( + ServiceManager.getService(Context.BACKUP_SERVICE)); + if (ibm != null) { + try { + ibm.dataChanged("com.android.providers.settings"); + } catch (Exception e) { + // Try again later + } + } + break; + case CMD_GET_CONFIGURED_NETWORKS: + replyToMessage(message, message.what, + mWifiConfigStore.getConfiguredNetworks()); + break; /* Do a redundant disconnect without transition */ case CMD_DISCONNECT: mWifiNative.disconnect(); @@ -2804,7 +2783,7 @@ public class WifiStateMachine extends StateMachine { * For an existing network, a network id is passed */ int netId = message.arg1; - WifiConfiguration config = (WifiConfiguration) message.obj; + config = (WifiConfiguration) message.obj; /* Save the network config */ if (config != null) { @@ -2826,26 +2805,46 @@ public class WifiStateMachine extends StateMachine { break; } break; + case WifiManager.SAVE_NETWORK: + config = (WifiConfiguration) message.obj; + NetworkUpdateResult result = mWifiConfigStore.saveNetwork(config); + if (result.getNetworkId() != WifiConfiguration.INVALID_NETWORK_ID) { + replyToMessage(message, WifiManager.SAVE_NETWORK_SUCCEEDED); + } else { + loge("Failed to save network"); + replyToMessage(message, WifiManager.SAVE_NETWORK_FAILED, + WifiManager.ERROR); + } + break; + case WifiManager.FORGET_NETWORK: + if (mWifiConfigStore.forgetNetwork(message.arg1)) { + replyToMessage(message, WifiManager.FORGET_NETWORK_SUCCEEDED); + } else { + loge("Failed to forget network"); + replyToMessage(message, WifiManager.FORGET_NETWORK_FAILED, + WifiManager.ERROR); + } + break; case WifiManager.START_WPS: WpsInfo wpsInfo = (WpsInfo) message.obj; - WpsResult result; + WpsResult wpsResult; switch (wpsInfo.setup) { case WpsInfo.PBC: - result = mWifiConfigStore.startWpsPbc(wpsInfo); + wpsResult = mWifiConfigStore.startWpsPbc(wpsInfo); break; case WpsInfo.KEYPAD: - result = mWifiConfigStore.startWpsWithPinFromAccessPoint(wpsInfo); + wpsResult = mWifiConfigStore.startWpsWithPinFromAccessPoint(wpsInfo); break; case WpsInfo.DISPLAY: - result = mWifiConfigStore.startWpsWithPinFromDevice(wpsInfo); + wpsResult = mWifiConfigStore.startWpsWithPinFromDevice(wpsInfo); break; default: - result = new WpsResult(Status.FAILURE); + wpsResult = new WpsResult(Status.FAILURE); loge("Invalid setup for WPS"); break; } - if (result.status == Status.SUCCESS) { - replyToMessage(message, WifiManager.START_WPS_SUCCEEDED, result); + if (wpsResult.status == Status.SUCCESS) { + replyToMessage(message, WifiManager.START_WPS_SUCCEEDED, wpsResult); transitionTo(mWpsRunningState); } else { loge("Failed to start WPS with config " + wpsInfo.toString()); @@ -2881,7 +2880,7 @@ public class WifiStateMachine extends StateMachine { public void enter() { mRssiPollToken++; if (mEnableRssiPolling) { - sendMessage(obtainMessage(CMD_RSSI_POLL, mRssiPollToken, 0)); + sendMessage(CMD_RSSI_POLL, mRssiPollToken, 0); } } @@ -2915,8 +2914,8 @@ public class WifiStateMachine extends StateMachine { transitionTo(mDisconnectingState); } break; - case CMD_SET_SCAN_MODE: - if (message.arg1 == SCAN_ONLY_MODE) { + case CMD_SET_OPERATIONAL_MODE: + if (message.arg1 != CONNECT_MODE) { sendMessage(CMD_DISCONNECT); deferMessage(message); } @@ -3152,8 +3151,8 @@ public class WifiStateMachine extends StateMachine { @Override public boolean processMessage(Message message) { switch (message.what) { - case CMD_SET_SCAN_MODE: - if (message.arg1 == SCAN_ONLY_MODE) { + case CMD_SET_OPERATIONAL_MODE: + if (message.arg1 != CONNECT_MODE) { deferMessage(message); } break; @@ -3259,11 +3258,9 @@ public class WifiStateMachine extends StateMachine { ++mPeriodicScanToken, 0), mSupplicantScanIntervalMs); ret = NOT_HANDLED; break; - case CMD_SET_SCAN_MODE: - if (message.arg1 == SCAN_ONLY_MODE) { - //Supplicant disconnect to prevent further connects - mWifiNative.disconnect(); - mIsScanMode = true; + case CMD_SET_OPERATIONAL_MODE: + if (message.arg1 != CONNECT_MODE) { + mOperationalMode = message.arg1; transitionTo(mScanModeState); } break; @@ -3396,7 +3393,7 @@ public class WifiStateMachine extends StateMachine { * or put the state machine out of connect mode */ case CMD_STOP_DRIVER: - case CMD_SET_SCAN_MODE: + case CMD_SET_OPERATIONAL_MODE: case WifiManager.CONNECT_NETWORK: case CMD_ENABLE_NETWORK: case CMD_RECONNECT: @@ -3459,7 +3456,7 @@ public class WifiStateMachine extends StateMachine { case CMD_STOP_AP: case CMD_START_DRIVER: case CMD_STOP_DRIVER: - case CMD_SET_SCAN_MODE: + case CMD_SET_OPERATIONAL_MODE: case CMD_SET_COUNTRY_CODE: case CMD_SET_FREQUENCY_BAND: case CMD_START_PACKET_FILTERING: @@ -3547,7 +3544,8 @@ public class WifiStateMachine extends StateMachine { if (message.arg1 == mTetherToken) { loge("Failed to get tether update, shutdown soft access point"); transitionTo(mSoftApStartedState); - sendMessage(CMD_STOP_AP); + // Needs to be first thing handled + sendMessageAtFrontOfQueue(CMD_STOP_AP); } break; case CMD_START_SUPPLICANT: @@ -3556,7 +3554,7 @@ public class WifiStateMachine extends StateMachine { case CMD_STOP_AP: case CMD_START_DRIVER: case CMD_STOP_DRIVER: - case CMD_SET_SCAN_MODE: + case CMD_SET_OPERATIONAL_MODE: case CMD_SET_COUNTRY_CODE: case CMD_SET_FREQUENCY_BAND: case CMD_START_PACKET_FILTERING: @@ -3578,7 +3576,7 @@ public class WifiStateMachine extends StateMachine { TetherStateChange stateChange = (TetherStateChange) message.obj; if (!isWifiTethered(stateChange.active)) { loge("Tethering reports wifi as untethered!, shut down soft Ap"); - setWifiApEnabled(null, false); + setHostApRunning(null, false); } return HANDLED; case CMD_STOP_AP: @@ -3612,13 +3610,15 @@ public class WifiStateMachine extends StateMachine { if (isWifiTethered(stateChange.active)) break; transitionTo(mSoftApStartedState); - sendMessage(CMD_STOP_AP); + // Needs to be first thing handled + sendMessageAtFrontOfQueue(CMD_STOP_AP); break; case CMD_TETHER_NOTIFICATION_TIMED_OUT: if (message.arg1 == mTetherToken) { loge("Failed to get tether update, force stop access point"); transitionTo(mSoftApStartedState); - sendMessage(CMD_STOP_AP); + // Needs to be first thing handled + sendMessageAtFrontOfQueue(CMD_STOP_AP); } break; case CMD_START_SUPPLICANT: @@ -3627,7 +3627,7 @@ public class WifiStateMachine extends StateMachine { case CMD_STOP_AP: case CMD_START_DRIVER: case CMD_STOP_DRIVER: - case CMD_SET_SCAN_MODE: + case CMD_SET_OPERATIONAL_MODE: case CMD_SET_COUNTRY_CODE: case CMD_SET_FREQUENCY_BAND: case CMD_START_PACKET_FILTERING: diff --git a/wifi/java/android/net/wifi/WifiWatchdogStateMachine.java b/wifi/java/android/net/wifi/WifiWatchdogStateMachine.java index 53e6b519ccf7..eb47a4a1251f 100644 --- a/wifi/java/android/net/wifi/WifiWatchdogStateMachine.java +++ b/wifi/java/android/net/wifi/WifiWatchdogStateMachine.java @@ -587,8 +587,9 @@ public class WifiWatchdogStateMachine extends StateMachine { break; case EVENT_WIFI_RADIO_STATE_CHANGE: - if ((Integer) msg.obj == WifiManager.WIFI_STATE_DISABLING) + if (msg.arg1 == WifiManager.WIFI_STATE_DISABLING) { transitionTo(mNotConnectedState); + } break; default: |