• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 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 package com.android.car.dialer;
17 
18 import android.content.Intent;
19 import android.graphics.PorterDuff;
20 import android.graphics.drawable.Drawable;
21 import android.os.Bundle;
22 import android.support.v4.app.Fragment;
23 import android.telecom.Call;
24 import android.telephony.PhoneNumberUtils;
25 import android.util.Log;
26 
27 import com.android.car.app.CarDrawerActivity;
28 import com.android.car.app.CarDrawerAdapter;
29 import com.android.car.app.DrawerItemViewHolder;
30 import com.android.car.dialer.bluetooth.UiBluetoothMonitor;
31 import com.android.car.dialer.telecom.PhoneLoader;
32 import com.android.car.dialer.telecom.UiCall;
33 import com.android.car.dialer.telecom.UiCallManager;
34 import com.android.car.dialer.telecom.UiCallManager.CallListener;
35 
36 import java.util.List;
37 
38 /**
39  * Main activity for the Dialer app. Displays different fragments depending on call and
40  * connectivity status:
41  * <ul>
42  * <li>OngoingCallFragment
43  * <li>NoHfpFragment
44  * <li>DialerFragment
45  * <li>StrequentFragment
46  * </ul>
47  */
48 public class TelecomActivity extends CarDrawerActivity implements
49         DialerFragment.DialerBackButtonListener {
50     private static final String TAG = "Em.TelecomActivity";
51 
52     private static final String ACTION_ANSWER_CALL = "com.android.car.dialer.ANSWER_CALL";
53     private static final String ACTION_END_CALL = "com.android.car.dialer.END_CALL";
54     private static final String DIALER_BACKSTACK = "DialerBackstack";
55     private static final String FRAGMENT_CLASS_KEY = "FRAGMENT_CLASS_KEY";
56 
57     private final UiBluetoothMonitor.Listener mBluetoothListener = this::updateCurrentFragment;
58 
59     private UiCallManager mUiCallManager;
60     private UiBluetoothMonitor mUiBluetoothMonitor;
61 
62     private Fragment mCurrentFragment;
63     private String mCurrentFragmentName;
64 
65     private int mLastNoHfpMessageId;
66     private StrequentsFragment mSpeedDialFragment;
67     private Fragment mOngoingCallFragment;
68 
69     private DialerFragment mDialerFragment;
70     private boolean mDialerFragmentOpened;
71 
72     @Override
onCreate(Bundle savedInstanceState)73     protected void onCreate(Bundle savedInstanceState) {
74         super.onCreate(savedInstanceState);
75 
76         if (vdebug()) {
77             Log.d(TAG, "onCreate");
78         }
79         getWindow().getDecorView().setBackgroundColor(getColor(R.color.phone_theme));
80         setTitle(getString(R.string.phone_app_name));
81 
82         mUiCallManager = UiCallManager.getInstance(this);
83         mUiBluetoothMonitor = UiBluetoothMonitor.getInstance();
84 
85         if (savedInstanceState != null) {
86             mCurrentFragmentName = savedInstanceState.getString(FRAGMENT_CLASS_KEY);
87         }
88 
89         if (vdebug()) {
90             Log.d(TAG, "onCreate done, mCurrentFragmentName:  " + mCurrentFragmentName);
91         }
92     }
93 
94     @Override
onDestroy()95     protected void onDestroy() {
96         super.onDestroy();
97         if (vdebug()) {
98             Log.d(TAG, "onDestroy");
99         }
100         mUiCallManager = null;
101     }
102 
103     @Override
onPause()104     protected void onPause() {
105         super.onPause();
106         mUiCallManager.removeListener(mCarCallListener);
107         mUiBluetoothMonitor.removeListener(mBluetoothListener);
108     }
109 
110     @Override
onSaveInstanceState(Bundle outState)111     public void onSaveInstanceState(Bundle outState) {
112         if (mCurrentFragment != null) {
113             outState.putString(FRAGMENT_CLASS_KEY, mCurrentFragmentName);
114         }
115         super.onSaveInstanceState(outState);
116     }
117 
118     @Override
onNewIntent(Intent i)119     protected void onNewIntent(Intent i) {
120         super.onNewIntent(i);
121         setIntent(i);
122     }
123 
124     @Override
onResume()125     protected void onResume() {
126         if (vdebug()) {
127             Log.d(TAG, "onResume");
128         }
129         super.onResume();
130 
131         // Update the current fragment before handling the intent so that any UI updates in
132         // handleIntent() is not overridden by updateCurrentFragment().
133         updateCurrentFragment();
134         handleIntent();
135 
136         mUiCallManager.addListener(mCarCallListener);
137         mUiBluetoothMonitor.addListener(mBluetoothListener);
138     }
139 
140     // TODO: move to base class.
setContentFragmentWithAnimations(Fragment fragment, int enter, int exit)141     private void setContentFragmentWithAnimations(Fragment fragment, int enter, int exit) {
142         if (vdebug()) {
143             Log.d(TAG, "setContentFragmentWithAnimations: " + fragment);
144         }
145 
146         maybeHideDialer();
147 
148         getSupportFragmentManager().beginTransaction()
149                 .setCustomAnimations(enter, exit)
150                 .replace(getContentContainerId(), fragment)
151                 .commitAllowingStateLoss();
152 
153         mCurrentFragmentName = fragment.getClass().getSimpleName();
154         mCurrentFragment = fragment;
155 
156         if (vdebug()) {
157             Log.d(TAG, "setContentFragmentWithAnimations, fragmentName:" + mCurrentFragmentName);
158         }
159     }
160 
handleIntent()161     private void handleIntent() {
162         Intent intent = getIntent();
163         String action = intent != null ? intent.getAction() : null;
164 
165         if (vdebug()) {
166             Log.d(TAG, "handleIntent, intent: " + intent + ", action: " + action);
167         }
168 
169         if (action == null || action.length() == 0) {
170             return;
171         }
172 
173         UiCall ringingCall;
174         switch (action) {
175             case ACTION_ANSWER_CALL:
176                 ringingCall = mUiCallManager.getCallWithState(Call.STATE_RINGING);
177                 if (ringingCall == null) {
178                     Log.e(TAG, "Unable to answer ringing call. There is none.");
179                 } else {
180                     mUiCallManager.answerCall(ringingCall);
181                 }
182                 break;
183 
184             case ACTION_END_CALL:
185                 ringingCall = mUiCallManager.getCallWithState(Call.STATE_RINGING);
186                 if (ringingCall == null) {
187                     Log.e(TAG, "Unable to end ringing call. There is none.");
188                 } else {
189                     mUiCallManager.disconnectCall(ringingCall);
190                 }
191                 break;
192 
193             case Intent.ACTION_DIAL:
194                 String number = PhoneNumberUtils.getNumberFromIntent(intent, this);
195                 if (!(mCurrentFragment instanceof NoHfpFragment)) {
196                     showDialerWithNumber(number);
197                 }
198                 break;
199 
200             default:
201                 // Do nothing.
202         }
203 
204         setIntent(null);
205     }
206 
207     /**
208      * Will switch to the drawer or no-hfp fragment as necessary.
209      */
updateCurrentFragment()210     private void updateCurrentFragment() {
211         if (vdebug()) {
212             Log.d(TAG, "updateCurrentFragment");
213         }
214 
215         // TODO: do nothing when activity isFinishing() == true.
216 
217         boolean callEmpty = mUiCallManager.getCalls().isEmpty();
218         if (!mUiBluetoothMonitor.isBluetoothEnabled() && callEmpty) {
219             showNoHfpFragment(R.string.bluetooth_disabled);
220         } else if (!mUiBluetoothMonitor.isBluetoothPaired() && callEmpty) {
221             showNoHfpFragment(R.string.bluetooth_unpaired);
222         } else if (!mUiBluetoothMonitor.isHfpConnected() && callEmpty) {
223             showNoHfpFragment(R.string.no_hfp);
224         } else {
225             UiCall ongoingCall = mUiCallManager.getPrimaryCall();
226 
227             if (vdebug()) {
228                 Log.d(TAG, "ongoingCall: " + ongoingCall + ", mCurrentFragment: "
229                         + mCurrentFragment);
230             }
231 
232             if (ongoingCall == null && mCurrentFragment instanceof OngoingCallFragment) {
233                 showSpeedDialFragment();
234             } else if (ongoingCall != null) {
235                 showOngoingCallFragment();
236             } else if (DialerFragment.class.getSimpleName().equals(mCurrentFragmentName)) {
237                 showDialer();
238             } else {
239                 showSpeedDialFragment();
240             }
241         }
242 
243         if (vdebug()) {
244             Log.d(TAG, "updateCurrentFragment: done");
245         }
246     }
247 
showSpeedDialFragment()248     private void showSpeedDialFragment() {
249         if (vdebug()) {
250             Log.d(TAG, "showSpeedDialFragment");
251         }
252 
253         if (mCurrentFragment instanceof StrequentsFragment) {
254             return;
255         }
256 
257         if (mSpeedDialFragment == null) {
258             mSpeedDialFragment = new StrequentsFragment();
259             Bundle args = new Bundle();
260             mSpeedDialFragment.setArguments(args);
261         }
262 
263         if (mCurrentFragment instanceof DialerFragment) {
264             setContentFragmentWithSlideAndDelayAnimation(mSpeedDialFragment);
265         } else {
266             setContentFragmentWithFadeAnimation(mSpeedDialFragment);
267         }
268     }
269 
showOngoingCallFragment()270     private void showOngoingCallFragment() {
271         if (vdebug()) {
272             Log.d(TAG, "showOngoingCallFragment");
273         }
274         if (mCurrentFragment instanceof OngoingCallFragment) {
275             closeDrawer();
276             return;
277         }
278 
279         if (mOngoingCallFragment == null) {
280             mOngoingCallFragment = new OngoingCallFragment();
281         }
282 
283         setContentFragmentWithFadeAnimation(mOngoingCallFragment);
284         closeDrawer();
285     }
286 
287     /**
288      * Displays the {@link DialerFragment} on top of the contents of the TelecomActivity.
289      */
showDialer()290     private void showDialer() {
291         if (vdebug()) {
292             Log.d(TAG, "showDialer");
293         }
294 
295         if (mDialerFragmentOpened) {
296             return;
297         }
298 
299         if (mDialerFragment == null) {
300             if (Log.isLoggable(TAG, Log.VERBOSE)) {
301                 Log.v(TAG, "showDialer: creating dialer");
302             }
303 
304             mDialerFragment = new DialerFragment();
305             mDialerFragment.setDialerBackButtonListener(this);
306         }
307 
308         if (Log.isLoggable(TAG, Log.VERBOSE)) {
309             Log.v(TAG, "adding dialer to fragment backstack");
310         }
311 
312         // Add the dialer fragment to the backstack so that it can be popped off to dismiss it.
313         getSupportFragmentManager().beginTransaction()
314                 .setCustomAnimations(R.anim.telecom_slide_in, R.anim.telecom_slide_out,
315                         R.anim.telecom_slide_in, R.anim.telecom_slide_out)
316                 .add(getContentContainerId(), mDialerFragment)
317                 .addToBackStack(DIALER_BACKSTACK)
318                 .commitAllowingStateLoss();
319 
320         mDialerFragmentOpened = true;
321 
322         if (Log.isLoggable(TAG, Log.VERBOSE)) {
323             Log.v(TAG, "done adding fragment to backstack");
324         }
325     }
326 
327     /**
328      * Checks if the dialpad fragment is opened and hides it if it is.
329      */
maybeHideDialer()330     private void maybeHideDialer() {
331         if (mDialerFragmentOpened) {
332             // Dismiss the dialer by removing it from the back stack.
333             getSupportFragmentManager().popBackStack();
334             mDialerFragmentOpened = false;
335         }
336     }
337 
338     @Override
onDialerBackClick()339     public void onDialerBackClick() {
340         maybeHideDialer();
341     }
342 
showDialerWithNumber(String number)343     private void showDialerWithNumber(String number) {
344         showDialer();
345         mDialerFragment.setDialNumber(number);
346     }
347 
showNoHfpFragment(int stringResId)348     private void showNoHfpFragment(int stringResId) {
349         if (mCurrentFragment instanceof NoHfpFragment && stringResId == mLastNoHfpMessageId) {
350             return;
351         }
352 
353         mLastNoHfpMessageId = stringResId;
354         String errorMessage = getString(stringResId);
355         NoHfpFragment frag = new NoHfpFragment();
356         frag.setErrorMessage(errorMessage);
357         setContentFragment(frag);
358         mCurrentFragment = frag;
359     }
360 
setContentFragmentWithSlideAndDelayAnimation(Fragment fragment)361     private void setContentFragmentWithSlideAndDelayAnimation(Fragment fragment) {
362         if (vdebug()) {
363             Log.d(TAG, "setContentFragmentWithSlideAndDelayAnimation, fragment: " + fragment);
364         }
365         setContentFragmentWithAnimations(fragment,
366                 R.anim.telecom_slide_in_with_delay, R.anim.telecom_slide_out);
367     }
368 
setContentFragmentWithFadeAnimation(Fragment fragment)369     private void setContentFragmentWithFadeAnimation(Fragment fragment) {
370         if (vdebug()) {
371             Log.d(TAG, "setContentFragmentWithFadeAnimation, fragment: " + fragment);
372         }
373         setContentFragmentWithAnimations(fragment,
374                 R.anim.telecom_fade_in, R.anim.telecom_fade_out);
375     }
376 
377     private final CallListener mCarCallListener = new UiCallManager.CallListener() {
378         @Override
379         public void onCallAdded(UiCall call) {
380             if (vdebug()) {
381                 Log.d(TAG, "onCallAdded");
382             }
383             updateCurrentFragment();
384         }
385 
386         @Override
387         public void onCallRemoved(UiCall call) {
388             if (vdebug()) {
389                 Log.d(TAG, "onCallRemoved");
390             }
391             updateCurrentFragment();
392         }
393 
394         @Override
395         public void onStateChanged(UiCall call, int state) {
396             if (vdebug()) {
397                 Log.d(TAG, "onStateChanged");
398             }
399             updateCurrentFragment();
400         }
401 
402         @Override
403         public void onCallUpdated(UiCall call) {
404             if (vdebug()) {
405                 Log.d(TAG, "onCallUpdated");
406             }
407             updateCurrentFragment();
408         }
409     };
410 
setContentFragment(Fragment fragment)411     private void setContentFragment(Fragment fragment) {
412         getSupportFragmentManager().beginTransaction()
413                 .replace(getContentContainerId(), fragment)
414                 .commit();
415     }
416 
vdebug()417     private static boolean vdebug() {
418         return Log.isLoggable(TAG, Log.DEBUG);
419     }
420 
421     @Override
getRootAdapter()422     protected CarDrawerAdapter getRootAdapter() {
423         return new DialerRootAdapter();
424     }
425 
426     class CallLogAdapter extends CarDrawerAdapter {
427         private List<CallLogListingTask.CallLogItem> mItems;
428 
CallLogAdapter(int titleResId, List<CallLogListingTask.CallLogItem> items)429         public CallLogAdapter(int titleResId, List<CallLogListingTask.CallLogItem> items) {
430             super(TelecomActivity.this,
431                     true  /* showDisabledListOnEmpty */,
432                     false /* useSmallLayout */);
433             setTitle(getString(titleResId));
434             mItems = items;
435         }
436 
437         @Override
getActualItemCount()438         protected int getActualItemCount() {
439             return mItems.size();
440         }
441 
442         @Override
populateViewHolder(DrawerItemViewHolder holder, int position)443         public void populateViewHolder(DrawerItemViewHolder holder, int position) {
444             holder.getTitle().setText(mItems.get(position).mTitle);
445             holder.getText().setText(mItems.get(position).mText);
446             holder.getIcon().setImageBitmap(mItems.get(position).mIcon);
447         }
448 
449         @Override
onItemClick(int position)450         public void onItemClick(int position) {
451             closeDrawer();
452             mUiCallManager.safePlaceCall(mItems.get(position).mNumber, false);
453         }
454     }
455 
456     private class DialerRootAdapter extends CarDrawerAdapter {
457         private static final int ITEM_DIAL = 0;
458         private static final int ITEM_CALLLOG_ALL = 1;
459         private static final int ITEM_CALLLOG_MISSED = 2;
460         private static final int ITEM_MAX = 3;
461 
DialerRootAdapter()462         DialerRootAdapter() {
463             super(TelecomActivity.this,
464                     false /* showDisabledListOnEmpty */,
465                     true  /* useSmallLayout */);
466             setTitle(getString(R.string.phone_app_name));
467         }
468 
469         @Override
getActualItemCount()470         protected int getActualItemCount() {
471             return ITEM_MAX;
472         }
473 
474         @Override
populateViewHolder(DrawerItemViewHolder holder, int position)475         public void populateViewHolder(DrawerItemViewHolder holder, int position) {
476             final int iconColor = getResources().getColor(R.color.car_tint);
477             int textResId, iconResId;
478             switch (position) {
479                 case ITEM_DIAL:
480                     textResId = R.string.calllog_dial_number;
481                     iconResId = R.drawable.ic_drawer_dialpad;
482                     break;
483                 case ITEM_CALLLOG_ALL:
484                     textResId = R.string.calllog_all;
485                     iconResId = R.drawable.ic_drawer_history;
486                     break;
487                 case ITEM_CALLLOG_MISSED:
488                     textResId = R.string.calllog_missed;
489                     iconResId = R.drawable.ic_drawer_call_missed;
490                     break;
491                 default:
492                     Log.wtf(TAG, "Unexpected position: " + position);
493                     return;
494             }
495             holder.getTitle().setText(textResId);
496             Drawable drawable = getDrawable(iconResId);
497             drawable.setColorFilter(iconColor, PorterDuff.Mode.SRC_IN);
498             holder.getIcon().setImageDrawable(drawable);
499             if (position > 0) {
500                 drawable = getDrawable(R.drawable.ic_chevron_right);
501                 drawable.setColorFilter(iconColor, PorterDuff.Mode.SRC_IN);
502                 holder.getRightIcon().setImageDrawable(drawable);
503             }
504         }
505 
506         @Override
onItemClick(int position)507         public void onItemClick(int position) {
508             switch (position) {
509                 case ITEM_DIAL:
510                     closeDrawer();
511                     showDialer();
512                     break;
513                 case ITEM_CALLLOG_ALL:
514                     loadCallHistoryAsync(PhoneLoader.CALL_TYPE_ALL, R.string.calllog_all);
515                     break;
516                 case ITEM_CALLLOG_MISSED:
517                     loadCallHistoryAsync(PhoneLoader.CALL_TYPE_MISSED, R.string.calllog_missed);
518                     break;
519                 default:
520                     Log.w(TAG, "Invalid position in ROOT menu! " + position);
521             }
522         }
523     }
524 
loadCallHistoryAsync(final int callType, final int titleResId)525     private void loadCallHistoryAsync(final int callType, final int titleResId) {
526         showLoadingProgressBar(true);
527         // Warning: much callbackiness!
528         // First load up the call log cursor using the PhoneLoader so that happens in a
529         // background thread. TODO: Why isn't PhoneLoader using a LoaderManager?
530         PhoneLoader.registerCallObserver(callType, this,
531             (loader, data) -> {
532                 // This callback runs on the thread that created the loader which is
533                 // the ui thread so spin off another async task because we still need
534                 // to pull together all the data along with the contact photo.
535                 CallLogListingTask task = new CallLogListingTask(TelecomActivity.this, data,
536                     (items) -> {
537                             showLoadingProgressBar(false);
538                             switchToAdapter(new CallLogAdapter(titleResId, items));
539                         });
540                 task.execute();
541             });
542     }
543 }
544