blob: 9bf498771f8471ee206e1fa66be7ec92502772d4 [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;
Ihab Awad60509462014-06-14 16:43:08 -070023import android.net.Uri;
Santos Cordon46592f02014-07-07 15:11:35 -070024import android.os.Binder;
Santos Cordonb64c1502014-05-21 21:21:49 -070025import android.os.Handler;
Santos Cordon23baed32014-06-27 14:45:39 -070026import android.os.Looper;
Santos Cordonb64c1502014-05-21 21:21:49 -070027import android.os.Message;
28import android.os.ServiceManager;
Santos Cordon46592f02014-07-07 15:11:35 -070029import android.phone.PhoneManager;
Santos Cordon23baed32014-06-27 14:45:39 -070030import android.telecomm.CallState;
Ihab Awad98a55602014-06-30 21:27:28 -070031import android.telecomm.PhoneAccount;
Ihab Awad502e5fe2014-07-09 12:34:48 -070032import android.telecomm.PhoneAccountMetadata;
Santos Cordon46592f02014-07-07 15:11:35 -070033import android.telecomm.TelecommManager;
34import android.telephony.TelephonyManager;
35
36import com.android.internal.telecomm.ITelecommService;
Santos Cordonb64c1502014-05-21 21:21:49 -070037
Ihab Awad502e5fe2014-07-09 12:34:48 -070038import java.util.ArrayList;
39import java.util.HashMap;
Ihab Awad60509462014-06-14 16:43:08 -070040import java.util.List;
Ihab Awad502e5fe2014-07-09 12:34:48 -070041import java.util.Map;
Santos Cordonb64c1502014-05-21 21:21:49 -070042
43/**
44 * Implementation of the ITelecomm interface.
45 */
46public class TelecommServiceImpl extends ITelecommService.Stub {
Santos Cordon23baed32014-06-27 14:45:39 -070047 /**
48 * A request object for use with {@link MainThreadHandler}. Requesters should wait() on the
49 * request after sending. The main thread will notify the request when it is complete.
50 */
51 private static final class MainThreadRequest {
52 /** The result of the request that is run on the main thread */
53 public Object result;
54 }
Santos Cordonb64c1502014-05-21 21:21:49 -070055
56 /**
57 * A handler that processes messages on the main thread in the phone process. Since many
58 * of the Phone calls are not thread safe this is needed to shuttle the requests from the
59 * inbound binder threads to the main thread in the phone process.
60 */
Santos Cordon23baed32014-06-27 14:45:39 -070061 private final class MainThreadHandler extends Handler {
Santos Cordonb64c1502014-05-21 21:21:49 -070062 @Override
63 public void handleMessage(Message msg) {
Santos Cordon23baed32014-06-27 14:45:39 -070064 if (msg.obj instanceof MainThreadRequest) {
65 MainThreadRequest request = (MainThreadRequest) msg.obj;
66 Object result = null;
67 switch (msg.what) {
68 case MSG_SILENCE_RINGER:
69 mCallsManager.getRinger().silence();
70 break;
71 case MSG_SHOW_CALL_SCREEN:
72 mCallsManager.getInCallController().bringToForeground(msg.arg1 == 1);
73 break;
Santos Cordon23baed32014-06-27 14:45:39 -070074 case MSG_END_CALL:
75 result = endCallInternal();
76 break;
77 case MSG_ACCEPT_RINGING_CALL:
78 acceptRingingCallInternal();
79 break;
Santos Cordon64c7e962014-07-02 15:15:27 -070080 case MSG_CANCEL_MISSED_CALLS_NOTIFICATION:
81 mMissedCallNotifier.clearMissedCalls();
82 break;
Santos Cordon23baed32014-06-27 14:45:39 -070083 }
84
85 if (result != null) {
86 request.result = result;
87 synchronized(request) {
88 request.notifyAll();
89 }
90 }
Santos Cordonb64c1502014-05-21 21:21:49 -070091 }
92 }
Santos Cordon23baed32014-06-27 14:45:39 -070093 }
Santos Cordonb64c1502014-05-21 21:21:49 -070094
Santos Cordon7da72ef2014-06-25 15:50:22 -070095 /** Private constructor; @see init() */
Santos Cordon23baed32014-06-27 14:45:39 -070096 private static final String TAG = TelecommServiceImpl.class.getSimpleName();
97
98 private static final String SERVICE_NAME = "telecomm";
99
100 private static final int MSG_SILENCE_RINGER = 1;
101 private static final int MSG_SHOW_CALL_SCREEN = 2;
Santos Cordon626697a2014-07-10 22:36:37 -0700102 private static final int MSG_END_CALL = 3;
103 private static final int MSG_ACCEPT_RINGING_CALL = 4;
104 private static final int MSG_CANCEL_MISSED_CALLS_NOTIFICATION = 5;
Santos Cordon23baed32014-06-27 14:45:39 -0700105
106 /** The singleton instance. */
107 private static TelecommServiceImpl sInstance;
108
Santos Cordon46592f02014-07-07 15:11:35 -0700109 private final MainThreadHandler mMainThreadHandler = new MainThreadHandler();
Santos Cordon23baed32014-06-27 14:45:39 -0700110 private final CallsManager mCallsManager = CallsManager.getInstance();
Santos Cordon64c7e962014-07-02 15:15:27 -0700111 private final MissedCallNotifier mMissedCallNotifier;
Santos Cordon46592f02014-07-07 15:11:35 -0700112 private final AppOpsManager mAppOpsManager;
Santos Cordon23baed32014-06-27 14:45:39 -0700113
Santos Cordon64c7e962014-07-02 15:15:27 -0700114 private TelecommServiceImpl(MissedCallNotifier missedCallNotifier) {
115 mMissedCallNotifier = missedCallNotifier;
Santos Cordon46592f02014-07-07 15:11:35 -0700116 mAppOpsManager =
117 (AppOpsManager) TelecommApp.getInstance().getSystemService(Context.APP_OPS_SERVICE);
118
Santos Cordon7da72ef2014-06-25 15:50:22 -0700119 publish();
120 }
121
Santos Cordonb64c1502014-05-21 21:21:49 -0700122 /**
123 * Initialize the singleton TelecommServiceImpl instance.
124 * This is only done once, at startup, from TelecommApp.onCreate().
125 */
Santos Cordon64c7e962014-07-02 15:15:27 -0700126 static TelecommServiceImpl init(MissedCallNotifier missedCallNotifier) {
Santos Cordonb64c1502014-05-21 21:21:49 -0700127 synchronized (TelecommServiceImpl.class) {
128 if (sInstance == null) {
Santos Cordon64c7e962014-07-02 15:15:27 -0700129 sInstance = new TelecommServiceImpl(missedCallNotifier);
Santos Cordonb64c1502014-05-21 21:21:49 -0700130 } else {
131 Log.wtf(TAG, "init() called multiple times! sInstance %s", sInstance);
132 }
133 return sInstance;
134 }
135 }
136
Santos Cordonb64c1502014-05-21 21:21:49 -0700137 //
Santos Cordon23baed32014-06-27 14:45:39 -0700138 // Implementation of the ITelecommService interface.
Santos Cordonb64c1502014-05-21 21:21:49 -0700139 //
140
Ihab Awad502e5fe2014-07-09 12:34:48 -0700141 private static Map<PhoneAccount, PhoneAccountMetadata> sMetadataByAccount = new HashMap<>();
142
143 static {
Ihab Awad98a55602014-06-30 21:27:28 -0700144 // TODO (STOPSHIP): Static list of Accounts for testing and UX work only.
145 ComponentName componentName = new ComponentName(
146 "com.android.telecomm",
147 TelecommServiceImpl.class.getName()); // This field is a no-op
148 Context app = TelecommApp.getInstance();
149
Ihab Awad502e5fe2014-07-09 12:34:48 -0700150 PhoneAccount[] accounts = new PhoneAccount[] {
Ihab Awad98a55602014-06-30 21:27:28 -0700151 new PhoneAccount(
152 componentName,
153 "account0",
154 Uri.parse("tel:999-555-1212"),
Ihab Awad502e5fe2014-07-09 12:34:48 -0700155 0),
Ihab Awad98a55602014-06-30 21:27:28 -0700156 new PhoneAccount(
157 componentName,
158 "account1",
159 Uri.parse("tel:333-111-2222"),
Ihab Awad502e5fe2014-07-09 12:34:48 -0700160 0),
Ihab Awad98a55602014-06-30 21:27:28 -0700161 new PhoneAccount(
162 componentName,
163 "account2",
164 Uri.parse("mailto:two@example.com"),
Ihab Awad502e5fe2014-07-09 12:34:48 -0700165 0),
Ihab Awad98a55602014-06-30 21:27:28 -0700166 new PhoneAccount(
167 componentName,
168 "account3",
169 Uri.parse("mailto:three@example.com"),
Ihab Awad502e5fe2014-07-09 12:34:48 -0700170 0)
171 };
172
173 sMetadataByAccount.put(
174 accounts[0],
175 new PhoneAccountMetadata(
176 accounts[0],
177 0,
178 app.getString(R.string.test_account_0_label),
179 app.getString(R.string.test_account_0_short_description)));
180 sMetadataByAccount.put(
181 accounts[1],
182 new PhoneAccountMetadata(
183 accounts[1],
184 0,
185 app.getString(R.string.test_account_1_label),
186 app.getString(R.string.test_account_1_short_description)));
187 sMetadataByAccount.put(
188 accounts[2],
189 new PhoneAccountMetadata(
190 accounts[2],
191 0,
192 app.getString(R.string.test_account_2_label),
193 app.getString(R.string.test_account_2_short_description)));
194 sMetadataByAccount.put(
195 accounts[3],
196 new PhoneAccountMetadata(
197 accounts[3],
198 0,
Ihab Awad98a55602014-06-30 21:27:28 -0700199 app.getString(R.string.test_account_3_label),
Ihab Awad502e5fe2014-07-09 12:34:48 -0700200 app.getString(R.string.test_account_3_short_description)));
Santos Cordon7da72ef2014-06-25 15:50:22 -0700201 }
202
203 @Override
Ihab Awad502e5fe2014-07-09 12:34:48 -0700204 public List<PhoneAccount> getEnabledPhoneAccounts() {
205 return new ArrayList<>(sMetadataByAccount.keySet());
Santos Cordon7da72ef2014-06-25 15:50:22 -0700206 }
207
208 @Override
Ihab Awad502e5fe2014-07-09 12:34:48 -0700209 public PhoneAccountMetadata getPhoneAccountMetadata(PhoneAccount account) {
210 return sMetadataByAccount.get(account);
211 }
212
213 @Override
214 public void registerPhoneAccount(PhoneAccount account, PhoneAccountMetadata metadata) {
215 enforceModifyPermissionOrCallingPackage(account.getComponentName().getPackageName());
216 // TODO(santoscordon) -- IMPLEMENT ...
217 }
218
219 @Override
220 public void unregisterPhoneAccount(PhoneAccount account) {
221 enforceModifyPermissionOrCallingPackage(account.getComponentName().getPackageName());
222 // TODO(santoscordon) -- IMPLEMENT ...
223 }
224
225 @Override
226 public void clearAccounts(String packageName) {
227 enforceModifyPermissionOrCallingPackage(packageName);
228 // TODO(santoscordon) -- IMPLEMENT ...
Santos Cordon7da72ef2014-06-25 15:50:22 -0700229 }
230
Santos Cordon23baed32014-06-27 14:45:39 -0700231 /**
232 * @see TelecommManager#silenceringer
233 */
Santos Cordon7da72ef2014-06-25 15:50:22 -0700234 @Override
Santos Cordonb64c1502014-05-21 21:21:49 -0700235 public void silenceRinger() {
236 Log.d(this, "silenceRinger");
Santos Cordonb64c1502014-05-21 21:21:49 -0700237 enforceModifyPermission();
Santos Cordon23baed32014-06-27 14:45:39 -0700238 sendRequestAsync(MSG_SILENCE_RINGER, 0);
Santos Cordonb64c1502014-05-21 21:21:49 -0700239 }
240
Santos Cordon23baed32014-06-27 14:45:39 -0700241 /**
242 * @see TelecommManager#getDefaultPhoneApp
243 */
Santos Cordon7da72ef2014-06-25 15:50:22 -0700244 @Override
245 public ComponentName getDefaultPhoneApp() {
246 Resources resources = TelecommApp.getInstance().getResources();
247 return new ComponentName(
248 resources.getString(R.string.ui_default_package),
249 resources.getString(R.string.dialer_default_class));
250 }
251
Santos Cordon23baed32014-06-27 14:45:39 -0700252 /**
253 * @see TelecommManager#isInAPhoneCall
254 */
255 @Override
256 public boolean isInAPhoneCall() {
257 enforceReadPermission();
Santos Cordon626697a2014-07-10 22:36:37 -0700258 // Do not use sendRequest() with this method since it could cause a deadlock with
259 // audio service, which we call into from the main thread: AudioManager.setMode().
260 return mCallsManager.hasAnyCalls();
Santos Cordon23baed32014-06-27 14:45:39 -0700261 }
262
263 /**
264 * @see TelecommManager#isRinging
265 */
266 @Override
267 public boolean isRinging() {
268 enforceReadPermission();
Santos Cordon626697a2014-07-10 22:36:37 -0700269 return mCallsManager.hasRingingCall();
Santos Cordon23baed32014-06-27 14:45:39 -0700270 }
271
272 /**
273 * @see TelecommManager#endCall
274 */
275 @Override
276 public boolean endCall() {
277 enforceModifyPermission();
278 return (boolean) sendRequest(MSG_END_CALL);
279 }
280
281 /**
282 * @see TelecommManager#acceptRingingCall
283 */
284 @Override
285 public void acceptRingingCall() {
286 enforceModifyPermission();
287 sendRequestAsync(MSG_ACCEPT_RINGING_CALL, 0);
288 }
289
Santos Cordon46592f02014-07-07 15:11:35 -0700290 /**
291 * @see PhoneManager#showCallScreen
292 */
Santos Cordon23baed32014-06-27 14:45:39 -0700293 @Override
294 public void showCallScreen(boolean showDialpad) {
Santos Cordon46592f02014-07-07 15:11:35 -0700295 enforceReadPermissionOrDefaultDialer();
Santos Cordon64c7e962014-07-02 15:15:27 -0700296 sendRequestAsync(MSG_SHOW_CALL_SCREEN, showDialpad ? 1 : 0);
297 }
298
Santos Cordon46592f02014-07-07 15:11:35 -0700299 /**
300 * @see PhoneManager#cancelMissedCallsNotification
301 */
Santos Cordon64c7e962014-07-02 15:15:27 -0700302 @Override
303 public void cancelMissedCallsNotification() {
Santos Cordon46592f02014-07-07 15:11:35 -0700304 enforceModifyPermissionOrDefaultDialer();
Santos Cordon64c7e962014-07-02 15:15:27 -0700305 sendRequestAsync(MSG_CANCEL_MISSED_CALLS_NOTIFICATION, 0);
Santos Cordon23baed32014-06-27 14:45:39 -0700306 }
307
Santos Cordon46592f02014-07-07 15:11:35 -0700308 /**
309 * @see PhoneManager#handlePinMmi
310 */
311 @Override
312 public boolean handlePinMmi(String dialString) {
313 enforceModifyPermissionOrDefaultDialer();
314
315 // Switch identity so that TelephonyManager checks Telecomm's permissions instead.
316 long token = Binder.clearCallingIdentity();
317 boolean retval = getTelephonyManager().handlePinMmi(dialString);
318 Binder.restoreCallingIdentity(token);
319
320 return retval;
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) {
330 call.answer();
331 }
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}