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