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.os.Bundle;
23 import android.provider.CallLog;
24 import android.telecom.Call;
25 import android.telephony.PhoneNumberUtils;
26 
27 import androidx.annotation.NonNull;
28 import androidx.fragment.app.Fragment;
29 import androidx.fragment.app.FragmentActivity;
30 import androidx.lifecycle.LiveData;
31 import androidx.lifecycle.MutableLiveData;
32 import androidx.lifecycle.ViewModelProvider;
33 
34 import com.android.car.apps.common.util.Themes;
35 import com.android.car.dialer.R;
36 import com.android.car.dialer.log.L;
37 import com.android.car.dialer.notification.NotificationService;
38 import com.android.car.dialer.telecom.UiCallManager;
39 import com.android.car.dialer.ui.activecall.InCallActivity;
40 import com.android.car.dialer.ui.common.DialerBaseFragment;
41 import com.android.car.dialer.ui.common.OnItemClickedListener;
42 import com.android.car.dialer.ui.dialpad.DialpadFragment;
43 import com.android.car.dialer.ui.search.ContactResultsFragment;
44 import com.android.car.dialer.ui.settings.DialerSettingsActivity;
45 import com.android.car.dialer.ui.warning.OverlayFragment;
46 import com.android.car.ui.baselayout.Insets;
47 import com.android.car.ui.baselayout.InsetsChangedListener;
48 import com.android.car.ui.core.CarUi;
49 import com.android.car.ui.toolbar.MenuItem;
50 import com.android.car.ui.toolbar.ToolbarController;
51 
52 import java.util.List;
53 import java.util.stream.Collectors;
54 
55 import javax.inject.Inject;
56 
57 import dagger.hilt.android.AndroidEntryPoint;
58 
59 /**
60  * Main activity for the Dialer app. It hosts most of the fragments for the app.
61  *
62  * <p>Start {@link InCallActivity} if there are ongoing calls
63  *
64  * <p>Based on call and connectivity status, it will choose the right page to display.
65  */
66 @AndroidEntryPoint(FragmentActivity.class)
67 public class TelecomActivity extends Hilt_TelecomActivity implements
68         DialerBaseFragment.DialerFragmentParent, InsetsChangedListener {
69     private static final String TAG = "CD.TelecomActivity";
70 
71     @Inject SharedPreferences mSharedPreferences;
72     @Inject UiCallManager mUiCallManager;
73     private LiveData<List<Call>> mOngoingCallListLiveData;
74     private LiveData<Boolean> mRefreshUiLiveData;
75     // View objects for this activity.
76     private TelecomPageTab.Factory mTabFactory;
77     private ToolbarController mCarUiToolbar;
78 
79     @Override
onCreate(Bundle savedInstanceState)80     protected void onCreate(Bundle savedInstanceState) {
81         super.onCreate(savedInstanceState);
82 
83         L.d(TAG, "onCreate");
84 
85         setContentView(R.layout.telecom_activity);
86 
87         mCarUiToolbar = CarUi.requireToolbar(this);
88 
89         setupTabLayout();
90 
91         TelecomActivityViewModel viewModel = new ViewModelProvider(this).get(
92                 TelecomActivityViewModel.class);
93 
94         mRefreshUiLiveData = viewModel.getRefreshTabsLiveData();
95         mRefreshUiLiveData.observe(this, v -> refreshUi());
96 
97         LiveData<Boolean> hasHfpDeviceConnectedLiveData = viewModel.hasHfpDeviceConnected();
98         hasHfpDeviceConnectedLiveData.observe(this, hasHfpDeviceConnected -> {
99             if (!Boolean.TRUE.equals(hasHfpDeviceConnected)) {
100                 new OverlayFragment().show(getSupportFragmentManager(), null);
101             }
102         });
103 
104         MutableLiveData<Integer> toolbarTitleMode = viewModel.getToolbarTitleMode();
105         toolbarTitleMode.setValue(Themes.getAttrInteger(this, R.attr.toolbarTitleMode));
106 
107         mOngoingCallListLiveData = viewModel.getOngoingCallListLiveData();
108         mOngoingCallListLiveData.observe(this, list -> maybeStartInCallActivity(list));
109 
110         handleIntent();
111     }
112 
113     @Override
onNewIntent(Intent i)114     protected void onNewIntent(Intent i) {
115         super.onNewIntent(i);
116         setIntent(i);
117         handleIntent();
118     }
119 
handleIntent()120     private void handleIntent() {
121         Intent intent = getIntent();
122         String action = intent != null ? intent.getAction() : null;
123         L.d(TAG, "handleIntent, intent: %s, action: %s", intent, action);
124         if (action == null || action.length() == 0) {
125             return;
126         }
127 
128         String number;
129         switch (action) {
130             case Intent.ACTION_DIAL:
131                 number = PhoneNumberUtils.getNumberFromIntent(intent, this);
132                 showDialPadFragment(number);
133                 break;
134 
135             case Intent.ACTION_CALL:
136                 number = PhoneNumberUtils.getNumberFromIntent(intent, this);
137                 mUiCallManager.placeCall(number);
138                 break;
139 
140             case Intent.ACTION_SEARCH:
141                 String searchQuery = intent.getStringExtra(SearchManager.QUERY);
142                 navigateToContactResultsFragment(searchQuery);
143                 break;
144             case Intent.ACTION_VIEW:
145                 if (CallLog.Calls.CONTENT_TYPE.equals(intent.getType())) {
146                     showTabPage(TelecomPageTab.Page.CALL_HISTORY);
147                     NotificationService.readAllMissedCall(this);
148                 }
149                 break;
150             default:
151                 // Do nothing.
152         }
153 
154         setIntent(null);
155 
156         // This is to start the incall activity when user taps on the dialer launch icon rapidly
157         maybeStartInCallActivity(mOngoingCallListLiveData.getValue());
158     }
159 
setupTabLayout()160     private void setupTabLayout() {
161         boolean[] tabSelectedListenerEnabled = new boolean[] { false };
162         OnItemClickedListener<TelecomPageTab> onTabSelected = tab -> {
163             if (tabSelectedListenerEnabled[0]) {
164                 Fragment fragment = tab.getFragment();
165                 setContentFragment(fragment, tab.getFragmentTag());
166             }
167         };
168         boolean wasContentFragmentRestored = false;
169         mTabFactory = new TelecomPageTab.Factory(this, onTabSelected, getSupportFragmentManager());
170         List<TelecomPageTab> tabs = mTabFactory.recreateTabs(getBaseContext(), false);
171         mCarUiToolbar.setTabs(tabs.stream()
172                 .map(TelecomPageTab::getToolbarTab)
173                 .collect(Collectors.toList()));
174 
175         for (int i = 0; i < tabs.size(); i++) {
176             if (tabs.get(i).wasFragmentRestored()) {
177                 mCarUiToolbar.selectTab(i);
178                 wasContentFragmentRestored = true;
179             }
180         }
181 
182         // Select the starting tab and set up the fragment for it.
183         if (!wasContentFragmentRestored) {
184             int startTabIndex = mTabFactory.getTabIndex(getTabFromSharedPreference());
185             mCarUiToolbar.selectTab(startTabIndex);
186             TelecomPageTab startTab = tabs.get(startTabIndex);
187             setContentFragment(startTab.getFragment(), startTab.getFragmentTag());
188         }
189         tabSelectedListenerEnabled[0] = true;
190     }
191 
refreshUi()192     private void refreshUi() {
193         L.v(TAG, "Refresh ui");
194 
195         List<TelecomPageTab> tabs = mTabFactory.recreateTabs(getBaseContext(), true);
196         mCarUiToolbar.setTabs(tabs.stream()
197                 .map(TelecomPageTab::getToolbarTab)
198                 .collect(Collectors.toList()));
199 
200         String startTab = getTabFromSharedPreference();
201         showTabPage(startTab);
202     }
203 
204     /**
205      * Switch to {@link DialpadFragment} and set the given number as dialed number.
206      */
showDialPadFragment(String number)207     private void showDialPadFragment(String number) {
208         int dialpadTabIndex = showTabPage(TelecomPageTab.Page.DIAL_PAD);
209 
210         if (dialpadTabIndex == -1) {
211             return;
212         }
213 
214         TelecomPageTab dialpadTab = mTabFactory.getTab(dialpadTabIndex);
215         Fragment fragment = dialpadTab.getFragment();
216         if (fragment instanceof DialpadFragment) {
217             ((DialpadFragment) fragment).setDialedNumber(number);
218         } else {
219             L.w(TAG, "Current tab is not a dialpad fragment!");
220         }
221     }
222 
showTabPage(@elecomPageTab.Page String tabPage)223     private int showTabPage(@TelecomPageTab.Page String tabPage) {
224         int tabIndex = mTabFactory.getTabIndex(tabPage);
225         if (tabIndex == -1) {
226             L.w(TAG, "Page %s is not a tab.", tabPage);
227             return -1;
228         }
229         getSupportFragmentManager().executePendingTransactions();
230         while (isBackNavigationAvailable()) {
231             getSupportFragmentManager().popBackStackImmediate();
232         }
233 
234         mCarUiToolbar.selectTab(tabIndex);
235         return tabIndex;
236     }
237 
setContentFragment(Fragment fragment, String fragmentTag)238     private void setContentFragment(Fragment fragment, String fragmentTag) {
239         L.d(TAG, "setContentFragment: %s", fragment);
240 
241         getSupportFragmentManager().executePendingTransactions();
242         while (getSupportFragmentManager().getBackStackEntryCount() > 0) {
243             getSupportFragmentManager().popBackStackImmediate();
244         }
245 
246         getSupportFragmentManager()
247                 .beginTransaction()
248                 .replace(R.id.fragment_container, fragment, fragmentTag)
249                 .addToBackStack(fragmentTag)
250                 .commit();
251     }
252 
253     @Override
pushContentFragment(@onNull Fragment topContentFragment, String fragmentTag)254     public void pushContentFragment(@NonNull Fragment topContentFragment, String fragmentTag) {
255         L.d(TAG, "pushContentFragment: %s", topContentFragment);
256 
257         getSupportFragmentManager()
258                 .beginTransaction()
259                 .replace(R.id.fragment_container, topContentFragment, fragmentTag)
260                 .addToBackStack(fragmentTag)
261                 .commit();
262     }
263 
264     @Override
onNavigateUp()265     public boolean onNavigateUp() {
266         if (isBackNavigationAvailable()) {
267             onBackPressed();
268             return true;
269         }
270         return super.onNavigateUp();
271     }
272 
273     @Override
onBackPressed()274     public void onBackPressed() {
275         // By default onBackPressed will pop all the fragments off the backstack and then finish
276         // the activity. We want to finish the activity while there is still one fragment on the
277         // backstack, because we use onBackStackChanged() to set up our fragments.
278         if (isBackNavigationAvailable()) {
279             super.onBackPressed();
280         } else {
281             finishAfterTransition();
282         }
283     }
284 
285     /**
286      * Handles the click action on the menu items.
287      */
onMenuItemClicked(MenuItem item)288     public void onMenuItemClicked(MenuItem item) {
289         switch (item.getId()) {
290             case R.id.menu_item_search:
291                 Intent searchIntent = new Intent(getApplicationContext(), TelecomActivity.class);
292                 searchIntent.setAction(Intent.ACTION_SEARCH);
293                 startActivity(searchIntent);
294                 break;
295             case R.id.menu_item_setting:
296                 Intent settingsIntent = new Intent(getApplicationContext(),
297                         DialerSettingsActivity.class);
298                 startActivity(settingsIntent);
299                 break;
300         }
301     }
302 
navigateToContactResultsFragment(String query)303     private void navigateToContactResultsFragment(String query) {
304         Fragment topFragment = getSupportFragmentManager().findFragmentById(
305                 R.id.fragment_container);
306 
307         // Top fragment is ContactResultsFragment, update search query
308         if (topFragment instanceof ContactResultsFragment) {
309             ((ContactResultsFragment) topFragment).setSearchQuery(query);
310             return;
311         }
312 
313         ContactResultsFragment fragment = ContactResultsFragment.newInstance(query);
314         pushContentFragment(fragment, ContactResultsFragment.FRAGMENT_TAG);
315     }
316 
maybeStartInCallActivity(List<Call> callList)317     private void maybeStartInCallActivity(List<Call> callList) {
318         if (callList == null || callList.isEmpty()) {
319             return;
320         }
321 
322         L.d(TAG, "Start InCallActivity");
323         Intent launchIntent = new Intent(getApplicationContext(), InCallActivity.class);
324         startActivity(launchIntent);
325     }
326 
327     /**
328      * If the back button on action bar is available to navigate up.
329      */
isBackNavigationAvailable()330     private boolean isBackNavigationAvailable() {
331         return getSupportFragmentManager().getBackStackEntryCount() > 1;
332     }
333 
getTabFromSharedPreference()334     private String getTabFromSharedPreference() {
335         String key = getResources().getString(R.string.pref_start_page_key);
336         String defaultValue = getResources().getString(R.string.tab_config_default_value);
337         return mSharedPreferences.getString(key, defaultValue);
338     }
339 
340     @Override
onCarUiInsetsChanged(Insets insets)341     public void onCarUiInsetsChanged(Insets insets) {
342         // Do nothing, this is just a marker that we will handle the insets in fragments.
343         // This is only necessary because the fragments are not immediately added to the
344         // activity when calling .commit()
345     }
346 }
347