blob: ca80e8a1de40e3030d7582bf08ccd6c9e5ed33f5 [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
Ihab Awad60509462014-06-14 16:43:08 -070019import com.google.android.collect.Lists;
20
21import com.android.internal.telecomm.ITelecommService;
22
Yorke Leeceb96c92014-06-11 16:34:44 -070023import android.content.ComponentName;
Ihab Awad98a55602014-06-30 21:27:28 -070024import android.content.Context;
Yorke Leeceb96c92014-06-11 16:34:44 -070025import android.content.res.Resources;
Ihab Awad60509462014-06-14 16:43:08 -070026import android.net.Uri;
Santos Cordonb64c1502014-05-21 21:21:49 -070027import android.os.Handler;
Santos Cordon23baed32014-06-27 14:45:39 -070028import android.os.Looper;
Santos Cordonb64c1502014-05-21 21:21:49 -070029import android.os.Message;
30import android.os.ServiceManager;
Santos Cordon23baed32014-06-27 14:45:39 -070031import android.telecomm.CallState;
Yorke Leeceb96c92014-06-11 16:34:44 -070032import android.text.TextUtils;
Ihab Awad98a55602014-06-30 21:27:28 -070033import android.telecomm.PhoneAccount;
Santos Cordonb64c1502014-05-21 21:21:49 -070034
Ihab Awad60509462014-06-14 16:43:08 -070035import java.util.List;
Santos Cordonb64c1502014-05-21 21:21:49 -070036
37/**
38 * Implementation of the ITelecomm interface.
39 */
40public class TelecommServiceImpl extends ITelecommService.Stub {
Santos Cordon23baed32014-06-27 14:45:39 -070041 /**
42 * A request object for use with {@link MainThreadHandler}. Requesters should wait() on the
43 * request after sending. The main thread will notify the request when it is complete.
44 */
45 private static final class MainThreadRequest {
46 /** The result of the request that is run on the main thread */
47 public Object result;
48 }
Santos Cordonb64c1502014-05-21 21:21:49 -070049
50 /**
51 * A handler that processes messages on the main thread in the phone process. Since many
52 * of the Phone calls are not thread safe this is needed to shuttle the requests from the
53 * inbound binder threads to the main thread in the phone process.
54 */
Santos Cordon23baed32014-06-27 14:45:39 -070055 private final class MainThreadHandler extends Handler {
Santos Cordonb64c1502014-05-21 21:21:49 -070056 @Override
57 public void handleMessage(Message msg) {
Santos Cordon23baed32014-06-27 14:45:39 -070058 if (msg.obj instanceof MainThreadRequest) {
59 MainThreadRequest request = (MainThreadRequest) msg.obj;
60 Object result = null;
61 switch (msg.what) {
62 case MSG_SILENCE_RINGER:
63 mCallsManager.getRinger().silence();
64 break;
65 case MSG_SHOW_CALL_SCREEN:
66 mCallsManager.getInCallController().bringToForeground(msg.arg1 == 1);
67 break;
68 case MSG_IS_IN_A_PHONE_CALL:
69 result = mCallsManager.hasAnyCalls();
70 break;
71 case MSG_IS_RINGING:
72 result = mCallsManager.hasRingingCall();
73 break;
74 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;
102 private static final int MSG_IS_IN_A_PHONE_CALL = 3;
103 private static final int MSG_IS_RINGING = 4;
104 private static final int MSG_END_CALL = 5;
105 private static final int MSG_ACCEPT_RINGING_CALL = 6;
Santos Cordon64c7e962014-07-02 15:15:27 -0700106 private static final int MSG_CANCEL_MISSED_CALLS_NOTIFICATION = 7;
Santos Cordon23baed32014-06-27 14:45:39 -0700107
108 /** The singleton instance. */
109 private static TelecommServiceImpl sInstance;
110
111 private final CallsManager mCallsManager = CallsManager.getInstance();
Santos Cordon64c7e962014-07-02 15:15:27 -0700112 private final MissedCallNotifier mMissedCallNotifier;
Santos Cordon23baed32014-06-27 14:45:39 -0700113 private final MainThreadHandler mMainThreadHandler = new MainThreadHandler();
114
Santos Cordon64c7e962014-07-02 15:15:27 -0700115 private TelecommServiceImpl(MissedCallNotifier missedCallNotifier) {
116 mMissedCallNotifier = missedCallNotifier;
Santos Cordon7da72ef2014-06-25 15:50:22 -0700117 publish();
118 }
119
Santos Cordonb64c1502014-05-21 21:21:49 -0700120 /**
121 * Initialize the singleton TelecommServiceImpl instance.
122 * This is only done once, at startup, from TelecommApp.onCreate().
123 */
Santos Cordon64c7e962014-07-02 15:15:27 -0700124 static TelecommServiceImpl init(MissedCallNotifier missedCallNotifier) {
Santos Cordonb64c1502014-05-21 21:21:49 -0700125 synchronized (TelecommServiceImpl.class) {
126 if (sInstance == null) {
Santos Cordon64c7e962014-07-02 15:15:27 -0700127 sInstance = new TelecommServiceImpl(missedCallNotifier);
Santos Cordonb64c1502014-05-21 21:21:49 -0700128 } else {
129 Log.wtf(TAG, "init() called multiple times! sInstance %s", sInstance);
130 }
131 return sInstance;
132 }
133 }
134
Santos Cordonb64c1502014-05-21 21:21:49 -0700135 //
Santos Cordon23baed32014-06-27 14:45:39 -0700136 // Implementation of the ITelecommService interface.
Santos Cordonb64c1502014-05-21 21:21:49 -0700137 //
138
139 @Override
Ihab Awad98a55602014-06-30 21:27:28 -0700140 public List<PhoneAccount> getAccounts() {
141 // TODO (STOPSHIP): Static list of Accounts for testing and UX work only.
142 ComponentName componentName = new ComponentName(
143 "com.android.telecomm",
144 TelecommServiceImpl.class.getName()); // This field is a no-op
145 Context app = TelecommApp.getInstance();
146
147 return Lists.newArrayList(
148 new PhoneAccount(
149 componentName,
150 "account0",
151 Uri.parse("tel:999-555-1212"),
152 app.getString(R.string.test_account_0_label),
153 app.getString(R.string.test_account_0_short_description),
154 true,
155 true),
156 new PhoneAccount(
157 componentName,
158 "account1",
159 Uri.parse("tel:333-111-2222"),
160 app.getString(R.string.test_account_1_label),
161 app.getString(R.string.test_account_1_short_description),
162 true,
163 false),
164 new PhoneAccount(
165 componentName,
166 "account2",
167 Uri.parse("mailto:two@example.com"),
168 app.getString(R.string.test_account_2_label),
169 app.getString(R.string.test_account_2_short_description),
170 true,
171 false),
172 new PhoneAccount(
173 componentName,
174 "account3",
175 Uri.parse("mailto:three@example.com"),
176 app.getString(R.string.test_account_3_label),
177 app.getString(R.string.test_account_3_short_description),
178 true,
179 false)
180 );
Santos Cordon7da72ef2014-06-25 15:50:22 -0700181 }
182
183 @Override
Ihab Awad98a55602014-06-30 21:27:28 -0700184 public void setEnabled(PhoneAccount account, boolean enabled) {
Santos Cordon7da72ef2014-06-25 15:50:22 -0700185 // Enforce MODIFY_PHONE_STATE ?
186 // TODO
187 }
188
189 @Override
Ihab Awad98a55602014-06-30 21:27:28 -0700190 public void setSystemDefault(PhoneAccount account) {
Santos Cordon7da72ef2014-06-25 15:50:22 -0700191 // Enforce MODIFY_PHONE_STATE ?
192 // TODO
193 }
194
Santos Cordon23baed32014-06-27 14:45:39 -0700195 /**
196 * @see TelecommManager#silenceringer
197 */
Santos Cordon7da72ef2014-06-25 15:50:22 -0700198 @Override
Santos Cordonb64c1502014-05-21 21:21:49 -0700199 public void silenceRinger() {
200 Log.d(this, "silenceRinger");
Santos Cordonb64c1502014-05-21 21:21:49 -0700201 enforceModifyPermission();
Santos Cordon23baed32014-06-27 14:45:39 -0700202 sendRequestAsync(MSG_SILENCE_RINGER, 0);
Santos Cordonb64c1502014-05-21 21:21:49 -0700203 }
204
Santos Cordon23baed32014-06-27 14:45:39 -0700205 /**
206 * @see TelecommManager#getDefaultPhoneApp
207 */
Santos Cordon7da72ef2014-06-25 15:50:22 -0700208 @Override
209 public ComponentName getDefaultPhoneApp() {
210 Resources resources = TelecommApp.getInstance().getResources();
211 return new ComponentName(
212 resources.getString(R.string.ui_default_package),
213 resources.getString(R.string.dialer_default_class));
214 }
215
Santos Cordon23baed32014-06-27 14:45:39 -0700216 /**
217 * @see TelecommManager#isInAPhoneCall
218 */
219 @Override
220 public boolean isInAPhoneCall() {
221 enforceReadPermission();
222 return (boolean) sendRequest(MSG_IS_IN_A_PHONE_CALL);
223 }
224
225 /**
226 * @see TelecommManager#isRinging
227 */
228 @Override
229 public boolean isRinging() {
230 enforceReadPermission();
231 return (boolean) sendRequest(MSG_IS_RINGING);
232 }
233
234 /**
235 * @see TelecommManager#endCall
236 */
237 @Override
238 public boolean endCall() {
239 enforceModifyPermission();
240 return (boolean) sendRequest(MSG_END_CALL);
241 }
242
243 /**
244 * @see TelecommManager#acceptRingingCall
245 */
246 @Override
247 public void acceptRingingCall() {
248 enforceModifyPermission();
249 sendRequestAsync(MSG_ACCEPT_RINGING_CALL, 0);
250 }
251
252 @Override
253 public void showCallScreen(boolean showDialpad) {
Santos Cordon64c7e962014-07-02 15:15:27 -0700254 sendRequestAsync(MSG_SHOW_CALL_SCREEN, showDialpad ? 1 : 0);
255 }
256
257 @Override
258 public void cancelMissedCallsNotification() {
259 sendRequestAsync(MSG_CANCEL_MISSED_CALLS_NOTIFICATION, 0);
Santos Cordon23baed32014-06-27 14:45:39 -0700260 }
261
Santos Cordon7da72ef2014-06-25 15:50:22 -0700262 //
263 // Supporting methods for the ITelephony interface implementation.
264 //
265
Santos Cordon23baed32014-06-27 14:45:39 -0700266 private void acceptRingingCallInternal() {
267 Call call = mCallsManager.getFirstCallWithState(CallState.RINGING);
268 if (call != null) {
269 call.answer();
270 }
271 }
272
273 private boolean endCallInternal() {
274 // Always operate on the foreground call if one exists, otherwise get the first call in
275 // priority order by call-state.
276 Call call = mCallsManager.getForegroundCall();
277 if (call == null) {
278 call = mCallsManager.getFirstCallWithState(
279 CallState.ACTIVE,
280 CallState.DIALING,
281 CallState.RINGING,
282 CallState.ON_HOLD);
283 }
284
285 if (call != null) {
286 if (call.getState() == CallState.RINGING) {
287 call.reject(false /* rejectWithMessage */, null);
288 } else {
289 call.disconnect();
290 }
291 return true;
292 }
293
294 return false;
Santos Cordonb64c1502014-05-21 21:21:49 -0700295 }
296
297 /**
298 * Make sure the caller has the MODIFY_PHONE_STATE permission.
299 *
300 * @throws SecurityException if the caller does not have the required permission
301 */
302 private void enforceModifyPermission() {
303 TelecommApp.getInstance().enforceCallingOrSelfPermission(
304 android.Manifest.permission.MODIFY_PHONE_STATE, null);
305 }
Santos Cordonf3671a62014-05-29 21:51:53 -0700306
Santos Cordon23baed32014-06-27 14:45:39 -0700307 private void enforceReadPermission() {
308 TelecommApp.getInstance().enforceCallingOrSelfPermission(
309 android.Manifest.permission.READ_PHONE_STATE, null);
Santos Cordonf3671a62014-05-29 21:51:53 -0700310 }
Yorke Leeceb96c92014-06-11 16:34:44 -0700311
Ihab Awad98a55602014-06-30 21:27:28 -0700312 private void showCallScreenInternal(boolean showDialpad) {
313 CallsManager.getInstance().getInCallController().bringToForeground(showDialpad);
314 }
Ihab Awad60509462014-06-14 16:43:08 -0700315
Santos Cordon7da72ef2014-06-25 15:50:22 -0700316 private void publish() {
317 Log.d(this, "publish: %s", this);
318 ServiceManager.addService(SERVICE_NAME, this);
Ihab Awad60509462014-06-14 16:43:08 -0700319 }
Santos Cordon23baed32014-06-27 14:45:39 -0700320
321 private MainThreadRequest sendRequestAsync(int command, int arg1) {
322 MainThreadRequest request = new MainThreadRequest();
323 mMainThreadHandler.obtainMessage(command, arg1, 0, request).sendToTarget();
324 return request;
325 }
326
327 /**
328 * Posts the specified command to be executed on the main thread, waits for the request to
329 * complete, and returns the result.
330 */
331 private Object sendRequest(int command) {
332 if (Looper.myLooper() == mMainThreadHandler.getLooper()) {
Sailesh Nepalbca199f2014-07-02 15:57:44 -0700333 MainThreadRequest request = new MainThreadRequest();
334 mMainThreadHandler.handleMessage(mMainThreadHandler.obtainMessage(command, request));
335 return request.result;
336 } else {
337 MainThreadRequest request = sendRequestAsync(command, 0);
Santos Cordon23baed32014-06-27 14:45:39 -0700338
Sailesh Nepalbca199f2014-07-02 15:57:44 -0700339 // Wait for the request to complete
340 synchronized (request) {
341 while (request.result == null) {
342 try {
343 request.wait();
344 } catch (InterruptedException e) {
345 // Do nothing, go back and wait until the request is complete
346 }
Santos Cordon23baed32014-06-27 14:45:39 -0700347 }
348 }
Sailesh Nepalbca199f2014-07-02 15:57:44 -0700349 return request.result;
Santos Cordon23baed32014-06-27 14:45:39 -0700350 }
Santos Cordon23baed32014-06-27 14:45:39 -0700351 }
Santos Cordonb64c1502014-05-21 21:21:49 -0700352}