diff options
19 files changed, 646 insertions, 655 deletions
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java index 8c84b4d..9ef13de 100644 --- a/core/java/android/accounts/AccountManager.java +++ b/core/java/android/accounts/AccountManager.java @@ -426,7 +426,7 @@ public class AccountManager { @RequiresPermission(GET_ACCOUNTS) public Account[] getAccounts() { try { - return mService.getAccounts(null); + return mService.getAccounts(null, mContext.getOpPackageName()); } catch (RemoteException e) { // won't ever happen throw new RuntimeException(e); @@ -451,7 +451,7 @@ public class AccountManager { @RequiresPermission(GET_ACCOUNTS) public Account[] getAccountsAsUser(int userId) { try { - return mService.getAccountsAsUser(null, userId); + return mService.getAccountsAsUser(null, userId, mContext.getOpPackageName()); } catch (RemoteException e) { // won't ever happen throw new RuntimeException(e); @@ -468,7 +468,7 @@ public class AccountManager { */ public Account[] getAccountsForPackage(String packageName, int uid) { try { - return mService.getAccountsForPackage(packageName, uid); + return mService.getAccountsForPackage(packageName, uid, mContext.getOpPackageName()); } catch (RemoteException re) { // won't ever happen throw new RuntimeException(re); @@ -485,7 +485,8 @@ public class AccountManager { */ public Account[] getAccountsByTypeForPackage(String type, String packageName) { try { - return mService.getAccountsByTypeForPackage(type, packageName); + return mService.getAccountsByTypeForPackage(type, packageName, + mContext.getOpPackageName()); } catch (RemoteException re) { // won't ever happen throw new RuntimeException(re); @@ -522,7 +523,8 @@ public class AccountManager { /** @hide Same as {@link #getAccountsByType(String)} but for a specific user. */ public Account[] getAccountsByTypeAsUser(String type, UserHandle userHandle) { try { - return mService.getAccountsAsUser(type, userHandle.getIdentifier()); + return mService.getAccountsAsUser(type, userHandle.getIdentifier(), + mContext.getOpPackageName()); } catch (RemoteException e) { // won't ever happen throw new RuntimeException(e); @@ -610,7 +612,7 @@ public class AccountManager { if (features == null) throw new IllegalArgumentException("features is null"); return new Future2Task<Boolean>(handler, callback) { public void doWork() throws RemoteException { - mService.hasFeatures(mResponse, account, features); + mService.hasFeatures(mResponse, account, features, mContext.getOpPackageName()); } public Boolean bundleToResult(Bundle bundle) throws AuthenticatorException { if (!bundle.containsKey(KEY_BOOLEAN_RESULT)) { @@ -662,7 +664,8 @@ public class AccountManager { if (type == null) throw new IllegalArgumentException("type is null"); return new Future2Task<Account[]>(handler, callback) { public void doWork() throws RemoteException { - mService.getAccountsByFeatures(mResponse, type, features); + mService.getAccountsByFeatures(mResponse, type, features, + mContext.getOpPackageName()); } public Account[] bundleToResult(Bundle bundle) throws AuthenticatorException { if (!bundle.containsKey(KEY_ACCOUNTS)) { diff --git a/core/java/android/accounts/IAccountManager.aidl b/core/java/android/accounts/IAccountManager.aidl index 04b3c88..4378df4 100644 --- a/core/java/android/accounts/IAccountManager.aidl +++ b/core/java/android/accounts/IAccountManager.aidl @@ -30,12 +30,14 @@ interface IAccountManager { String getPassword(in Account account); String getUserData(in Account account, String key); AuthenticatorDescription[] getAuthenticatorTypes(int userId); - Account[] getAccounts(String accountType); - Account[] getAccountsForPackage(String packageName, int uid); - Account[] getAccountsByTypeForPackage(String type, String packageName); - Account[] getAccountsAsUser(String accountType, int userId); - void hasFeatures(in IAccountManagerResponse response, in Account account, in String[] features); - void getAccountsByFeatures(in IAccountManagerResponse response, String accountType, in String[] features); + Account[] getAccounts(String accountType, String opPackageName); + Account[] getAccountsForPackage(String packageName, int uid, String opPackageName); + Account[] getAccountsByTypeForPackage(String type, String packageName, String opPackageName); + Account[] getAccountsAsUser(String accountType, int userId, String opPackageName); + void hasFeatures(in IAccountManagerResponse response, in Account account, in String[] features, + String opPackageName); + void getAccountsByFeatures(in IAccountManagerResponse response, String accountType, + in String[] features, String opPackageName); boolean addAccountExplicitly(in Account account, String password, in Bundle extras); void removeAccount(in IAccountManagerResponse response, in Account account, boolean expectActivityLaunch); diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 42ac67c..09c0a6e 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -235,8 +235,10 @@ public class AppOpsManager { public static final int OP_WRITE_EXTERNAL_STORAGE = 60; /** @hide Turned on the screen. */ public static final int OP_TURN_SCREEN_ON = 61; + /** @hide Get device accounts. */ + public static final int OP_GET_ACCOUNTS = 62; /** @hide */ - public static final int _NUM_OP = 62; + public static final int _NUM_OP = 63; /** Access to coarse location information. */ public static final String OPSTR_COARSE_LOCATION = "android:coarse_location"; @@ -331,6 +333,9 @@ public class AppOpsManager { /** Required to write/modify/update system settingss. */ public static final String OPSTR_WRITE_SETTINGS = "android:write_settings"; + /** @hide Get device accounts. */ + public static final String OPSTR_GET_ACCOUNTS + = "android:get_accounts"; /** * This maps each operation to the operation that serves as the @@ -403,6 +408,7 @@ public class AppOpsManager { OP_READ_EXTERNAL_STORAGE, OP_WRITE_EXTERNAL_STORAGE, OP_TURN_SCREEN_ON, + OP_GET_ACCOUNTS, }; /** @@ -472,6 +478,7 @@ public class AppOpsManager { OPSTR_READ_EXTERNAL_STORAGE, OPSTR_WRITE_EXTERNAL_STORAGE, null, + OPSTR_GET_ACCOUNTS }; /** @@ -541,6 +548,7 @@ public class AppOpsManager { "READ_EXTERNAL_STORAGE", "WRITE_EXTERNAL_STORAGE", "TURN_ON_SCREEN", + "GET_ACCOUNTS", }; /** @@ -610,6 +618,7 @@ public class AppOpsManager { Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE, null, // no permission for turning the screen on + Manifest.permission.GET_ACCOUNTS }; /** @@ -680,6 +689,7 @@ public class AppOpsManager { null, // READ_EXTERNAL_STORAGE null, // WRITE_EXTERNAL_STORAGE null, // TURN_ON_SCREEN + null, // GET_ACCOUNTS }; /** @@ -749,6 +759,7 @@ public class AppOpsManager { false, // READ_EXTERNAL_STORAGE false, // WRITE_EXTERNAL_STORAGE false, // TURN_ON_SCREEN + false, // GET_ACCOUNTS }; /** @@ -817,6 +828,7 @@ public class AppOpsManager { AppOpsManager.MODE_ALLOWED, AppOpsManager.MODE_ALLOWED, AppOpsManager.MODE_ALLOWED, // OP_TURN_ON_SCREEN + AppOpsManager.MODE_ALLOWED, }; /** @@ -889,6 +901,7 @@ public class AppOpsManager { false, false, false, + false }; /** diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index 4055836..9a2a241 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -1243,6 +1243,8 @@ public class ConnectivityManager { /** The hardware does not support this request. */ public static final int ERROR_HARDWARE_UNSUPPORTED = -30; + /** The hardware returned an error. */ + public static final int ERROR_HARDWARE_ERROR = -31; public static final int NATT_PORT = 4500; diff --git a/core/java/android/net/NetworkFactory.java b/core/java/android/net/NetworkFactory.java index 5f46c73..cab88b9 100644 --- a/core/java/android/net/NetworkFactory.java +++ b/core/java/android/net/NetworkFactory.java @@ -169,7 +169,8 @@ public class NetworkFactory extends Handler { } } - private void handleAddRequest(NetworkRequest request, int score) { + @VisibleForTesting + protected void handleAddRequest(NetworkRequest request, int score) { NetworkRequestInfo n = mNetworkRequests.get(request.requestId); if (n == null) { if (DBG) log("got request " + request + " with score " + score); @@ -184,7 +185,8 @@ public class NetworkFactory extends Handler { evalRequest(n); } - private void handleRemoveRequest(NetworkRequest request) { + @VisibleForTesting + protected void handleRemoveRequest(NetworkRequest request) { NetworkRequestInfo n = mNetworkRequests.get(request.requestId); if (n != null) { mNetworkRequests.remove(request.requestId); diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index c8bb675..b8b6444 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -1056,6 +1056,11 @@ <permission android:name="android.permission.CONNECTIVITY_INTERNAL" android:protectionLevel="signature|privileged" /> + <!-- Allows a system application to access hardware packet offload capabilities. + @hide --> + <permission android:name="android.permission.PACKET_KEEPALIVE_OFFLOAD" + android:protectionLevel="signature|privileged" /> + <!-- @SystemApi @hide --> <permission android:name="android.permission.RECEIVE_DATA_ACTIVITY_CHANGE" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java index f4439bf..012dc9c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java @@ -390,7 +390,8 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL serviceIntent.setAction(CameraPrewarmService.ACTION_PREWARM); try { if (getContext().bindServiceAsUser(serviceIntent, mPrewarmConnection, - Context.BIND_AUTO_CREATE, new UserHandle(UserHandle.USER_CURRENT))) { + Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE, + new UserHandle(UserHandle.USER_CURRENT))) { mPrewarmBound = true; } } catch (SecurityException e) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java index b93586e..9321938 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java @@ -111,12 +111,6 @@ public class NotificationPanelView extends PanelView implements private boolean mQsTracking; /** - * Handles launching the secure camera properly even when other applications may be using the - * camera hardware. - */ - private SecureCameraLaunchManager mSecureCameraLaunchManager; - - /** * If set, the ongoing touch gesture might both trigger the expansion in {@link PanelView} and * the expansion for quick settings. */ @@ -262,8 +256,6 @@ public class NotificationPanelView extends PanelView implements mKeyguardBottomArea = (KeyguardBottomAreaView) findViewById(R.id.keyguard_bottom_area); mQsNavbarScrim = findViewById(R.id.qs_navbar_scrim); mAfforanceHelper = new KeyguardAffordanceHelper(this, getContext()); - mSecureCameraLaunchManager = - new SecureCameraLaunchManager(getContext(), mKeyguardBottomArea); mLastOrientation = getResources().getConfiguration().orientation; // recompute internal state when qspanel height changes @@ -368,16 +360,6 @@ public class NotificationPanelView extends PanelView implements updateMaxHeadsUpTranslation(); } - @Override - public void onAttachedToWindow() { - mSecureCameraLaunchManager.create(); - } - - @Override - public void onDetachedFromWindow() { - mSecureCameraLaunchManager.destroy(); - } - private void startQsSizeChangeAnimation(int oldHeight, final int newHeight) { if (mQsSizeChangeAnimator != null) { oldHeight = (int) mQsSizeChangeAnimator.getAnimatedValue(); @@ -1963,7 +1945,7 @@ public class NotificationPanelView extends PanelView implements } else { EventLogTags.writeSysuiLockscreenGesture( EventLogConstants.SYSUI_LOCKSCREEN_GESTURE_SWIPE_CAMERA, lengthDp, velocityDp); - mSecureCameraLaunchManager.startSecureCameraLaunch(); + mKeyguardBottomArea.launchCamera(); } mStatusBar.startLaunchTransitionTimeout(); mBlockTouches = true; @@ -2010,7 +1992,6 @@ public class NotificationPanelView extends PanelView implements boolean camera = getLayoutDirection() == LAYOUT_DIRECTION_RTL ? !rightIcon : rightIcon; if (camera) { - mSecureCameraLaunchManager.onSwipingStarted(); mKeyguardBottomArea.bindCameraPrewarmService(); } requestDisallowInterceptTouchEvent(true); 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 55b1127..638cd1c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -3535,6 +3535,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, mHandler.removeMessages(MSG_LAUNCH_TRANSITION_TIMEOUT); releaseGestureWakeLock(); mNotificationPanel.onAffordanceLaunchEnded(); + mNotificationPanel.animate().cancel(); mNotificationPanel.setAlpha(1f); return staying; } @@ -3928,7 +3929,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode, } private void vibrateForCameraGesture() { - mVibrator.vibrate(1000L); + mVibrator.vibrate(750L); } public void onScreenTurnedOn() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SecureCameraLaunchManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SecureCameraLaunchManager.java deleted file mode 100644 index 45c8938..0000000 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SecureCameraLaunchManager.java +++ /dev/null @@ -1,284 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.systemui.statusbar.phone; - -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; -import android.hardware.camera2.CameraManager; -import android.os.AsyncTask; -import android.os.Handler; -import android.provider.MediaStore; -import android.util.Log; - -import com.android.internal.widget.LockPatternUtils; -import com.android.keyguard.KeyguardUpdateMonitor; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * Handles launching the secure camera properly even when other applications may be using the camera - * hardware. - * - * When other applications (e.g., Face Unlock) are using the camera, they must close the camera to - * allow the secure camera to open it. Since we want to minimize the delay when opening the secure - * camera, other apps should close the camera at the first possible opportunity (i.e., as soon as - * the user begins swiping to go to the secure camera). - * - * If the camera is unavailable when the user begins to swipe, the SecureCameraLaunchManager sends a - * broadcast to tell other apps to close the camera. When and if the user completes their swipe to - * launch the secure camera, the SecureCameraLaunchManager delays launching the secure camera until - * a callback indicates that the camera has become available. If it doesn't receive that callback - * within a specified timeout period, the secure camera is launched anyway. - * - * Ideally, the secure camera would handle waiting for the camera to become available. This allows - * some of the time necessary to close the camera to happen in parallel with starting the secure - * camera app. We can't rely on all third-party camera apps to handle this. However, an app can - * put com.android.systemui.statusbar.phone.will_wait_for_camera_available in its meta-data to - * indicate that it will be responsible for waiting for the camera to become available. - * - * It is assumed that the functions in this class, including the constructor, will be called from - * the UI thread. - */ -public class SecureCameraLaunchManager { - private static final boolean DEBUG = false; - private static final String TAG = "SecureCameraLaunchManager"; - - // Action sent as a broadcast to tell other apps to stop using the camera. Other apps that use - // the camera from keyguard (e.g., Face Unlock) should listen for this broadcast and close the - // camera as soon as possible after receiving it. - private static final String CLOSE_CAMERA_ACTION_NAME = - "com.android.systemui.statusbar.phone.CLOSE_CAMERA"; - - // Apps should put this field in their meta-data to indicate that they will take on the - // responsibility of waiting for the camera to become available. If this field is present, the - // SecureCameraLaunchManager launches the secure camera even if the camera hardware has not - // become available. Having the secure camera app do the waiting is the optimal approach, but - // without this field, the SecureCameraLaunchManager doesn't launch the secure camera until the - // camera hardware is available. - private static final String META_DATA_WILL_WAIT_FOR_CAMERA_AVAILABLE = - "com.android.systemui.statusbar.phone.will_wait_for_camera_available"; - - // If the camera hardware hasn't become available after this period of time, the - // SecureCameraLaunchManager launches the secure camera anyway. - private static final int CAMERA_AVAILABILITY_TIMEOUT_MS = 1000; - - private Context mContext; - private Handler mHandler; - private LockPatternUtils mLockPatternUtils; - private KeyguardBottomAreaView mKeyguardBottomArea; - - private CameraManager mCameraManager; - private CameraAvailabilityCallback mCameraAvailabilityCallback; - private Map<String, Boolean> mCameraAvailabilityMap; - private boolean mWaitingToLaunchSecureCamera; - private Runnable mLaunchCameraRunnable; - - private class CameraAvailabilityCallback extends CameraManager.AvailabilityCallback { - @Override - public void onCameraUnavailable(String cameraId) { - if (DEBUG) Log.d(TAG, "onCameraUnavailble(" + cameraId + ")"); - mCameraAvailabilityMap.put(cameraId, false); - } - - @Override - public void onCameraAvailable(String cameraId) { - if (DEBUG) Log.d(TAG, "onCameraAvailable(" + cameraId + ")"); - mCameraAvailabilityMap.put(cameraId, true); - - // If we were waiting for the camera hardware to become available to launch the - // secure camera, we can launch it now if all cameras are available. If one or more - // cameras are still not available, we will get this callback again for those - // cameras. - if (mWaitingToLaunchSecureCamera && areAllCamerasAvailable()) { - mKeyguardBottomArea.launchCamera(); - mWaitingToLaunchSecureCamera = false; - - // We no longer need to launch the camera after the timeout hits. - mHandler.removeCallbacks(mLaunchCameraRunnable); - } - } - } - - public SecureCameraLaunchManager(Context context, KeyguardBottomAreaView keyguardBottomArea) { - mContext = context; - mHandler = new Handler(); - mLockPatternUtils = new LockPatternUtils(context); - mKeyguardBottomArea = keyguardBottomArea; - - mCameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE); - mCameraAvailabilityCallback = new CameraAvailabilityCallback(); - - // An onCameraAvailable() or onCameraUnavailable() callback will be received for each camera - // when the availability callback is registered, thus initializing the map. - // - // Keeping track of the state of all cameras using the onCameraAvailable() and - // onCameraUnavailable() callbacks can get messy when dealing with hot-pluggable cameras. - // However, we have a timeout in place such that we will never hang waiting for cameras. - mCameraAvailabilityMap = new HashMap<String, Boolean>(); - - mWaitingToLaunchSecureCamera = false; - mLaunchCameraRunnable = new Runnable() { - @Override - public void run() { - if (mWaitingToLaunchSecureCamera) { - Log.w(TAG, "Timeout waiting for camera availability"); - mKeyguardBottomArea.launchCamera(); - mWaitingToLaunchSecureCamera = false; - } - } - }; - } - - /** - * Initializes the SecureCameraManager and starts listening for camera availability. - */ - public void create() { - mCameraManager.registerAvailabilityCallback(mCameraAvailabilityCallback, mHandler); - } - - /** - * Stops listening for camera availability and cleans up the SecureCameraManager. - */ - public void destroy() { - mCameraManager.unregisterAvailabilityCallback(mCameraAvailabilityCallback); - } - - /** - * Called when the user is starting to swipe horizontally, possibly to start the secure camera. - * Although this swipe ultimately may not result in the secure camera opening, we need to stop - * all other camera usage (e.g., Face Unlock) as soon as possible. We send out a broadcast to - * notify other apps that they should close the camera immediately. The broadcast is sent even - * if the camera appears to be available, because there could be an app that is about to open - * the camera. - */ - public void onSwipingStarted() { - if (DEBUG) Log.d(TAG, "onSwipingStarted"); - AsyncTask.execute(new Runnable() { - @Override - public void run() { - Intent intent = new Intent(); - intent.setAction(CLOSE_CAMERA_ACTION_NAME); - intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); - mContext.sendBroadcast(intent); - } - }); - } - - /** - * Called when the secure camera should be started. If the camera is available or the secure - * camera app has indicated that it will wait for camera availability, the secure camera app is - * launched immediately. Otherwise, we wait for the camera to become available (or timeout) - * before launching the secure camera. - */ - public void startSecureCameraLaunch() { - if (DEBUG) Log.d(TAG, "startSecureCameraLunch"); - if (areAllCamerasAvailable() || targetWillWaitForCameraAvailable()) { - mKeyguardBottomArea.launchCamera(); - } else { - mWaitingToLaunchSecureCamera = true; - mHandler.postDelayed(mLaunchCameraRunnable, CAMERA_AVAILABILITY_TIMEOUT_MS); - } - } - - /** - * Returns true if all of the cameras we are tracking are currently available. - */ - private boolean areAllCamerasAvailable() { - for (boolean cameraAvailable: mCameraAvailabilityMap.values()) { - if (!cameraAvailable) { - return false; - } - } - return true; - } - - /** - * Determines if the secure camera app will wait for the camera hardware to become available - * before trying to open the camera. If so, we can fire off an intent to start the secure - * camera app before the camera is available. Otherwise, it is our responsibility to wait for - * the camera hardware to become available before firing off the intent to start the secure - * camera. - * - * Ideally we are able to fire off the secure camera intent as early as possibly so that, if the - * camera is closing, it can continue to close while the secure camera app is opening. This - * improves secure camera startup time. - */ - private boolean targetWillWaitForCameraAvailable() { - // Create intent that would launch the secure camera. - Intent intent = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE) - .addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); - PackageManager packageManager = mContext.getPackageManager(); - - // Get the list of applications that can handle the intent. - final List<ResolveInfo> appList = packageManager.queryIntentActivitiesAsUser( - intent, PackageManager.MATCH_DEFAULT_ONLY, KeyguardUpdateMonitor.getCurrentUser()); - if (appList.size() == 0) { - if (DEBUG) Log.d(TAG, "No targets found for secure camera intent"); - return false; - } - - // Get the application that the intent resolves to. - ResolveInfo resolved = packageManager.resolveActivityAsUser(intent, - PackageManager.MATCH_DEFAULT_ONLY | PackageManager.GET_META_DATA, - KeyguardUpdateMonitor.getCurrentUser()); - - if (resolved == null || resolved.activityInfo == null) { - return false; - } - - // If we would need to launch the resolver activity, then we can't assume that the target - // is one that would wait for the camera. - if (wouldLaunchResolverActivity(resolved, appList)) { - if (DEBUG) Log.d(TAG, "Secure camera intent would launch resolver"); - return false; - } - - // If the target doesn't have meta-data we must assume it won't wait for the camera. - if (resolved.activityInfo.metaData == null || resolved.activityInfo.metaData.isEmpty()) { - if (DEBUG) Log.d(TAG, "No meta-data found for secure camera application"); - return false; - } - - // Check the secure camera app meta-data to see if it indicates that it will wait for the - // camera to become available. - boolean willWaitForCameraAvailability = - resolved.activityInfo.metaData.getBoolean(META_DATA_WILL_WAIT_FOR_CAMERA_AVAILABLE); - - if (DEBUG) Log.d(TAG, "Target will wait for camera: " + willWaitForCameraAvailability); - - return willWaitForCameraAvailability; - } - - /** - * Determines if the activity that would be launched by the intent is the ResolverActivity. - */ - private boolean wouldLaunchResolverActivity(ResolveInfo resolved, List<ResolveInfo> appList) { - // If the list contains the resolved activity, then it can't be the ResolverActivity itself. - for (int i = 0; i < appList.size(); i++) { - ResolveInfo tmp = appList.get(i); - if (tmp.activityInfo.name.equals(resolved.activityInfo.name) - && tmp.activityInfo.packageName.equals(resolved.activityInfo.packageName)) { - return false; - } - } - return true; - } -} diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 1f4427f..d1e1683 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -359,6 +359,9 @@ public class ConnectivityService extends IConnectivityManager.Stub */ private static final int EVENT_REGISTER_NETWORK_LISTENER_WITH_INTENT = 31; + /** Handler thread used for both of the handlers below. */ + @VisibleForTesting + protected final HandlerThread mHandlerThread; /** Handler used for internal events. */ final private InternalHandler mHandler; /** Handler used for incoming {@link NetworkStateTracker} events. */ @@ -614,6 +617,11 @@ public class ConnectivityService extends IConnectivityManager.Stub } private LegacyTypeTracker mLegacyTypeTracker = new LegacyTypeTracker(); + @VisibleForTesting + protected HandlerThread createHandlerThread() { + return new HandlerThread("ConnectivityServiceThread"); + } + public ConnectivityService(Context context, INetworkManagementService netManager, INetworkStatsService statsService, INetworkPolicyManager policyManager) { if (DBG) log("ConnectivityService starting up"); @@ -627,10 +635,10 @@ public class ConnectivityService extends IConnectivityManager.Stub mDefaultMobileDataRequest = createInternetRequestForTransport( NetworkCapabilities.TRANSPORT_CELLULAR); - HandlerThread handlerThread = new HandlerThread("ConnectivityServiceThread"); - handlerThread.start(); - mHandler = new InternalHandler(handlerThread.getLooper()); - mTrackerHandler = new NetworkStateTrackerHandler(handlerThread.getLooper()); + mHandlerThread = createHandlerThread(); + mHandlerThread.start(); + mHandler = new InternalHandler(mHandlerThread.getLooper()); + mTrackerHandler = new NetworkStateTrackerHandler(mHandlerThread.getLooper()); // setup our unique device name if (TextUtils.isEmpty(SystemProperties.get("net.hostname"))) { @@ -1458,7 +1466,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } private void enforceKeepalivePermission() { - mContext.enforceCallingPermission(KeepaliveTracker.PERMISSION, "ConnectivityService"); + mContext.enforceCallingOrSelfPermission(KeepaliveTracker.PERMISSION, "ConnectivityService"); } public void sendConnectedBroadcast(NetworkInfo info) { diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java index 8b0e6f2..d590d7a 100644 --- a/services/core/java/com/android/server/accounts/AccountManagerService.java +++ b/services/core/java/com/android/server/accounts/AccountManagerService.java @@ -32,6 +32,7 @@ import android.accounts.IAccountManagerResponse; import android.app.ActivityManager; import android.app.ActivityManagerNative; import android.app.AppGlobals; +import android.app.AppOpsManager; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; @@ -122,6 +123,7 @@ public class AccountManagerService private final Context mContext; private final PackageManager mPackageManager; + private final AppOpsManager mAppOpsManager; private UserManager mUserManager; private final MessageHandler mMessageHandler; @@ -266,6 +268,7 @@ public class AccountManagerService IAccountAuthenticatorCache authenticatorCache) { mContext = context; mPackageManager = packageManager; + mAppOpsManager = mContext.getSystemService(AppOpsManager.class); mMessageHandler = new MessageHandler(FgThread.get().getLooper()); @@ -510,7 +513,7 @@ public class AccountManagerService // Check if there's a shared account that needs to be created as an account Account[] sharedAccounts = getSharedAccountsAsUser(userId); if (sharedAccounts == null || sharedAccounts.length == 0) return; - Account[] accounts = getAccountsAsUser(null, userId); + Account[] accounts = getAccountsAsUser(null, userId, mContext.getOpPackageName()); for (Account sa : sharedAccounts) { if (ArrayUtils.contains(accounts, sa)) continue; // Account doesn't exist. Copy it now. @@ -868,7 +871,8 @@ public class AccountManagerService // Confirm that the owner's account still exists before this step. UserAccounts owner = getUserAccounts(UserHandle.USER_OWNER); synchronized (owner.cacheLock) { - for (Account acc : getAccounts(UserHandle.USER_OWNER)) { + for (Account acc : getAccounts(UserHandle.USER_OWNER, + mContext.getOpPackageName())) { if (acc.equals(account)) { mAuthenticator.addAccountFromCredentials( this, account, accountCredentials); @@ -988,7 +992,7 @@ public class AccountManagerService @Override public void hasFeatures(IAccountManagerResponse response, - Account account, String[] features) { + Account account, String[] features, String opPackageName) { int callingUid = Binder.getCallingUid(); if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "hasFeatures: " + account @@ -1001,7 +1005,8 @@ public class AccountManagerService if (account == null) throw new IllegalArgumentException("account is null"); if (features == null) throw new IllegalArgumentException("features is null"); int userId = UserHandle.getCallingUserId(); - checkReadAccountsPermitted(callingUid, account.type, userId); + checkReadAccountsPermitted(callingUid, account.type, userId, + opPackageName); long identityToken = clearCallingIdentity(); try { @@ -2507,9 +2512,10 @@ public class AccountManagerService * Returns the accounts visible to the client within the context of a specific user * @hide */ - public Account[] getAccounts(int userId) { + public Account[] getAccounts(int userId, String opPackageName) { int callingUid = Binder.getCallingUid(); - List<String> visibleAccountTypes = getTypesVisibleToCaller(callingUid, userId); + List<String> visibleAccountTypes = getTypesVisibleToCaller(callingUid, userId, + opPackageName); if (visibleAccountTypes.isEmpty()) { return new Account[0]; } @@ -2571,15 +2577,16 @@ public class AccountManagerService } @Override - public Account[] getAccountsAsUser(String type, int userId) { - return getAccountsAsUser(type, userId, null, -1); + public Account[] getAccountsAsUser(String type, int userId, String opPackageName) { + return getAccountsAsUser(type, userId, null, -1, opPackageName); } private Account[] getAccountsAsUser( String type, int userId, String callingPackage, - int packageUid) { + int packageUid, + String opPackageName) { int callingUid = Binder.getCallingUid(); // Only allow the system process to read accounts of other users if (userId != UserHandle.getCallingUserId() @@ -2602,7 +2609,8 @@ public class AccountManagerService callingUid = packageUid; } - List<String> visibleAccountTypes = getTypesVisibleToCaller(callingUid, userId); + List<String> visibleAccountTypes = getTypesVisibleToCaller(callingUid, userId, + opPackageName); if (visibleAccountTypes.isEmpty() || (type != null && !visibleAccountTypes.contains(type))) { return new Account[0]; @@ -2741,22 +2749,24 @@ public class AccountManagerService } @Override - public Account[] getAccounts(String type) { - return getAccountsAsUser(type, UserHandle.getCallingUserId()); + public Account[] getAccounts(String type, String opPackageName) { + return getAccountsAsUser(type, UserHandle.getCallingUserId(), opPackageName); } @Override - public Account[] getAccountsForPackage(String packageName, int uid) { + public Account[] getAccountsForPackage(String packageName, int uid, String opPackageName) { int callingUid = Binder.getCallingUid(); if (!UserHandle.isSameApp(callingUid, Process.myUid())) { throw new SecurityException("getAccountsForPackage() called from unauthorized uid " + callingUid + " with uid=" + uid); } - return getAccountsAsUser(null, UserHandle.getCallingUserId(), packageName, uid); + return getAccountsAsUser(null, UserHandle.getCallingUserId(), packageName, uid, + opPackageName); } @Override - public Account[] getAccountsByTypeForPackage(String type, String packageName) { + public Account[] getAccountsByTypeForPackage(String type, String packageName, + String opPackageName) { int packageUid = -1; try { packageUid = AppGlobals.getPackageManager().getPackageUid( @@ -2765,14 +2775,16 @@ public class AccountManagerService Slog.e(TAG, "Couldn't determine the packageUid for " + packageName + re); return new Account[0]; } - return getAccountsAsUser(type, UserHandle.getCallingUserId(), packageName, packageUid); + return getAccountsAsUser(type, UserHandle.getCallingUserId(), packageName, + packageUid, opPackageName); } @Override public void getAccountsByFeatures( IAccountManagerResponse response, String type, - String[] features) { + String[] features, + String opPackageName) { int callingUid = Binder.getCallingUid(); if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "getAccounts: accountType " + type @@ -2785,7 +2797,8 @@ public class AccountManagerService if (type == null) throw new IllegalArgumentException("accountType is null"); int userId = UserHandle.getCallingUserId(); - List<String> visibleAccountTypes = getTypesVisibleToCaller(callingUid, userId); + List<String> visibleAccountTypes = getTypesVisibleToCaller(callingUid, userId, + opPackageName); if (!visibleAccountTypes.contains(type)) { Bundle result = new Bundle(); // Need to return just the accounts that are from matching signatures. @@ -3685,31 +3698,22 @@ public class AccountManagerService } } - private boolean isPermitted(int callingUid, String... permissions) { + private boolean isPermitted(String opPackageName, int callingUid, String... permissions) { for (String perm : permissions) { if (mContext.checkCallingOrSelfPermission(perm) == PackageManager.PERMISSION_GRANTED) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, " caller uid " + callingUid + " has " + perm); } - return true; + final int opCode = AppOpsManager.permissionToOpCode(perm); + if (opCode == AppOpsManager.OP_NONE || mAppOpsManager.noteOp( + opCode, callingUid, opPackageName) == AppOpsManager.MODE_ALLOWED) { + return true; + } } } return false; } - /** Succeeds if any of the specified permissions are granted. */ - private void checkBinderPermission(String... permissions) { - final int callingUid = Binder.getCallingUid(); - if (isPermitted(callingUid, permissions)) { - String msg = String.format( - "caller uid %s lacks any of %s", - callingUid, - TextUtils.join(",", permissions)); - Log.w(TAG, " " + msg); - throw new SecurityException(msg); - } - } - private int handleIncomingUser(int userId) { try { return ActivityManagerNative.getDefault().handleIncomingUser( @@ -3763,11 +3767,13 @@ public class AccountManagerService return fromAuthenticator || hasExplicitGrants || isPrivileged; } - private boolean isAccountVisibleToCaller(String accountType, int callingUid, int userId) { + private boolean isAccountVisibleToCaller(String accountType, int callingUid, int userId, + String opPackageName) { if (accountType == null) { return false; } else { - return getTypesVisibleToCaller(callingUid, userId).contains(accountType); + return getTypesVisibleToCaller(callingUid, userId, + opPackageName).contains(accountType); } } @@ -3779,9 +3785,10 @@ public class AccountManagerService } } - private List<String> getTypesVisibleToCaller(int callingUid, int userId) { + private List<String> getTypesVisibleToCaller(int callingUid, int userId, + String opPackageName) { boolean isPermitted = - isPermitted(callingUid, Manifest.permission.GET_ACCOUNTS, + isPermitted(opPackageName, callingUid, Manifest.permission.GET_ACCOUNTS, Manifest.permission.GET_ACCOUNTS_PRIVILEGED); Log.i(TAG, String.format("getTypesVisibleToCaller: isPermitted? %s", isPermitted)); return getTypesForCaller(callingUid, userId, isPermitted); @@ -3877,8 +3884,9 @@ public class AccountManagerService private void checkReadAccountsPermitted( int callingUid, String accountType, - int userId) { - if (!isAccountVisibleToCaller(accountType, callingUid, userId)) { + int userId, + String opPackageName) { + if (!isAccountVisibleToCaller(accountType, callingUid, userId, opPackageName)) { String msg = String.format( "caller uid %s cannot access %s accounts", callingUid, diff --git a/services/core/java/com/android/server/connectivity/KeepalivePacketData.java b/services/core/java/com/android/server/connectivity/KeepalivePacketData.java index 64b9399..2ccfdd1 100644 --- a/services/core/java/com/android/server/connectivity/KeepalivePacketData.java +++ b/services/core/java/com/android/server/connectivity/KeepalivePacketData.java @@ -71,6 +71,7 @@ public class KeepalivePacketData { // Check we have two IP addresses of the same family. if (srcAddress == null || dstAddress == null || !srcAddress.getClass().getName().equals(dstAddress.getClass().getName())) { + throw new InvalidPacketException(ERROR_INVALID_IP_ADDRESS); } // Set the protocol. @@ -102,7 +103,7 @@ public class KeepalivePacketData { InetAddress srcAddress, int srcPort, InetAddress dstAddress, int dstPort) throws InvalidPacketException { - if (!(srcAddress instanceof Inet4Address)) { + if (!(srcAddress instanceof Inet4Address) || !(dstAddress instanceof Inet4Address)) { throw new InvalidPacketException(ERROR_INVALID_IP_ADDRESS); } diff --git a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java index c78f347..90c9ddf 100644 --- a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java +++ b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java @@ -62,8 +62,7 @@ public class KeepaliveTracker { private static final String TAG = "KeepaliveTracker"; private static final boolean DBG = true; - // TODO: Change this to a system-only permission. - public static final String PERMISSION = android.Manifest.permission.CHANGE_NETWORK_STATE; + public static final String PERMISSION = android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD; /** Keeps track of keepalive requests. */ private final HashMap <NetworkAgentInfo, HashMap<Integer, KeepaliveInfo>> mKeepalives = @@ -208,6 +207,8 @@ public class KeepaliveTracker { Log.d(TAG, "Stopping keepalive " + mSlot + " on " + mNai.name()); mNai.asyncChannel.sendMessage(CMD_STOP_PACKET_KEEPALIVE, mSlot); } + // TODO: at the moment we unconditionally return failure here. In cases where the + // NetworkAgent is alive, should we ask it to reply, so it can return failure? notifyMessenger(mSlot, reason); unlinkDeathRecipient(); } @@ -233,17 +234,14 @@ public class KeepaliveTracker { mKeepalives.put(nai, networkKeepalives); } - // Find the lowest-numbered free slot. + // Find the lowest-numbered free slot. Slot numbers start from 1, because that's what two + // separate chipset implementations independently came up with. int slot; - for (slot = 0; slot < networkKeepalives.size(); slot++) { + for (slot = 1; slot <= networkKeepalives.size(); slot++) { if (networkKeepalives.get(slot) == null) { return slot; } } - // No free slot, pick one at the end. - - // HACK for broadcom hardware that does not support slot 0! - if (slot == 0) slot = 1; return slot; } @@ -267,14 +265,15 @@ public class KeepaliveTracker { } public void handleStopKeepalive(NetworkAgentInfo nai, int slot, int reason) { + String networkName = (nai == null) ? "(null)" : nai.name(); HashMap <Integer, KeepaliveInfo> networkKeepalives = mKeepalives.get(nai); if (networkKeepalives == null) { - Log.e(TAG, "Attempt to stop keepalive on nonexistent network " + nai.name()); + Log.e(TAG, "Attempt to stop keepalive on nonexistent network " + networkName); return; } KeepaliveInfo ki = networkKeepalives.get(slot); if (ki == null) { - Log.e(TAG, "Attempt to stop nonexistent keepalive " + slot + " on " + nai.name()); + Log.e(TAG, "Attempt to stop nonexistent keepalive " + slot + " on " + networkName); return; } ki.stop(reason); @@ -332,6 +331,11 @@ public class KeepaliveTracker { public void startNattKeepalive(NetworkAgentInfo nai, int intervalSeconds, Messenger messenger, IBinder binder, String srcAddrString, int srcPort, String dstAddrString, int dstPort) { + if (nai == null) { + notifyMessenger(messenger, NO_KEEPALIVE, ERROR_INVALID_NETWORK); + return; + } + InetAddress srcAddress, dstAddress; try { srcAddress = NetworkUtils.numericToInetAddress(srcAddrString); diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java index c998c2c..334bc18 100644 --- a/services/core/java/com/android/server/content/SyncManager.java +++ b/services/core/java/com/android/server/content/SyncManager.java @@ -340,7 +340,8 @@ public class SyncManager { for (UserInfo user : mUserManager.getUsers(true)) { // Skip any partially created/removed users if (user.partial) continue; - Account[] accountsForUser = AccountManagerService.getSingleton().getAccounts(user.id); + Account[] accountsForUser = AccountManagerService.getSingleton().getAccounts( + user.id, mContext.getOpPackageName()); mSyncStorageEngine.doDatabaseCleanup(accountsForUser, user.id); } } @@ -1232,7 +1233,8 @@ public class SyncManager { } // Schedule sync for any accounts under started user - final Account[] accounts = AccountManagerService.getSingleton().getAccounts(userId); + final Account[] accounts = AccountManagerService.getSingleton().getAccounts(userId, + mContext.getOpPackageName()); for (Account account : accounts) { scheduleSync(account, userId, SyncOperation.REASON_USER_START, null, null, 0 /* no delay */, 0 /* No flex */, diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml index 919293a..8bb20a6 100644 --- a/services/tests/servicestests/AndroidManifest.xml +++ b/services/tests/servicestests/AndroidManifest.xml @@ -39,6 +39,7 @@ <uses-permission android:name="android.permission.MODIFY_PHONE_STATE" /> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" /> + <uses-permission android:name="android.permission.PACKET_KEEPALIVE_OFFLOAD" /> <application> <uses-library android:name="android.test.runner" /> diff --git a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java index b4c76b7..97e16da 100644 --- a/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/ConnectivityServiceTest.java @@ -20,35 +20,9 @@ import static android.net.ConnectivityManager.CONNECTIVITY_ACTION; import static android.net.ConnectivityManager.TYPE_MOBILE; import static android.net.ConnectivityManager.TYPE_WIFI; import static android.net.ConnectivityManager.getNetworkTypeName; -import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL; -import static android.net.NetworkCapabilities.NET_CAPABILITY_CBS; -import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN; -import static android.net.NetworkCapabilities.NET_CAPABILITY_EIMS; -import static android.net.NetworkCapabilities.NET_CAPABILITY_FOTA; -import static android.net.NetworkCapabilities.NET_CAPABILITY_IA; -import static android.net.NetworkCapabilities.NET_CAPABILITY_IMS; -import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; -import static android.net.NetworkCapabilities.NET_CAPABILITY_MMS; -import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; -import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED; -import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN; -import static android.net.NetworkCapabilities.NET_CAPABILITY_RCS; -import static android.net.NetworkCapabilities.NET_CAPABILITY_SUPL; -import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED; -import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED; -import static android.net.NetworkCapabilities.NET_CAPABILITY_WIFI_P2P; -import static android.net.NetworkCapabilities.NET_CAPABILITY_XCAP; -import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; -import static android.net.NetworkCapabilities.TRANSPORT_WIFI; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.eq; -import static org.mockito.Matchers.isA; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.doThrow; +import static android.net.NetworkCapabilities.*; + import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.verify; import android.app.PendingIntent; import android.content.BroadcastReceiver; @@ -58,8 +32,12 @@ import android.content.Intent; import android.content.IntentFilter; import android.net.ConnectivityManager; import android.net.ConnectivityManager.NetworkCallback; +import android.net.ConnectivityManager.PacketKeepalive; +import android.net.ConnectivityManager.PacketKeepaliveCallback; import android.net.INetworkPolicyManager; import android.net.INetworkStatsService; +import android.net.IpPrefix; +import android.net.LinkAddress; import android.net.LinkProperties; import android.net.Network; import android.net.NetworkAgent; @@ -74,8 +52,11 @@ import android.net.RouteInfo; import android.os.ConditionVariable; import android.os.Handler; import android.os.HandlerThread; -import android.os.Looper; import android.os.INetworkManagementService; +import android.os.Looper; +import android.os.Message; +import android.os.MessageQueue; +import android.os.MessageQueue.IdleHandler; import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.LargeTest; import android.util.Log; @@ -84,10 +65,10 @@ import android.util.LogPrinter; import com.android.server.connectivity.NetworkAgentInfo; import com.android.server.connectivity.NetworkMonitor; -import org.mockito.ArgumentCaptor; - import java.net.InetAddress; -import java.util.concurrent.Future; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; /** @@ -99,24 +80,7 @@ import java.util.concurrent.atomic.AtomicBoolean; public class ConnectivityServiceTest extends AndroidTestCase { private static final String TAG = "ConnectivityServiceTest"; - private static final String MOBILE_IFACE = "rmnet3"; - private static final String WIFI_IFACE = "wlan6"; - - private static final RouteInfo MOBILE_ROUTE_V4 = RouteInfo.makeHostRoute(parse("10.0.0.33"), - MOBILE_IFACE); - private static final RouteInfo MOBILE_ROUTE_V6 = RouteInfo.makeHostRoute(parse("fd00::33"), - MOBILE_IFACE); - - private static final RouteInfo WIFI_ROUTE_V4 = RouteInfo.makeHostRoute(parse("192.168.0.66"), - parse("192.168.0.1"), - WIFI_IFACE); - private static final RouteInfo WIFI_ROUTE_V6 = RouteInfo.makeHostRoute(parse("fd00::66"), - parse("fd00::"), - WIFI_IFACE); - - private INetworkManagementService mNetManager; - private INetworkStatsService mStatsService; - private INetworkPolicyManager mPolicyService; + private static final int TIMEOUT_MS = 500; private BroadcastInterceptingContext mServiceContext; private WrappedConnectivityService mService; @@ -148,14 +112,93 @@ public class ConnectivityServiceTest extends AndroidTestCase { } } + /** + * A subclass of HandlerThread that allows callers to wait for it to become idle. waitForIdle + * will return immediately if the handler is already idle. + */ + private class IdleableHandlerThread extends HandlerThread { + private IdleHandler mIdleHandler; + + public IdleableHandlerThread(String name) { + super(name); + } + + public void waitForIdle(int timeoutMs) { + final ConditionVariable cv = new ConditionVariable(); + final MessageQueue queue = getLooper().getQueue(); + + synchronized (queue) { + if (queue.isIdle()) { + return; + } + + assertNull("BUG: only one idle handler allowed", mIdleHandler); + mIdleHandler = new IdleHandler() { + public boolean queueIdle() { + cv.open(); + mIdleHandler = null; + return false; // Remove the handler. + } + }; + queue.addIdleHandler(mIdleHandler); + } + + if (!cv.block(timeoutMs)) { + fail("HandlerThread " + getName() + + " did not become idle after " + timeoutMs + " ms"); + queue.removeIdleHandler(mIdleHandler); + } + } + } + + // Tests that IdleableHandlerThread works as expected. + public void testIdleableHandlerThread() { + final int attempts = 50; // Causes the test to take about 200ms on bullhead-eng. + + // Tests that waitForIdle returns immediately if the service is already idle. + for (int i = 0; i < attempts; i++) { + mService.waitForIdle(); + } + + // Bring up a network that we can use to send messages to ConnectivityService. + ConditionVariable cv = waitForConnectivityBroadcasts(1); + mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + mWiFiNetworkAgent.connect(false); + waitFor(cv); + Network n = mWiFiNetworkAgent.getNetwork(); + assertNotNull(n); + + // Tests that calling waitForIdle waits for messages to be processed. + for (int i = 0; i < attempts; i++) { + mWiFiNetworkAgent.setSignalStrength(i); + mService.waitForIdle(); + assertEquals(i, mCm.getNetworkCapabilities(n).getSignalStrength()); + } + + // Ensure that not calling waitForIdle causes a race condition. + for (int i = 0; i < attempts; i++) { + mWiFiNetworkAgent.setSignalStrength(i); + if (i != mCm.getNetworkCapabilities(n).getSignalStrength()) { + // We hit a race condition, as expected. Pass the test. + return; + } + } + + // No race? There is a bug in this test. + fail("expected race condition at least once in " + attempts + " attempts"); + } + private class MockNetworkAgent { private final WrappedNetworkMonitor mWrappedNetworkMonitor; private final NetworkInfo mNetworkInfo; private final NetworkCapabilities mNetworkCapabilities; - private final Thread mThread; + private final IdleableHandlerThread mHandlerThread; private final ConditionVariable mDisconnected = new ConditionVariable(); private int mScore; private NetworkAgent mNetworkAgent; + private int mStartKeepaliveError = PacketKeepalive.ERROR_HARDWARE_UNSUPPORTED; + private int mStopKeepaliveError = PacketKeepalive.NO_KEEPALIVE; + private Integer mExpectedKeepaliveSlot = null; MockNetworkAgent(int transport) { final int type = transportToLegacyType(transport); @@ -173,26 +216,42 @@ public class ConnectivityServiceTest extends AndroidTestCase { default: throw new UnsupportedOperationException("unimplemented network type"); } - final ConditionVariable initComplete = new ConditionVariable(); - final ConditionVariable networkMonitorAvailable = mService.getNetworkMonitorCreatedCV(); - mThread = new Thread() { - public void run() { - Looper.prepare(); - mNetworkAgent = new NetworkAgent(Looper.myLooper(), mServiceContext, - "Mock" + typeName, mNetworkInfo, mNetworkCapabilities, - new LinkProperties(), mScore, new NetworkMisc()) { - public void unwanted() { mDisconnected.open(); } - }; - initComplete.open(); - Looper.loop(); + mHandlerThread = new IdleableHandlerThread("Mock-" + typeName); + mHandlerThread.start(); + mNetworkAgent = new NetworkAgent(mHandlerThread.getLooper(), mServiceContext, + "Mock-" + typeName, mNetworkInfo, mNetworkCapabilities, + new LinkProperties(), mScore, new NetworkMisc()) { + @Override + public void unwanted() { mDisconnected.open(); } + + @Override + public void startPacketKeepalive(Message msg) { + int slot = msg.arg1; + if (mExpectedKeepaliveSlot != null) { + assertEquals((int) mExpectedKeepaliveSlot, slot); + } + onPacketKeepaliveEvent(slot, mStartKeepaliveError); + } + + @Override + public void stopPacketKeepalive(Message msg) { + onPacketKeepaliveEvent(msg.arg1, mStopKeepaliveError); } }; - mThread.start(); - waitFor(initComplete); - waitFor(networkMonitorAvailable); + // Waits for the NetworkAgent to be registered, which includes the creation of the + // NetworkMonitor. + mService.waitForIdle(); mWrappedNetworkMonitor = mService.getLastCreatedWrappedNetworkMonitor(); } + public void waitForIdle(int timeoutMs) { + mHandlerThread.waitForIdle(timeoutMs); + } + + public void waitForIdle() { + waitForIdle(TIMEOUT_MS); + } + public void adjustScore(int change) { mScore += change; mNetworkAgent.sendNetworkScore(mScore); @@ -203,6 +262,11 @@ public class ConnectivityServiceTest extends AndroidTestCase { mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities); } + public void setSignalStrength(int signalStrength) { + mNetworkCapabilities.setSignalStrength(signalStrength); + mNetworkAgent.sendNetworkCapabilities(mNetworkCapabilities); + } + public void connectWithoutInternet() { mNetworkInfo.setDetailedState(DetailedState.CONNECTED, null, null); mNetworkAgent.sendNetworkInfo(mNetworkInfo); @@ -272,15 +336,46 @@ public class ConnectivityServiceTest extends AndroidTestCase { public WrappedNetworkMonitor getWrappedNetworkMonitor() { return mWrappedNetworkMonitor; } + + public void sendLinkProperties(LinkProperties lp) { + mNetworkAgent.sendLinkProperties(lp); + } + + public void setStartKeepaliveError(int error) { + mStartKeepaliveError = error; + } + + public void setStopKeepaliveError(int error) { + mStopKeepaliveError = error; + } + + public void setExpectedKeepaliveSlot(Integer slot) { + mExpectedKeepaliveSlot = slot; + } } + /** + * A NetworkFactory that allows tests to wait until any in-flight NetworkRequest add or remove + * operations have been processed. Before ConnectivityService can add or remove any requests, + * the factory must be told to expect those operations by calling expectAddRequests or + * expectRemoveRequests. + */ private static class MockNetworkFactory extends NetworkFactory { private final ConditionVariable mNetworkStartedCV = new ConditionVariable(); private final ConditionVariable mNetworkStoppedCV = new ConditionVariable(); - private final ConditionVariable mNetworkRequestedCV = new ConditionVariable(); - private final ConditionVariable mNetworkReleasedCV = new ConditionVariable(); private final AtomicBoolean mNetworkStarted = new AtomicBoolean(false); + // Used to expect that requests be removed or added on a separate thread, without sleeping. + // Callers can call either expectAddRequests() or expectRemoveRequests() exactly once, then + // cause some other thread to add or remove requests, then call waitForRequests(). We can + // either expect requests to be added or removed, but not both, because CountDownLatch can + // only count in one direction. + private CountDownLatch mExpectations; + + // Whether we are currently expecting requests to be added or removed. Valid only if + // mExpectations is non-null. + private boolean mExpectingAdditions; + public MockNetworkFactory(Looper looper, Context context, String logTag, NetworkCapabilities filter) { super(looper, context, logTag, filter); @@ -314,28 +409,75 @@ public class ConnectivityServiceTest extends AndroidTestCase { return mNetworkStoppedCV; } - protected void needNetworkFor(NetworkRequest networkRequest, int score) { - super.needNetworkFor(networkRequest, score); - mNetworkRequestedCV.open(); + @Override + protected void handleAddRequest(NetworkRequest request, int score) { + // If we're expecting anything, we must be expecting additions. + if (mExpectations != null && !mExpectingAdditions) { + fail("Can't add requests while expecting requests to be removed"); + } + + // Add the request. + super.handleAddRequest(request, score); + + // Reduce the number of request additions we're waiting for. + if (mExpectingAdditions) { + assertTrue("Added more requests than expected", mExpectations.getCount() > 0); + mExpectations.countDown(); + } + } + + @Override + protected void handleRemoveRequest(NetworkRequest request) { + // If we're expecting anything, we must be expecting removals. + if (mExpectations != null && mExpectingAdditions) { + fail("Can't remove requests while expecting requests to be added"); + } + + // Remove the request. + super.handleRemoveRequest(request); + + // Reduce the number of request removals we're waiting for. + if (!mExpectingAdditions) { + assertTrue("Removed more requests than expected", mExpectations.getCount() > 0); + mExpectations.countDown(); + } } - protected void releaseNetworkFor(NetworkRequest networkRequest) { - super.releaseNetworkFor(networkRequest); - mNetworkReleasedCV.open(); + private void assertNoExpectations() { + if (mExpectations != null) { + fail("Can't add expectation, " + mExpectations.getCount() + " already pending"); + } + } + + // Expects that count requests will be added. + public void expectAddRequests(final int count) { + assertNoExpectations(); + mExpectingAdditions = true; + mExpectations = new CountDownLatch(count); } - public ConditionVariable getNetworkRequestedCV() { - mNetworkRequestedCV.close(); - return mNetworkRequestedCV; + // Expects that count requests will be removed. + public void expectRemoveRequests(final int count) { + assertNoExpectations(); + mExpectingAdditions = false; + mExpectations = new CountDownLatch(count); } - public ConditionVariable getNetworkReleasedCV() { - mNetworkReleasedCV.close(); - return mNetworkReleasedCV; + // Waits for the expected request additions or removals to happen within a timeout. + public void waitForRequests() throws InterruptedException { + assertNotNull("Nothing to wait for", mExpectations); + mExpectations.await(TIMEOUT_MS, TimeUnit.MILLISECONDS); + final long count = mExpectations.getCount(); + final String msg = count + " requests still not " + + (mExpectingAdditions ? "added" : "removed") + + " after " + TIMEOUT_MS + " ms"; + assertEquals(msg, 0, count); + mExpectations = null; } - public void waitForNetworkRequests(final int count) { - waitFor(new Criteria() { public boolean get() { return count == getRequestCount(); } }); + public void waitForNetworkRequests(final int count) throws InterruptedException { + waitForRequests(); + assertEquals(count, getMyRequestCount()); } } @@ -356,7 +498,6 @@ public class ConnectivityServiceTest extends AndroidTestCase { } private class WrappedConnectivityService extends ConnectivityService { - private final ConditionVariable mNetworkMonitorCreated = new ConditionVariable(); private WrappedNetworkMonitor mLastCreatedNetworkMonitor; public WrappedConnectivityService(Context context, INetworkManagementService netManager, @@ -365,6 +506,11 @@ public class ConnectivityServiceTest extends AndroidTestCase { } @Override + protected HandlerThread createHandlerThread() { + return new IdleableHandlerThread("WrappedConnectivityService"); + } + + @Override protected int getDefaultTcpRwnd() { // Prevent wrapped ConnectivityService from trying to write to SystemProperties. return 0; @@ -397,7 +543,6 @@ public class ConnectivityServiceTest extends AndroidTestCase { final WrappedNetworkMonitor monitor = new WrappedNetworkMonitor(context, handler, nai, defaultRequest); mLastCreatedNetworkMonitor = monitor; - mNetworkMonitorCreated.open(); return monitor; } @@ -405,10 +550,14 @@ public class ConnectivityServiceTest extends AndroidTestCase { return mLastCreatedNetworkMonitor; } - public ConditionVariable getNetworkMonitorCreatedCV() { - mNetworkMonitorCreated.close(); - return mNetworkMonitorCreated; + public void waitForIdle(int timeoutMs) { + ((IdleableHandlerThread) mHandlerThread).waitForIdle(timeoutMs); } + + public void waitForIdle() { + waitForIdle(TIMEOUT_MS); + } + } private interface Criteria { @@ -431,23 +580,11 @@ public class ConnectivityServiceTest extends AndroidTestCase { } /** - * Wait up to 500ms for {@code conditonVariable} to open. - * Fails if 500ms goes by before {@code conditionVariable} opens. + * Wait up to TIMEOUT_MS for {@code conditionVariable} to open. + * Fails if TIMEOUT_MS goes by before {@code conditionVariable} opens. */ static private void waitFor(ConditionVariable conditionVariable) { - assertTrue(conditionVariable.block(500)); - } - - /** - * This should only be used to verify that nothing happens, in other words that no unexpected - * changes occur. It should never be used to wait for a specific positive signal to occur. - */ - private void shortSleep() { - // TODO: Instead of sleeping, instead wait for all message loops to idle. - try { - Thread.sleep(500); - } catch (InterruptedException e) { - } + assertTrue(conditionVariable.block(TIMEOUT_MS)); } @Override @@ -455,13 +592,11 @@ public class ConnectivityServiceTest extends AndroidTestCase { super.setUp(); mServiceContext = new MockContext(getContext()); + mService = new WrappedConnectivityService(mServiceContext, + mock(INetworkManagementService.class), + mock(INetworkStatsService.class), + mock(INetworkPolicyManager.class)); - mNetManager = mock(INetworkManagementService.class); - mStatsService = mock(INetworkStatsService.class); - mPolicyService = mock(INetworkPolicyManager.class); - - mService = new WrappedConnectivityService( - mServiceContext, mNetManager, mStatsService, mPolicyService); mService.systemReady(); mCm = new ConnectivityManager(getContext(), mService); } @@ -583,11 +718,11 @@ public class ConnectivityServiceTest extends AndroidTestCase { // Test bringing up unvalidated cellular mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); mCellNetworkAgent.connect(false); - shortSleep(); + mService.waitForIdle(); verifyActiveNetwork(TRANSPORT_WIFI); // Test cellular disconnect. mCellNetworkAgent.disconnect(); - shortSleep(); + mService.waitForIdle(); verifyActiveNetwork(TRANSPORT_WIFI); // Test bringing up validated cellular mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); @@ -797,6 +932,11 @@ public class ConnectivityServiceTest extends AndroidTestCase { LOST } + /** + * Utility NetworkCallback for testing. The caller must explicitly test for all the callbacks + * this class receives, by calling expectCallback() exactly once each time a callback is + * received. assertNoCallback may be called at any time. + */ private class TestNetworkCallback extends NetworkCallback { private final ConditionVariable mConditionVariable = new ConditionVariable(); private CallbackState mLastCallback = CallbackState.NONE; @@ -819,14 +959,15 @@ public class ConnectivityServiceTest extends AndroidTestCase { mConditionVariable.open(); } - ConditionVariable getConditionVariable() { + void expectCallback(CallbackState state) { + waitFor(mConditionVariable); + assertEquals(state, mLastCallback); mLastCallback = CallbackState.NONE; mConditionVariable.close(); - return mConditionVariable; } - CallbackState getLastCallback() { - return mLastCallback; + void assertNoCallback() { + assertEquals(CallbackState.NONE, mLastCallback); } } @@ -842,98 +983,68 @@ public class ConnectivityServiceTest extends AndroidTestCase { mCm.registerNetworkCallback(cellRequest, cellNetworkCallback); // Test unvalidated networks - ConditionVariable cellCv = cellNetworkCallback.getConditionVariable(); - ConditionVariable wifiCv = wifiNetworkCallback.getConditionVariable(); ConditionVariable cv = waitForConnectivityBroadcasts(1); mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); mCellNetworkAgent.connect(false); - waitFor(cellCv); - assertEquals(CallbackState.AVAILABLE, cellNetworkCallback.getLastCallback()); - assertEquals(CallbackState.NONE, wifiNetworkCallback.getLastCallback()); + cellNetworkCallback.expectCallback(CallbackState.AVAILABLE); + wifiNetworkCallback.assertNoCallback(); assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); waitFor(cv); - cellCv = cellNetworkCallback.getConditionVariable(); - wifiCv = wifiNetworkCallback.getConditionVariable(); // This should not trigger spurious onAvailable() callbacks, b/21762680. mCellNetworkAgent.adjustScore(-1); - shortSleep(); - assertEquals(CallbackState.NONE, wifiNetworkCallback.getLastCallback()); - assertEquals(CallbackState.NONE, cellNetworkCallback.getLastCallback()); + mService.waitForIdle(); + wifiNetworkCallback.assertNoCallback(); + cellNetworkCallback.assertNoCallback(); assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); - cellCv = cellNetworkCallback.getConditionVariable(); - wifiCv = wifiNetworkCallback.getConditionVariable(); cv = waitForConnectivityBroadcasts(2); mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(false); - waitFor(wifiCv); - assertEquals(CallbackState.AVAILABLE, wifiNetworkCallback.getLastCallback()); - assertEquals(CallbackState.NONE, cellNetworkCallback.getLastCallback()); + wifiNetworkCallback.expectCallback(CallbackState.AVAILABLE); + cellNetworkCallback.assertNoCallback(); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); waitFor(cv); - cellCv = cellNetworkCallback.getConditionVariable(); - wifiCv = wifiNetworkCallback.getConditionVariable(); cv = waitForConnectivityBroadcasts(2); mWiFiNetworkAgent.disconnect(); - waitFor(wifiCv); - assertEquals(CallbackState.LOST, wifiNetworkCallback.getLastCallback()); - assertEquals(CallbackState.NONE, cellNetworkCallback.getLastCallback()); + wifiNetworkCallback.expectCallback(CallbackState.LOST); + cellNetworkCallback.assertNoCallback(); waitFor(cv); - cellCv = cellNetworkCallback.getConditionVariable(); - wifiCv = wifiNetworkCallback.getConditionVariable(); cv = waitForConnectivityBroadcasts(1); mCellNetworkAgent.disconnect(); - waitFor(cellCv); - assertEquals(CallbackState.LOST, cellNetworkCallback.getLastCallback()); - assertEquals(CallbackState.NONE, wifiNetworkCallback.getLastCallback()); + cellNetworkCallback.expectCallback(CallbackState.LOST); + wifiNetworkCallback.assertNoCallback(); waitFor(cv); // Test validated networks - - cellCv = cellNetworkCallback.getConditionVariable(); - wifiCv = wifiNetworkCallback.getConditionVariable(); mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); mCellNetworkAgent.connect(true); - waitFor(cellCv); - assertEquals(CallbackState.AVAILABLE, cellNetworkCallback.getLastCallback()); - assertEquals(CallbackState.NONE, wifiNetworkCallback.getLastCallback()); + cellNetworkCallback.expectCallback(CallbackState.AVAILABLE); + wifiNetworkCallback.assertNoCallback(); assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); - cellCv = cellNetworkCallback.getConditionVariable(); - wifiCv = wifiNetworkCallback.getConditionVariable(); // This should not trigger spurious onAvailable() callbacks, b/21762680. mCellNetworkAgent.adjustScore(-1); - shortSleep(); - assertEquals(CallbackState.NONE, wifiNetworkCallback.getLastCallback()); - assertEquals(CallbackState.NONE, cellNetworkCallback.getLastCallback()); + mService.waitForIdle(); + wifiNetworkCallback.assertNoCallback(); + cellNetworkCallback.assertNoCallback(); assertEquals(mCellNetworkAgent.getNetwork(), mCm.getActiveNetwork()); - cellCv = cellNetworkCallback.getConditionVariable(); - wifiCv = wifiNetworkCallback.getConditionVariable(); mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(true); - waitFor(wifiCv); - assertEquals(CallbackState.AVAILABLE, wifiNetworkCallback.getLastCallback()); - waitFor(cellCv); - assertEquals(CallbackState.LOSING, cellNetworkCallback.getLastCallback()); + wifiNetworkCallback.expectCallback(CallbackState.AVAILABLE); + cellNetworkCallback.expectCallback(CallbackState.LOSING); assertEquals(mWiFiNetworkAgent.getNetwork(), mCm.getActiveNetwork()); - cellCv = cellNetworkCallback.getConditionVariable(); - wifiCv = wifiNetworkCallback.getConditionVariable(); mWiFiNetworkAgent.disconnect(); - waitFor(wifiCv); - assertEquals(CallbackState.LOST, wifiNetworkCallback.getLastCallback()); - assertEquals(CallbackState.NONE, cellNetworkCallback.getLastCallback()); + wifiNetworkCallback.expectCallback(CallbackState.LOST); + cellNetworkCallback.assertNoCallback(); - cellCv = cellNetworkCallback.getConditionVariable(); - wifiCv = wifiNetworkCallback.getConditionVariable(); mCellNetworkAgent.disconnect(); - waitFor(cellCv); - assertEquals(CallbackState.LOST, cellNetworkCallback.getLastCallback()); - assertEquals(CallbackState.NONE, wifiNetworkCallback.getLastCallback()); + cellNetworkCallback.expectCallback(CallbackState.LOST); + wifiNetworkCallback.assertNoCallback(); } private void tryNetworkFactoryRequests(int capability) throws Exception { @@ -957,18 +1068,21 @@ public class ConnectivityServiceTest extends AndroidTestCase { mServiceContext, "testFactory", filter); testFactory.setScoreFilter(40); ConditionVariable cv = testFactory.getNetworkStartedCV(); + testFactory.expectAddRequests(1); testFactory.register(); + testFactory.waitForNetworkRequests(1); int expectedRequestCount = 1; NetworkCallback networkCallback = null; // For non-INTERNET capabilities we cannot rely on the default request being present, so // add one. if (capability != NET_CAPABILITY_INTERNET) { - testFactory.waitForNetworkRequests(1); assertFalse(testFactory.getMyStartRequested()); NetworkRequest request = new NetworkRequest.Builder().addCapability(capability).build(); networkCallback = new NetworkCallback(); + testFactory.expectAddRequests(1); mCm.requestNetwork(request, networkCallback); expectedRequestCount++; + testFactory.waitForNetworkRequests(expectedRequestCount); } waitFor(cv); assertEquals(expectedRequestCount, testFactory.getMyRequestCount()); @@ -981,13 +1095,20 @@ public class ConnectivityServiceTest extends AndroidTestCase { // unvalidated penalty. testAgent.adjustScore(40); cv = testFactory.getNetworkStoppedCV(); + + // When testAgent connects, ConnectivityService will re-send us all current requests with + // the new score. There are expectedRequestCount such requests, and we must wait for all of + // them. + testFactory.expectAddRequests(expectedRequestCount); testAgent.connect(false); testAgent.addCapability(capability); waitFor(cv); - assertEquals(expectedRequestCount, testFactory.getMyRequestCount()); + testFactory.waitForNetworkRequests(expectedRequestCount); assertFalse(testFactory.getMyStartRequested()); // Bring in a bunch of requests. + testFactory.expectAddRequests(10); + assertEquals(expectedRequestCount, testFactory.getMyRequestCount()); ConnectivityManager.NetworkCallback[] networkCallbacks = new ConnectivityManager.NetworkCallback[10]; for (int i = 0; i< networkCallbacks.length; i++) { @@ -1000,6 +1121,7 @@ public class ConnectivityServiceTest extends AndroidTestCase { assertFalse(testFactory.getMyStartRequested()); // Remove the requests. + testFactory.expectRemoveRequests(10); for (int i = 0; i < networkCallbacks.length; i++) { mCm.unregisterNetworkCallback(networkCallbacks[i]); } @@ -1088,10 +1210,8 @@ public class ConnectivityServiceTest extends AndroidTestCase { // Test bringing up unvalidated cellular with MMS mCellNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); mCellNetworkAgent.addCapability(NET_CAPABILITY_MMS); - cv = networkCallback.getConditionVariable(); mCellNetworkAgent.connectWithoutInternet(); - waitFor(cv); - assertEquals(CallbackState.AVAILABLE, networkCallback.getLastCallback()); + networkCallback.expectCallback(CallbackState.AVAILABLE); verifyActiveNetwork(TRANSPORT_WIFI); // Test releasing NetworkRequest disconnects cellular with MMS cv = mCellNetworkAgent.getDisconnectedCV(); @@ -1114,12 +1234,10 @@ public class ConnectivityServiceTest extends AndroidTestCase { final TestNetworkCallback networkCallback = new TestNetworkCallback(); mCm.requestNetwork(builder.build(), networkCallback); // Test bringing up MMS cellular network - cv = networkCallback.getConditionVariable(); MockNetworkAgent mmsNetworkAgent = new MockNetworkAgent(TRANSPORT_CELLULAR); mmsNetworkAgent.addCapability(NET_CAPABILITY_MMS); mmsNetworkAgent.connectWithoutInternet(); - waitFor(cv); - assertEquals(CallbackState.AVAILABLE, networkCallback.getLastCallback()); + networkCallback.expectCallback(CallbackState.AVAILABLE); verifyActiveNetwork(TRANSPORT_CELLULAR); // Test releasing MMS NetworkRequest does not disconnect main cellular NetworkAgent cv = mmsNetworkAgent.getDisconnectedCV(); @@ -1139,133 +1257,245 @@ public class ConnectivityServiceTest extends AndroidTestCase { final NetworkRequest validatedRequest = new NetworkRequest.Builder() .addCapability(NET_CAPABILITY_VALIDATED).build(); mCm.registerNetworkCallback(validatedRequest, validatedCallback); - ConditionVariable validatedCv = validatedCallback.getConditionVariable(); // Bring up a network with a captive portal. // Expect onAvailable callback of listen for NET_CAPABILITY_CAPTIVE_PORTAL. - ConditionVariable cv = captivePortalCallback.getConditionVariable(); mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.connectWithCaptivePortal(); - waitFor(cv); - assertEquals(CallbackState.AVAILABLE, captivePortalCallback.getLastCallback()); + captivePortalCallback.expectCallback(CallbackState.AVAILABLE); // Take down network. // Expect onLost callback. - cv = captivePortalCallback.getConditionVariable(); mWiFiNetworkAgent.disconnect(); - waitFor(cv); - assertEquals(CallbackState.LOST, captivePortalCallback.getLastCallback()); + captivePortalCallback.expectCallback(CallbackState.LOST); // Bring up a network with a captive portal. // Expect onAvailable callback of listen for NET_CAPABILITY_CAPTIVE_PORTAL. - cv = captivePortalCallback.getConditionVariable(); mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.connectWithCaptivePortal(); - waitFor(cv); - assertEquals(CallbackState.AVAILABLE, captivePortalCallback.getLastCallback()); + captivePortalCallback.expectCallback(CallbackState.AVAILABLE); // Make captive portal disappear then revalidate. // Expect onLost callback because network no longer provides NET_CAPABILITY_CAPTIVE_PORTAL. - cv = captivePortalCallback.getConditionVariable(); mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 204; mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), true); - waitFor(cv); - assertEquals(CallbackState.LOST, captivePortalCallback.getLastCallback()); + captivePortalCallback.expectCallback(CallbackState.LOST); // Expect NET_CAPABILITY_VALIDATED onAvailable callback. - waitFor(validatedCv); - assertEquals(CallbackState.AVAILABLE, validatedCallback.getLastCallback()); + validatedCallback.expectCallback(CallbackState.AVAILABLE); // Break network connectivity. // Expect NET_CAPABILITY_VALIDATED onLost callback. - validatedCv = validatedCallback.getConditionVariable(); mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 500; mCm.reportNetworkConnectivity(mWiFiNetworkAgent.getNetwork(), false); - waitFor(validatedCv); - assertEquals(CallbackState.LOST, validatedCallback.getLastCallback()); + validatedCallback.expectCallback(CallbackState.LOST); + } + + private static class TestKeepaliveCallback extends PacketKeepaliveCallback { + + public static enum CallbackType { ON_STARTED, ON_STOPPED, ON_ERROR }; + + private class CallbackValue { + public CallbackType callbackType; + public int error; + + public CallbackValue(CallbackType type) { + this.callbackType = type; + this.error = PacketKeepalive.SUCCESS; + assertTrue("onError callback must have error", type != CallbackType.ON_ERROR); + } + + public CallbackValue(CallbackType type, int error) { + this.callbackType = type; + this.error = error; + assertEquals("error can only be set for onError", type, CallbackType.ON_ERROR); + } + + @Override + public boolean equals(Object o) { + return o instanceof CallbackValue && + this.callbackType == ((CallbackValue) o).callbackType && + this.error == ((CallbackValue) o).error; + } + + @Override + public String toString() { + return String.format("%s(%s, %d)", getClass().getSimpleName(), callbackType, error); + } + } + + private LinkedBlockingQueue<CallbackValue> mCallbacks = new LinkedBlockingQueue<>(); + + @Override + public void onStarted() { + mCallbacks.add(new CallbackValue(CallbackType.ON_STARTED)); + } + + @Override + public void onStopped() { + mCallbacks.add(new CallbackValue(CallbackType.ON_STOPPED)); + } + + @Override + public void onError(int error) { + mCallbacks.add(new CallbackValue(CallbackType.ON_ERROR, error)); + } + + private void expectCallback(CallbackValue callbackValue) { + try { + assertEquals( + callbackValue, + mCallbacks.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS)); + } catch (InterruptedException e) { + fail(callbackValue.callbackType + " callback not seen after " + TIMEOUT_MS + " ms"); + } + } + + public void expectStarted() { + expectCallback(new CallbackValue(CallbackType.ON_STARTED)); + } + + public void expectStopped() { + expectCallback(new CallbackValue(CallbackType.ON_STOPPED)); + } + + public void expectError(int error) { + expectCallback(new CallbackValue(CallbackType.ON_ERROR, error)); + } } -// @Override -// public void tearDown() throws Exception { -// super.tearDown(); -// } -// -// public void testMobileConnectedAddedRoutes() throws Exception { -// Future<?> nextConnBroadcast; -// -// // bring up mobile network -// mMobile.info.setDetailedState(DetailedState.CONNECTED, null, null); -// mMobile.link.setInterfaceName(MOBILE_IFACE); -// mMobile.link.addRoute(MOBILE_ROUTE_V4); -// mMobile.link.addRoute(MOBILE_ROUTE_V6); -// mMobile.doReturnDefaults(); -// -// cv = waitForConnectivityBroadcasts(1); -// mTrackerHandler.obtainMessage(EVENT_STATE_CHANGED, mMobile.info).sendToTarget(); -// waitFor(cv); -// -// // verify that both routes were added -// int mobileNetId = mMobile.tracker.getNetwork().netId; -// verify(mNetManager).addRoute(eq(mobileNetId), eq(MOBILE_ROUTE_V4)); -// verify(mNetManager).addRoute(eq(mobileNetId), eq(MOBILE_ROUTE_V6)); -// } -// -// public void testMobileWifiHandoff() throws Exception { -// Future<?> nextConnBroadcast; -// -// // bring up mobile network -// mMobile.info.setDetailedState(DetailedState.CONNECTED, null, null); -// mMobile.link.setInterfaceName(MOBILE_IFACE); -// mMobile.link.addRoute(MOBILE_ROUTE_V4); -// mMobile.link.addRoute(MOBILE_ROUTE_V6); -// mMobile.doReturnDefaults(); -// -// cv = waitForConnectivityBroadcasts(1); -// mTrackerHandler.obtainMessage(EVENT_STATE_CHANGED, mMobile.info).sendToTarget(); -// waitFor(cv); -// -// reset(mNetManager); -// -// // now bring up wifi network -// mWifi.info.setDetailedState(DetailedState.CONNECTED, null, null); -// mWifi.link.setInterfaceName(WIFI_IFACE); -// mWifi.link.addRoute(WIFI_ROUTE_V4); -// mWifi.link.addRoute(WIFI_ROUTE_V6); -// mWifi.doReturnDefaults(); -// -// // expect that mobile will be torn down -// doReturn(true).when(mMobile.tracker).teardown(); -// -// cv = waitForConnectivityBroadcasts(1); -// mTrackerHandler.obtainMessage(EVENT_STATE_CHANGED, mWifi.info).sendToTarget(); -// waitFor(cv); -// -// // verify that wifi routes added, and teardown requested -// int wifiNetId = mWifi.tracker.getNetwork().netId; -// verify(mNetManager).addRoute(eq(wifiNetId), eq(WIFI_ROUTE_V4)); -// verify(mNetManager).addRoute(eq(wifiNetId), eq(WIFI_ROUTE_V6)); -// verify(mMobile.tracker).teardown(); -// -// int mobileNetId = mMobile.tracker.getNetwork().netId; -// -// reset(mNetManager, mMobile.tracker); -// -// // tear down mobile network, as requested -// mMobile.info.setDetailedState(DetailedState.DISCONNECTED, null, null); -// mMobile.link.clear(); -// mMobile.doReturnDefaults(); -// -// cv = waitForConnectivityBroadcasts(1); -// mTrackerHandler.obtainMessage(EVENT_STATE_CHANGED, mMobile.info).sendToTarget(); -// waitFor(cv); -// -// verify(mNetManager).removeRoute(eq(mobileNetId), eq(MOBILE_ROUTE_V4)); -// verify(mNetManager).removeRoute(eq(mobileNetId), eq(MOBILE_ROUTE_V6)); -// -// } - - private static InetAddress parse(String addr) { - return InetAddress.parseNumericAddress(addr); + private Network connectKeepaliveNetwork(LinkProperties lp) { + // Ensure the network is disconnected before we do anything. + if (mWiFiNetworkAgent != null) { + assertNull(mCm.getNetworkCapabilities(mWiFiNetworkAgent.getNetwork())); + } + + mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); + ConditionVariable cv = waitForConnectivityBroadcasts(1); + mWiFiNetworkAgent.connect(true); + waitFor(cv); + verifyActiveNetwork(TRANSPORT_WIFI); + mWiFiNetworkAgent.sendLinkProperties(lp); + mService.waitForIdle(); + return mWiFiNetworkAgent.getNetwork(); } + public void testPacketKeepalives() throws Exception { + InetAddress myIPv4 = InetAddress.getByName("192.0.2.129"); + InetAddress notMyIPv4 = InetAddress.getByName("192.0.2.35"); + InetAddress myIPv6 = InetAddress.getByName("2001:db8::1"); + InetAddress dstIPv4 = InetAddress.getByName("8.8.8.8"); + InetAddress dstIPv6 = InetAddress.getByName("2001:4860:4860::8888"); + + LinkProperties lp = new LinkProperties(); + lp.setInterfaceName("wlan12"); + lp.addLinkAddress(new LinkAddress(myIPv6, 64)); + lp.addLinkAddress(new LinkAddress(myIPv4, 25)); + lp.addRoute(new RouteInfo(InetAddress.getByName("fe80::1234"))); + lp.addRoute(new RouteInfo(InetAddress.getByName("192.0.2.254"))); + + Network notMyNet = new Network(61234); + Network myNet = connectKeepaliveNetwork(lp); + + TestKeepaliveCallback callback = new TestKeepaliveCallback(); + PacketKeepalive ka; + + // Attempt to start keepalives with invalid parameters and check for errors. + ka = mCm.startNattKeepalive(notMyNet, 25, callback, myIPv4, 1234, dstIPv4); + callback.expectError(PacketKeepalive.ERROR_INVALID_NETWORK); + + ka = mCm.startNattKeepalive(myNet, 19, callback, notMyIPv4, 1234, dstIPv4); + callback.expectError(PacketKeepalive.ERROR_INVALID_INTERVAL); + + ka = mCm.startNattKeepalive(myNet, 25, callback, myIPv4, 1234, dstIPv6); + callback.expectError(PacketKeepalive.ERROR_INVALID_IP_ADDRESS); + + ka = mCm.startNattKeepalive(myNet, 25, callback, myIPv6, 1234, dstIPv4); + callback.expectError(PacketKeepalive.ERROR_INVALID_IP_ADDRESS); + + ka = mCm.startNattKeepalive(myNet, 25, callback, myIPv6, 1234, dstIPv6); + callback.expectError(PacketKeepalive.ERROR_INVALID_IP_ADDRESS); // NAT-T is IPv4-only. + + ka = mCm.startNattKeepalive(myNet, 25, callback, myIPv4, 123456, dstIPv4); + callback.expectError(PacketKeepalive.ERROR_INVALID_PORT); + + ka = mCm.startNattKeepalive(myNet, 25, callback, myIPv4, 123456, dstIPv4); + callback.expectError(PacketKeepalive.ERROR_INVALID_PORT); + + ka = mCm.startNattKeepalive(myNet, 25, callback, myIPv4, 12345, dstIPv4); + callback.expectError(PacketKeepalive.ERROR_HARDWARE_UNSUPPORTED); + + ka = mCm.startNattKeepalive(myNet, 25, callback, myIPv4, 12345, dstIPv4); + callback.expectError(PacketKeepalive.ERROR_HARDWARE_UNSUPPORTED); + + // Check that a started keepalive can be stopped. + mWiFiNetworkAgent.setStartKeepaliveError(PacketKeepalive.SUCCESS); + ka = mCm.startNattKeepalive(myNet, 25, callback, myIPv4, 12345, dstIPv4); + callback.expectStarted(); + mWiFiNetworkAgent.setStopKeepaliveError(PacketKeepalive.SUCCESS); + ka.stop(); + callback.expectStopped(); + + // Check that deleting the IP address stops the keepalive. + LinkProperties bogusLp = new LinkProperties(lp); + ka = mCm.startNattKeepalive(myNet, 25, callback, myIPv4, 12345, dstIPv4); + callback.expectStarted(); + bogusLp.removeLinkAddress(new LinkAddress(myIPv4, 25)); + bogusLp.addLinkAddress(new LinkAddress(notMyIPv4, 25)); + mWiFiNetworkAgent.sendLinkProperties(bogusLp); + callback.expectError(PacketKeepalive.ERROR_INVALID_IP_ADDRESS); + mWiFiNetworkAgent.sendLinkProperties(lp); + + // Check that a started keepalive is stopped correctly when the network disconnects. + ka = mCm.startNattKeepalive(myNet, 25, callback, myIPv4, 12345, dstIPv4); + callback.expectStarted(); + mWiFiNetworkAgent.disconnect(); + callback.expectError(PacketKeepalive.ERROR_INVALID_NETWORK); + + // ... and that stopping it after that has no adverse effects. + assertNull(mCm.getNetworkCapabilities(myNet)); + ka.stop(); + + // Reconnect. + myNet = connectKeepaliveNetwork(lp); + mWiFiNetworkAgent.setStartKeepaliveError(PacketKeepalive.SUCCESS); + + // Check things work as expected when the keepalive is stopped and the network disconnects. + ka = mCm.startNattKeepalive(myNet, 25, callback, myIPv4, 12345, dstIPv4); + callback.expectStarted(); + ka.stop(); + mWiFiNetworkAgent.disconnect(); + mService.waitForIdle(); + callback.expectStopped(); + + // Reconnect. + myNet = connectKeepaliveNetwork(lp); + mWiFiNetworkAgent.setStartKeepaliveError(PacketKeepalive.SUCCESS); + + // Check that keepalive slots start from 1 and increment. The first one gets slot 1. + mWiFiNetworkAgent.setExpectedKeepaliveSlot(1); + ka = mCm.startNattKeepalive(myNet, 25, callback, myIPv4, 12345, dstIPv4); + callback.expectStarted(); + + // The second one gets slot 2. + mWiFiNetworkAgent.setExpectedKeepaliveSlot(2); + TestKeepaliveCallback callback2 = new TestKeepaliveCallback(); + PacketKeepalive ka2 = mCm.startNattKeepalive(myNet, 25, callback2, myIPv4, 6789, dstIPv4); + callback2.expectStarted(); + + // Now stop the first one and create a third. This also gets slot 1. + ka.stop(); + callback.expectStopped(); + + mWiFiNetworkAgent.setExpectedKeepaliveSlot(1); + TestKeepaliveCallback callback3 = new TestKeepaliveCallback(); + PacketKeepalive ka3 = mCm.startNattKeepalive(myNet, 25, callback3, myIPv4, 9876, dstIPv4); + callback3.expectStarted(); + + ka2.stop(); + callback2.expectStopped(); + + ka3.stop(); + callback3.expectStopped(); + } } diff --git a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java index 0f20dde..f9aa124 100644 --- a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java @@ -82,7 +82,7 @@ public class AccountManagerServiceTest extends AndroidTestCase { mAms.addAccountExplicitly(a31, "p31", null); mAms.addAccountExplicitly(a32, "p32", null); - Account[] accounts = mAms.getAccounts(null); + Account[] accounts = mAms.getAccounts(null, mContext.getOpPackageName()); Arrays.sort(accounts, new AccountSorter()); assertEquals(6, accounts.length); assertEquals(a11, accounts[0]); @@ -92,7 +92,7 @@ public class AccountManagerServiceTest extends AndroidTestCase { assertEquals(a22, accounts[4]); assertEquals(a32, accounts[5]); - accounts = mAms.getAccounts("type1" ); + accounts = mAms.getAccounts("type1", mContext.getOpPackageName()); Arrays.sort(accounts, new AccountSorter()); assertEquals(3, accounts.length); assertEquals(a11, accounts[0]); @@ -101,7 +101,7 @@ public class AccountManagerServiceTest extends AndroidTestCase { mAms.removeAccountInternal(a21); - accounts = mAms.getAccounts("type1" ); + accounts = mAms.getAccounts("type1", mContext.getOpPackageName()); Arrays.sort(accounts, new AccountSorter()); assertEquals(2, accounts.length); assertEquals(a11, accounts[0]); diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 5ed41c6..135a97a 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -267,6 +267,16 @@ public class CarrierConfigManager { "carrier_instant_lettering_available_bool"; /** + * When IMS instant lettering is available for a carrier (see + * {@link #KEY_CARRIER_INSTANT_LETTERING_AVAILABLE_BOOL}), determines the list of characters + * which may not be contained in messages. Should be specified as a regular expression suitable + * for use with {@link String#matches(String)}. + * @hide + */ + public static final String KEY_CARRIER_INSTANT_LETTERING_INVALID_CHARS_STRING = + "carrier_instant_lettering_invalid_chars_string"; + + /** * If Voice Radio Technology is RIL_RADIO_TECHNOLOGY_LTE:14 or RIL_RADIO_TECHNOLOGY_UNKNOWN:0 * this is the value that should be used instead. A configuration value of * RIL_RADIO_TECHNOLOGY_UNKNOWN:0 means there is no replacement value and that the default @@ -438,6 +448,7 @@ public class CarrierConfigManager { sDefaults.putBoolean(KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL, true); sDefaults.putBoolean(KEY_CARRIER_ALLOW_TURNOFF_IMS_BOOL, true); sDefaults.putBoolean(KEY_CARRIER_INSTANT_LETTERING_AVAILABLE_BOOL, false); + sDefaults.putString(KEY_CARRIER_INSTANT_LETTERING_INVALID_CHARS_STRING, ""); sDefaults.putBoolean(KEY_DISABLE_CDMA_ACTIVATION_CODE_BOOL, false); sDefaults.putBoolean(KEY_DTMF_TYPE_ENABLED_BOOL, false); sDefaults.putBoolean(KEY_ENABLE_DIALER_KEY_VIBRATION_BOOL, true); |