blob: 6ba90d0a4aeb160f4558a0adb56e97f8e9a33c77 [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;
80 }
81
82 if (result != null) {
83 request.result = result;
84 synchronized(request) {
85 request.notifyAll();
86 }
87 }
Santos Cordonb64c1502014-05-21 21:21:49 -070088 }
89 }
Santos Cordon23baed32014-06-27 14:45:39 -070090 }
Santos Cordonb64c1502014-05-21 21:21:49 -070091
Santos Cordon7da72ef2014-06-25 15:50:22 -070092 /** Private constructor; @see init() */
Santos Cordon23baed32014-06-27 14:45:39 -070093 private static final String TAG = TelecommServiceImpl.class.getSimpleName();
94
95 private static final String SERVICE_NAME = "telecomm";
96
97 private static final int MSG_SILENCE_RINGER = 1;
98 private static final int MSG_SHOW_CALL_SCREEN = 2;
99 private static final int MSG_IS_IN_A_PHONE_CALL = 3;
100 private static final int MSG_IS_RINGING = 4;
101 private static final int MSG_END_CALL = 5;
102 private static final int MSG_ACCEPT_RINGING_CALL = 6;
103
104 /** The singleton instance. */
105 private static TelecommServiceImpl sInstance;
106
107 private final CallsManager mCallsManager = CallsManager.getInstance();
108
109 private final MainThreadHandler mMainThreadHandler = new MainThreadHandler();
110
Santos Cordon7da72ef2014-06-25 15:50:22 -0700111 private TelecommServiceImpl() {
112 publish();
113 }
114
Santos Cordonb64c1502014-05-21 21:21:49 -0700115 /**
116 * Initialize the singleton TelecommServiceImpl instance.
117 * This is only done once, at startup, from TelecommApp.onCreate().
118 */
119 static TelecommServiceImpl init() {
120 synchronized (TelecommServiceImpl.class) {
121 if (sInstance == null) {
122 sInstance = new TelecommServiceImpl();
123 } else {
124 Log.wtf(TAG, "init() called multiple times! sInstance %s", sInstance);
125 }
126 return sInstance;
127 }
128 }
129
Santos Cordonb64c1502014-05-21 21:21:49 -0700130 //
Santos Cordon23baed32014-06-27 14:45:39 -0700131 // Implementation of the ITelecommService interface.
Santos Cordonb64c1502014-05-21 21:21:49 -0700132 //
133
134 @Override
Ihab Awad98a55602014-06-30 21:27:28 -0700135 public List<PhoneAccount> getAccounts() {
136 // TODO (STOPSHIP): Static list of Accounts for testing and UX work only.
137 ComponentName componentName = new ComponentName(
138 "com.android.telecomm",
139 TelecommServiceImpl.class.getName()); // This field is a no-op
140 Context app = TelecommApp.getInstance();
141
142 return Lists.newArrayList(
143 new PhoneAccount(
144 componentName,
145 "account0",
146 Uri.parse("tel:999-555-1212"),
147 app.getString(R.string.test_account_0_label),
148 app.getString(R.string.test_account_0_short_description),
149 true,
150 true),
151 new PhoneAccount(
152 componentName,
153 "account1",
154 Uri.parse("tel:333-111-2222"),
155 app.getString(R.string.test_account_1_label),
156 app.getString(R.string.test_account_1_short_description),
157 true,
158 false),
159 new PhoneAccount(
160 componentName,
161 "account2",
162 Uri.parse("mailto:two@example.com"),
163 app.getString(R.string.test_account_2_label),
164 app.getString(R.string.test_account_2_short_description),
165 true,
166 false),
167 new PhoneAccount(
168 componentName,
169 "account3",
170 Uri.parse("mailto:three@example.com"),
171 app.getString(R.string.test_account_3_label),
172 app.getString(R.string.test_account_3_short_description),
173 true,
174 false)
175 );
Santos Cordon7da72ef2014-06-25 15:50:22 -0700176 }
177
178 @Override
Ihab Awad98a55602014-06-30 21:27:28 -0700179 public void setEnabled(PhoneAccount account, boolean enabled) {
Santos Cordon7da72ef2014-06-25 15:50:22 -0700180 // Enforce MODIFY_PHONE_STATE ?
181 // TODO
182 }
183
184 @Override
Ihab Awad98a55602014-06-30 21:27:28 -0700185 public void setSystemDefault(PhoneAccount account) {
Santos Cordon7da72ef2014-06-25 15:50:22 -0700186 // Enforce MODIFY_PHONE_STATE ?
187 // TODO
188 }
189
Santos Cordon23baed32014-06-27 14:45:39 -0700190 /**
191 * @see TelecommManager#silenceringer
192 */
Santos Cordon7da72ef2014-06-25 15:50:22 -0700193 @Override
Santos Cordonb64c1502014-05-21 21:21:49 -0700194 public void silenceRinger() {
195 Log.d(this, "silenceRinger");
Santos Cordonb64c1502014-05-21 21:21:49 -0700196 enforceModifyPermission();
Santos Cordon23baed32014-06-27 14:45:39 -0700197 sendRequestAsync(MSG_SILENCE_RINGER, 0);
Santos Cordonb64c1502014-05-21 21:21:49 -0700198 }
199
Santos Cordon23baed32014-06-27 14:45:39 -0700200 /**
201 * @see TelecommManager#getDefaultPhoneApp
202 */
Santos Cordon7da72ef2014-06-25 15:50:22 -0700203 @Override
204 public ComponentName getDefaultPhoneApp() {
205 Resources resources = TelecommApp.getInstance().getResources();
206 return new ComponentName(
207 resources.getString(R.string.ui_default_package),
208 resources.getString(R.string.dialer_default_class));
209 }
210
Santos Cordon23baed32014-06-27 14:45:39 -0700211 /**
212 * @see TelecommManager#isInAPhoneCall
213 */
214 @Override
215 public boolean isInAPhoneCall() {
216 enforceReadPermission();
217 return (boolean) sendRequest(MSG_IS_IN_A_PHONE_CALL);
218 }
219
220 /**
221 * @see TelecommManager#isRinging
222 */
223 @Override
224 public boolean isRinging() {
225 enforceReadPermission();
226 return (boolean) sendRequest(MSG_IS_RINGING);
227 }
228
229 /**
230 * @see TelecommManager#endCall
231 */
232 @Override
233 public boolean endCall() {
234 enforceModifyPermission();
235 return (boolean) sendRequest(MSG_END_CALL);
236 }
237
238 /**
239 * @see TelecommManager#acceptRingingCall
240 */
241 @Override
242 public void acceptRingingCall() {
243 enforceModifyPermission();
244 sendRequestAsync(MSG_ACCEPT_RINGING_CALL, 0);
245 }
246
247 @Override
248 public void showCallScreen(boolean showDialpad) {
249 mMainThreadHandler.obtainMessage(MSG_SHOW_CALL_SCREEN, showDialpad ? 1 : 0, 0)
250 .sendToTarget();
251 }
252
Santos Cordon7da72ef2014-06-25 15:50:22 -0700253 //
254 // Supporting methods for the ITelephony interface implementation.
255 //
256
Santos Cordon23baed32014-06-27 14:45:39 -0700257 private void acceptRingingCallInternal() {
258 Call call = mCallsManager.getFirstCallWithState(CallState.RINGING);
259 if (call != null) {
260 call.answer();
261 }
262 }
263
264 private boolean endCallInternal() {
265 // Always operate on the foreground call if one exists, otherwise get the first call in
266 // priority order by call-state.
267 Call call = mCallsManager.getForegroundCall();
268 if (call == null) {
269 call = mCallsManager.getFirstCallWithState(
270 CallState.ACTIVE,
271 CallState.DIALING,
272 CallState.RINGING,
273 CallState.ON_HOLD);
274 }
275
276 if (call != null) {
277 if (call.getState() == CallState.RINGING) {
278 call.reject(false /* rejectWithMessage */, null);
279 } else {
280 call.disconnect();
281 }
282 return true;
283 }
284
285 return false;
Santos Cordonb64c1502014-05-21 21:21:49 -0700286 }
287
288 /**
289 * Make sure the caller has the MODIFY_PHONE_STATE permission.
290 *
291 * @throws SecurityException if the caller does not have the required permission
292 */
293 private void enforceModifyPermission() {
294 TelecommApp.getInstance().enforceCallingOrSelfPermission(
295 android.Manifest.permission.MODIFY_PHONE_STATE, null);
296 }
Santos Cordonf3671a62014-05-29 21:51:53 -0700297
Santos Cordon23baed32014-06-27 14:45:39 -0700298 private void enforceReadPermission() {
299 TelecommApp.getInstance().enforceCallingOrSelfPermission(
300 android.Manifest.permission.READ_PHONE_STATE, null);
Santos Cordonf3671a62014-05-29 21:51:53 -0700301 }
Yorke Leeceb96c92014-06-11 16:34:44 -0700302
Ihab Awad98a55602014-06-30 21:27:28 -0700303 private void showCallScreenInternal(boolean showDialpad) {
304 CallsManager.getInstance().getInCallController().bringToForeground(showDialpad);
305 }
Ihab Awad60509462014-06-14 16:43:08 -0700306
Santos Cordon7da72ef2014-06-25 15:50:22 -0700307 private void publish() {
308 Log.d(this, "publish: %s", this);
309 ServiceManager.addService(SERVICE_NAME, this);
Ihab Awad60509462014-06-14 16:43:08 -0700310 }
Santos Cordon23baed32014-06-27 14:45:39 -0700311
312 private MainThreadRequest sendRequestAsync(int command, int arg1) {
313 MainThreadRequest request = new MainThreadRequest();
314 mMainThreadHandler.obtainMessage(command, arg1, 0, request).sendToTarget();
315 return request;
316 }
317
318 /**
319 * Posts the specified command to be executed on the main thread, waits for the request to
320 * complete, and returns the result.
321 */
322 private Object sendRequest(int command) {
323 if (Looper.myLooper() == mMainThreadHandler.getLooper()) {
324 throw new RuntimeException("This method will deadlock if called from the main thread.");
325 }
326
327 MainThreadRequest request = sendRequestAsync(command, 0);
328
329 // Wait for the request to complete
330 synchronized (request) {
331 while (request.result == null) {
332 try {
333 request.wait();
334 } catch (InterruptedException e) {
335 // Do nothing, go back and wait until the request is complete
336 }
337 }
338 }
339 return request.result;
340 }
Santos Cordonb64c1502014-05-21 21:21:49 -0700341}