blob: a9bbf8e81b7007f0f7c4bcd73dfce4399c9749a4 [file] [log] [blame]
Santos Cordonb64c1502014-05-21 21:21:49 -07001/*
2 * Copyright (C) 2014 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
17package com.android.telecomm;
18
Santos Cordon46592f02014-07-07 15:11:35 -070019import android.app.AppOpsManager;
Yorke Leeceb96c92014-06-11 16:34:44 -070020import android.content.ComponentName;
Ihab Awad98a55602014-06-30 21:27:28 -070021import android.content.Context;
Yorke Leeceb96c92014-06-11 16:34:44 -070022import android.content.res.Resources;
Santos Cordon46592f02014-07-07 15:11:35 -070023import android.os.Binder;
Santos Cordonb64c1502014-05-21 21:21:49 -070024import android.os.Handler;
Santos Cordon23baed32014-06-27 14:45:39 -070025import android.os.Looper;
Santos Cordonb64c1502014-05-21 21:21:49 -070026import android.os.Message;
27import android.os.ServiceManager;
Santos Cordon46592f02014-07-07 15:11:35 -070028import android.phone.PhoneManager;
Santos Cordon23baed32014-06-27 14:45:39 -070029import android.telecomm.CallState;
Ihab Awad98a55602014-06-30 21:27:28 -070030import android.telecomm.PhoneAccount;
Ihab Awad502e5fe2014-07-09 12:34:48 -070031import android.telecomm.PhoneAccountMetadata;
Santos Cordon46592f02014-07-07 15:11:35 -070032import android.telecomm.TelecommManager;
33import android.telephony.TelephonyManager;
34
35import com.android.internal.telecomm.ITelecommService;
Santos Cordonb64c1502014-05-21 21:21:49 -070036
Ihab Awad60509462014-06-14 16:43:08 -070037import java.util.List;
Santos Cordonb64c1502014-05-21 21:21:49 -070038
39/**
40 * Implementation of the ITelecomm interface.
41 */
42public class TelecommServiceImpl extends ITelecommService.Stub {
Santos Cordon23baed32014-06-27 14:45:39 -070043 /**
44 * A request object for use with {@link MainThreadHandler}. Requesters should wait() on the
45 * request after sending. The main thread will notify the request when it is complete.
46 */
47 private static final class MainThreadRequest {
48 /** The result of the request that is run on the main thread */
49 public Object result;
50 }
Santos Cordonb64c1502014-05-21 21:21:49 -070051
52 /**
53 * A handler that processes messages on the main thread in the phone process. Since many
54 * of the Phone calls are not thread safe this is needed to shuttle the requests from the
55 * inbound binder threads to the main thread in the phone process.
56 */
Santos Cordon23baed32014-06-27 14:45:39 -070057 private final class MainThreadHandler extends Handler {
Santos Cordonb64c1502014-05-21 21:21:49 -070058 @Override
59 public void handleMessage(Message msg) {
Santos Cordon23baed32014-06-27 14:45:39 -070060 if (msg.obj instanceof MainThreadRequest) {
61 MainThreadRequest request = (MainThreadRequest) msg.obj;
62 Object result = null;
63 switch (msg.what) {
64 case MSG_SILENCE_RINGER:
65 mCallsManager.getRinger().silence();
66 break;
67 case MSG_SHOW_CALL_SCREEN:
68 mCallsManager.getInCallController().bringToForeground(msg.arg1 == 1);
69 break;
Santos Cordon23baed32014-06-27 14:45:39 -070070 case MSG_END_CALL:
71 result = endCallInternal();
72 break;
73 case MSG_ACCEPT_RINGING_CALL:
74 acceptRingingCallInternal();
75 break;
Santos Cordon64c7e962014-07-02 15:15:27 -070076 case MSG_CANCEL_MISSED_CALLS_NOTIFICATION:
77 mMissedCallNotifier.clearMissedCalls();
78 break;
Sailesh Nepalb88795a2014-07-15 14:53:27 -070079 case MSG_IS_TTY_SUPPORTED:
80 result = mCallsManager.isTtySupported();
81 break;
82 case MSG_GET_CURRENT_TTY_MODE:
83 result = mCallsManager.getCurrentTtyMode();
84 break;
Santos Cordon23baed32014-06-27 14:45:39 -070085 }
86
87 if (result != null) {
88 request.result = result;
89 synchronized(request) {
90 request.notifyAll();
91 }
92 }
Santos Cordonb64c1502014-05-21 21:21:49 -070093 }
94 }
Santos Cordon23baed32014-06-27 14:45:39 -070095 }
Santos Cordonb64c1502014-05-21 21:21:49 -070096
Santos Cordon7da72ef2014-06-25 15:50:22 -070097 /** Private constructor; @see init() */
Santos Cordon23baed32014-06-27 14:45:39 -070098 private static final String TAG = TelecommServiceImpl.class.getSimpleName();
99
100 private static final String SERVICE_NAME = "telecomm";
101
102 private static final int MSG_SILENCE_RINGER = 1;
103 private static final int MSG_SHOW_CALL_SCREEN = 2;
Santos Cordon626697a2014-07-10 22:36:37 -0700104 private static final int MSG_END_CALL = 3;
105 private static final int MSG_ACCEPT_RINGING_CALL = 4;
106 private static final int MSG_CANCEL_MISSED_CALLS_NOTIFICATION = 5;
Sailesh Nepalb88795a2014-07-15 14:53:27 -0700107 private static final int MSG_IS_TTY_SUPPORTED = 6;
108 private static final int MSG_GET_CURRENT_TTY_MODE = 7;
Santos Cordon23baed32014-06-27 14:45:39 -0700109
110 /** The singleton instance. */
111 private static TelecommServiceImpl sInstance;
112
Santos Cordon46592f02014-07-07 15:11:35 -0700113 private final MainThreadHandler mMainThreadHandler = new MainThreadHandler();
Santos Cordon23baed32014-06-27 14:45:39 -0700114 private final CallsManager mCallsManager = CallsManager.getInstance();
Santos Cordon64c7e962014-07-02 15:15:27 -0700115 private final MissedCallNotifier mMissedCallNotifier;
Santos Cordon176ae282014-07-14 02:02:14 -0700116 private final PhoneAccountRegistrar mPhoneAccountRegistrar;
Santos Cordon46592f02014-07-07 15:11:35 -0700117 private final AppOpsManager mAppOpsManager;
Santos Cordon23baed32014-06-27 14:45:39 -0700118
Santos Cordon176ae282014-07-14 02:02:14 -0700119 private TelecommServiceImpl(
120 MissedCallNotifier missedCallNotifier, PhoneAccountRegistrar phoneAccountRegistrar) {
Santos Cordon64c7e962014-07-02 15:15:27 -0700121 mMissedCallNotifier = missedCallNotifier;
Santos Cordon176ae282014-07-14 02:02:14 -0700122 mPhoneAccountRegistrar = phoneAccountRegistrar;
Santos Cordon46592f02014-07-07 15:11:35 -0700123 mAppOpsManager =
124 (AppOpsManager) TelecommApp.getInstance().getSystemService(Context.APP_OPS_SERVICE);
125
Santos Cordon7da72ef2014-06-25 15:50:22 -0700126 publish();
127 }
128
Santos Cordonb64c1502014-05-21 21:21:49 -0700129 /**
130 * Initialize the singleton TelecommServiceImpl instance.
131 * This is only done once, at startup, from TelecommApp.onCreate().
132 */
Santos Cordon176ae282014-07-14 02:02:14 -0700133 static TelecommServiceImpl init(
134 MissedCallNotifier missedCallNotifier, PhoneAccountRegistrar phoneAccountRegistrar) {
Santos Cordonb64c1502014-05-21 21:21:49 -0700135 synchronized (TelecommServiceImpl.class) {
136 if (sInstance == null) {
Santos Cordon176ae282014-07-14 02:02:14 -0700137 sInstance = new TelecommServiceImpl(missedCallNotifier, phoneAccountRegistrar);
Santos Cordonb64c1502014-05-21 21:21:49 -0700138 } else {
139 Log.wtf(TAG, "init() called multiple times! sInstance %s", sInstance);
140 }
141 return sInstance;
142 }
143 }
144
Santos Cordonb64c1502014-05-21 21:21:49 -0700145 //
Santos Cordon23baed32014-06-27 14:45:39 -0700146 // Implementation of the ITelecommService interface.
Santos Cordonb64c1502014-05-21 21:21:49 -0700147 //
148
Santos Cordon7da72ef2014-06-25 15:50:22 -0700149 @Override
Ihab Awad104f8062014-07-17 11:29:35 -0700150 public PhoneAccount getDefaultOutgoingPhoneAccount() {
151 try {
152 return mPhoneAccountRegistrar.getDefaultOutgoingPhoneAccount();
153 } catch (Exception e) {
154 Log.e(this, e, "getDefaultOutgoingPhoneAccount");
155 throw e;
156 }
157 }
158
159 @Override
Ihab Awad502e5fe2014-07-09 12:34:48 -0700160 public List<PhoneAccount> getEnabledPhoneAccounts() {
Ihab Awad104f8062014-07-17 11:29:35 -0700161 try {
162 return mPhoneAccountRegistrar.getEnabledPhoneAccounts();
163 } catch (Exception e) {
164 Log.e(this, e, "getEnabledPhoneAccounts");
165 throw e;
166 }
Santos Cordon7da72ef2014-06-25 15:50:22 -0700167 }
168
169 @Override
Ihab Awad502e5fe2014-07-09 12:34:48 -0700170 public PhoneAccountMetadata getPhoneAccountMetadata(PhoneAccount account) {
Ihab Awad104f8062014-07-17 11:29:35 -0700171 try {
172 return mPhoneAccountRegistrar.getPhoneAccountMetadata(account);
173 } catch (Exception e) {
174 Log.e(this, e, "getPhoneAccountMetadata %s", account);
175 throw e;
Santos Cordon176ae282014-07-14 02:02:14 -0700176 }
Ihab Awad502e5fe2014-07-09 12:34:48 -0700177 }
178
179 @Override
Ihab Awad104f8062014-07-17 11:29:35 -0700180 public void registerPhoneAccount(PhoneAccountMetadata metadata) {
181 try {
182 enforceModifyPermissionOrCallingPackage(
183 metadata.getAccount().getComponentName().getPackageName());
184 mPhoneAccountRegistrar.registerPhoneAccount(metadata);
185 } catch (Exception e) {
186 Log.e(this, e, "registerPhoneAccount %s", metadata);
187 throw e;
188 }
Ihab Awad502e5fe2014-07-09 12:34:48 -0700189 }
190
191 @Override
192 public void unregisterPhoneAccount(PhoneAccount account) {
Ihab Awad104f8062014-07-17 11:29:35 -0700193 try {
194 enforceModifyPermissionOrCallingPackage(account.getComponentName().getPackageName());
195 mPhoneAccountRegistrar.unregisterPhoneAccount(account);
196 } catch (Exception e) {
197 Log.e(this, e, "unregisterPhoneAccount %s", account);
198 throw e;
199 }
Ihab Awad502e5fe2014-07-09 12:34:48 -0700200 }
201
202 @Override
203 public void clearAccounts(String packageName) {
Ihab Awad104f8062014-07-17 11:29:35 -0700204 try {
205 enforceModifyPermissionOrCallingPackage(packageName);
206 mPhoneAccountRegistrar.clearAccounts(packageName);
207 } catch (Exception e) {
208 Log.e(this, e, "clearAccounts %s", packageName);
209 throw e;
210 }
Santos Cordon7da72ef2014-06-25 15:50:22 -0700211 }
212
Santos Cordon23baed32014-06-27 14:45:39 -0700213 /**
Santos Cordon176ae282014-07-14 02:02:14 -0700214 * @see TelecommManager#silenceRinger
Santos Cordon23baed32014-06-27 14:45:39 -0700215 */
Santos Cordon7da72ef2014-06-25 15:50:22 -0700216 @Override
Santos Cordonb64c1502014-05-21 21:21:49 -0700217 public void silenceRinger() {
218 Log.d(this, "silenceRinger");
Santos Cordonb64c1502014-05-21 21:21:49 -0700219 enforceModifyPermission();
Santos Cordon23baed32014-06-27 14:45:39 -0700220 sendRequestAsync(MSG_SILENCE_RINGER, 0);
Santos Cordonb64c1502014-05-21 21:21:49 -0700221 }
222
Santos Cordon23baed32014-06-27 14:45:39 -0700223 /**
224 * @see TelecommManager#getDefaultPhoneApp
225 */
Santos Cordon7da72ef2014-06-25 15:50:22 -0700226 @Override
227 public ComponentName getDefaultPhoneApp() {
228 Resources resources = TelecommApp.getInstance().getResources();
229 return new ComponentName(
230 resources.getString(R.string.ui_default_package),
231 resources.getString(R.string.dialer_default_class));
232 }
233
Santos Cordon23baed32014-06-27 14:45:39 -0700234 /**
235 * @see TelecommManager#isInAPhoneCall
236 */
237 @Override
238 public boolean isInAPhoneCall() {
239 enforceReadPermission();
Santos Cordon626697a2014-07-10 22:36:37 -0700240 // Do not use sendRequest() with this method since it could cause a deadlock with
241 // audio service, which we call into from the main thread: AudioManager.setMode().
242 return mCallsManager.hasAnyCalls();
Santos Cordon23baed32014-06-27 14:45:39 -0700243 }
244
245 /**
246 * @see TelecommManager#isRinging
247 */
248 @Override
249 public boolean isRinging() {
250 enforceReadPermission();
Santos Cordon626697a2014-07-10 22:36:37 -0700251 return mCallsManager.hasRingingCall();
Santos Cordon23baed32014-06-27 14:45:39 -0700252 }
253
254 /**
255 * @see TelecommManager#endCall
256 */
257 @Override
258 public boolean endCall() {
259 enforceModifyPermission();
260 return (boolean) sendRequest(MSG_END_CALL);
261 }
262
263 /**
264 * @see TelecommManager#acceptRingingCall
265 */
266 @Override
267 public void acceptRingingCall() {
268 enforceModifyPermission();
269 sendRequestAsync(MSG_ACCEPT_RINGING_CALL, 0);
270 }
271
Santos Cordon46592f02014-07-07 15:11:35 -0700272 /**
273 * @see PhoneManager#showCallScreen
274 */
Santos Cordon23baed32014-06-27 14:45:39 -0700275 @Override
276 public void showCallScreen(boolean showDialpad) {
Santos Cordon46592f02014-07-07 15:11:35 -0700277 enforceReadPermissionOrDefaultDialer();
Santos Cordon64c7e962014-07-02 15:15:27 -0700278 sendRequestAsync(MSG_SHOW_CALL_SCREEN, showDialpad ? 1 : 0);
279 }
280
Santos Cordon46592f02014-07-07 15:11:35 -0700281 /**
282 * @see PhoneManager#cancelMissedCallsNotification
283 */
Santos Cordon64c7e962014-07-02 15:15:27 -0700284 @Override
285 public void cancelMissedCallsNotification() {
Santos Cordon46592f02014-07-07 15:11:35 -0700286 enforceModifyPermissionOrDefaultDialer();
Santos Cordon64c7e962014-07-02 15:15:27 -0700287 sendRequestAsync(MSG_CANCEL_MISSED_CALLS_NOTIFICATION, 0);
Santos Cordon23baed32014-06-27 14:45:39 -0700288 }
289
Santos Cordon46592f02014-07-07 15:11:35 -0700290 /**
291 * @see PhoneManager#handlePinMmi
292 */
293 @Override
294 public boolean handlePinMmi(String dialString) {
295 enforceModifyPermissionOrDefaultDialer();
296
297 // Switch identity so that TelephonyManager checks Telecomm's permissions instead.
298 long token = Binder.clearCallingIdentity();
299 boolean retval = getTelephonyManager().handlePinMmi(dialString);
300 Binder.restoreCallingIdentity(token);
301
302 return retval;
303 }
304
Sailesh Nepalb88795a2014-07-15 14:53:27 -0700305 /**
306 * @see TelecommManager#isTtySupported
307 */
308 @Override
309 public boolean isTtySupported() {
310 enforceReadPermission();
311 return (boolean) sendRequest(MSG_IS_TTY_SUPPORTED);
312 }
313
314 /**
315 * @see TelecommManager#getCurrentTtyMode
316 */
317 @Override
318 public int getCurrentTtyMode() {
319 enforceReadPermission();
320 return (int) sendRequest(MSG_GET_CURRENT_TTY_MODE);
321 }
322
Santos Cordon7da72ef2014-06-25 15:50:22 -0700323 //
Santos Cordon46592f02014-07-07 15:11:35 -0700324 // Supporting methods for the ITelecommService interface implementation.
Santos Cordon7da72ef2014-06-25 15:50:22 -0700325 //
326
Santos Cordon23baed32014-06-27 14:45:39 -0700327 private void acceptRingingCallInternal() {
328 Call call = mCallsManager.getFirstCallWithState(CallState.RINGING);
329 if (call != null) {
Andrew Lee38931d02014-07-16 10:17:36 -0700330 call.answer(call.getVideoState());
Santos Cordon23baed32014-06-27 14:45:39 -0700331 }
332 }
333
334 private boolean endCallInternal() {
335 // Always operate on the foreground call if one exists, otherwise get the first call in
336 // priority order by call-state.
337 Call call = mCallsManager.getForegroundCall();
338 if (call == null) {
339 call = mCallsManager.getFirstCallWithState(
340 CallState.ACTIVE,
341 CallState.DIALING,
342 CallState.RINGING,
343 CallState.ON_HOLD);
344 }
345
346 if (call != null) {
347 if (call.getState() == CallState.RINGING) {
348 call.reject(false /* rejectWithMessage */, null);
349 } else {
350 call.disconnect();
351 }
352 return true;
353 }
354
355 return false;
Santos Cordonb64c1502014-05-21 21:21:49 -0700356 }
357
358 /**
359 * Make sure the caller has the MODIFY_PHONE_STATE permission.
360 *
361 * @throws SecurityException if the caller does not have the required permission
362 */
363 private void enforceModifyPermission() {
364 TelecommApp.getInstance().enforceCallingOrSelfPermission(
365 android.Manifest.permission.MODIFY_PHONE_STATE, null);
366 }
Santos Cordonf3671a62014-05-29 21:51:53 -0700367
Santos Cordon46592f02014-07-07 15:11:35 -0700368 private void enforceModifyPermissionOrDefaultDialer() {
369 if (!isDefaultDialerCalling()) {
370 enforceModifyPermission();
371 }
372 }
373
Ihab Awad502e5fe2014-07-09 12:34:48 -0700374 private void enforceModifyPermissionOrCallingPackage(String packageName) {
375 // TODO(santoscordon): Use a new telecomm permission for this instead of reusing modify.
376 try {
377 enforceModifyPermission();
378 } catch (SecurityException e) {
379 enforceCallingPackage(packageName);
380 }
381 }
382
Santos Cordon23baed32014-06-27 14:45:39 -0700383 private void enforceReadPermission() {
384 TelecommApp.getInstance().enforceCallingOrSelfPermission(
385 android.Manifest.permission.READ_PHONE_STATE, null);
Santos Cordonf3671a62014-05-29 21:51:53 -0700386 }
Yorke Leeceb96c92014-06-11 16:34:44 -0700387
Santos Cordon46592f02014-07-07 15:11:35 -0700388 private void enforceReadPermissionOrDefaultDialer() {
389 if (!isDefaultDialerCalling()) {
390 enforceReadPermission();
391 }
392 }
393
Ihab Awad502e5fe2014-07-09 12:34:48 -0700394 private void enforceCallingPackage(String packageName) {
395 mAppOpsManager.checkPackage(Binder.getCallingUid(), packageName);
396 }
397
Ihab Awad98a55602014-06-30 21:27:28 -0700398 private void showCallScreenInternal(boolean showDialpad) {
399 CallsManager.getInstance().getInCallController().bringToForeground(showDialpad);
400 }
Ihab Awad60509462014-06-14 16:43:08 -0700401
Santos Cordon46592f02014-07-07 15:11:35 -0700402 private boolean isDefaultDialerCalling() {
403 ComponentName defaultDialerComponent = getDefaultPhoneApp();
404 if (defaultDialerComponent != null) {
405 try {
406 mAppOpsManager.checkPackage(
407 Binder.getCallingUid(), defaultDialerComponent.getPackageName());
408 return true;
409 } catch (SecurityException e) {
410 Log.e(TAG, e, "Could not get default dialer.");
411 }
412 }
413 return false;
414 }
415
416 private TelephonyManager getTelephonyManager() {
417 return (TelephonyManager)
418 TelecommApp.getInstance().getSystemService(Context.TELEPHONY_SERVICE);
419 }
420
Santos Cordon7da72ef2014-06-25 15:50:22 -0700421 private void publish() {
422 Log.d(this, "publish: %s", this);
423 ServiceManager.addService(SERVICE_NAME, this);
Ihab Awad60509462014-06-14 16:43:08 -0700424 }
Santos Cordon23baed32014-06-27 14:45:39 -0700425
426 private MainThreadRequest sendRequestAsync(int command, int arg1) {
427 MainThreadRequest request = new MainThreadRequest();
428 mMainThreadHandler.obtainMessage(command, arg1, 0, request).sendToTarget();
429 return request;
430 }
431
432 /**
433 * Posts the specified command to be executed on the main thread, waits for the request to
434 * complete, and returns the result.
435 */
436 private Object sendRequest(int command) {
437 if (Looper.myLooper() == mMainThreadHandler.getLooper()) {
Sailesh Nepalbca199f2014-07-02 15:57:44 -0700438 MainThreadRequest request = new MainThreadRequest();
439 mMainThreadHandler.handleMessage(mMainThreadHandler.obtainMessage(command, request));
440 return request.result;
441 } else {
442 MainThreadRequest request = sendRequestAsync(command, 0);
Santos Cordon23baed32014-06-27 14:45:39 -0700443
Sailesh Nepalbca199f2014-07-02 15:57:44 -0700444 // Wait for the request to complete
445 synchronized (request) {
446 while (request.result == null) {
447 try {
448 request.wait();
449 } catch (InterruptedException e) {
450 // Do nothing, go back and wait until the request is complete
451 }
Santos Cordon23baed32014-06-27 14:45:39 -0700452 }
453 }
Sailesh Nepalbca199f2014-07-02 15:57:44 -0700454 return request.result;
Santos Cordon23baed32014-06-27 14:45:39 -0700455 }
Santos Cordon23baed32014-06-27 14:45:39 -0700456 }
Santos Cordonb64c1502014-05-21 21:21:49 -0700457}