blob: d723aef78b99ef9d4dbffb70259ab4026c7e6e6e [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;
Yorke Leeceb96c92014-06-11 16:34:44 -070024import android.content.res.Resources;
Ihab Awad60509462014-06-14 16:43:08 -070025import android.net.Uri;
Santos Cordonb64c1502014-05-21 21:21:49 -070026import android.os.Handler;
Santos Cordon23baed32014-06-27 14:45:39 -070027import android.os.Looper;
Santos Cordonb64c1502014-05-21 21:21:49 -070028import android.os.Message;
29import android.os.ServiceManager;
Santos Cordon23baed32014-06-27 14:45:39 -070030import android.telecomm.CallState;
Ihab Awad60509462014-06-14 16:43:08 -070031import android.telecomm.Subscription;
Yorke Leeceb96c92014-06-11 16:34:44 -070032import android.text.TextUtils;
Santos Cordonb64c1502014-05-21 21:21:49 -070033
Ihab Awad60509462014-06-14 16:43:08 -070034import java.util.List;
Santos Cordonb64c1502014-05-21 21:21:49 -070035
36/**
37 * Implementation of the ITelecomm interface.
38 */
39public class TelecommServiceImpl extends ITelecommService.Stub {
Santos Cordon23baed32014-06-27 14:45:39 -070040 /**
41 * A request object for use with {@link MainThreadHandler}. Requesters should wait() on the
42 * request after sending. The main thread will notify the request when it is complete.
43 */
44 private static final class MainThreadRequest {
45 /** The result of the request that is run on the main thread */
46 public Object result;
47 }
Santos Cordonb64c1502014-05-21 21:21:49 -070048
49 /**
50 * A handler that processes messages on the main thread in the phone process. Since many
51 * of the Phone calls are not thread safe this is needed to shuttle the requests from the
52 * inbound binder threads to the main thread in the phone process.
53 */
Santos Cordon23baed32014-06-27 14:45:39 -070054 private final class MainThreadHandler extends Handler {
Santos Cordonb64c1502014-05-21 21:21:49 -070055 @Override
56 public void handleMessage(Message msg) {
Santos Cordon23baed32014-06-27 14:45:39 -070057 if (msg.obj instanceof MainThreadRequest) {
58 MainThreadRequest request = (MainThreadRequest) msg.obj;
59 Object result = null;
60 switch (msg.what) {
61 case MSG_SILENCE_RINGER:
62 mCallsManager.getRinger().silence();
63 break;
64 case MSG_SHOW_CALL_SCREEN:
65 mCallsManager.getInCallController().bringToForeground(msg.arg1 == 1);
66 break;
67 case MSG_IS_IN_A_PHONE_CALL:
68 result = mCallsManager.hasAnyCalls();
69 break;
70 case MSG_IS_RINGING:
71 result = mCallsManager.hasRingingCall();
72 break;
73 case MSG_END_CALL:
74 result = endCallInternal();
75 break;
76 case MSG_ACCEPT_RINGING_CALL:
77 acceptRingingCallInternal();
78 break;
Santos Cordon64c7e962014-07-02 15:15:27 -070079 case MSG_CANCEL_MISSED_CALLS_NOTIFICATION:
80 mMissedCallNotifier.clearMissedCalls();
81 break;
Santos Cordon23baed32014-06-27 14:45:39 -070082 }
83
84 if (result != null) {
85 request.result = result;
86 synchronized(request) {
87 request.notifyAll();
88 }
89 }
Santos Cordonb64c1502014-05-21 21:21:49 -070090 }
91 }
Santos Cordon23baed32014-06-27 14:45:39 -070092 }
Santos Cordonb64c1502014-05-21 21:21:49 -070093
Santos Cordon7da72ef2014-06-25 15:50:22 -070094 /** Private constructor; @see init() */
Santos Cordon23baed32014-06-27 14:45:39 -070095 private static final String TAG = TelecommServiceImpl.class.getSimpleName();
96
97 private static final String SERVICE_NAME = "telecomm";
98
99 private static final int MSG_SILENCE_RINGER = 1;
100 private static final int MSG_SHOW_CALL_SCREEN = 2;
101 private static final int MSG_IS_IN_A_PHONE_CALL = 3;
102 private static final int MSG_IS_RINGING = 4;
103 private static final int MSG_END_CALL = 5;
104 private static final int MSG_ACCEPT_RINGING_CALL = 6;
Santos Cordon64c7e962014-07-02 15:15:27 -0700105 private static final int MSG_CANCEL_MISSED_CALLS_NOTIFICATION = 7;
Santos Cordon23baed32014-06-27 14:45:39 -0700106
107 /** The singleton instance. */
108 private static TelecommServiceImpl sInstance;
109
110 private final CallsManager mCallsManager = CallsManager.getInstance();
Santos Cordon64c7e962014-07-02 15:15:27 -0700111 private final MissedCallNotifier mMissedCallNotifier;
Santos Cordon23baed32014-06-27 14:45:39 -0700112 private final MainThreadHandler mMainThreadHandler = new MainThreadHandler();
113
Santos Cordon64c7e962014-07-02 15:15:27 -0700114 private TelecommServiceImpl(MissedCallNotifier missedCallNotifier) {
115 mMissedCallNotifier = missedCallNotifier;
Santos Cordon7da72ef2014-06-25 15:50:22 -0700116 publish();
117 }
118
Santos Cordonb64c1502014-05-21 21:21:49 -0700119 /**
120 * Initialize the singleton TelecommServiceImpl instance.
121 * This is only done once, at startup, from TelecommApp.onCreate().
122 */
Santos Cordon64c7e962014-07-02 15:15:27 -0700123 static TelecommServiceImpl init(MissedCallNotifier missedCallNotifier) {
Santos Cordonb64c1502014-05-21 21:21:49 -0700124 synchronized (TelecommServiceImpl.class) {
125 if (sInstance == null) {
Santos Cordon64c7e962014-07-02 15:15:27 -0700126 sInstance = new TelecommServiceImpl(missedCallNotifier);
Santos Cordonb64c1502014-05-21 21:21:49 -0700127 } else {
128 Log.wtf(TAG, "init() called multiple times! sInstance %s", sInstance);
129 }
130 return sInstance;
131 }
132 }
133
Santos Cordonb64c1502014-05-21 21:21:49 -0700134 //
Santos Cordon23baed32014-06-27 14:45:39 -0700135 // Implementation of the ITelecommService interface.
Santos Cordonb64c1502014-05-21 21:21:49 -0700136 //
137
138 @Override
Santos Cordon7da72ef2014-06-25 15:50:22 -0700139 public List<Subscription> getSubscriptions() {
140 return sSubscriptions;
141 }
142
143 @Override
144 public void setEnabled(Subscription subscription, boolean enabled) {
145 // Enforce MODIFY_PHONE_STATE ?
146 // TODO
147 }
148
149 @Override
150 public void setSystemDefault(Subscription subscription) {
151 // Enforce MODIFY_PHONE_STATE ?
152 // TODO
153 }
154
Santos Cordon23baed32014-06-27 14:45:39 -0700155 /**
156 * @see TelecommManager#silenceringer
157 */
Santos Cordon7da72ef2014-06-25 15:50:22 -0700158 @Override
Santos Cordonb64c1502014-05-21 21:21:49 -0700159 public void silenceRinger() {
160 Log.d(this, "silenceRinger");
Santos Cordonb64c1502014-05-21 21:21:49 -0700161 enforceModifyPermission();
Santos Cordon23baed32014-06-27 14:45:39 -0700162 sendRequestAsync(MSG_SILENCE_RINGER, 0);
Santos Cordonb64c1502014-05-21 21:21:49 -0700163 }
164
Santos Cordon23baed32014-06-27 14:45:39 -0700165 /**
166 * @see TelecommManager#getDefaultPhoneApp
167 */
Santos Cordon7da72ef2014-06-25 15:50:22 -0700168 @Override
169 public ComponentName getDefaultPhoneApp() {
170 Resources resources = TelecommApp.getInstance().getResources();
171 return new ComponentName(
172 resources.getString(R.string.ui_default_package),
173 resources.getString(R.string.dialer_default_class));
174 }
175
Santos Cordon23baed32014-06-27 14:45:39 -0700176 /**
177 * @see TelecommManager#isInAPhoneCall
178 */
179 @Override
180 public boolean isInAPhoneCall() {
181 enforceReadPermission();
182 return (boolean) sendRequest(MSG_IS_IN_A_PHONE_CALL);
183 }
184
185 /**
186 * @see TelecommManager#isRinging
187 */
188 @Override
189 public boolean isRinging() {
190 enforceReadPermission();
191 return (boolean) sendRequest(MSG_IS_RINGING);
192 }
193
194 /**
195 * @see TelecommManager#endCall
196 */
197 @Override
198 public boolean endCall() {
199 enforceModifyPermission();
200 return (boolean) sendRequest(MSG_END_CALL);
201 }
202
203 /**
204 * @see TelecommManager#acceptRingingCall
205 */
206 @Override
207 public void acceptRingingCall() {
208 enforceModifyPermission();
209 sendRequestAsync(MSG_ACCEPT_RINGING_CALL, 0);
210 }
211
212 @Override
213 public void showCallScreen(boolean showDialpad) {
Santos Cordon64c7e962014-07-02 15:15:27 -0700214 sendRequestAsync(MSG_SHOW_CALL_SCREEN, showDialpad ? 1 : 0);
215 }
216
217 @Override
218 public void cancelMissedCallsNotification() {
219 sendRequestAsync(MSG_CANCEL_MISSED_CALLS_NOTIFICATION, 0);
Santos Cordon23baed32014-06-27 14:45:39 -0700220 }
221
Santos Cordon7da72ef2014-06-25 15:50:22 -0700222 //
223 // Supporting methods for the ITelephony interface implementation.
224 //
225
Santos Cordon23baed32014-06-27 14:45:39 -0700226 private void acceptRingingCallInternal() {
227 Call call = mCallsManager.getFirstCallWithState(CallState.RINGING);
228 if (call != null) {
229 call.answer();
230 }
231 }
232
233 private boolean endCallInternal() {
234 // Always operate on the foreground call if one exists, otherwise get the first call in
235 // priority order by call-state.
236 Call call = mCallsManager.getForegroundCall();
237 if (call == null) {
238 call = mCallsManager.getFirstCallWithState(
239 CallState.ACTIVE,
240 CallState.DIALING,
241 CallState.RINGING,
242 CallState.ON_HOLD);
243 }
244
245 if (call != null) {
246 if (call.getState() == CallState.RINGING) {
247 call.reject(false /* rejectWithMessage */, null);
248 } else {
249 call.disconnect();
250 }
251 return true;
252 }
253
254 return false;
Santos Cordonb64c1502014-05-21 21:21:49 -0700255 }
256
257 /**
258 * Make sure the caller has the MODIFY_PHONE_STATE permission.
259 *
260 * @throws SecurityException if the caller does not have the required permission
261 */
262 private void enforceModifyPermission() {
263 TelecommApp.getInstance().enforceCallingOrSelfPermission(
264 android.Manifest.permission.MODIFY_PHONE_STATE, null);
265 }
Santos Cordonf3671a62014-05-29 21:51:53 -0700266
Santos Cordon23baed32014-06-27 14:45:39 -0700267 private void enforceReadPermission() {
268 TelecommApp.getInstance().enforceCallingOrSelfPermission(
269 android.Manifest.permission.READ_PHONE_STATE, null);
Santos Cordonf3671a62014-05-29 21:51:53 -0700270 }
Yorke Leeceb96c92014-06-11 16:34:44 -0700271
Ihab Awad60509462014-06-14 16:43:08 -0700272 // TODO (STOPSHIP): Static list of Subscriptions for testing and UX work only.
273
274 private static final ComponentName sComponentName = new ComponentName(
275 "com.android.telecomm",
276 TelecommServiceImpl.class.getName()); // This field is a no-op
277
278 private static final List<Subscription> sSubscriptions = Lists.newArrayList(
279 new Subscription(
280 sComponentName,
281 "subscription0",
282 Uri.parse("tel:999-555-1212"),
283 R.string.test_subscription_0_label,
284 R.string.test_subscription_0_short_description,
285 R.drawable.q_mobile,
286 true,
287 true),
288 new Subscription(
289 sComponentName,
290 "subscription1",
291 Uri.parse("tel:333-111-2222"),
292 R.string.test_subscription_1_label,
293 R.string.test_subscription_1_short_description,
294 R.drawable.market_wireless,
295 true,
296 false),
297 new Subscription(
298 sComponentName,
299 "subscription2",
300 Uri.parse("mailto:two@example.com"),
301 R.string.test_subscription_2_label,
302 R.string.test_subscription_2_short_description,
303 R.drawable.talk_to_your_circles,
304 true,
305 false),
306 new Subscription(
307 sComponentName,
308 "subscription3",
309 Uri.parse("mailto:three@example.com"),
310 R.string.test_subscription_3_label,
311 R.string.test_subscription_3_short_description,
312 R.drawable.chat_with_others,
313 true,
314 false)
315 );
316
Santos Cordon7da72ef2014-06-25 15:50:22 -0700317 private void publish() {
318 Log.d(this, "publish: %s", this);
319 ServiceManager.addService(SERVICE_NAME, this);
Ihab Awad60509462014-06-14 16:43:08 -0700320 }
Santos Cordon23baed32014-06-27 14:45:39 -0700321
322 private MainThreadRequest sendRequestAsync(int command, int arg1) {
323 MainThreadRequest request = new MainThreadRequest();
324 mMainThreadHandler.obtainMessage(command, arg1, 0, request).sendToTarget();
325 return request;
326 }
327
328 /**
329 * Posts the specified command to be executed on the main thread, waits for the request to
330 * complete, and returns the result.
331 */
332 private Object sendRequest(int command) {
333 if (Looper.myLooper() == mMainThreadHandler.getLooper()) {
334 throw new RuntimeException("This method will deadlock if called from the main thread.");
335 }
336
337 MainThreadRequest request = sendRequestAsync(command, 0);
338
339 // 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 }
347 }
348 }
349 return request.result;
350 }
Santos Cordonb64c1502014-05-21 21:21:49 -0700351}