1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.bluetooth.mapclient; 18 19 import static org.mockito.Mockito.*; 20 21 import android.bluetooth.BluetoothAdapter; 22 import android.bluetooth.BluetoothDevice; 23 import android.bluetooth.BluetoothProfile; 24 import android.bluetooth.SdpMasRecord; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.os.Handler; 28 import android.os.Looper; 29 import android.os.Message; 30 import android.support.test.InstrumentationRegistry; 31 import android.support.test.filters.MediumTest; 32 import android.support.test.filters.Suppress; 33 import android.support.test.runner.AndroidJUnit4; 34 import android.util.Log; 35 36 import com.android.bluetooth.R; 37 38 import org.junit.After; 39 import org.junit.Assert; 40 import org.junit.Assume; 41 import org.junit.Before; 42 import org.junit.Test; 43 import org.junit.runner.RunWith; 44 import org.mockito.Mock; 45 import org.mockito.MockitoAnnotations; 46 47 import java.util.concurrent.CountDownLatch; 48 import java.util.concurrent.TimeUnit; 49 50 @Suppress // TODO: enable when b/74609188 is debugged 51 @MediumTest 52 @RunWith(AndroidJUnit4.class) 53 public class MapClientStateMachineTest { 54 private static final String TAG = "MapStateMachineTest"; 55 private static final Integer TIMEOUT = 3000; 56 57 private BluetoothAdapter mAdapter; 58 private MceStateMachine mMceStateMachine = null; 59 private BluetoothDevice mTestDevice; 60 private Context mTargetContext; 61 62 private FakeMapClientService mFakeMapClientService; 63 private CountDownLatch mConnectedLatch = null; 64 private CountDownLatch mDisconnectedLatch = null; 65 private Handler mHandler; 66 67 @Mock 68 private MasClient mMockMasClient; 69 70 71 @Before 72 public void setUp() { 73 MockitoAnnotations.initMocks(this); 74 mTargetContext = InstrumentationRegistry.getTargetContext(); 75 Assume.assumeTrue("Ignore test when MapClientService is not enabled", 76 mTargetContext.getResources().getBoolean(R.bool.profile_supported_mapmce)); 77 78 // This line must be called to make sure relevant objects are initialized properly 79 mAdapter = BluetoothAdapter.getDefaultAdapter(); 80 // Get a device for testing 81 mTestDevice = mAdapter.getRemoteDevice("00:01:02:03:04:05"); 82 83 mConnectedLatch = new CountDownLatch(1); 84 mDisconnectedLatch = new CountDownLatch(1); 85 mFakeMapClientService = new FakeMapClientService(); 86 when(mMockMasClient.makeRequest(any(Request.class))).thenReturn(true); 87 mMceStateMachine = new MceStateMachine(mFakeMapClientService, mTestDevice, mMockMasClient); 88 Assert.assertNotNull(mMceStateMachine); 89 if (Looper.myLooper() == null) { 90 Looper.prepare(); 91 } 92 mHandler = new Handler(); 93 } 94 95 @After 96 public void tearDown() { 97 if (!mTargetContext.getResources().getBoolean(R.bool.profile_supported_mapmce)) { 98 return; 99 } 100 if (mMceStateMachine != null) { 101 mMceStateMachine.doQuit(); 102 } 103 } 104 105 /** 106 * Test that default state is STATE_CONNECTING 107 */ 108 @Test 109 public void testDefaultConnectingState() { 110 Log.i(TAG, "in testDefaultConnectingState"); 111 Assert.assertEquals(BluetoothProfile.STATE_CONNECTING, mMceStateMachine.getState()); 112 } 113 114 /** 115 * Test transition from 116 * STATE_CONNECTING --> (receive MSG_MAS_DISCONNECTED) --> STATE_DISCONNECTED 117 */ 118 @Test 119 public void testStateTransitionFromConnectingToDisconnected() { 120 Log.i(TAG, "in testStateTransitionFromConnectingToDisconnected"); 121 setupSdpRecordReceipt(); 122 Message msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_DISCONNECTED); 123 mMceStateMachine.getCurrentState().processMessage(msg); 124 125 // Wait until the message is processed and a broadcast request is sent to 126 // to MapClientService to change 127 // state from STATE_CONNECTING to STATE_DISCONNECTED 128 boolean result = false; 129 try { 130 result = mDisconnectedLatch.await(TIMEOUT, TimeUnit.MILLISECONDS); 131 } catch (InterruptedException e) { 132 e.printStackTrace(); 133 } 134 // Test that the latch reached zero; i.e., that a broadcast of state-change was received. 135 Assert.assertTrue(result); 136 // When the state reaches STATE_DISCONNECTED, MceStateMachine object is in the process of 137 // being dismantled; i.e., can't rely on getting its current state. That means can't 138 // test its current state = STATE_DISCONNECTED. 139 } 140 141 /** 142 * Test transition from STATE_CONNECTING --> (receive MSG_MAS_CONNECTED) --> STATE_CONNECTED 143 */ 144 @Test 145 public void testStateTransitionFromConnectingToConnected() { 146 Log.i(TAG, "in testStateTransitionFromConnectingToConnected"); 147 148 setupSdpRecordReceipt(); 149 Message msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_CONNECTED); 150 mMceStateMachine.getCurrentState().processMessage(msg); 151 152 // Wait until the message is processed and a broadcast request is sent to 153 // to MapClientService to change 154 // state from STATE_CONNECTING to STATE_CONNECTED 155 boolean result = false; 156 try { 157 result = mConnectedLatch.await(TIMEOUT, TimeUnit.MILLISECONDS); 158 } catch (InterruptedException e) { 159 e.printStackTrace(); 160 } 161 // Test that the latch reached zero; i.e., that a broadcast of state-change was received. 162 Assert.assertTrue(result); 163 Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, mMceStateMachine.getState()); 164 } 165 166 private void setupSdpRecordReceipt() { 167 // Setup receipt of SDP record 168 SdpMasRecord record = new SdpMasRecord(1, 1, 1, 1, 1, 1, "blah"); 169 Message msg = Message.obtain(mHandler, MceStateMachine.MSG_MAS_SDP_DONE, record); 170 mMceStateMachine.getCurrentState().processMessage(msg); 171 } 172 173 private class FakeMapClientService extends MapClientService { 174 @Override 175 void cleanupDevice(BluetoothDevice device) {} 176 @Override 177 public void sendBroadcast(Intent intent, String receiverPermission) { 178 int prevState = intent.getIntExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, -1); 179 int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1); 180 Log.i(TAG, "received broadcast: prevState = " + prevState 181 + ", state = " + state); 182 if (prevState == BluetoothProfile.STATE_CONNECTING 183 && state == BluetoothProfile.STATE_CONNECTED) { 184 mConnectedLatch.countDown(); 185 } else if (prevState == BluetoothProfile.STATE_CONNECTING 186 && state == BluetoothProfile.STATE_DISCONNECTED) { 187 mDisconnectedLatch.countDown(); 188 } 189 } 190 } 191 } 192