1 /* 2 * Copyright (C) 2020 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 android.telecom.cts; 18 19 import static android.telecom.cts.TestUtils.WAIT_FOR_STATE_CHANGE_TIMEOUT_MS; 20 21 import static com.android.compatibility.common.util.ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn; 22 23 import android.app.AppOpsManager; 24 import android.content.ComponentName; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.content.ServiceConnection; 28 import android.content.pm.PackageManager; 29 import android.net.Uri; 30 import android.os.Bundle; 31 import android.os.IBinder; 32 import android.os.RemoteException; 33 import android.telecom.Call; 34 import android.telecom.TelecomManager; 35 import android.telecom.cts.thirdptyincallservice.CtsThirdPartyInCallService; 36 import android.telecom.cts.thirdptyincallservice.CtsThirdPartyInCallServiceControl; 37 import android.telecom.cts.thirdptyincallservice.ICtsThirdPartyInCallServiceControl; 38 import android.util.Log; 39 40 import com.android.compatibility.common.util.ApiTest; 41 42 import java.util.concurrent.CountDownLatch; 43 import java.util.concurrent.TimeUnit; 44 45 public class ThirdPartyInCallServiceAppOpsPermissionTest extends BaseTelecomTestWithMockServices { 46 47 private static final String TAG = ThirdPartyInCallServiceAppOpsPermissionTest 48 .class.getSimpleName(); 49 private static final String THIRD_PARITY_PACKAGE_NAME = CtsThirdPartyInCallService 50 .class.getPackage().getName(); 51 private static final Uri TEST_URI = Uri.parse("tel:555-TEST"); 52 private static final String TEST_KEY = "woowoo"; 53 private static final String TEST_VALUE = "yay"; 54 private Context mContext; 55 private AppOpsManager mAppOpsManager; 56 private PackageManager mPackageManager; 57 private TelecomManager mTelecomManager; 58 ICtsThirdPartyInCallServiceControl mICtsThirdPartyInCallServiceControl; 59 private boolean mExpectedTearDownBindingStatus; 60 61 @Override setUp()62 public void setUp() throws Exception { 63 super.setUp(); 64 if (!mShouldTestTelecom) { 65 return; 66 } 67 mContext = getInstrumentation().getContext(); 68 mAppOpsManager = mContext.getSystemService(AppOpsManager.class); 69 mPackageManager = mContext.getPackageManager(); 70 mTelecomManager = mContext.getSystemService(TelecomManager.class); 71 setupConnectionService(null, FLAG_REGISTER | FLAG_ENABLE); 72 setUpControl(); 73 mICtsThirdPartyInCallServiceControl.resetLatchForServiceBound(false); 74 mExpectedTearDownBindingStatus = true; 75 } 76 77 @Override tearDown()78 public void tearDown() throws Exception { 79 resetRemoteCalls(); 80 super.tearDown(); 81 if (mShouldTestTelecom && mExpectedTearDownBindingStatus) { 82 // A bind status of false (unbound) requires tearDown() to run to tear down the 83 // ConnectionService. If tearDown generates an exception, this assert will also fail, 84 // so it is safe after tearDown. 85 assertBindStatus(/* true: bind, false: unbind */false, /* expected result */true); 86 } 87 } 88 testWithoutAppOpsPermission()89 public void testWithoutAppOpsPermission() throws Exception { 90 if (!mShouldTestTelecom) { 91 return; 92 } 93 setInCallServiceAppOpsPermission(false); 94 int previousCallCount = mICtsThirdPartyInCallServiceControl.getLocalCallCount(); 95 addAndVerifyNewIncomingCall(TEST_URI, null); 96 assertBindStatus(/* true: bind, false: unbind */true, /* expected result */false); 97 assertCallCount(previousCallCount); 98 // Third Party InCallService hasn't been bound yet, unbound latch can be null when tearDown. 99 mExpectedTearDownBindingStatus = false; 100 } 101 testWithAppOpsPermission()102 public void testWithAppOpsPermission() throws Exception { 103 if (!mShouldTestTelecom) { 104 return; 105 } 106 // Grant App Ops Permission 107 setInCallServiceAppOpsPermission(true); 108 109 int previousCallCount = mICtsThirdPartyInCallServiceControl.getLocalCallCount(); 110 addAndVerifyNewIncomingCall(TEST_URI, null); 111 assertBindStatus(/* true: bind, false: unbind */true, /* expected result */true); 112 assertCallCount(previousCallCount + 1); 113 mICtsThirdPartyInCallServiceControl.resetLatchForServiceBound(true); 114 115 // Revoke App Ops Permission 116 setInCallServiceAppOpsPermission(false); 117 } 118 119 /** 120 * Verifies that {@link android.telecom.Call#putExtras(Bundle)} changes made in one 121 * {@link android.telecom.InCallService} instance will be seen in other running 122 * {@link android.telecom.InCallService} instances. 123 * @throws Exception 124 */ 125 @ApiTest(apis = {"android.telecom.Call#putExtras", 126 "android.telecom.Call.Callback#onDetailsChanged"}) testExtrasPropagation()127 public void testExtrasPropagation() throws Exception { 128 if (!mShouldTestTelecom) { 129 return; 130 } 131 // Grant App Ops Permission 132 setInCallServiceAppOpsPermission(true); 133 try { 134 // Make a new call. 135 int previousCallCount = mICtsThirdPartyInCallServiceControl.getLocalCallCount(); 136 addAndVerifyNewIncomingCall(TEST_URI, null); 137 assertBindStatus(/* true: bind, false: unbind */true, /* expected result */true); 138 assertCallCount(previousCallCount + 1); 139 android.telecom.Call call = mInCallCallbacks.getService().getLastCall(); 140 141 // Make it active 142 final MockConnection connection = verifyConnectionForIncomingCall(); 143 connection.setActive(); 144 assertCallState(call, Call.STATE_ACTIVE); 145 146 // Prime the controlled other ICS to expect some known extras. 147 mICtsThirdPartyInCallServiceControl.setExpectedExtra(TEST_KEY, TEST_VALUE); 148 149 // From the main ICS in the CTS test runner, we'll add an extra. 150 Bundle newExtras = new Bundle(); 151 newExtras.putString(TEST_KEY, TEST_VALUE); 152 call.putExtras(newExtras); 153 154 // Now wait for the other ICS to have received that extra. 155 assertTrue(mICtsThirdPartyInCallServiceControl.waitUntilExpectedExtrasReceived()); 156 } finally { 157 mICtsThirdPartyInCallServiceControl.resetLatchForServiceBound(true); 158 // Revoke App Ops Permission 159 setInCallServiceAppOpsPermission(false); 160 } 161 } 162 163 /** 164 * 165 * @param bind: check the status of InCallService bind latches. 166 * Values: true (bound latch), false (unbound latch). 167 * @param success: whether the latch should have counted down. 168 */ assertBindStatus(boolean bind, boolean success)169 private void assertBindStatus(boolean bind, boolean success) { 170 waitUntilConditionIsTrueOrTimeout(new Condition() { 171 @Override 172 public Object expected() { 173 return success; 174 } 175 176 @Override 177 public Object actual() { 178 try { 179 return mICtsThirdPartyInCallServiceControl.checkBindStatus(bind); 180 } catch (RemoteException re) { 181 Log.e(TAG, "Remote exception when checking bind status: " + re); 182 return false; 183 } 184 } 185 }, WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, "Unable to " + (bind ? "Bind" : "Unbind") 186 + " third party in call service"); 187 } 188 resetRemoteCalls()189 private void resetRemoteCalls() { 190 if (mICtsThirdPartyInCallServiceControl != null) { 191 try { 192 mICtsThirdPartyInCallServiceControl.resetCalls(); 193 } catch (Exception e) { 194 Log.w(TAG, "resetRemoteCalls ran into an exception: " + e); 195 } 196 } 197 } 198 assertCallCount(int expected)199 private void assertCallCount(int expected) { 200 waitUntilConditionIsTrueOrTimeout(new Condition() { 201 @Override 202 public Object expected() { 203 return expected; 204 } 205 206 @Override 207 public Object actual() { 208 try { 209 return mICtsThirdPartyInCallServiceControl.getLocalCallCount(); 210 } catch (RemoteException re) { 211 Log.e(TAG, "Remote exception when getting local call count: " + re); 212 return -1; 213 } 214 } 215 }, WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, 216 "Failed to match localCallCount and expected: " + expected); 217 } 218 setUpControl()219 private void setUpControl() throws InterruptedException { 220 Intent bindIntent = new Intent(CtsThirdPartyInCallServiceControl.CONTROL_INTERFACE_ACTION); 221 // mContext is android.telecom.cts, which doesn't include thirdptyincallservice. 222 ComponentName controlComponentName = 223 ComponentName.createRelative( 224 CtsThirdPartyInCallServiceControl.class.getPackage().getName(), 225 CtsThirdPartyInCallServiceControl.class.getName()); 226 227 bindIntent.setComponent(controlComponentName); 228 final CountDownLatch bindLatch = new CountDownLatch(1); 229 boolean success = mContext.bindService(bindIntent, new ServiceConnection() { 230 @Override 231 public void onServiceConnected(ComponentName name, IBinder service) { 232 Log.i(TAG, "Service Connected: " + name); 233 mICtsThirdPartyInCallServiceControl = 234 ICtsThirdPartyInCallServiceControl.Stub.asInterface(service); 235 bindLatch.countDown(); 236 } 237 238 @Override 239 public void onServiceDisconnected(ComponentName name) { 240 mICtsThirdPartyInCallServiceControl = null; 241 } 242 }, Context.BIND_AUTO_CREATE); 243 if (!success) { 244 fail("Failed to get control interface -- bind error"); 245 } 246 bindLatch.await(WAIT_FOR_STATE_CHANGE_TIMEOUT_MS, TimeUnit.MILLISECONDS); 247 } 248 setInCallServiceAppOpsPermission(boolean allow)249 private void setInCallServiceAppOpsPermission(boolean allow) 250 throws PackageManager.NameNotFoundException { 251 int uid = mPackageManager.getApplicationInfo(THIRD_PARITY_PACKAGE_NAME, 0).uid; 252 invokeMethodWithShellPermissionsNoReturn(mAppOpsManager, 253 (appOpsMan) -> appOpsMan.setUidMode(AppOpsManager.OPSTR_MANAGE_ONGOING_CALLS, 254 uid, allow ? AppOpsManager.MODE_ALLOWED : AppOpsManager.opToDefaultMode( 255 AppOpsManager.OPSTR_MANAGE_ONGOING_CALLS))); 256 } 257 } 258