diff options
author | 2017-04-30 18:45:31 +0000 | |
---|---|---|
committer | 2017-04-30 18:45:31 +0000 | |
commit | 317c8634f5db2f0c94163a8063b87af206f5593a (patch) | |
tree | 97c161c91ee98f9431b123cd5d59a5071f834ccd | |
parent | 8a40a4ca2767ca2ffdeef8bf44a12e17933dd17f (diff) | |
parent | 63ca3e2414dc058548d32db60d60bf98addb6306 (diff) |
Merge changes I8d149ab0,Ia0a52819,If54a89cb,I20faa733,Ib32dfd23 into oc-dev am: 07ea44909f
am: 63ca3e2414
Change-Id: Ieb34996878d9689e2bd29590a302a95dd7d2d83c
-rw-r--r-- | api/current.txt | 16 | ||||
-rw-r--r-- | api/system-current.txt | 16 | ||||
-rw-r--r-- | api/test-current.txt | 16 | ||||
-rw-r--r-- | wifi/java/android/net/wifi/WifiManager.java | 239 | ||||
-rw-r--r-- | wifi/tests/src/android/net/wifi/WifiManagerTest.java | 761 |
5 files changed, 1035 insertions, 13 deletions
diff --git a/api/current.txt b/api/current.txt index c0e0a770287c..71e5c91cbdb0 100644 --- a/api/current.txt +++ b/api/current.txt @@ -26569,6 +26569,7 @@ package android.net.wifi { method public void setTdlsEnabled(java.net.InetAddress, boolean); method public void setTdlsEnabledWithMacAddress(java.lang.String, boolean); method public boolean setWifiEnabled(boolean); + method public void startLocalOnlyHotspot(android.net.wifi.WifiManager.LocalOnlyHotspotCallback, android.os.Handler); method public boolean startScan(); method public void startWps(android.net.wifi.WpsInfo, android.net.wifi.WifiManager.WpsCallback); method public int updateNetwork(android.net.wifi.WifiConfiguration); @@ -26619,6 +26620,21 @@ package android.net.wifi { field public static final int WPS_WEP_PROHIBITED = 4; // 0x4 } + public static class WifiManager.LocalOnlyHotspotCallback { + ctor public WifiManager.LocalOnlyHotspotCallback(); + method public void onFailed(int); + method public void onStarted(android.net.wifi.WifiManager.LocalOnlyHotspotReservation); + method public void onStopped(); + field public static final int ERROR_GENERIC = 2; // 0x2 + field public static final int ERROR_INCOMPATIBLE_MODE = 3; // 0x3 + field public static final int ERROR_NO_CHANNEL = 1; // 0x1 + } + + public class WifiManager.LocalOnlyHotspotReservation implements java.lang.AutoCloseable { + method public void close(); + method public android.net.wifi.WifiConfiguration getConfig(); + } + public class WifiManager.MulticastLock { method public void acquire(); method public boolean isHeld(); diff --git a/api/system-current.txt b/api/system-current.txt index 5faac780d0d0..5081583b4969 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -29151,6 +29151,7 @@ package android.net.wifi { method public boolean setWifiApConfiguration(android.net.wifi.WifiConfiguration); method public boolean setWifiApEnabled(android.net.wifi.WifiConfiguration, boolean); method public boolean setWifiEnabled(boolean); + method public void startLocalOnlyHotspot(android.net.wifi.WifiManager.LocalOnlyHotspotCallback, android.os.Handler); method public deprecated boolean startLocationRestrictedScan(android.os.WorkSource); method public boolean startScan(); method public boolean startScan(android.os.WorkSource); @@ -29228,6 +29229,21 @@ package android.net.wifi { method public abstract void onSuccess(); } + public static class WifiManager.LocalOnlyHotspotCallback { + ctor public WifiManager.LocalOnlyHotspotCallback(); + method public void onFailed(int); + method public void onStarted(android.net.wifi.WifiManager.LocalOnlyHotspotReservation); + method public void onStopped(); + field public static final int ERROR_GENERIC = 2; // 0x2 + field public static final int ERROR_INCOMPATIBLE_MODE = 3; // 0x3 + field public static final int ERROR_NO_CHANNEL = 1; // 0x1 + } + + public class WifiManager.LocalOnlyHotspotReservation implements java.lang.AutoCloseable { + method public void close(); + method public android.net.wifi.WifiConfiguration getConfig(); + } + public class WifiManager.MulticastLock { method public void acquire(); method public boolean isHeld(); diff --git a/api/test-current.txt b/api/test-current.txt index bfd722aaf092..35683bf185e2 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -26677,6 +26677,7 @@ package android.net.wifi { method public void setTdlsEnabled(java.net.InetAddress, boolean); method public void setTdlsEnabledWithMacAddress(java.lang.String, boolean); method public boolean setWifiEnabled(boolean); + method public void startLocalOnlyHotspot(android.net.wifi.WifiManager.LocalOnlyHotspotCallback, android.os.Handler); method public boolean startScan(); method public void startWps(android.net.wifi.WpsInfo, android.net.wifi.WifiManager.WpsCallback); method public int updateNetwork(android.net.wifi.WifiConfiguration); @@ -26727,6 +26728,21 @@ package android.net.wifi { field public static final int WPS_WEP_PROHIBITED = 4; // 0x4 } + public static class WifiManager.LocalOnlyHotspotCallback { + ctor public WifiManager.LocalOnlyHotspotCallback(); + method public void onFailed(int); + method public void onStarted(android.net.wifi.WifiManager.LocalOnlyHotspotReservation); + method public void onStopped(); + field public static final int ERROR_GENERIC = 2; // 0x2 + field public static final int ERROR_INCOMPATIBLE_MODE = 3; // 0x3 + field public static final int ERROR_NO_CHANNEL = 1; // 0x1 + } + + public class WifiManager.LocalOnlyHotspotReservation implements java.lang.AutoCloseable { + method public void close(); + method public android.net.wifi.WifiConfiguration getConfig(); + } + public class WifiManager.MulticastLock { method public void acquire(); method public boolean isHeld(); diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java index 7bf1d60d578b..0e1eb15ca9e5 100644 --- a/wifi/java/android/net/wifi/WifiManager.java +++ b/wifi/java/android/net/wifi/WifiManager.java @@ -40,6 +40,7 @@ import android.os.WorkSource; import android.util.Log; import android.util.SparseArray; +import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.AsyncChannel; import com.android.internal.util.Protocol; @@ -47,6 +48,7 @@ import com.android.server.net.NetworkPinner; import dalvik.system.CloseGuard; +import java.lang.ref.WeakReference; import java.net.InetAddress; import java.util.Collections; import java.util.List; @@ -851,6 +853,22 @@ public class WifiManager { private CountDownLatch mConnected; private Looper mLooper; + /* LocalOnlyHotspot callback message types */ + /** @hide */ + public static final int HOTSPOT_STARTED = 0; + /** @hide */ + public static final int HOTSPOT_STOPPED = 1; + /** @hide */ + public static final int HOTSPOT_FAILED = 2; + /** @hide */ + public static final int HOTSPOT_OBSERVER_REGISTERED = 3; + + private final Object mLock = new Object(); // lock guarding access to the following vars + @GuardedBy("mLock") + private LocalOnlyHotspotCallbackProxy mLOHSCallbackProxy; + @GuardedBy("mLock") + private LocalOnlyHotspotObserverProxy mLOHSObserverProxy; + /** * Create a new WifiManager instance. * Applications will almost always want to use @@ -1864,12 +1882,27 @@ public class WifiManager { * operating status. * @param handler Handler to be used for callbacks. If the caller passes a null Handler, the * main thread will be used. - * - * @hide */ public void startLocalOnlyHotspot(LocalOnlyHotspotCallback callback, @Nullable Handler handler) { - throw new UnsupportedOperationException("LocalOnlyHotspot is still in development"); + synchronized (mLock) { + Looper looper = (handler == null) ? mContext.getMainLooper() : handler.getLooper(); + LocalOnlyHotspotCallbackProxy proxy = + new LocalOnlyHotspotCallbackProxy(this, looper, callback); + try { + WifiConfiguration config = mService.startLocalOnlyHotspot( + proxy.getMessenger(), new Binder()); + if (config == null) { + // Send message to the proxy to make sure we call back on the correct thread + proxy.notifyFailed( + LocalOnlyHotspotCallback.ERROR_INCOMPATIBLE_MODE); + return; + } + mLOHSCallbackProxy = proxy; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } /** @@ -1886,7 +1919,9 @@ public class WifiManager { * @hide */ public void cancelLocalOnlyHotspotRequest() { - throw new UnsupportedOperationException("LocalOnlyHotspot is still in development"); + synchronized (mLock) { + stopLocalOnlyHotspot(); + } } /** @@ -1900,7 +1935,18 @@ public class WifiManager { * method on their LocalOnlyHotspotReservation. */ private void stopLocalOnlyHotspot() { - throw new UnsupportedOperationException("LocalOnlyHotspot is still in development"); + synchronized (mLock) { + if (mLOHSCallbackProxy == null) { + // nothing to do, the callback was already cleaned up. + return; + } + mLOHSCallbackProxy = null; + try { + mService.stopLocalOnlyHotspot(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } /** @@ -1922,7 +1968,18 @@ public class WifiManager { */ public void watchLocalOnlyHotspot(LocalOnlyHotspotObserver observer, @Nullable Handler handler) { - throw new UnsupportedOperationException("LocalOnlyHotspot is still in development"); + synchronized (mLock) { + Looper looper = (handler == null) ? mContext.getMainLooper() : handler.getLooper(); + mLOHSObserverProxy = new LocalOnlyHotspotObserverProxy(this, looper, observer); + try { + mService.startWatchLocalOnlyHotspot( + mLOHSObserverProxy.getMessenger(), new Binder()); + mLOHSObserverProxy.registered(); + } catch (RemoteException e) { + mLOHSObserverProxy = null; + throw e.rethrowFromSystemServer(); + } + } } /** @@ -1932,10 +1989,20 @@ public class WifiManager { * @hide */ public void unregisterLocalOnlyHotspotObserver() { - throw new UnsupportedOperationException("LocalOnlyHotspot is still in development"); + synchronized (mLock) { + if (mLOHSObserverProxy == null) { + // nothing to do, the callback was already cleaned up + return; + } + mLOHSObserverProxy = null; + try { + mService.stopWatchLocalOnlyHotspot(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } } - /** * Gets the Wi-Fi enabled state. * @return One of {@link #WIFI_AP_STATE_DISABLED}, @@ -2205,8 +2272,6 @@ public class WifiManager { * any further callbacks. If the LocalOnlyHotspot is stopped due to a * user triggered mode change, applications will be notified via the {@link * LocalOnlyHotspotCallback#onStopped()} callback. - * - * @hide */ public class LocalOnlyHotspotReservation implements AutoCloseable { @@ -2249,8 +2314,6 @@ public class WifiManager { /** * Callback class for applications to receive updates about the LocalOnlyHotspot status. - * - * @hide */ public static class LocalOnlyHotspotCallback { public static final int ERROR_NO_CHANNEL = 1; @@ -2283,6 +2346,88 @@ public class WifiManager { } /** + * Callback proxy for LocalOnlyHotspotCallback objects. + */ + private static class LocalOnlyHotspotCallbackProxy { + private final Handler mHandler; + private final WeakReference<WifiManager> mWifiManager; + private final Looper mLooper; + private final Messenger mMessenger; + + /** + * Constructs a {@link LocalOnlyHotspotCallback} using the specified looper. All callbacks + * will be delivered on the thread of the specified looper. + * + * @param manager WifiManager + * @param looper Looper for delivering callbacks + * @param callback LocalOnlyHotspotCallback to notify the calling application. + */ + LocalOnlyHotspotCallbackProxy(WifiManager manager, Looper looper, + final LocalOnlyHotspotCallback callback) { + mWifiManager = new WeakReference<>(manager); + mLooper = looper; + + mHandler = new Handler(looper) { + @Override + public void handleMessage(Message msg) { + Log.d(TAG, "LocalOnlyHotspotCallbackProxy: handle message what: " + + msg.what + " msg: " + msg); + + WifiManager manager = mWifiManager.get(); + if (manager == null) { + Log.w(TAG, "LocalOnlyHotspotCallbackProxy: handle message post GC"); + return; + } + + switch (msg.what) { + case HOTSPOT_STARTED: + WifiConfiguration config = (WifiConfiguration) msg.obj; + if (config == null) { + Log.e(TAG, "LocalOnlyHotspotCallbackProxy: config cannot be null."); + callback.onFailed(LocalOnlyHotspotCallback.ERROR_GENERIC); + return; + } + callback.onStarted(manager.new LocalOnlyHotspotReservation(config)); + break; + case HOTSPOT_STOPPED: + Log.w(TAG, "LocalOnlyHotspotCallbackProxy: hotspot stopped"); + callback.onStopped(); + break; + case HOTSPOT_FAILED: + int reasonCode = msg.arg1; + Log.w(TAG, "LocalOnlyHotspotCallbackProxy: failed to start. reason: " + + reasonCode); + callback.onFailed(reasonCode); + Log.w(TAG, "done with the callback..."); + break; + default: + Log.e(TAG, "LocalOnlyHotspotCallbackProxy unhandled message. type: " + + msg.what); + } + } + }; + mMessenger = new Messenger(mHandler); + } + + public Messenger getMessenger() { + return mMessenger; + } + + /** + * Helper method allowing the the incoming application call to move the onFailed callback + * over to the desired callback thread. + * + * @param reason int representing the error type + */ + public void notifyFailed(int reason) throws RemoteException { + Message msg = Message.obtain(); + msg.what = HOTSPOT_FAILED; + msg.arg1 = reason; + mMessenger.send(msg); + } + } + + /** * LocalOnlyHotspotSubscription that is an AutoCloseable object for tracking applications * watching for LocalOnlyHotspot changes. * @@ -2291,7 +2436,6 @@ public class WifiManager { public class LocalOnlyHotspotSubscription implements AutoCloseable { private final CloseGuard mCloseGuard = CloseGuard.get(); - /** @hide */ @VisibleForTesting public LocalOnlyHotspotSubscription() { mCloseGuard.open("close"); @@ -2343,6 +2487,75 @@ public class WifiManager { public void onStopped() {}; } + /** + * Callback proxy for LocalOnlyHotspotObserver objects. + */ + private static class LocalOnlyHotspotObserverProxy { + private final Handler mHandler; + private final WeakReference<WifiManager> mWifiManager; + private final Looper mLooper; + private final Messenger mMessenger; + + /** + * Constructs a {@link LocalOnlyHotspotObserverProxy} using the specified looper. + * All callbacks will be delivered on the thread of the specified looper. + * + * @param manager WifiManager + * @param looper Looper for delivering callbacks + * @param observer LocalOnlyHotspotObserver to notify the calling application. + */ + LocalOnlyHotspotObserverProxy(WifiManager manager, Looper looper, + final LocalOnlyHotspotObserver observer) { + mWifiManager = new WeakReference<>(manager); + mLooper = looper; + + mHandler = new Handler(looper) { + @Override + public void handleMessage(Message msg) { + Log.d(TAG, "LocalOnlyHotspotObserverProxy: handle message what: " + + msg.what + " msg: " + msg); + + WifiManager manager = mWifiManager.get(); + if (manager == null) { + Log.w(TAG, "LocalOnlyHotspotObserverProxy: handle message post GC"); + return; + } + + switch (msg.what) { + case HOTSPOT_OBSERVER_REGISTERED: + observer.onRegistered(manager.new LocalOnlyHotspotSubscription()); + break; + case HOTSPOT_STARTED: + WifiConfiguration config = (WifiConfiguration) msg.obj; + if (config == null) { + Log.e(TAG, "LocalOnlyHotspotObserverProxy: config cannot be null."); + return; + } + observer.onStarted(config); + break; + case HOTSPOT_STOPPED: + observer.onStopped(); + break; + default: + Log.e(TAG, "LocalOnlyHotspotObserverProxy unhandled message. type: " + + msg.what); + } + } + }; + mMessenger = new Messenger(mHandler); + } + + public Messenger getMessenger() { + return mMessenger; + } + + public void registered() throws RemoteException { + Message msg = Message.obtain(); + msg.what = HOTSPOT_OBSERVER_REGISTERED; + mMessenger.send(msg); + } + } + // Ensure that multiple ServiceHandler threads do not interleave message dispatch. private static final Object sServiceHandlerDispatchLock = new Object(); diff --git a/wifi/tests/src/android/net/wifi/WifiManagerTest.java b/wifi/tests/src/android/net/wifi/WifiManagerTest.java new file mode 100644 index 000000000000..3c0fc6ef0aa8 --- /dev/null +++ b/wifi/tests/src/android/net/wifi/WifiManagerTest.java @@ -0,0 +1,761 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net.wifi; + +import static android.net.wifi.WifiManager.HOTSPOT_FAILED; +import static android.net.wifi.WifiManager.HOTSPOT_STARTED; +import static android.net.wifi.WifiManager.HOTSPOT_STOPPED; +import static android.net.wifi.WifiManager.LocalOnlyHotspotCallback.ERROR_GENERIC; +import static android.net.wifi.WifiManager.LocalOnlyHotspotCallback.ERROR_INCOMPATIBLE_MODE; +import static android.net.wifi.WifiManager.LocalOnlyHotspotCallback.ERROR_NO_CHANNEL; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.*; + +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.net.wifi.WifiManager.LocalOnlyHotspotCallback; +import android.net.wifi.WifiManager.LocalOnlyHotspotObserver; +import android.net.wifi.WifiManager.LocalOnlyHotspotReservation; +import android.net.wifi.WifiManager.LocalOnlyHotspotSubscription; +import android.os.Handler; +import android.os.IBinder; +import android.os.Message; +import android.os.Messenger; +import android.os.test.TestLooper; +import android.test.suitebuilder.annotation.SmallTest; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Unit tests for {@link android.net.wifi.WifiManager}. + */ +@SmallTest +public class WifiManagerTest { + + private static final int ERROR_NOT_SET = -1; + private static final int ERROR_TEST_REASON = 5; + + @Mock Context mContext; + @Mock IWifiManager mWifiService; + @Mock ApplicationInfo mApplicationInfo; + @Mock WifiConfiguration mApConfig; + @Mock IBinder mAppBinder; + + private Handler mHandler; + private TestLooper mLooper; + private WifiManager mWifiManager; + private Messenger mWifiServiceMessenger; + final ArgumentCaptor<Messenger> mMessengerCaptor = ArgumentCaptor.forClass(Messenger.class); + + @Before public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + mLooper = new TestLooper(); + mHandler = spy(new Handler(mLooper.getLooper())); + when(mContext.getApplicationInfo()).thenReturn(mApplicationInfo); + + mWifiServiceMessenger = new Messenger(mHandler); + mWifiManager = new WifiManager(mContext, mWifiService, mLooper.getLooper()); + } + + /** + * Check the call to startSoftAp calls WifiService to startSoftAp with the provided + * WifiConfiguration. Verify that the return value is propagated to the caller. + */ + @Test + public void testStartSoftApCallsServiceWithWifiConfig() throws Exception { + when(mWifiService.startSoftAp(eq(mApConfig))).thenReturn(true); + assertTrue(mWifiManager.startSoftAp(mApConfig)); + + when(mWifiService.startSoftAp(eq(mApConfig))).thenReturn(false); + assertFalse(mWifiManager.startSoftAp(mApConfig)); + } + + /** + * Check the call to startSoftAp calls WifiService to startSoftAp with a null config. Verify + * that the return value is propagated to the caller. + */ + @Test + public void testStartSoftApCallsServiceWithNullConfig() throws Exception { + when(mWifiService.startSoftAp(eq(null))).thenReturn(true); + assertTrue(mWifiManager.startSoftAp(null)); + + when(mWifiService.startSoftAp(eq(null))).thenReturn(false); + assertFalse(mWifiManager.startSoftAp(null)); + } + + /** + * Check the call to stopSoftAp calls WifiService to stopSoftAp. + */ + @Test + public void testStopSoftApCallsService() throws Exception { + when(mWifiService.stopSoftAp()).thenReturn(true); + assertTrue(mWifiManager.stopSoftAp()); + + when(mWifiService.stopSoftAp()).thenReturn(false); + assertFalse(mWifiManager.stopSoftAp()); + } + + /** + * Test creation of a LocalOnlyHotspotReservation and verify that close properly calls + * WifiService.stopLocalOnlyHotspot. + */ + @Test + public void testCreationAndCloseOfLocalOnlyHotspotReservation() throws Exception { + TestLocalOnlyHotspotCallback callback = new TestLocalOnlyHotspotCallback(); + when(mWifiService.startLocalOnlyHotspot(any(Messenger.class), any(IBinder.class))) + .thenReturn(mApConfig); + mWifiManager.startLocalOnlyHotspot(callback, mHandler); + + callback.onStarted(mWifiManager.new LocalOnlyHotspotReservation(mApConfig)); + + assertEquals(mApConfig, callback.mRes.getConfig()); + callback.mRes.close(); + verify(mWifiService).stopLocalOnlyHotspot(); + } + + /** + * Verify stopLOHS is called when try-with-resources is used properly. + */ + @Test + public void testLocalOnlyHotspotReservationCallsStopProperlyInTryWithResources() + throws Exception { + TestLocalOnlyHotspotCallback callback = new TestLocalOnlyHotspotCallback(); + when(mWifiService.startLocalOnlyHotspot(any(Messenger.class), any(IBinder.class))) + .thenReturn(mApConfig); + mWifiManager.startLocalOnlyHotspot(callback, mHandler); + + callback.onStarted(mWifiManager.new LocalOnlyHotspotReservation(mApConfig)); + + try (WifiManager.LocalOnlyHotspotReservation res = callback.mRes) { + assertEquals(mApConfig, res.getConfig()); + } + + verify(mWifiService).stopLocalOnlyHotspot(); + } + + /** + * Test creation of a LocalOnlyHotspotSubscription. + * TODO: when registrations are tracked, verify removal on close. + */ + @Test + public void testCreationOfLocalOnlyHotspotSubscription() throws Exception { + try (WifiManager.LocalOnlyHotspotSubscription sub = + mWifiManager.new LocalOnlyHotspotSubscription()) { + sub.close(); + } + } + + public class TestLocalOnlyHotspotCallback extends LocalOnlyHotspotCallback { + public boolean mOnStartedCalled = false; + public boolean mOnStoppedCalled = false; + public int mFailureReason = -1; + public LocalOnlyHotspotReservation mRes = null; + public long mCallingThreadId = -1; + + @Override + public void onStarted(LocalOnlyHotspotReservation r) { + mRes = r; + mOnStartedCalled = true; + mCallingThreadId = Thread.currentThread().getId(); + } + + @Override + public void onStopped() { + mOnStoppedCalled = true; + mCallingThreadId = Thread.currentThread().getId(); + } + + @Override + public void onFailed(int reason) { + mFailureReason = reason; + mCallingThreadId = Thread.currentThread().getId(); + } + } + + /** + * Verify callback is properly plumbed when called. + */ + @Test + public void testLocalOnlyHotspotCallback() { + TestLocalOnlyHotspotCallback callback = new TestLocalOnlyHotspotCallback(); + assertFalse(callback.mOnStartedCalled); + assertFalse(callback.mOnStoppedCalled); + assertEquals(ERROR_NOT_SET, callback.mFailureReason); + assertEquals(null, callback.mRes); + + // test onStarted + WifiManager.LocalOnlyHotspotReservation res = + mWifiManager.new LocalOnlyHotspotReservation(mApConfig); + callback.onStarted(res); + assertEquals(res, callback.mRes); + assertTrue(callback.mOnStartedCalled); + assertFalse(callback.mOnStoppedCalled); + assertEquals(ERROR_NOT_SET, callback.mFailureReason); + + // test onStopped + callback.onStopped(); + assertEquals(res, callback.mRes); + assertTrue(callback.mOnStartedCalled); + assertTrue(callback.mOnStoppedCalled); + assertEquals(ERROR_NOT_SET, callback.mFailureReason); + + // test onFailed + callback.onFailed(ERROR_TEST_REASON); + assertEquals(res, callback.mRes); + assertTrue(callback.mOnStartedCalled); + assertTrue(callback.mOnStoppedCalled); + assertEquals(ERROR_TEST_REASON, callback.mFailureReason); + } + + public class TestLocalOnlyHotspotObserver extends LocalOnlyHotspotObserver { + public boolean mOnRegistered = false; + public boolean mOnStartedCalled = false; + public boolean mOnStoppedCalled = false; + public WifiConfiguration mConfig = null; + public LocalOnlyHotspotSubscription mSub = null; + public long mCallingThreadId = -1; + + @Override + public void onRegistered(LocalOnlyHotspotSubscription sub) { + mOnRegistered = true; + mSub = sub; + mCallingThreadId = Thread.currentThread().getId(); + } + + @Override + public void onStarted(WifiConfiguration config) { + mOnStartedCalled = true; + mConfig = config; + mCallingThreadId = Thread.currentThread().getId(); + } + + @Override + public void onStopped() { + mOnStoppedCalled = true; + mCallingThreadId = Thread.currentThread().getId(); + } + } + + /** + * Verify observer is properly plumbed when called. + */ + @Test + public void testLocalOnlyHotspotObserver() { + TestLocalOnlyHotspotObserver observer = new TestLocalOnlyHotspotObserver(); + assertFalse(observer.mOnRegistered); + assertFalse(observer.mOnStartedCalled); + assertFalse(observer.mOnStoppedCalled); + assertEquals(null, observer.mConfig); + assertEquals(null, observer.mSub); + + WifiManager.LocalOnlyHotspotSubscription sub = + mWifiManager.new LocalOnlyHotspotSubscription(); + observer.onRegistered(sub); + assertTrue(observer.mOnRegistered); + assertFalse(observer.mOnStartedCalled); + assertFalse(observer.mOnStoppedCalled); + assertEquals(null, observer.mConfig); + assertEquals(sub, observer.mSub); + + observer.onStarted(mApConfig); + assertTrue(observer.mOnRegistered); + assertTrue(observer.mOnStartedCalled); + assertFalse(observer.mOnStoppedCalled); + assertEquals(mApConfig, observer.mConfig); + assertEquals(sub, observer.mSub); + + observer.onStopped(); + assertTrue(observer.mOnRegistered); + assertTrue(observer.mOnStartedCalled); + assertTrue(observer.mOnStoppedCalled); + assertEquals(mApConfig, observer.mConfig); + assertEquals(sub, observer.mSub); + } + + /** + * Verify call to startLocalOnlyHotspot goes to WifiServiceImpl. + */ + @Test + public void testStartLocalOnlyHotspot() throws Exception { + TestLocalOnlyHotspotCallback callback = new TestLocalOnlyHotspotCallback(); + mWifiManager.startLocalOnlyHotspot(callback, mHandler); + + verify(mWifiService).startLocalOnlyHotspot(any(Messenger.class), any(IBinder.class)); + } + + /** + * Verify a SecurityException is thrown for callers without proper permissions for + * startLocalOnlyHotspot. + */ + @Test(expected = SecurityException.class) + public void testStartLocalOnlyHotspotThrowsSecurityException() throws Exception { + TestLocalOnlyHotspotCallback callback = new TestLocalOnlyHotspotCallback(); + doThrow(new SecurityException()).when(mWifiService) + .startLocalOnlyHotspot(any(Messenger.class), any(IBinder.class)); + mWifiManager.startLocalOnlyHotspot(callback, mHandler); + } + + /** + * Verify an IllegalStateException is thrown for callers that already have a pending request for + * startLocalOnlyHotspot. + */ + @Test(expected = IllegalStateException.class) + public void testStartLocalOnlyHotspotThrowsIllegalStateException() throws Exception { + TestLocalOnlyHotspotCallback callback = new TestLocalOnlyHotspotCallback(); + doThrow(new IllegalStateException()).when(mWifiService) + .startLocalOnlyHotspot(any(Messenger.class), any(IBinder.class)); + mWifiManager.startLocalOnlyHotspot(callback, mHandler); + } + + /** + * Verify that the handler provided by the caller is used for the callbacks. + */ + @Test + public void testCorrectLooperIsUsedForHandler() throws Exception { + // record thread from looper.getThread and check ids. + TestLocalOnlyHotspotCallback callback = new TestLocalOnlyHotspotCallback(); + when(mWifiService.startLocalOnlyHotspot(any(Messenger.class), any(IBinder.class))) + .thenReturn(null); + mWifiManager.startLocalOnlyHotspot(callback, mHandler); + mLooper.dispatchAll(); + assertEquals(ERROR_INCOMPATIBLE_MODE, callback.mFailureReason); + assertEquals(mLooper.getLooper().getThread().getId(), callback.mCallingThreadId); + } + + /** + * Verify that the main looper's thread is used if a handler is not provided by the reqiestomg + * application. + */ + @Test + public void testMainLooperIsUsedWhenHandlerNotProvided() throws Exception { + // record thread from looper.getThread and check ids. + TestLooper altLooper = new TestLooper(); + when(mContext.getMainLooper()).thenReturn(altLooper.getLooper()); + TestLocalOnlyHotspotCallback callback = new TestLocalOnlyHotspotCallback(); + when(mWifiService.startLocalOnlyHotspot(any(Messenger.class), any(IBinder.class))) + .thenReturn(null); + mWifiManager.startLocalOnlyHotspot(callback, null); + altLooper.dispatchAll(); + assertEquals(ERROR_INCOMPATIBLE_MODE, callback.mFailureReason); + assertEquals(altLooper.getLooper().getThread().getId(), callback.mCallingThreadId); + } + + /** + * Verify the LOHS onStarted callback is triggered when WifiManager receives a HOTSPOT_STARTED + * message from WifiServiceImpl. + */ + @Test + public void testOnStartedIsCalledWithReservation() throws Exception { + TestLocalOnlyHotspotCallback callback = new TestLocalOnlyHotspotCallback(); + TestLooper callbackLooper = new TestLooper(); + Handler callbackHandler = new Handler(callbackLooper.getLooper()); + when(mWifiService.startLocalOnlyHotspot(mMessengerCaptor.capture(), + any(IBinder.class))).thenReturn(mApConfig); + mWifiManager.startLocalOnlyHotspot(callback, callbackHandler); + callbackLooper.dispatchAll(); + mLooper.dispatchAll(); + assertFalse(callback.mOnStartedCalled); + assertEquals(null, callback.mRes); + // now trigger the callback + Message msg = new Message(); + msg.what = HOTSPOT_STARTED; + msg.obj = mApConfig; + mMessengerCaptor.getValue().send(msg); + mLooper.dispatchAll(); + callbackLooper.dispatchAll(); + assertTrue(callback.mOnStartedCalled); + assertEquals(mApConfig, callback.mRes.getConfig()); + } + + /** + * Verify onFailed is called if WifiServiceImpl sends a HOTSPOT_STARTED message with a null + * config. + */ + @Test + public void testOnStartedIsCalledWithNullConfig() throws Exception { + TestLocalOnlyHotspotCallback callback = new TestLocalOnlyHotspotCallback(); + TestLooper callbackLooper = new TestLooper(); + Handler callbackHandler = new Handler(callbackLooper.getLooper()); + when(mWifiService.startLocalOnlyHotspot(mMessengerCaptor.capture(), + any(IBinder.class))).thenReturn(mApConfig); + mWifiManager.startLocalOnlyHotspot(callback, callbackHandler); + callbackLooper.dispatchAll(); + mLooper.dispatchAll(); + assertFalse(callback.mOnStartedCalled); + assertEquals(null, callback.mRes); + // now trigger the callback + Message msg = new Message(); + msg.what = HOTSPOT_STARTED; + mMessengerCaptor.getValue().send(msg); + mLooper.dispatchAll(); + callbackLooper.dispatchAll(); + assertFalse(callback.mOnStartedCalled); + assertEquals(ERROR_GENERIC, callback.mFailureReason); + } + + /** + * Verify onStopped is called if WifiServiceImpl sends a HOTSPOT_STOPPED message. + */ + @Test + public void testOnStoppedIsCalled() throws Exception { + TestLocalOnlyHotspotCallback callback = new TestLocalOnlyHotspotCallback(); + TestLooper callbackLooper = new TestLooper(); + Handler callbackHandler = new Handler(callbackLooper.getLooper()); + when(mWifiService.startLocalOnlyHotspot(mMessengerCaptor.capture(), + any(IBinder.class))).thenReturn(mApConfig); + mWifiManager.startLocalOnlyHotspot(callback, callbackHandler); + callbackLooper.dispatchAll(); + mLooper.dispatchAll(); + assertFalse(callback.mOnStoppedCalled); + // now trigger the callback + Message msg = new Message(); + msg.what = HOTSPOT_STOPPED; + mMessengerCaptor.getValue().send(msg); + mLooper.dispatchAll(); + callbackLooper.dispatchAll(); + assertTrue(callback.mOnStoppedCalled); + } + + /** + * Verify onFailed is called if WifiServiceImpl sends a HOTSPOT_FAILED message. + */ + @Test + public void testOnFailedIsCalled() throws Exception { + TestLocalOnlyHotspotCallback callback = new TestLocalOnlyHotspotCallback(); + TestLooper callbackLooper = new TestLooper(); + Handler callbackHandler = new Handler(callbackLooper.getLooper()); + when(mWifiService.startLocalOnlyHotspot(mMessengerCaptor.capture(), + any(IBinder.class))).thenReturn(mApConfig); + mWifiManager.startLocalOnlyHotspot(callback, callbackHandler); + callbackLooper.dispatchAll(); + mLooper.dispatchAll(); + assertEquals(ERROR_NOT_SET, callback.mFailureReason); + // now trigger the callback + Message msg = new Message(); + msg.what = HOTSPOT_FAILED; + msg.arg1 = ERROR_NO_CHANNEL; + mMessengerCaptor.getValue().send(msg); + mLooper.dispatchAll(); + callbackLooper.dispatchAll(); + assertEquals(ERROR_NO_CHANNEL, callback.mFailureReason); + } + + /** + * Verify the handler passed in to startLocalOnlyHotspot is correctly used for callbacks when a + * null WifiConfig is returned. + */ + @Test + public void testLocalOnlyHotspotCallbackFullOnNullConfig() throws Exception { + TestLocalOnlyHotspotCallback callback = new TestLocalOnlyHotspotCallback(); + when(mWifiService.startLocalOnlyHotspot(any(Messenger.class), any(IBinder.class))) + .thenReturn(null); + mWifiManager.startLocalOnlyHotspot(callback, mHandler); + mLooper.dispatchAll(); + assertEquals(ERROR_INCOMPATIBLE_MODE, callback.mFailureReason); + assertFalse(callback.mOnStartedCalled); + assertFalse(callback.mOnStoppedCalled); + assertEquals(null, callback.mRes); + } + + /** + * Verify a SecurityException resulting from an application without necessary permissions will + * bubble up through the call to start LocalOnlyHotspot and will not trigger other callbacks. + */ + @Test(expected = SecurityException.class) + public void testLocalOnlyHotspotCallbackFullOnSecurityException() throws Exception { + TestLocalOnlyHotspotCallback callback = new TestLocalOnlyHotspotCallback(); + doThrow(new SecurityException()).when(mWifiService) + .startLocalOnlyHotspot(any(Messenger.class), any(IBinder.class)); + try { + mWifiManager.startLocalOnlyHotspot(callback, mHandler); + } catch (SecurityException e) { + assertEquals(ERROR_NOT_SET, callback.mFailureReason); + assertFalse(callback.mOnStartedCalled); + assertFalse(callback.mOnStoppedCalled); + assertEquals(null, callback.mRes); + throw e; + } + + } + + /** + * Verify the handler passed to startLocalOnlyHotspot is correctly used for callbacks when + * SoftApMode fails due to a underlying error. + */ + @Test + public void testLocalOnlyHotspotCallbackFullOnNoChannelError() throws Exception { + TestLocalOnlyHotspotCallback callback = new TestLocalOnlyHotspotCallback(); + when(mWifiService.startLocalOnlyHotspot(any(Messenger.class), any(IBinder.class))) + .thenReturn(mApConfig); + mWifiManager.startLocalOnlyHotspot(callback, mHandler); + mLooper.dispatchAll(); + //assertEquals(ERROR_NO_CHANNEL, callback.mFailureReason); + assertFalse(callback.mOnStartedCalled); + assertFalse(callback.mOnStoppedCalled); + assertEquals(null, callback.mRes); + } + + /** + * Verify that the call to cancel a LOHS request does call stopLOHS. + */ + @Test + public void testCancelLocalOnlyHotspotRequestCallsStopOnWifiService() throws Exception { + TestLocalOnlyHotspotCallback callback = new TestLocalOnlyHotspotCallback(); + when(mWifiService.startLocalOnlyHotspot(any(Messenger.class), any(IBinder.class))) + .thenReturn(mApConfig); + mWifiManager.startLocalOnlyHotspot(callback, mHandler); + mWifiManager.cancelLocalOnlyHotspotRequest(); + verify(mWifiService).stopLocalOnlyHotspot(); + } + + /** + * Verify that we do not crash if cancelLocalOnlyHotspotRequest is called without an existing + * callback stored. + */ + @Test + public void testCancelLocalOnlyHotspotReturnsWithoutExistingRequest() { + mWifiManager.cancelLocalOnlyHotspotRequest(); + } + + /** + * Verify that the callback is not triggered if the LOHS request was already cancelled. + */ + @Test + public void testCallbackAfterLocalOnlyHotspotWasCancelled() throws Exception { + TestLocalOnlyHotspotCallback callback = new TestLocalOnlyHotspotCallback(); + when(mWifiService.startLocalOnlyHotspot(any(Messenger.class), any(IBinder.class))) + .thenReturn(mApConfig); + mWifiManager.startLocalOnlyHotspot(callback, mHandler); + mWifiManager.cancelLocalOnlyHotspotRequest(); + verify(mWifiService).stopLocalOnlyHotspot(); + mLooper.dispatchAll(); + assertEquals(ERROR_NOT_SET, callback.mFailureReason); + assertFalse(callback.mOnStartedCalled); + assertFalse(callback.mOnStoppedCalled); + assertEquals(null, callback.mRes); + } + + /** + * Verify that calling cancel LOHS request does not crash if an error callback was already + * handled. + */ + @Test + public void testCancelAfterLocalOnlyHotspotCallbackTriggered() throws Exception { + TestLocalOnlyHotspotCallback callback = new TestLocalOnlyHotspotCallback(); + when(mWifiService.startLocalOnlyHotspot(any(Messenger.class), any(IBinder.class))) + .thenReturn(null); + mWifiManager.startLocalOnlyHotspot(callback, mHandler); + mLooper.dispatchAll(); + assertEquals(ERROR_INCOMPATIBLE_MODE, callback.mFailureReason); + assertFalse(callback.mOnStartedCalled); + assertFalse(callback.mOnStoppedCalled); + assertEquals(null, callback.mRes); + mWifiManager.cancelLocalOnlyHotspotRequest(); + verify(mWifiService, never()).stopLocalOnlyHotspot(); + } + + /** + * Verify the watchLocalOnlyHotspot call goes to WifiServiceImpl. + */ + public void testWatchLocalOnlyHotspot() throws Exception { + TestLocalOnlyHotspotObserver observer = new TestLocalOnlyHotspotObserver(); + + mWifiManager.watchLocalOnlyHotspot(observer, mHandler); + verify(mWifiService).startWatchLocalOnlyHotspot(any(Messenger.class), any(IBinder.class)); + } + + /** + * Verify a SecurityException is thrown for callers without proper permissions for + * startWatchLocalOnlyHotspot. + */ + @Test(expected = SecurityException.class) + public void testStartWatchLocalOnlyHotspotThrowsSecurityException() throws Exception { + TestLocalOnlyHotspotObserver observer = new TestLocalOnlyHotspotObserver(); + doThrow(new SecurityException()).when(mWifiService) + .startWatchLocalOnlyHotspot(any(Messenger.class), any(IBinder.class)); + mWifiManager.watchLocalOnlyHotspot(observer, mHandler); + } + + /** + * Verify an IllegalStateException is thrown for callers that already have a pending request for + * watchLocalOnlyHotspot. + */ + @Test(expected = IllegalStateException.class) + public void testStartWatchLocalOnlyHotspotThrowsIllegalStateException() throws Exception { + TestLocalOnlyHotspotObserver observer = new TestLocalOnlyHotspotObserver(); + doThrow(new IllegalStateException()).when(mWifiService) + .startWatchLocalOnlyHotspot(any(Messenger.class), any(IBinder.class)); + mWifiManager.watchLocalOnlyHotspot(observer, mHandler); + } + + /** + * Verify that the handler provided by the caller is used for the observer. + */ + @Test + public void testCorrectLooperIsUsedForObserverHandler() throws Exception { + // record thread from looper.getThread and check ids. + TestLocalOnlyHotspotObserver observer = new TestLocalOnlyHotspotObserver(); + mWifiManager.watchLocalOnlyHotspot(observer, mHandler); + mLooper.dispatchAll(); + assertTrue(observer.mOnRegistered); + assertEquals(mLooper.getLooper().getThread().getId(), observer.mCallingThreadId); + } + + /** + * Verify that the main looper's thread is used if a handler is not provided by the requesting + * application. + */ + @Test + public void testMainLooperIsUsedWhenHandlerNotProvidedForObserver() throws Exception { + // record thread from looper.getThread and check ids. + TestLooper altLooper = new TestLooper(); + when(mContext.getMainLooper()).thenReturn(altLooper.getLooper()); + TestLocalOnlyHotspotObserver observer = new TestLocalOnlyHotspotObserver(); + mWifiManager.watchLocalOnlyHotspot(observer, null); + altLooper.dispatchAll(); + assertTrue(observer.mOnRegistered); + assertEquals(altLooper.getLooper().getThread().getId(), observer.mCallingThreadId); + } + + /** + * Verify the LOHS onRegistered observer callback is triggered when WifiManager receives a + * HOTSPOT_OBSERVER_REGISTERED message from WifiServiceImpl. + */ + @Test + public void testOnRegisteredIsCalledWithSubscription() throws Exception { + TestLocalOnlyHotspotObserver observer = new TestLocalOnlyHotspotObserver(); + TestLooper observerLooper = new TestLooper(); + Handler observerHandler = new Handler(observerLooper.getLooper()); + assertFalse(observer.mOnRegistered); + assertEquals(null, observer.mSub); + mWifiManager.watchLocalOnlyHotspot(observer, observerHandler); + verify(mWifiService).startWatchLocalOnlyHotspot(mMessengerCaptor.capture(), + any(IBinder.class)); + // now trigger the callback + observerLooper.dispatchAll(); + mLooper.dispatchAll(); + assertTrue(observer.mOnRegistered); + assertNotNull(observer.mSub); + } + + /** + * Verify the LOHS onStarted observer callback is triggered when WifiManager receives a + * HOTSPOT_STARTED message from WifiServiceImpl. + */ + @Test + public void testObserverOnStartedIsCalledWithWifiConfig() throws Exception { + TestLocalOnlyHotspotObserver observer = new TestLocalOnlyHotspotObserver(); + TestLooper observerLooper = new TestLooper(); + Handler observerHandler = new Handler(observerLooper.getLooper()); + mWifiManager.watchLocalOnlyHotspot(observer, observerHandler); + verify(mWifiService).startWatchLocalOnlyHotspot(mMessengerCaptor.capture(), + any(IBinder.class)); + observerLooper.dispatchAll(); + mLooper.dispatchAll(); + assertFalse(observer.mOnStartedCalled); + // now trigger the callback + Message msg = new Message(); + msg.what = HOTSPOT_STARTED; + msg.obj = mApConfig; + mMessengerCaptor.getValue().send(msg); + mLooper.dispatchAll(); + observerLooper.dispatchAll(); + assertTrue(observer.mOnStartedCalled); + assertEquals(mApConfig, observer.mConfig); + } + + /** + * Verify the LOHS onStarted observer callback is triggered not when WifiManager receives a + * HOTSPOT_STARTED message from WifiServiceImpl with a null config. + */ + @Test + public void testObserverOnStartedNotCalledWithNullConfig() throws Exception { + TestLocalOnlyHotspotObserver observer = new TestLocalOnlyHotspotObserver(); + TestLooper observerLooper = new TestLooper(); + Handler observerHandler = new Handler(observerLooper.getLooper()); + mWifiManager.watchLocalOnlyHotspot(observer, observerHandler); + verify(mWifiService).startWatchLocalOnlyHotspot(mMessengerCaptor.capture(), + any(IBinder.class)); + observerLooper.dispatchAll(); + mLooper.dispatchAll(); + assertFalse(observer.mOnStartedCalled); + // now trigger the callback + Message msg = new Message(); + msg.what = HOTSPOT_STARTED; + mMessengerCaptor.getValue().send(msg); + mLooper.dispatchAll(); + observerLooper.dispatchAll(); + assertFalse(observer.mOnStartedCalled); + assertEquals(null, observer.mConfig); + } + + + /** + * Verify the LOHS onStopped observer callback is triggered when WifiManager receives a + * HOTSPOT_STOPPED message from WifiServiceImpl. + */ + @Test + public void testObserverOnStoppedIsCalled() throws Exception { + TestLocalOnlyHotspotObserver observer = new TestLocalOnlyHotspotObserver(); + TestLooper observerLooper = new TestLooper(); + Handler observerHandler = new Handler(observerLooper.getLooper()); + mWifiManager.watchLocalOnlyHotspot(observer, observerHandler); + verify(mWifiService).startWatchLocalOnlyHotspot(mMessengerCaptor.capture(), + any(IBinder.class)); + observerLooper.dispatchAll(); + mLooper.dispatchAll(); + assertFalse(observer.mOnStoppedCalled); + // now trigger the callback + Message msg = new Message(); + msg.what = HOTSPOT_STOPPED; + mMessengerCaptor.getValue().send(msg); + mLooper.dispatchAll(); + observerLooper.dispatchAll(); + assertTrue(observer.mOnStoppedCalled); + } + + /** + * Verify WifiServiceImpl is not called if there is not a registered LOHS observer callback. + */ + @Test + public void testUnregisterWifiServiceImplNotCalledWithoutRegisteredObserver() throws Exception { + mWifiManager.unregisterLocalOnlyHotspotObserver(); + verifyZeroInteractions(mWifiService); + } + + /** + * Verify WifiServiceImpl is called when there is a registered LOHS observer callback. + */ + @Test + public void testUnregisterWifiServiceImplCalledWithRegisteredObserver() throws Exception { + TestLocalOnlyHotspotObserver observer = new TestLocalOnlyHotspotObserver(); + TestLooper observerLooper = new TestLooper(); + Handler observerHandler = new Handler(observerLooper.getLooper()); + mWifiManager.watchLocalOnlyHotspot(observer, observerHandler); + mWifiManager.unregisterLocalOnlyHotspotObserver(); + verify(mWifiService).stopWatchLocalOnlyHotspot(); + } + +} |