• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 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 
17 package com.android.car.dialer.ui;
18 
19 import android.app.SearchManager;
20 import android.content.Intent;
21 import android.content.SharedPreferences;
22 import android.graphics.drawable.Drawable;
23 import android.os.Bundle;
24 import android.telecom.Call;
25 import android.telephony.PhoneNumberUtils;
26 import android.view.Menu;
27 import android.view.MenuInflater;
28 import android.view.MenuItem;
29 import android.view.View;
30 import android.widget.Toolbar;
31 
32 import androidx.annotation.NonNull;
33 import androidx.annotation.Nullable;
34 import androidx.fragment.app.Fragment;
35 import androidx.fragment.app.FragmentActivity;
36 import androidx.fragment.app.FragmentManager;
37 import androidx.lifecycle.LiveData;
38 import androidx.lifecycle.ViewModelProviders;
39 import androidx.preference.PreferenceManager;
40 
41 import com.android.car.apps.common.util.Themes;
42 import com.android.car.apps.common.widget.CarTabLayout;
43 import com.android.car.dialer.Constants;
44 import com.android.car.dialer.R;
45 import com.android.car.dialer.log.L;
46 import com.android.car.dialer.notification.NotificationService;
47 import com.android.car.dialer.telecom.UiCallManager;
48 import com.android.car.dialer.ui.activecall.InCallActivity;
49 import com.android.car.dialer.ui.activecall.InCallViewModel;
50 import com.android.car.dialer.ui.calllog.CallHistoryFragment;
51 import com.android.car.dialer.ui.common.DialerBaseFragment;
52 import com.android.car.dialer.ui.contact.ContactListFragment;
53 import com.android.car.dialer.ui.dialpad.DialpadFragment;
54 import com.android.car.dialer.ui.favorite.FavoriteFragment;
55 import com.android.car.dialer.ui.search.ContactResultsFragment;
56 import com.android.car.dialer.ui.settings.DialerSettingsActivity;
57 import com.android.car.dialer.ui.warning.NoHfpFragment;
58 
59 import java.util.List;
60 
61 /**
62  * Main activity for the Dialer app. It contains two layers:
63  * <ul>
64  * <li>Overlay layer for {@link NoHfpFragment}
65  * <li>Content layer for {@link FavoriteFragment} {@link CallHistoryFragment} {@link
66  * ContactListFragment} and {@link DialpadFragment}
67  *
68  * <p>Start {@link InCallActivity} if there are ongoing calls
69  *
70  * <p>Based on call and connectivity status, it will choose the right page to display.
71  */
72 public class TelecomActivity extends FragmentActivity implements
73         DialerBaseFragment.DialerFragmentParent, FragmentManager.OnBackStackChangedListener {
74     private static final String TAG = "CD.TelecomActivity";
75 
76     private LiveData<String> mBluetoothErrorMsgLiveData;
77     private LiveData<Integer> mDialerAppStateLiveData;
78     private LiveData<List<Call>> mOngoingCallListLiveData;
79 
80     // View objects for this activity.
81     private CarTabLayout<TelecomPageTab> mTabLayout;
82     private TelecomPageTab.Factory mTabFactory;
83     private Toolbar mToolbar;
84     private View mToolbarContainer;
85 
86     @Override
onCreate(Bundle savedInstanceState)87     protected void onCreate(Bundle savedInstanceState) {
88         super.onCreate(savedInstanceState);
89         L.d(TAG, "onCreate");
90         setContentView(R.layout.telecom_activity);
91 
92         mToolbar = findViewById(R.id.car_toolbar);
93         setActionBar(mToolbar);
94         getActionBar().setLogo(R.drawable.sized_logo);
95 
96         mToolbarContainer = findViewById(R.id.car_toolbar_container);
97 
98         setupTabLayout();
99 
100         TelecomActivityViewModel viewModel = ViewModelProviders.of(this).get(
101                 TelecomActivityViewModel.class);
102         mBluetoothErrorMsgLiveData = viewModel.getErrorMessage();
103         mDialerAppStateLiveData = viewModel.getDialerAppState();
104         mDialerAppStateLiveData.observe(this,
105                 dialerAppState -> updateCurrentFragment(dialerAppState));
106 
107         InCallViewModel inCallViewModel = ViewModelProviders.of(this).get(InCallViewModel.class);
108         mOngoingCallListLiveData = inCallViewModel.getOngoingCallList();
109         // The mOngoingCallListLiveData needs to be active to get calculated.
110         mOngoingCallListLiveData.observe(this, this::maybeStartInCallActivity);
111 
112         handleIntent();
113     }
114 
115     @Override
onStart()116     public void onStart() {
117         getSupportFragmentManager().addOnBackStackChangedListener(this);
118         onBackStackChanged();
119         super.onStart();
120         L.d(TAG, "onStart");
121     }
122 
123     @Override
onStop()124     public void onStop() {
125         super.onStop();
126         L.d(TAG, "onStop");
127         getSupportFragmentManager().removeOnBackStackChangedListener(this);
128     }
129 
130     @Override
onNewIntent(Intent i)131     protected void onNewIntent(Intent i) {
132         super.onNewIntent(i);
133         setIntent(i);
134         handleIntent();
135     }
136 
137     @Override
setBackground(Drawable background)138     public void setBackground(Drawable background) {
139         findViewById(android.R.id.content).setBackground(background);
140     }
141 
handleIntent()142     private void handleIntent() {
143         Intent intent = getIntent();
144         String action = intent != null ? intent.getAction() : null;
145         L.d(TAG, "handleIntent, intent: %s, action: %s", intent, action);
146         if (action == null || action.length() == 0) {
147             return;
148         }
149 
150         String number;
151         switch (action) {
152             case Intent.ACTION_DIAL:
153                 number = PhoneNumberUtils.getNumberFromIntent(intent, this);
154                 if (TelecomActivityViewModel.DialerAppState.BLUETOOTH_ERROR
155                         != mDialerAppStateLiveData.getValue()) {
156                     showDialPadFragment(number);
157                 }
158                 break;
159 
160             case Intent.ACTION_CALL:
161                 number = PhoneNumberUtils.getNumberFromIntent(intent, this);
162                 UiCallManager.get().placeCall(number);
163                 break;
164 
165             case Intent.ACTION_SEARCH:
166                 String searchQuery = intent.getStringExtra(SearchManager.QUERY);
167                 navigateToContactResultsFragment(searchQuery);
168                 break;
169 
170             case Constants.Intents.ACTION_SHOW_PAGE:
171                 if (TelecomActivityViewModel.DialerAppState.BLUETOOTH_ERROR
172                         != mDialerAppStateLiveData.getValue()) {
173                     showTabPage(intent.getStringExtra(Constants.Intents.EXTRA_SHOW_PAGE));
174                     if (intent.getBooleanExtra(Constants.Intents.EXTRA_ACTION_READ_MISSED, false)) {
175                         NotificationService.readAllMissedCall(this);
176                     }
177                 }
178                 break;
179 
180             default:
181                 // Do nothing.
182         }
183 
184         setIntent(null);
185 
186         // This is to start the incall activity when user taps on the dialer launch icon rapidly
187         maybeStartInCallActivity(mOngoingCallListLiveData.getValue());
188     }
189 
190     /**
191      * Update the current visible fragment of this Activity based on the state of the application.
192      * <ul>
193      * <li> If bluetooth is not connected or there is an active call, show overlay, lock drawer,
194      * hide action bar and hide the content layer.
195      * <li> Otherwise, show the content layer, show action bar, hide the overlay and reset drawer
196      * lock mode.
197      */
updateCurrentFragment( @elecomActivityViewModel.DialerAppState int dialerAppState)198     private void updateCurrentFragment(
199             @TelecomActivityViewModel.DialerAppState int dialerAppState) {
200         L.d(TAG, "updateCurrentFragment, dialerAppState: %d", dialerAppState);
201 
202         boolean isOverlayFragmentVisible =
203                 TelecomActivityViewModel.DialerAppState.DEFAULT != dialerAppState;
204         findViewById(R.id.content_container)
205                 .setVisibility(isOverlayFragmentVisible ? View.GONE : View.VISIBLE);
206         findViewById(R.id.overlay_container)
207                 .setVisibility(isOverlayFragmentVisible ? View.VISIBLE : View.GONE);
208 
209         switch (dialerAppState) {
210             case TelecomActivityViewModel.DialerAppState.BLUETOOTH_ERROR:
211                 showNoHfpOverlay(mBluetoothErrorMsgLiveData.getValue());
212                 break;
213 
214             case TelecomActivityViewModel.DialerAppState.EMERGENCY_DIALPAD:
215                 setOverlayFragment(DialpadFragment.newEmergencyDialpad());
216                 break;
217 
218             case TelecomActivityViewModel.DialerAppState.DEFAULT:
219             default:
220                 clearOverlayFragment();
221                 break;
222         }
223     }
224 
showNoHfpOverlay(String errorMsg)225     private void showNoHfpOverlay(String errorMsg) {
226         Fragment overlayFragment = getCurrentOverlayFragment();
227         if (overlayFragment instanceof NoHfpFragment) {
228             ((NoHfpFragment) overlayFragment).setErrorMessage(errorMsg);
229         } else {
230             setOverlayFragment(NoHfpFragment.newInstance(errorMsg));
231         }
232     }
233 
setOverlayFragment(@onNull Fragment overlayFragment)234     private void setOverlayFragment(@NonNull Fragment overlayFragment) {
235         L.d(TAG, "setOverlayFragment: %s", overlayFragment);
236 
237         getSupportFragmentManager()
238                 .beginTransaction()
239                 .replace(R.id.overlay_container, overlayFragment)
240                 .commitNow();
241     }
242 
clearOverlayFragment()243     private void clearOverlayFragment() {
244         L.d(TAG, "clearOverlayFragment");
245 
246         Fragment overlayFragment = getCurrentOverlayFragment();
247         if (overlayFragment == null) {
248             return;
249         }
250 
251         getSupportFragmentManager()
252                 .beginTransaction()
253                 .remove(overlayFragment)
254                 .commitNow();
255     }
256 
257     /** Returns the fragment that is currently being displayed as the overlay view on top. */
258     @Nullable
getCurrentOverlayFragment()259     private Fragment getCurrentOverlayFragment() {
260         return getSupportFragmentManager().findFragmentById(R.id.overlay_container);
261     }
262 
setupTabLayout()263     private void setupTabLayout() {
264         mTabLayout = findViewById(R.id.tab_layout);
265 
266         boolean hasContentFragment = false;
267 
268         mTabFactory = new TelecomPageTab.Factory(this, getSupportFragmentManager());
269         for (int i = 0; i < mTabFactory.getTabCount(); i++) {
270             TelecomPageTab telecomPageTab = mTabFactory.createTab(getBaseContext(), i);
271             mTabLayout.addCarTab(telecomPageTab);
272 
273             if (telecomPageTab.wasFragmentRestored()) {
274                 mTabLayout.selectCarTab(i);
275                 hasContentFragment = true;
276             }
277         }
278 
279         // Select the starting tab and set up the fragment for it.
280         if (!hasContentFragment) {
281             int startTabIndex = getTabFromSharedPreference();
282             TelecomPageTab startTab = mTabLayout.get(startTabIndex);
283             mTabLayout.selectCarTab(startTabIndex);
284             setContentFragment(startTab.getFragment(), startTab.getFragmentTag());
285         }
286 
287         mTabLayout.addOnCarTabSelectedListener(
288                 new CarTabLayout.SimpleOnCarTabSelectedListener<TelecomPageTab>() {
289                     @Override
290                     public void onCarTabSelected(TelecomPageTab telecomPageTab) {
291                         Fragment fragment = telecomPageTab.getFragment();
292                         setContentFragment(fragment, telecomPageTab.getFragmentTag());
293                     }
294                 });
295     }
296 
297     /** Switch to {@link DialpadFragment} and set the given number as dialed number. */
showDialPadFragment(String number)298     private void showDialPadFragment(String number) {
299         int dialpadTabIndex = showTabPage(TelecomPageTab.Page.DIAL_PAD);
300 
301         if (dialpadTabIndex == -1) {
302             return;
303         }
304 
305         TelecomPageTab dialpadTab = mTabLayout.get(dialpadTabIndex);
306         Fragment fragment = dialpadTab.getFragment();
307         if (fragment instanceof DialpadFragment) {
308             ((DialpadFragment) fragment).setDialedNumber(number);
309         } else {
310             L.w(TAG, "Current tab is not a dialpad fragment!");
311         }
312     }
313 
showTabPage(@elecomPageTab.Page String tabPage)314     private int showTabPage(@TelecomPageTab.Page String tabPage) {
315         int tabIndex = mTabFactory.getTabIndex(tabPage);
316         if (tabIndex == -1) {
317             L.w(TAG, "Page %s is not a tab.", tabPage);
318             return -1;
319         }
320         getSupportFragmentManager().executePendingTransactions();
321         while (getSupportFragmentManager().getBackStackEntryCount() > 1) {
322             getSupportFragmentManager().popBackStackImmediate();
323         }
324 
325         mTabLayout.selectCarTab(tabIndex);
326         return tabIndex;
327     }
328 
setContentFragment(Fragment fragment, String fragmentTag)329     private void setContentFragment(Fragment fragment, String fragmentTag) {
330         L.d(TAG, "setContentFragment: %s", fragment);
331 
332         getSupportFragmentManager().executePendingTransactions();
333         while (getSupportFragmentManager().getBackStackEntryCount() > 0) {
334             getSupportFragmentManager().popBackStackImmediate();
335         }
336 
337         getSupportFragmentManager()
338                 .beginTransaction()
339                 .replace(R.id.content_fragment_container, fragment, fragmentTag)
340                 .addToBackStack(fragmentTag)
341                 .commit();
342     }
343 
344     @Override
pushContentFragment(@onNull Fragment topContentFragment, String fragmentTag)345     public void pushContentFragment(@NonNull Fragment topContentFragment, String fragmentTag) {
346         L.d(TAG, "pushContentFragment: %s", topContentFragment);
347 
348         getSupportFragmentManager()
349                 .beginTransaction()
350                 .replace(R.id.content_fragment_container, topContentFragment)
351                 .addToBackStack(fragmentTag)
352                 .commit();
353     }
354 
355     @Override
onBackStackChanged()356     public void onBackStackChanged() {
357         boolean isBackNavigationAvailable = isBackNavigationAvailable();
358         mTabLayout.setVisibility(isBackNavigationAvailable ? View.GONE : View.VISIBLE);
359         int displayOptions = Themes.getAttrInteger(
360                 this,
361                 isBackNavigationAvailable ? R.style.HomeAsUpDisplayOptions
362                         : R.style.RootToolbarDisplayOptions,
363                 android.R.attr.displayOptions);
364         getActionBar().setDisplayOptions(displayOptions);
365     }
366 
367     @Override
onNavigateUp()368     public boolean onNavigateUp() {
369         if (isBackNavigationAvailable()) {
370             onBackPressed();
371             return true;
372         }
373         return super.onNavigateUp();
374     }
375 
376     @Override
onCreateOptionsMenu(Menu menu)377     public boolean onCreateOptionsMenu(Menu menu) {
378         MenuInflater inflater = getMenuInflater();
379         inflater.inflate(R.menu.main_menu, menu);
380 
381         MenuItem searchMenu = menu.findItem(R.id.menu_contacts_search);
382         Intent searchIntent = new Intent(getApplicationContext(), TelecomActivity.class);
383         searchIntent.setAction(Intent.ACTION_SEARCH);
384         searchMenu.setIntent(searchIntent);
385 
386         MenuItem settingsMenu = menu.findItem(R.id.menu_dialer_setting);
387         Intent settingsIntent = new Intent(getApplicationContext(), DialerSettingsActivity.class);
388         settingsMenu.setIntent(settingsIntent);
389         return true;
390     }
391 
navigateToContactResultsFragment(String query)392     private void navigateToContactResultsFragment(String query) {
393         Fragment topFragment = getSupportFragmentManager().findFragmentById(
394                 R.id.content_fragment_container);
395 
396         // Top fragment is ContactResultsFragment, update search query
397         if (topFragment instanceof ContactResultsFragment) {
398             ((ContactResultsFragment) topFragment).setSearchQuery(query);
399             return;
400         }
401 
402         ContactResultsFragment fragment = ContactResultsFragment.newInstance(query);
403         pushContentFragment(fragment, ContactResultsFragment.FRAGMENT_TAG);
404     }
405 
maybeStartInCallActivity(List<Call> callList)406     private void maybeStartInCallActivity(List<Call> callList) {
407         if (callList == null || callList.isEmpty()) {
408             return;
409         }
410 
411         L.d(TAG, "Start InCallActivity");
412         Intent launchIntent = new Intent(getApplicationContext(), InCallActivity.class);
413         startActivity(launchIntent);
414     }
415 
416     /** If the back button on action bar is available to navigate up. */
isBackNavigationAvailable()417     private boolean isBackNavigationAvailable() {
418         return getSupportFragmentManager().getBackStackEntryCount() > 1;
419     }
420 
getTabFromSharedPreference()421     private int getTabFromSharedPreference() {
422         String key = getResources().getString(R.string.pref_start_page_key);
423         String defaultValue = getResources().getStringArray(R.array.tabs_config)[0];
424         SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
425         return mTabFactory.getTabIndex(sharedPreferences.getString(key, defaultValue));
426     }
427 
428     /** Sets the background of the Activity's action bar to a {@link Drawable} */
setActionBarBackground(@ullable Drawable drawable)429     public void setActionBarBackground(@Nullable Drawable drawable) {
430         if (mToolbarContainer != null) {
431             mToolbarContainer.setBackground(drawable);
432         } else {
433             mToolbar.setBackground(drawable);
434         }
435     }
436 }
437