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