blob: 588f9d77eb31eeb15e4ef472d9be1ac1afd98692 [file] [log] [blame]
Eric Erfanianccca3152017-02-22 16:32:36 -08001/*
2 * Copyright (C) 2013 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
17package com.android.dialer.app;
18
19import android.app.Fragment;
20import android.app.FragmentTransaction;
Eric Erfanian10b34a52017-05-04 08:23:17 -070021import android.app.KeyguardManager;
Eric Erfanianccca3152017-02-22 16:32:36 -080022import android.content.ActivityNotFoundException;
23import android.content.Context;
24import android.content.Intent;
25import android.content.pm.PackageManager;
26import android.content.pm.ResolveInfo;
27import android.content.res.Configuration;
28import android.content.res.Resources;
29import android.database.Cursor;
30import android.net.Uri;
31import android.os.Bundle;
Eric Erfaniand8046e52017-04-06 09:41:50 -070032import android.os.SystemClock;
Eric Erfanianccca3152017-02-22 16:32:36 -080033import android.os.Trace;
34import android.provider.CallLog.Calls;
35import android.speech.RecognizerIntent;
36import android.support.annotation.MainThread;
37import android.support.annotation.NonNull;
38import android.support.annotation.VisibleForTesting;
39import android.support.design.widget.CoordinatorLayout;
Eric Erfanianfc37b022017-03-21 10:11:17 -070040import android.support.design.widget.FloatingActionButton;
Eric Erfanianccca3152017-02-22 16:32:36 -080041import android.support.design.widget.Snackbar;
42import android.support.v4.app.ActivityCompat;
43import android.support.v4.view.ViewPager;
44import android.support.v7.app.ActionBar;
45import android.telecom.PhoneAccount;
Eric Erfanian2ca43182017-08-31 06:57:16 -070046import android.telecom.TelecomManager;
Eric Erfanianccca3152017-02-22 16:32:36 -080047import android.text.Editable;
48import android.text.TextUtils;
49import android.text.TextWatcher;
50import android.view.DragEvent;
51import android.view.Gravity;
Eric Erfanianccca3152017-02-22 16:32:36 -080052import android.view.Menu;
53import android.view.MenuItem;
54import android.view.MotionEvent;
55import android.view.View;
56import android.view.View.OnDragListener;
Eric Erfanianccca3152017-02-22 16:32:36 -080057import android.view.animation.Animation;
58import android.view.animation.AnimationUtils;
59import android.widget.AbsListView.OnScrollListener;
60import android.widget.EditText;
61import android.widget.ImageButton;
62import android.widget.PopupMenu;
63import android.widget.TextView;
64import android.widget.Toast;
65import com.android.contacts.common.dialog.ClearFrequentsDialog;
66import com.android.contacts.common.list.OnPhoneNumberPickerActionListener;
67import com.android.contacts.common.list.PhoneNumberListAdapter;
Eric Erfanianccca3152017-02-22 16:32:36 -080068import com.android.contacts.common.list.PhoneNumberPickerFragment.CursorReranker;
69import com.android.contacts.common.list.PhoneNumberPickerFragment.OnLoadFinishedListener;
70import com.android.contacts.common.widget.FloatingActionButtonController;
71import com.android.dialer.animation.AnimUtils;
72import com.android.dialer.animation.AnimationListenerAdapter;
Eric Erfaniand5e47f62017-03-15 14:41:07 -070073import com.android.dialer.app.calllog.CallLogActivity;
Eric Erfanian2ca43182017-08-31 06:57:16 -070074import com.android.dialer.app.calllog.CallLogAdapter;
75import com.android.dialer.app.calllog.CallLogAsync;
Eric Erfanianccca3152017-02-22 16:32:36 -080076import com.android.dialer.app.calllog.CallLogFragment;
77import com.android.dialer.app.calllog.CallLogNotificationsService;
Eric Erfanian2ca43182017-08-31 06:57:16 -070078import com.android.dialer.app.calllog.IntentProvider;
Eric Erfaniand8046e52017-04-06 09:41:50 -070079import com.android.dialer.app.list.DialtactsPagerAdapter;
Eric Erfanian2ca43182017-08-31 06:57:16 -070080import com.android.dialer.app.list.DialtactsPagerAdapter.TabIndex;
Eric Erfanianccca3152017-02-22 16:32:36 -080081import com.android.dialer.app.list.DragDropController;
82import com.android.dialer.app.list.ListsFragment;
Eric Erfaniand8046e52017-04-06 09:41:50 -070083import com.android.dialer.app.list.OldSpeedDialFragment;
Eric Erfanianccca3152017-02-22 16:32:36 -080084import com.android.dialer.app.list.OnDragDropListener;
85import com.android.dialer.app.list.OnListFragmentScrolledListener;
86import com.android.dialer.app.list.PhoneFavoriteSquareTileView;
87import com.android.dialer.app.list.RegularSearchFragment;
88import com.android.dialer.app.list.SearchFragment;
89import com.android.dialer.app.list.SmartDialSearchFragment;
Eric Erfanianccca3152017-02-22 16:32:36 -080090import com.android.dialer.app.settings.DialerSettingsActivity;
91import com.android.dialer.app.widget.ActionBarController;
92import com.android.dialer.app.widget.SearchEditTextLayout;
Eric Erfaniand5e47f62017-03-15 14:41:07 -070093import com.android.dialer.callcomposer.CallComposerActivity;
Eric Erfanian2ca43182017-08-31 06:57:16 -070094import com.android.dialer.calldetails.CallDetailsActivity;
95import com.android.dialer.callintent.CallInitiationType;
Eric Erfanianccca3152017-02-22 16:32:36 -080096import com.android.dialer.callintent.CallIntentBuilder;
Eric Erfanian8369df02017-05-03 10:27:13 -070097import com.android.dialer.callintent.CallSpecificAppData;
Eric Erfanianccca3152017-02-22 16:32:36 -080098import com.android.dialer.common.Assert;
99import com.android.dialer.common.LogUtil;
Eric Erfanian2ca43182017-08-31 06:57:16 -0700100import com.android.dialer.compat.telephony.TelephonyManagerCompat;
101import com.android.dialer.configprovider.ConfigProviderBindings;
102import com.android.dialer.constants.ActivityRequestCodes;
Eric Erfanianccca3152017-02-22 16:32:36 -0800103import com.android.dialer.database.Database;
104import com.android.dialer.database.DialerDatabaseHelper;
Eric Erfanian2ca43182017-08-31 06:57:16 -0700105import com.android.dialer.dialpadview.DialpadFragment;
106import com.android.dialer.dialpadview.DialpadFragment.DialpadListener;
107import com.android.dialer.dialpadview.DialpadFragment.LastOutgoingCallCallback;
Eric Erfanianccca3152017-02-22 16:32:36 -0800108import com.android.dialer.interactions.PhoneNumberInteraction;
109import com.android.dialer.interactions.PhoneNumberInteraction.InteractionErrorCode;
Eric Erfanian8369df02017-05-03 10:27:13 -0700110import com.android.dialer.logging.DialerImpression;
Eric Erfanianccca3152017-02-22 16:32:36 -0800111import com.android.dialer.logging.Logger;
Eric Erfanian8369df02017-05-03 10:27:13 -0700112import com.android.dialer.logging.ScreenEvent;
Eric Erfanian2ca43182017-08-31 06:57:16 -0700113import com.android.dialer.logging.UiAction;
114import com.android.dialer.main.Main;
115import com.android.dialer.main.MainComponent;
Eric Erfanianccca3152017-02-22 16:32:36 -0800116import com.android.dialer.p13n.inference.P13nRanking;
117import com.android.dialer.p13n.inference.protocol.P13nRanker;
118import com.android.dialer.p13n.inference.protocol.P13nRanker.P13nRefreshCompleteListener;
119import com.android.dialer.p13n.logging.P13nLogger;
120import com.android.dialer.p13n.logging.P13nLogging;
Eric Erfanian2ca43182017-08-31 06:57:16 -0700121import com.android.dialer.performancereport.PerformanceReport;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700122import com.android.dialer.postcall.PostCall;
Eric Erfanianccca3152017-02-22 16:32:36 -0800123import com.android.dialer.proguard.UsedByReflection;
Eric Erfanian2ca43182017-08-31 06:57:16 -0700124import com.android.dialer.searchfragment.list.NewSearchFragment;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700125import com.android.dialer.simulator.Simulator;
126import com.android.dialer.simulator.SimulatorComponent;
Eric Erfanianccca3152017-02-22 16:32:36 -0800127import com.android.dialer.smartdial.SmartDialNameMatcher;
128import com.android.dialer.smartdial.SmartDialPrefix;
129import com.android.dialer.telecom.TelecomUtil;
130import com.android.dialer.util.DialerUtils;
Eric Erfanianccca3152017-02-22 16:32:36 -0800131import com.android.dialer.util.PermissionsUtil;
132import com.android.dialer.util.TouchPointManager;
133import com.android.dialer.util.TransactionSafeActivity;
134import com.android.dialer.util.ViewUtil;
135import java.util.ArrayList;
136import java.util.Arrays;
137import java.util.List;
138import java.util.Locale;
Eric Erfaniand8046e52017-04-06 09:41:50 -0700139import java.util.concurrent.TimeUnit;
Eric Erfanianccca3152017-02-22 16:32:36 -0800140
141/** The dialer tab's title is 'phone', a more common name (see strings.xml). */
142@UsedByReflection(value = "AndroidManifest-app.xml")
143public class DialtactsActivity extends TransactionSafeActivity
144 implements View.OnClickListener,
145 DialpadFragment.OnDialpadQueryChangedListener,
146 OnListFragmentScrolledListener,
147 CallLogFragment.HostInterface,
Eric Erfanian2ca43182017-08-31 06:57:16 -0700148 CallLogAdapter.OnActionModeStateChangedListener,
Eric Erfanianccca3152017-02-22 16:32:36 -0800149 DialpadFragment.HostInterface,
Eric Erfaniand8046e52017-04-06 09:41:50 -0700150 OldSpeedDialFragment.HostInterface,
Eric Erfanianccca3152017-02-22 16:32:36 -0800151 SearchFragment.HostInterface,
152 OnDragDropListener,
153 OnPhoneNumberPickerActionListener,
154 PopupMenu.OnMenuItemClickListener,
155 ViewPager.OnPageChangeListener,
156 ActionBarController.ActivityUi,
157 PhoneNumberInteraction.InteractionErrorListener,
158 PhoneNumberInteraction.DisambigDialogDismissedListener,
Eric Erfanian2ca43182017-08-31 06:57:16 -0700159 ActivityCompat.OnRequestPermissionsResultCallback,
160 DialpadListener {
Eric Erfanianccca3152017-02-22 16:32:36 -0800161
162 public static final boolean DEBUG = false;
163 @VisibleForTesting public static final String TAG_DIALPAD_FRAGMENT = "dialpad";
164 private static final String ACTION_SHOW_TAB = "ACTION_SHOW_TAB";
165 @VisibleForTesting public static final String EXTRA_SHOW_TAB = "EXTRA_SHOW_TAB";
166 public static final String EXTRA_CLEAR_NEW_VOICEMAILS = "EXTRA_CLEAR_NEW_VOICEMAILS";
Eric Erfanian2ca43182017-08-31 06:57:16 -0700167 private static final String KEY_LAST_TAB = "last_tab";
Eric Erfanianccca3152017-02-22 16:32:36 -0800168 private static final String TAG = "DialtactsActivity";
169 private static final String KEY_IN_REGULAR_SEARCH_UI = "in_regular_search_ui";
170 private static final String KEY_IN_DIALPAD_SEARCH_UI = "in_dialpad_search_ui";
calderwoodra50715262017-09-08 09:20:08 -0700171 private static final String KEY_IN_NEW_SEARCH_UI = "in_new_search_ui";
Eric Erfanianccca3152017-02-22 16:32:36 -0800172 private static final String KEY_SEARCH_QUERY = "search_query";
173 private static final String KEY_FIRST_LAUNCH = "first_launch";
174 private static final String KEY_WAS_CONFIGURATION_CHANGE = "was_configuration_change";
175 private static final String KEY_IS_DIALPAD_SHOWN = "is_dialpad_shown";
Eric Erfanian2ca43182017-08-31 06:57:16 -0700176 private static final String TAG_NEW_SEARCH_FRAGMENT = "new_search";
Eric Erfanianccca3152017-02-22 16:32:36 -0800177 private static final String TAG_REGULAR_SEARCH_FRAGMENT = "search";
178 private static final String TAG_SMARTDIAL_SEARCH_FRAGMENT = "smartdial";
179 private static final String TAG_FAVORITES_FRAGMENT = "favorites";
180 /** Just for backward compatibility. Should behave as same as {@link Intent#ACTION_DIAL}. */
181 private static final String ACTION_TOUCH_DIALER = "com.android.phone.action.TOUCH_DIALER";
182
Eric Erfanianccca3152017-02-22 16:32:36 -0800183 private static final int FAB_SCALE_IN_DELAY_MS = 300;
Eric Erfaniand8046e52017-04-06 09:41:50 -0700184
185 /**
186 * Minimum time the history tab must have been selected for it to be marked as seen in onStop()
187 */
188 private static final long HISTORY_TAB_SEEN_TIMEOUT = TimeUnit.SECONDS.toMillis(3);
189
Eric Erfanianccca3152017-02-22 16:32:36 -0800190 /** Fragment containing the dialpad that slides into view */
191 protected DialpadFragment mDialpadFragment;
192
193 private CoordinatorLayout mParentLayout;
194 /** Fragment for searching phone numbers using the alphanumeric keyboard. */
195 private RegularSearchFragment mRegularSearchFragment;
196
197 /** Fragment for searching phone numbers using the dialpad. */
198 private SmartDialSearchFragment mSmartDialSearchFragment;
199
Eric Erfanian2ca43182017-08-31 06:57:16 -0700200 /** new Fragment for search phone numbers using the keyboard and the dialpad. */
201 private NewSearchFragment mNewSearchFragment;
202
Eric Erfanianccca3152017-02-22 16:32:36 -0800203 /** Animation that slides in. */
204 private Animation mSlideIn;
205
206 /** Animation that slides out. */
207 private Animation mSlideOut;
208 /** Fragment containing the speed dial list, call history list, and all contacts list. */
209 private ListsFragment mListsFragment;
210 /**
211 * Tracks whether onSaveInstanceState has been called. If true, no fragment transactions can be
212 * commited.
213 */
214 private boolean mStateSaved;
215
calderwoodra50715262017-09-08 09:20:08 -0700216 private boolean mIsKeyboardOpen;
217 private boolean mInNewSearch;
Eric Erfanianccca3152017-02-22 16:32:36 -0800218 private boolean mIsRestarting;
219 private boolean mInDialpadSearch;
220 private boolean mInRegularSearch;
221 private boolean mClearSearchOnPause;
222 private boolean mIsDialpadShown;
223 private boolean mShowDialpadOnResume;
224 /** Whether or not the device is in landscape orientation. */
225 private boolean mIsLandscape;
226 /** True if the dialpad is only temporarily showing due to being in call */
227 private boolean mInCallDialpadUp;
228 /** True when this activity has been launched for the first time. */
229 private boolean mFirstLaunch;
230 /**
231 * Search query to be applied to the SearchView in the ActionBar once onCreateOptionsMenu has been
232 * called.
233 */
234 private String mPendingSearchViewQuery;
235
236 private PopupMenu mOverflowMenu;
237 private EditText mSearchView;
238 private View mVoiceSearchButton;
239 private String mSearchQuery;
240 private String mDialpadQuery;
241 private DialerDatabaseHelper mDialerDatabaseHelper;
242 private DragDropController mDragDropController;
243 private ActionBarController mActionBarController;
244 private FloatingActionButtonController mFloatingActionButtonController;
245 private boolean mWasConfigurationChange;
Eric Erfaniand8046e52017-04-06 09:41:50 -0700246 private long timeTabSelected;
Eric Erfanianccca3152017-02-22 16:32:36 -0800247
248 private P13nLogger mP13nLogger;
249 private P13nRanker mP13nRanker;
Eric Erfanian2ca43182017-08-31 06:57:16 -0700250 public boolean isMultiSelectModeEnabled;
251
252 private boolean isLastTabEnabled;
Eric Erfanianccca3152017-02-22 16:32:36 -0800253
254 AnimationListenerAdapter mSlideInListener =
255 new AnimationListenerAdapter() {
256 @Override
257 public void onAnimationEnd(Animation animation) {
258 maybeEnterSearchUi();
259 }
260 };
261 /** Listener for after slide out animation completes on dialer fragment. */
262 AnimationListenerAdapter mSlideOutListener =
263 new AnimationListenerAdapter() {
264 @Override
265 public void onAnimationEnd(Animation animation) {
266 commitDialpadFragmentHide();
267 }
268 };
269 /** Listener used to send search queries to the phone search fragment. */
270 private final TextWatcher mPhoneSearchQueryTextListener =
271 new TextWatcher() {
272 @Override
273 public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
274
275 @Override
276 public void onTextChanged(CharSequence s, int start, int before, int count) {
277 final String newText = s.toString();
278 if (newText.equals(mSearchQuery)) {
279 // If the query hasn't changed (perhaps due to activity being destroyed
280 // and restored, or user launching the same DIAL intent twice), then there is
281 // no need to do anything here.
282 return;
283 }
Eric Erfanian2ca43182017-08-31 06:57:16 -0700284
285 if (count != 0) {
286 PerformanceReport.recordClick(UiAction.Type.TEXT_CHANGE_WITH_INPUT);
287 }
288
Eric Erfanianccca3152017-02-22 16:32:36 -0800289 if (DEBUG) {
290 LogUtil.v("DialtactsActivity.onTextChanged", "called with new query: " + newText);
291 LogUtil.v("DialtactsActivity.onTextChanged", "previous query: " + mSearchQuery);
292 }
293 mSearchQuery = newText;
294
Eric Erfanian2ca43182017-08-31 06:57:16 -0700295 // TODO(calderwoodra): show p13n when newText is empty.
Eric Erfanianccca3152017-02-22 16:32:36 -0800296 // Show search fragment only when the query string is changed to non-empty text.
297 if (!TextUtils.isEmpty(newText)) {
298 // Call enterSearchUi only if we are switching search modes, or showing a search
299 // fragment for the first time.
300 final boolean sameSearchMode =
301 (mIsDialpadShown && mInDialpadSearch) || (!mIsDialpadShown && mInRegularSearch);
302 if (!sameSearchMode) {
303 enterSearchUi(mIsDialpadShown, mSearchQuery, true /* animate */);
304 }
305 }
306
307 if (mSmartDialSearchFragment != null && mSmartDialSearchFragment.isVisible()) {
308 mSmartDialSearchFragment.setQueryString(mSearchQuery);
309 } else if (mRegularSearchFragment != null && mRegularSearchFragment.isVisible()) {
310 mRegularSearchFragment.setQueryString(mSearchQuery);
Eric Erfanian2ca43182017-08-31 06:57:16 -0700311 } else if (mNewSearchFragment != null && mNewSearchFragment.isVisible()) {
calderwoodraaf3cc172017-09-01 23:28:21 -0700312 mNewSearchFragment.setQuery(mSearchQuery, getCallInitiationType());
Eric Erfanianccca3152017-02-22 16:32:36 -0800313 }
314 }
315
316 @Override
317 public void afterTextChanged(Editable s) {}
318 };
319 /** Open the search UI when the user clicks on the search box. */
320 private final View.OnClickListener mSearchViewOnClickListener =
321 new View.OnClickListener() {
322 @Override
323 public void onClick(View v) {
324 if (!isInSearchUi()) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700325 PerformanceReport.recordClick(UiAction.Type.OPEN_SEARCH);
Eric Erfanianccca3152017-02-22 16:32:36 -0800326 mActionBarController.onSearchBoxTapped();
327 enterSearchUi(
328 false /* smartDialSearch */, mSearchView.getText().toString(), true /* animate */);
329 }
330 }
331 };
332
333 private int mActionBarHeight;
334 private int mPreviouslySelectedTabIndex;
Eric Erfanian2ca43182017-08-31 06:57:16 -0700335
Eric Erfanianccca3152017-02-22 16:32:36 -0800336 /**
337 * The text returned from a voice search query. Set in {@link #onActivityResult} and used in
338 * {@link #onResume()} to populate the search box.
339 */
340 private String mVoiceSearchQuery;
341
342 /**
343 * @param tab the TAB_INDEX_* constant in {@link ListsFragment}
344 * @return A intent that will open the DialtactsActivity into the specified tab. The intent for
345 * each tab will be unique.
346 */
347 public static Intent getShowTabIntent(Context context, int tab) {
348 Intent intent = new Intent(context, DialtactsActivity.class);
349 intent.setAction(ACTION_SHOW_TAB);
350 intent.putExtra(DialtactsActivity.EXTRA_SHOW_TAB, tab);
351 intent.setData(
352 new Uri.Builder()
353 .scheme("intent")
354 .authority(context.getPackageName())
355 .appendPath(TAG)
356 .appendQueryParameter(DialtactsActivity.EXTRA_SHOW_TAB, String.valueOf(tab))
357 .build());
358
359 return intent;
360 }
361
362 @Override
363 public boolean dispatchTouchEvent(MotionEvent ev) {
364 if (ev.getAction() == MotionEvent.ACTION_DOWN) {
365 TouchPointManager.getInstance().setPoint((int) ev.getRawX(), (int) ev.getRawY());
366 }
367 return super.dispatchTouchEvent(ev);
368 }
369
370 @Override
371 protected void onCreate(Bundle savedInstanceState) {
372 Trace.beginSection(TAG + " onCreate");
373 super.onCreate(savedInstanceState);
374
375 mFirstLaunch = true;
Eric Erfanian2ca43182017-08-31 06:57:16 -0700376 isLastTabEnabled = ConfigProviderBindings.get(this).getBoolean("last_tab_enabled", false);
Eric Erfanianccca3152017-02-22 16:32:36 -0800377
378 final Resources resources = getResources();
379 mActionBarHeight = resources.getDimensionPixelSize(R.dimen.action_bar_height_large);
380
381 Trace.beginSection(TAG + " setContentView");
382 setContentView(R.layout.dialtacts_activity);
383 Trace.endSection();
384 getWindow().setBackgroundDrawable(null);
385
386 Trace.beginSection(TAG + " setup Views");
387 final ActionBar actionBar = getActionBarSafely();
388 actionBar.setCustomView(R.layout.search_edittext);
389 actionBar.setDisplayShowCustomEnabled(true);
390 actionBar.setBackgroundDrawable(null);
391
392 SearchEditTextLayout searchEditTextLayout =
Eric Erfanian2ca43182017-08-31 06:57:16 -0700393 actionBar.getCustomView().findViewById(R.id.search_view_container);
Eric Erfanianccca3152017-02-22 16:32:36 -0800394
395 mActionBarController = new ActionBarController(this, searchEditTextLayout);
396
Eric Erfanian2ca43182017-08-31 06:57:16 -0700397 mSearchView = searchEditTextLayout.findViewById(R.id.search_view);
Eric Erfanianccca3152017-02-22 16:32:36 -0800398 mSearchView.addTextChangedListener(mPhoneSearchQueryTextListener);
calderwoodra273fd7b2017-08-31 16:06:59 -0700399 mSearchView.setHint(getSearchBoxHint());
calderwoodra50715262017-09-08 09:20:08 -0700400
Eric Erfanianccca3152017-02-22 16:32:36 -0800401 mVoiceSearchButton = searchEditTextLayout.findViewById(R.id.voice_search_button);
402 searchEditTextLayout
Eric Erfanian10b34a52017-05-04 08:23:17 -0700403 .findViewById(R.id.search_box_collapsed)
Eric Erfanianccca3152017-02-22 16:32:36 -0800404 .setOnClickListener(mSearchViewOnClickListener);
calderwoodra50715262017-09-08 09:20:08 -0700405 searchEditTextLayout
406 .findViewById(R.id.search_back_button)
407 .setOnClickListener(v -> exitSearchUi());
Eric Erfanianccca3152017-02-22 16:32:36 -0800408
409 mIsLandscape =
410 getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE;
Eric Erfaniand8046e52017-04-06 09:41:50 -0700411 mPreviouslySelectedTabIndex = DialtactsPagerAdapter.TAB_INDEX_SPEED_DIAL;
Eric Erfanian2ca43182017-08-31 06:57:16 -0700412 FloatingActionButton floatingActionButton = findViewById(R.id.floating_action_button);
Eric Erfanianccca3152017-02-22 16:32:36 -0800413 floatingActionButton.setOnClickListener(this);
414 mFloatingActionButtonController =
Eric Erfanianfc37b022017-03-21 10:11:17 -0700415 new FloatingActionButtonController(this, floatingActionButton);
Eric Erfanianccca3152017-02-22 16:32:36 -0800416
417 ImageButton optionsMenuButton =
Eric Erfanian2ca43182017-08-31 06:57:16 -0700418 searchEditTextLayout.findViewById(R.id.dialtacts_options_menu_button);
Eric Erfanianccca3152017-02-22 16:32:36 -0800419 optionsMenuButton.setOnClickListener(this);
420 mOverflowMenu = buildOptionsMenu(optionsMenuButton);
421 optionsMenuButton.setOnTouchListener(mOverflowMenu.getDragToOpenListener());
422
423 // Add the favorites fragment but only if savedInstanceState is null. Otherwise the
424 // fragment manager is responsible for recreating it.
425 if (savedInstanceState == null) {
426 getFragmentManager()
427 .beginTransaction()
428 .add(R.id.dialtacts_frame, new ListsFragment(), TAG_FAVORITES_FRAGMENT)
429 .commit();
430 } else {
431 mSearchQuery = savedInstanceState.getString(KEY_SEARCH_QUERY);
432 mInRegularSearch = savedInstanceState.getBoolean(KEY_IN_REGULAR_SEARCH_UI);
433 mInDialpadSearch = savedInstanceState.getBoolean(KEY_IN_DIALPAD_SEARCH_UI);
calderwoodra50715262017-09-08 09:20:08 -0700434 mInNewSearch = savedInstanceState.getBoolean(KEY_IN_NEW_SEARCH_UI);
Eric Erfanianccca3152017-02-22 16:32:36 -0800435 mFirstLaunch = savedInstanceState.getBoolean(KEY_FIRST_LAUNCH);
436 mWasConfigurationChange = savedInstanceState.getBoolean(KEY_WAS_CONFIGURATION_CHANGE);
437 mShowDialpadOnResume = savedInstanceState.getBoolean(KEY_IS_DIALPAD_SHOWN);
438 mActionBarController.restoreInstanceState(savedInstanceState);
439 }
440
441 final boolean isLayoutRtl = ViewUtil.isRtl();
442 if (mIsLandscape) {
443 mSlideIn =
444 AnimationUtils.loadAnimation(
445 this, isLayoutRtl ? R.anim.dialpad_slide_in_left : R.anim.dialpad_slide_in_right);
446 mSlideOut =
447 AnimationUtils.loadAnimation(
448 this, isLayoutRtl ? R.anim.dialpad_slide_out_left : R.anim.dialpad_slide_out_right);
449 } else {
450 mSlideIn = AnimationUtils.loadAnimation(this, R.anim.dialpad_slide_in_bottom);
451 mSlideOut = AnimationUtils.loadAnimation(this, R.anim.dialpad_slide_out_bottom);
452 }
453
454 mSlideIn.setInterpolator(AnimUtils.EASE_IN);
455 mSlideOut.setInterpolator(AnimUtils.EASE_OUT);
456
457 mSlideIn.setAnimationListener(mSlideInListener);
458 mSlideOut.setAnimationListener(mSlideOutListener);
459
460 mParentLayout = (CoordinatorLayout) findViewById(R.id.dialtacts_mainlayout);
461 mParentLayout.setOnDragListener(new LayoutOnDragListener());
Eric Erfanianfc37b022017-03-21 10:11:17 -0700462 ViewUtil.doOnGlobalLayout(
463 floatingActionButton,
464 view -> {
465 int screenWidth = mParentLayout.getWidth();
466 mFloatingActionButtonController.setScreenWidth(screenWidth);
467 mFloatingActionButtonController.align(getFabAlignment(), false /* animate */);
468 });
Eric Erfanianccca3152017-02-22 16:32:36 -0800469
470 Trace.endSection();
471
472 Trace.beginSection(TAG + " initialize smart dialing");
473 mDialerDatabaseHelper = Database.get(this).getDatabaseHelper(this);
474 SmartDialPrefix.initializeNanpSettings(this);
475 Trace.endSection();
476
477 mP13nLogger = P13nLogging.get(getApplicationContext());
478 mP13nRanker = P13nRanking.get(getApplicationContext());
479 Trace.endSection();
480 }
481
482 @NonNull
483 private ActionBar getActionBarSafely() {
484 return Assert.isNotNull(getSupportActionBar());
485 }
486
487 @Override
488 protected void onResume() {
zachhcc2cde02017-08-31 12:34:18 -0700489 LogUtil.enterBlock("DialtactsActivity.onResume");
Eric Erfanianccca3152017-02-22 16:32:36 -0800490 Trace.beginSection(TAG + " onResume");
491 super.onResume();
492
Eric Erfanian2ca43182017-08-31 06:57:16 -0700493 // Some calls may not be recorded (eg. from quick contact),
494 // so we should restart recording after these calls. (Recorded call is stopped)
495 PostCall.restartPerformanceRecordingIfARecentCallExist(this);
496 if (!PerformanceReport.isRecording()) {
497 PerformanceReport.startRecording();
498 }
499
Eric Erfanianccca3152017-02-22 16:32:36 -0800500 mStateSaved = false;
501 if (mFirstLaunch) {
zachhcc2cde02017-08-31 12:34:18 -0700502 LogUtil.i("DialtactsActivity.onResume", "mFirstLaunch true, displaying fragment");
Eric Erfanianccca3152017-02-22 16:32:36 -0800503 displayFragment(getIntent());
504 } else if (!phoneIsInUse() && mInCallDialpadUp) {
zachhcc2cde02017-08-31 12:34:18 -0700505 LogUtil.i("DialtactsActivity.onResume", "phone not in use, hiding dialpad fragment");
Eric Erfanianccca3152017-02-22 16:32:36 -0800506 hideDialpadFragment(false, true);
507 mInCallDialpadUp = false;
508 } else if (mShowDialpadOnResume) {
zachhcc2cde02017-08-31 12:34:18 -0700509 LogUtil.i("DialtactsActivity.onResume", "showing dialpad on resume");
Eric Erfanianccca3152017-02-22 16:32:36 -0800510 showDialpadFragment(false);
511 mShowDialpadOnResume = false;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700512 } else {
513 PostCall.promptUserForMessageIfNecessary(this, mParentLayout);
Eric Erfanianccca3152017-02-22 16:32:36 -0800514 }
515
516 // If there was a voice query result returned in the {@link #onActivityResult} callback, it
517 // will have been stashed in mVoiceSearchQuery since the search results fragment cannot be
518 // shown until onResume has completed. Active the search UI and set the search term now.
519 if (!TextUtils.isEmpty(mVoiceSearchQuery)) {
520 mActionBarController.onSearchBoxTapped();
521 mSearchView.setText(mVoiceSearchQuery);
522 mVoiceSearchQuery = null;
523 }
524
Eric Erfanianccca3152017-02-22 16:32:36 -0800525 if (mIsRestarting) {
526 // This is only called when the activity goes from resumed -> paused -> resumed, so it
527 // will not cause an extra view to be sent out on rotation
528 if (mIsDialpadShown) {
529 Logger.get(this).logScreenView(ScreenEvent.Type.DIALPAD, this);
530 }
531 mIsRestarting = false;
532 }
533
534 prepareVoiceSearchButton();
535 if (!mWasConfigurationChange) {
536 mDialerDatabaseHelper.startSmartDialUpdateThread();
537 }
538 mFloatingActionButtonController.align(getFabAlignment(), false /* animate */);
539
Eric Erfanian10b34a52017-05-04 08:23:17 -0700540 if (mFirstLaunch) {
541 // Only process the Intent the first time onResume() is called after receiving it
542 if (Calls.CONTENT_TYPE.equals(getIntent().getType())) {
543 // Externally specified extras take precedence to EXTRA_SHOW_TAB, which is only
544 // used internally.
545 final Bundle extras = getIntent().getExtras();
546 if (extras != null && extras.getInt(Calls.EXTRA_CALL_TYPE_FILTER) == Calls.VOICEMAIL_TYPE) {
547 mListsFragment.showTab(DialtactsPagerAdapter.TAB_INDEX_VOICEMAIL);
548 Logger.get(this).logImpression(DialerImpression.Type.VVM_NOTIFICATION_CLICKED);
549 } else {
550 mListsFragment.showTab(DialtactsPagerAdapter.TAB_INDEX_HISTORY);
551 }
552 } else if (getIntent().hasExtra(EXTRA_SHOW_TAB)) {
553 int index =
554 getIntent().getIntExtra(EXTRA_SHOW_TAB, DialtactsPagerAdapter.TAB_INDEX_SPEED_DIAL);
555 if (index < mListsFragment.getTabCount()) {
556 // Hide dialpad since this is an explicit intent to show a specific tab, which is coming
557 // from missed call or voicemail notification.
558 hideDialpadFragment(false, false);
559 exitSearchUi();
560 mListsFragment.showTab(index);
561 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800562 }
Eric Erfanian10b34a52017-05-04 08:23:17 -0700563
564 if (getIntent().getBooleanExtra(EXTRA_CLEAR_NEW_VOICEMAILS, false)) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700565 LogUtil.i("DialtactsActivity.onResume", "clearing all new voicemails");
566 CallLogNotificationsService.markAllNewVoicemailsAsOld(this);
Eric Erfanianccca3152017-02-22 16:32:36 -0800567 }
568 }
569
Eric Erfanian10b34a52017-05-04 08:23:17 -0700570 mFirstLaunch = false;
Eric Erfanianccca3152017-02-22 16:32:36 -0800571
572 setSearchBoxHint();
Eric Erfanian10b34a52017-05-04 08:23:17 -0700573 timeTabSelected = SystemClock.elapsedRealtime();
Eric Erfanianccca3152017-02-22 16:32:36 -0800574
575 mP13nLogger.reset();
576 mP13nRanker.refresh(
577 new P13nRefreshCompleteListener() {
578 @Override
579 public void onP13nRefreshComplete() {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700580 // TODO(strongarm): make zero-query search results visible
Eric Erfanianccca3152017-02-22 16:32:36 -0800581 }
582 });
583 Trace.endSection();
584 }
585
586 @Override
587 protected void onRestart() {
588 super.onRestart();
589 mIsRestarting = true;
590 }
591
592 @Override
593 protected void onPause() {
594 if (mClearSearchOnPause) {
595 hideDialpadAndSearchUi();
596 mClearSearchOnPause = false;
597 }
598 if (mSlideOut.hasStarted() && !mSlideOut.hasEnded()) {
599 commitDialpadFragmentHide();
600 }
601 super.onPause();
602 }
603
604 @Override
Eric Erfaniand8046e52017-04-06 09:41:50 -0700605 protected void onStop() {
606 super.onStop();
607 boolean timeoutElapsed =
608 SystemClock.elapsedRealtime() - timeTabSelected >= HISTORY_TAB_SEEN_TIMEOUT;
609 boolean isOnHistoryTab =
610 mListsFragment.getCurrentTabIndex() == DialtactsPagerAdapter.TAB_INDEX_HISTORY;
Eric Erfanian10b34a52017-05-04 08:23:17 -0700611 if (isOnHistoryTab
612 && timeoutElapsed
613 && !isChangingConfigurations()
614 && !getSystemService(KeyguardManager.class).isKeyguardLocked()) {
Eric Erfaniand8046e52017-04-06 09:41:50 -0700615 mListsFragment.markMissedCallsAsReadAndRemoveNotifications();
616 }
Eric Erfanian2ca43182017-08-31 06:57:16 -0700617 DialerUtils.getDefaultSharedPreferenceForDeviceProtectedStorageContext(this)
618 .edit()
619 .putInt(KEY_LAST_TAB, mListsFragment.getCurrentTabIndex())
620 .apply();
Eric Erfaniand8046e52017-04-06 09:41:50 -0700621 }
622
623 @Override
Eric Erfanianccca3152017-02-22 16:32:36 -0800624 protected void onSaveInstanceState(Bundle outState) {
625 super.onSaveInstanceState(outState);
626 outState.putString(KEY_SEARCH_QUERY, mSearchQuery);
627 outState.putBoolean(KEY_IN_REGULAR_SEARCH_UI, mInRegularSearch);
628 outState.putBoolean(KEY_IN_DIALPAD_SEARCH_UI, mInDialpadSearch);
calderwoodra50715262017-09-08 09:20:08 -0700629 outState.putBoolean(KEY_IN_NEW_SEARCH_UI, mInNewSearch);
Eric Erfanianccca3152017-02-22 16:32:36 -0800630 outState.putBoolean(KEY_FIRST_LAUNCH, mFirstLaunch);
631 outState.putBoolean(KEY_IS_DIALPAD_SHOWN, mIsDialpadShown);
632 outState.putBoolean(KEY_WAS_CONFIGURATION_CHANGE, isChangingConfigurations());
633 mActionBarController.saveInstanceState(outState);
634 mStateSaved = true;
635 }
636
637 @Override
638 public void onAttachFragment(final Fragment fragment) {
zachhcc2cde02017-08-31 12:34:18 -0700639 LogUtil.i("DialtactsActivity.onAttachFragment", "fragment: %s", fragment);
Eric Erfanianccca3152017-02-22 16:32:36 -0800640 if (fragment instanceof DialpadFragment) {
641 mDialpadFragment = (DialpadFragment) fragment;
642 if (!mIsDialpadShown && !mShowDialpadOnResume) {
zachhcc2cde02017-08-31 12:34:18 -0700643 LogUtil.i("DialtactsActivity.onAttachFragment", "hiding dialpad fragment");
Eric Erfanianccca3152017-02-22 16:32:36 -0800644 final FragmentTransaction transaction = getFragmentManager().beginTransaction();
645 transaction.hide(mDialpadFragment);
646 transaction.commit();
647 }
648 } else if (fragment instanceof SmartDialSearchFragment) {
649 mSmartDialSearchFragment = (SmartDialSearchFragment) fragment;
650 mSmartDialSearchFragment.setOnPhoneNumberPickerActionListener(this);
651 if (!TextUtils.isEmpty(mDialpadQuery)) {
652 mSmartDialSearchFragment.setAddToContactNumber(mDialpadQuery);
653 }
654 } else if (fragment instanceof SearchFragment) {
655 mRegularSearchFragment = (RegularSearchFragment) fragment;
656 mRegularSearchFragment.setOnPhoneNumberPickerActionListener(this);
657 } else if (fragment instanceof ListsFragment) {
658 mListsFragment = (ListsFragment) fragment;
659 mListsFragment.addOnPageChangeListener(this);
Eric Erfanian2ca43182017-08-31 06:57:16 -0700660 } else if (fragment instanceof NewSearchFragment) {
661 mNewSearchFragment = (NewSearchFragment) fragment;
662 updateSearchFragmentPosition();
Eric Erfanianccca3152017-02-22 16:32:36 -0800663 }
664 if (fragment instanceof SearchFragment) {
665 final SearchFragment searchFragment = (SearchFragment) fragment;
666 searchFragment.setReranker(
667 new CursorReranker() {
668 @Override
669 @MainThread
670 public Cursor rerankCursor(Cursor data) {
671 Assert.isMainThread();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700672 String queryString = searchFragment.getQueryString();
673 return mP13nRanker.rankCursor(data, queryString == null ? 0 : queryString.length());
Eric Erfanianccca3152017-02-22 16:32:36 -0800674 }
675 });
676 searchFragment.addOnLoadFinishedListener(
677 new OnLoadFinishedListener() {
678 @Override
679 public void onLoadFinished() {
680 mP13nLogger.onSearchQuery(
681 searchFragment.getQueryString(),
682 (PhoneNumberListAdapter) searchFragment.getAdapter());
683 }
684 });
685 }
686 }
687
688 protected void handleMenuSettings() {
689 final Intent intent = new Intent(this, DialerSettingsActivity.class);
690 startActivity(intent);
691 }
692
693 @Override
694 public void onClick(View view) {
695 int resId = view.getId();
696 if (resId == R.id.floating_action_button) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700697 if (!mIsDialpadShown) {
yueg49df7f62017-09-12 15:15:27 -0700698 LogUtil.i(
699 "DialtactsActivity.onClick", "floating action button clicked, going to show dialpad");
Eric Erfanian2ca43182017-08-31 06:57:16 -0700700 PerformanceReport.recordClick(UiAction.Type.OPEN_DIALPAD);
Eric Erfanianccca3152017-02-22 16:32:36 -0800701 mInCallDialpadUp = false;
702 showDialpadFragment(true);
Eric Erfanian90508232017-03-24 09:31:16 -0700703 PostCall.closePrompt();
yueg49df7f62017-09-12 15:15:27 -0700704 } else {
705 LogUtil.i(
706 "DialtactsActivity.onClick",
707 "floating action button clicked, but dialpad is already showing");
Eric Erfanianccca3152017-02-22 16:32:36 -0800708 }
709 } else if (resId == R.id.voice_search_button) {
710 try {
711 startActivityForResult(
712 new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH),
Eric Erfanian2ca43182017-08-31 06:57:16 -0700713 ActivityRequestCodes.DIALTACTS_VOICE_SEARCH);
Eric Erfanianccca3152017-02-22 16:32:36 -0800714 } catch (ActivityNotFoundException e) {
715 Toast.makeText(
716 DialtactsActivity.this, R.string.voice_search_not_available, Toast.LENGTH_SHORT)
717 .show();
718 }
719 } else if (resId == R.id.dialtacts_options_menu_button) {
720 mOverflowMenu.show();
721 } else {
722 Assert.fail("Unexpected onClick event from " + view);
723 }
724 }
725
726 @Override
727 public boolean onMenuItemClick(MenuItem item) {
728 if (!isSafeToCommitTransactions()) {
729 return true;
730 }
731
732 int resId = item.getItemId();
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700733 if (resId == R.id.menu_history) {
Eric Erfanian2ca43182017-08-31 06:57:16 -0700734 PerformanceReport.recordClick(UiAction.Type.OPEN_CALL_HISTORY);
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700735 final Intent intent = new Intent(this, CallLogActivity.class);
736 startActivity(intent);
Eric Erfanianccca3152017-02-22 16:32:36 -0800737 } else if (resId == R.id.menu_clear_frequents) {
738 ClearFrequentsDialog.show(getFragmentManager());
739 Logger.get(this).logScreenView(ScreenEvent.Type.CLEAR_FREQUENTS, this);
740 return true;
741 } else if (resId == R.id.menu_call_settings) {
742 handleMenuSettings();
743 Logger.get(this).logScreenView(ScreenEvent.Type.SETTINGS, this);
744 return true;
Eric Erfanian2ca43182017-08-31 06:57:16 -0700745 } else if (resId == R.id.menu_new_ui_launcher_shortcut) {
746 MainComponent.get(this).getMain().createNewUiLauncherShortcut(this);
747 return true;
Eric Erfanianccca3152017-02-22 16:32:36 -0800748 }
749 return false;
750 }
751
752 @Override
753 protected void onActivityResult(int requestCode, int resultCode, Intent data) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700754 LogUtil.i(
755 "DialtactsActivity.onActivityResult",
756 "requestCode:%d, resultCode:%d",
757 requestCode,
758 resultCode);
Eric Erfanian2ca43182017-08-31 06:57:16 -0700759 if (requestCode == ActivityRequestCodes.DIALTACTS_VOICE_SEARCH) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800760 if (resultCode == RESULT_OK) {
761 final ArrayList<String> matches =
762 data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);
763 if (matches.size() > 0) {
764 mVoiceSearchQuery = matches.get(0);
765 } else {
766 LogUtil.i("DialtactsActivity.onActivityResult", "voice search - nothing heard");
767 }
768 } else {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700769 LogUtil.e("DialtactsActivity.onActivityResult", "voice search failed");
Eric Erfanianccca3152017-02-22 16:32:36 -0800770 }
Eric Erfanian2ca43182017-08-31 06:57:16 -0700771 } else if (requestCode == ActivityRequestCodes.DIALTACTS_CALL_COMPOSER) {
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700772 if (resultCode == RESULT_FIRST_USER) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800773 LogUtil.i(
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700774 "DialtactsActivity.onActivityResult", "returned from call composer, error occurred");
Eric Erfanianccca3152017-02-22 16:32:36 -0800775 String message =
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700776 getString(
777 R.string.call_composer_connection_failed,
778 data.getStringExtra(CallComposerActivity.KEY_CONTACT_NAME));
Eric Erfanianccca3152017-02-22 16:32:36 -0800779 Snackbar.make(mParentLayout, message, Snackbar.LENGTH_LONG).show();
780 } else {
781 LogUtil.i("DialtactsActivity.onActivityResult", "returned from call composer, no error");
782 }
Eric Erfanian2ca43182017-08-31 06:57:16 -0700783 } else if (requestCode == ActivityRequestCodes.DIALTACTS_CALL_DETAILS) {
784 if (resultCode == RESULT_OK
785 && data != null
786 && data.getBooleanExtra(CallDetailsActivity.EXTRA_HAS_ENRICHED_CALL_DATA, false)) {
787 String number = data.getStringExtra(CallDetailsActivity.EXTRA_PHONE_NUMBER);
788 int snackbarDurationMillis = 5_000;
789 Snackbar.make(mParentLayout, getString(R.string.ec_data_deleted), snackbarDurationMillis)
790 .setAction(
791 R.string.view_conversation,
792 v -> startActivity(IntentProvider.getSendSmsIntentProvider(number).getIntent(this)))
793 .setActionTextColor(getResources().getColor(R.color.dialer_snackbar_action_text_color))
794 .show();
795 }
Eric Erfanianccca3152017-02-22 16:32:36 -0800796 }
797 super.onActivityResult(requestCode, resultCode, data);
798 }
799
800 /**
801 * Update the number of unread voicemails (potentially other tabs) displayed next to the tab icon.
802 */
803 public void updateTabUnreadCounts() {
804 mListsFragment.updateTabUnreadCounts();
805 }
806
807 /**
808 * Initiates a fragment transaction to show the dialpad fragment. Animations and other visual
809 * updates are handled by a callback which is invoked after the dialpad fragment is shown.
810 *
811 * @see #onDialpadShown
812 */
813 private void showDialpadFragment(boolean animate) {
zachhcc2cde02017-08-31 12:34:18 -0700814 LogUtil.i("DialtactActivity.showDialpadFragment", "animate: %b", animate);
815 if (mIsDialpadShown) {
816 LogUtil.i("DialtactsActivity.showDialpadFragment", "dialpad already shown");
817 return;
818 }
819 if (mStateSaved) {
820 LogUtil.i("DialtactsActivity.showDialpadFragment", "state already saved");
Eric Erfanianccca3152017-02-22 16:32:36 -0800821 return;
822 }
823 mIsDialpadShown = true;
824
825 mListsFragment.setUserVisibleHint(false);
826
827 final FragmentTransaction ft = getFragmentManager().beginTransaction();
828 if (mDialpadFragment == null) {
829 mDialpadFragment = new DialpadFragment();
830 ft.add(R.id.dialtacts_container, mDialpadFragment, TAG_DIALPAD_FRAGMENT);
831 } else {
832 ft.show(mDialpadFragment);
833 }
834
835 mDialpadFragment.setAnimate(animate);
836 Logger.get(this).logScreenView(ScreenEvent.Type.DIALPAD, this);
837 ft.commit();
838
839 if (animate) {
840 mFloatingActionButtonController.scaleOut();
Eric Erfanian2ca43182017-08-31 06:57:16 -0700841 maybeEnterSearchUi();
Eric Erfanianccca3152017-02-22 16:32:36 -0800842 } else {
843 mFloatingActionButtonController.setVisible(false);
844 maybeEnterSearchUi();
845 }
846 mActionBarController.onDialpadUp();
847
848 Assert.isNotNull(mListsFragment.getView()).animate().alpha(0).withLayer();
849
Eric Erfanian2ca43182017-08-31 06:57:16 -0700850 // adjust the title, so the user will know where we're at when the activity start/resumes.
Eric Erfanianccca3152017-02-22 16:32:36 -0800851 setTitle(R.string.launcherDialpadActivityLabel);
852 }
853
Eric Erfanian2ca43182017-08-31 06:57:16 -0700854 @Override
855 public void getLastOutgoingCall(LastOutgoingCallCallback callback) {
856 new CallLogAsync()
857 .getLastOutgoingCall(
858 new CallLogAsync.GetLastOutgoingCallArgs(this, callback::lastOutgoingCall));
859 }
860
Eric Erfanianccca3152017-02-22 16:32:36 -0800861 /** Callback from child DialpadFragment when the dialpad is shown. */
Eric Erfanian2ca43182017-08-31 06:57:16 -0700862 @Override
Eric Erfanianccca3152017-02-22 16:32:36 -0800863 public void onDialpadShown() {
zachhcc2cde02017-08-31 12:34:18 -0700864 LogUtil.enterBlock("DialtactsActivity.onDialpadShown");
Eric Erfanianccca3152017-02-22 16:32:36 -0800865 Assert.isNotNull(mDialpadFragment);
866 if (mDialpadFragment.getAnimate()) {
867 Assert.isNotNull(mDialpadFragment.getView()).startAnimation(mSlideIn);
868 } else {
869 mDialpadFragment.setYFraction(0);
870 }
871
872 updateSearchFragmentPosition();
873 }
874
calderwoodra50715262017-09-08 09:20:08 -0700875 @Override
876 public void onCallPlacedFromDialpad() {
877 hideDialpadFragment(false /* animate */, true /*clearDialpad */);
878 exitSearchUi();
879 }
880
Eric Erfanianccca3152017-02-22 16:32:36 -0800881 /**
882 * Initiates animations and other visual updates to hide the dialpad. The fragment is hidden in a
883 * callback after the hide animation ends.
884 *
885 * @see #commitDialpadFragmentHide
886 */
calderwoodra50715262017-09-08 09:20:08 -0700887 private void hideDialpadFragment(boolean animate, boolean clearDialpad) {
zachhcc2cde02017-08-31 12:34:18 -0700888 LogUtil.enterBlock("DialtactsActivity.hideDialpadFragment");
Eric Erfanianccca3152017-02-22 16:32:36 -0800889 if (mDialpadFragment == null || mDialpadFragment.getView() == null) {
890 return;
891 }
892 if (clearDialpad) {
893 // Temporarily disable accessibility when we clear the dialpad, since it should be
894 // invisible and should not announce anything.
895 mDialpadFragment
896 .getDigitsWidget()
897 .setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
898 mDialpadFragment.clearDialpad();
899 mDialpadFragment
900 .getDigitsWidget()
901 .setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_AUTO);
902 }
903 if (!mIsDialpadShown) {
904 return;
905 }
906 mIsDialpadShown = false;
907 mDialpadFragment.setAnimate(animate);
908 mListsFragment.setUserVisibleHint(true);
909 mListsFragment.sendScreenViewForCurrentPosition();
910
911 updateSearchFragmentPosition();
912
913 mFloatingActionButtonController.align(getFabAlignment(), animate);
914 if (animate) {
915 mDialpadFragment.getView().startAnimation(mSlideOut);
916 } else {
917 commitDialpadFragmentHide();
918 }
919
920 mActionBarController.onDialpadDown();
921
Eric Erfanian2ca43182017-08-31 06:57:16 -0700922 // reset the title to normal.
Eric Erfanianccca3152017-02-22 16:32:36 -0800923 setTitle(R.string.launcherActivityLabel);
924 }
925
926 /** Finishes hiding the dialpad fragment after any animations are completed. */
927 private void commitDialpadFragmentHide() {
Eric Erfanian10b34a52017-05-04 08:23:17 -0700928 if (!mStateSaved
929 && mDialpadFragment != null
930 && !mDialpadFragment.isHidden()
931 && !isDestroyed()) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800932 final FragmentTransaction ft = getFragmentManager().beginTransaction();
933 ft.hide(mDialpadFragment);
934 ft.commit();
935 }
936 mFloatingActionButtonController.scaleIn(AnimUtils.NO_DELAY);
937 }
938
939 private void updateSearchFragmentPosition() {
940 SearchFragment fragment = null;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700941 if (mSmartDialSearchFragment != null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800942 fragment = mSmartDialSearchFragment;
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700943 } else if (mRegularSearchFragment != null) {
Eric Erfanianccca3152017-02-22 16:32:36 -0800944 fragment = mRegularSearchFragment;
945 }
Eric Erfaniand5e47f62017-03-15 14:41:07 -0700946 LogUtil.d(
947 "DialtactsActivity.updateSearchFragmentPosition",
948 "fragment: %s, isVisible: %b",
949 fragment,
950 fragment != null && fragment.isVisible());
951 if (fragment != null) {
952 // We need to force animation here even when fragment is not visible since it might not be
953 // visible immediately after screen orientation change and dialpad height would not be
954 // available immediately which is required to update position. By forcing an animation,
955 // position will be updated after a delay by when the dialpad height would be available.
Eric Erfanianccca3152017-02-22 16:32:36 -0800956 fragment.updatePosition(true /* animate */);
Eric Erfanian2ca43182017-08-31 06:57:16 -0700957 } else if (mNewSearchFragment != null) {
958 int animationDuration = getResources().getInteger(R.integer.dialpad_slide_in_duration);
959 int actionbarHeight = getResources().getDimensionPixelSize(R.dimen.action_bar_height_large);
960 int shadowHeight = getResources().getDrawable(R.drawable.search_shadow).getIntrinsicHeight();
961 int start = isDialpadShown() ? actionbarHeight - shadowHeight : 0;
962 int end = isDialpadShown() ? 0 : actionbarHeight - shadowHeight;
963 mNewSearchFragment.animatePosition(start, end, animationDuration);
Eric Erfanianccca3152017-02-22 16:32:36 -0800964 }
965 }
966
967 @Override
968 public boolean isInSearchUi() {
calderwoodra50715262017-09-08 09:20:08 -0700969 return mInDialpadSearch || mInRegularSearch || mInNewSearch;
Eric Erfanianccca3152017-02-22 16:32:36 -0800970 }
971
972 @Override
973 public boolean hasSearchQuery() {
974 return !TextUtils.isEmpty(mSearchQuery);
975 }
976
Eric Erfanianccca3152017-02-22 16:32:36 -0800977 private void setNotInSearchUi() {
978 mInDialpadSearch = false;
979 mInRegularSearch = false;
calderwoodra50715262017-09-08 09:20:08 -0700980 mInNewSearch = false;
Eric Erfanianccca3152017-02-22 16:32:36 -0800981 }
982
983 private void hideDialpadAndSearchUi() {
984 if (mIsDialpadShown) {
985 hideDialpadFragment(false, true);
986 } else {
987 exitSearchUi();
988 }
989 }
990
991 private void prepareVoiceSearchButton() {
992 final Intent voiceIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
993 if (canIntentBeHandled(voiceIntent)) {
994 mVoiceSearchButton.setVisibility(View.VISIBLE);
995 mVoiceSearchButton.setOnClickListener(this);
996 } else {
997 mVoiceSearchButton.setVisibility(View.GONE);
998 }
999 }
1000
1001 public boolean isNearbyPlacesSearchEnabled() {
1002 return false;
1003 }
1004
1005 protected int getSearchBoxHint() {
1006 return R.string.dialer_hint_find_contact;
1007 }
1008
1009 /** Sets the hint text for the contacts search box */
1010 private void setSearchBoxHint() {
1011 SearchEditTextLayout searchEditTextLayout =
1012 (SearchEditTextLayout)
1013 getActionBarSafely().getCustomView().findViewById(R.id.search_view_container);
1014 ((TextView) searchEditTextLayout.findViewById(R.id.search_box_start_search))
1015 .setHint(getSearchBoxHint());
1016 }
1017
1018 protected OptionsPopupMenu buildOptionsMenu(View invoker) {
1019 final OptionsPopupMenu popupMenu = new OptionsPopupMenu(this, invoker);
1020 popupMenu.inflate(R.menu.dialtacts_options);
1021 popupMenu.setOnMenuItemClickListener(this);
1022 return popupMenu;
1023 }
1024
1025 @Override
1026 public boolean onCreateOptionsMenu(Menu menu) {
1027 if (mPendingSearchViewQuery != null) {
1028 mSearchView.setText(mPendingSearchViewQuery);
1029 mPendingSearchViewQuery = null;
1030 }
1031 if (mActionBarController != null) {
1032 mActionBarController.restoreActionBarOffset();
1033 }
1034 return false;
1035 }
1036
1037 /**
1038 * Returns true if the intent is due to hitting the green send key (hardware call button:
1039 * KEYCODE_CALL) while in a call.
1040 *
1041 * @param intent the intent that launched this activity
1042 * @return true if the intent is due to hitting the green send key while in a call
1043 */
1044 private boolean isSendKeyWhileInCall(Intent intent) {
1045 // If there is a call in progress and the user launched the dialer by hitting the call
1046 // button, go straight to the in-call screen.
1047 final boolean callKey = Intent.ACTION_CALL_BUTTON.equals(intent.getAction());
1048
1049 // When KEYCODE_CALL event is handled it dispatches an intent with the ACTION_CALL_BUTTON.
1050 // Besides of checking the intent action, we must check if the phone is really during a
1051 // call in order to decide whether to ignore the event or continue to display the activity.
1052 if (callKey && phoneIsInUse()) {
1053 TelecomUtil.showInCallScreen(this, false);
1054 return true;
1055 }
1056
1057 return false;
1058 }
1059
1060 /**
1061 * Sets the current tab based on the intent's request type
1062 *
1063 * @param intent Intent that contains information about which tab should be selected
1064 */
1065 private void displayFragment(Intent intent) {
1066 // If we got here by hitting send and we're in call forward along to the in-call activity
1067 if (isSendKeyWhileInCall(intent)) {
1068 finish();
1069 return;
1070 }
1071
zachhcc2cde02017-08-31 12:34:18 -07001072 boolean showDialpadChooser =
Eric Erfanianccca3152017-02-22 16:32:36 -08001073 !ACTION_SHOW_TAB.equals(intent.getAction())
1074 && phoneIsInUse()
1075 && !DialpadFragment.isAddCallMode(intent);
zachhcc2cde02017-08-31 12:34:18 -07001076 boolean isDialIntent = intent.getData() != null && isDialIntent(intent);
calderwoodra48cce922017-09-14 14:28:46 -07001077 boolean isAddCallIntent = DialpadFragment.isAddCallMode(intent);
1078 if (showDialpadChooser || isDialIntent || isAddCallIntent) {
zachhcc2cde02017-08-31 12:34:18 -07001079 LogUtil.i(
1080 "DialtactsActivity.displayFragment",
calderwoodra48cce922017-09-14 14:28:46 -07001081 "show dialpad fragment (showDialpadChooser: %b, isDialIntent: %b, isAddCallIntent: %b)",
zachhcc2cde02017-08-31 12:34:18 -07001082 showDialpadChooser,
calderwoodra48cce922017-09-14 14:28:46 -07001083 isDialIntent,
1084 isAddCallIntent);
Eric Erfanianccca3152017-02-22 16:32:36 -08001085 showDialpadFragment(false);
1086 mDialpadFragment.setStartedFromNewIntent(true);
1087 if (showDialpadChooser && !mDialpadFragment.isVisible()) {
1088 mInCallDialpadUp = true;
1089 }
Eric Erfanian2ca43182017-08-31 06:57:16 -07001090 } else if (isLastTabEnabled) {
1091 @TabIndex
1092 int tabIndex =
1093 DialerUtils.getDefaultSharedPreferenceForDeviceProtectedStorageContext(this)
1094 .getInt(KEY_LAST_TAB, DialtactsPagerAdapter.TAB_INDEX_SPEED_DIAL);
1095 // If voicemail tab is saved and its availability changes, we still move to the voicemail tab
1096 // but it is quickly removed and shown the contacts tab.
1097 if (mListsFragment != null) {
1098 mListsFragment.showTab(tabIndex);
1099 PerformanceReport.setStartingTabIndex(tabIndex);
1100 } else {
1101 PerformanceReport.setStartingTabIndex(DialtactsPagerAdapter.TAB_INDEX_SPEED_DIAL);
1102 }
Eric Erfanianccca3152017-02-22 16:32:36 -08001103 }
1104 }
1105
1106 @Override
1107 public void onNewIntent(Intent newIntent) {
zachhcc2cde02017-08-31 12:34:18 -07001108 LogUtil.enterBlock("DialtactsActivity.onNewIntent");
Eric Erfanianccca3152017-02-22 16:32:36 -08001109 setIntent(newIntent);
Eric Erfanian10b34a52017-05-04 08:23:17 -07001110 mFirstLaunch = true;
Eric Erfanianccca3152017-02-22 16:32:36 -08001111
1112 mStateSaved = false;
1113 displayFragment(newIntent);
1114
1115 invalidateOptionsMenu();
1116 }
1117
1118 /** Returns true if the given intent contains a phone number to populate the dialer with */
1119 private boolean isDialIntent(Intent intent) {
1120 final String action = intent.getAction();
1121 if (Intent.ACTION_DIAL.equals(action) || ACTION_TOUCH_DIALER.equals(action)) {
1122 return true;
1123 }
1124 if (Intent.ACTION_VIEW.equals(action)) {
1125 final Uri data = intent.getData();
1126 if (data != null && PhoneAccount.SCHEME_TEL.equals(data.getScheme())) {
1127 return true;
1128 }
1129 }
1130 return false;
1131 }
1132
1133 /** Shows the search fragment */
1134 private void enterSearchUi(boolean smartDialSearch, String query, boolean animate) {
zachhcc2cde02017-08-31 12:34:18 -07001135 LogUtil.i("DialtactsActivity.enterSearchUi", "smart dial: %b", smartDialSearch);
Eric Erfanianccca3152017-02-22 16:32:36 -08001136 if (mStateSaved || getFragmentManager().isDestroyed()) {
1137 // Weird race condition where fragment is doing work after the activity is destroyed
1138 // due to talkback being on (b/10209937). Just return since we can't do any
1139 // constructive here.
zachhcc2cde02017-08-31 12:34:18 -07001140 LogUtil.i(
1141 "DialtactsActivity.enterSearchUi",
1142 "not entering search UI (mStateSaved: %b, isDestroyed: %b)",
1143 mStateSaved,
1144 getFragmentManager().isDestroyed());
Eric Erfanianccca3152017-02-22 16:32:36 -08001145 return;
1146 }
1147
Eric Erfanianccca3152017-02-22 16:32:36 -08001148 final FragmentTransaction transaction = getFragmentManager().beginTransaction();
1149 if (mInDialpadSearch && mSmartDialSearchFragment != null) {
1150 transaction.remove(mSmartDialSearchFragment);
1151 } else if (mInRegularSearch && mRegularSearchFragment != null) {
1152 transaction.remove(mRegularSearchFragment);
1153 }
1154
1155 final String tag;
calderwoodra50715262017-09-08 09:20:08 -07001156 mInDialpadSearch = false;
1157 mInRegularSearch = false;
1158 mInNewSearch = false;
Eric Erfanian2ca43182017-08-31 06:57:16 -07001159 boolean useNewSearch =
1160 ConfigProviderBindings.get(this).getBoolean("enable_new_search_fragment", false);
1161 if (useNewSearch) {
1162 tag = TAG_NEW_SEARCH_FRAGMENT;
calderwoodra50715262017-09-08 09:20:08 -07001163 mInNewSearch = true;
Eric Erfanian2ca43182017-08-31 06:57:16 -07001164 } else if (smartDialSearch) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001165 tag = TAG_SMARTDIAL_SEARCH_FRAGMENT;
calderwoodra50715262017-09-08 09:20:08 -07001166 mInDialpadSearch = true;
Eric Erfanianccca3152017-02-22 16:32:36 -08001167 } else {
1168 tag = TAG_REGULAR_SEARCH_FRAGMENT;
calderwoodra50715262017-09-08 09:20:08 -07001169 mInRegularSearch = true;
Eric Erfanianccca3152017-02-22 16:32:36 -08001170 }
Eric Erfanianccca3152017-02-22 16:32:36 -08001171
1172 mFloatingActionButtonController.scaleOut();
1173
Eric Erfanianccca3152017-02-22 16:32:36 -08001174 if (animate) {
1175 transaction.setCustomAnimations(android.R.animator.fade_in, 0);
1176 } else {
1177 transaction.setTransition(FragmentTransaction.TRANSIT_NONE);
1178 }
Eric Erfanian2ca43182017-08-31 06:57:16 -07001179
1180 Fragment fragment = getFragmentManager().findFragmentByTag(tag);
Eric Erfanianccca3152017-02-22 16:32:36 -08001181 if (fragment == null) {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001182 if (useNewSearch) {
1183 fragment = new NewSearchFragment();
1184 } else if (smartDialSearch) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001185 fragment = new SmartDialSearchFragment();
1186 } else {
1187 fragment = Bindings.getLegacy(this).newRegularSearchFragment();
Eric Erfanian2ca43182017-08-31 06:57:16 -07001188 ((SearchFragment) fragment)
1189 .setOnTouchListener(
1190 (v, event) -> {
1191 // Show the FAB when the user touches the lists fragment and the soft
1192 // keyboard is hidden.
1193 hideDialpadFragment(true, false);
1194 v.performClick();
1195 return false;
1196 });
Eric Erfanianccca3152017-02-22 16:32:36 -08001197 }
1198 transaction.add(R.id.dialtacts_frame, fragment, tag);
1199 } else {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001200 // TODO(calderwoodra): if this is a transition from dialpad to searchbar, animate fragment
1201 // down, and vice versa. Perhaps just add a coordinator behavior with the search bar.
Eric Erfanianccca3152017-02-22 16:32:36 -08001202 transaction.show(fragment);
1203 }
Eric Erfanian2ca43182017-08-31 06:57:16 -07001204
Eric Erfanianccca3152017-02-22 16:32:36 -08001205 // DialtactsActivity will provide the options menu
1206 fragment.setHasOptionsMenu(false);
Eric Erfanian2ca43182017-08-31 06:57:16 -07001207
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001208 // Will show empty list if P13nRanker is not enabled. Else, re-ranked list by the ranker.
Eric Erfanian2ca43182017-08-31 06:57:16 -07001209 if (!useNewSearch) {
1210 ((SearchFragment) fragment)
1211 .setShowEmptyListForNullQuery(mP13nRanker.shouldShowEmptyListForNullQuery());
1212 } else {
1213 // TODO(calderwoodra): add p13n ranker to new search.
1214 }
1215
1216 if (!smartDialSearch && !useNewSearch) {
1217 ((SearchFragment) fragment).setQueryString(query);
1218 } else if (useNewSearch) {
calderwoodraaf3cc172017-09-01 23:28:21 -07001219 ((NewSearchFragment) fragment).setQuery(query, getCallInitiationType());
Eric Erfanianccca3152017-02-22 16:32:36 -08001220 }
1221 transaction.commit();
1222
1223 if (animate) {
1224 Assert.isNotNull(mListsFragment.getView()).animate().alpha(0).withLayer();
1225 }
1226 mListsFragment.setUserVisibleHint(false);
1227
1228 if (smartDialSearch) {
1229 Logger.get(this).logScreenView(ScreenEvent.Type.SMART_DIAL_SEARCH, this);
1230 } else {
1231 Logger.get(this).logScreenView(ScreenEvent.Type.REGULAR_SEARCH, this);
1232 }
1233 }
1234
1235 /** Hides the search fragment */
1236 private void exitSearchUi() {
zachhcc2cde02017-08-31 12:34:18 -07001237 LogUtil.enterBlock("DialtactsActivity.exitSearchUi");
1238
Eric Erfanianccca3152017-02-22 16:32:36 -08001239 // See related bug in enterSearchUI();
1240 if (getFragmentManager().isDestroyed() || mStateSaved) {
1241 return;
1242 }
1243
1244 mSearchView.setText(null);
1245
1246 if (mDialpadFragment != null) {
1247 mDialpadFragment.clearDialpad();
1248 }
1249
1250 setNotInSearchUi();
1251
1252 // Restore the FAB for the lists fragment.
1253 if (getFabAlignment() != FloatingActionButtonController.ALIGN_END) {
1254 mFloatingActionButtonController.setVisible(false);
1255 }
1256 mFloatingActionButtonController.scaleIn(FAB_SCALE_IN_DELAY_MS);
1257 onPageScrolled(mListsFragment.getCurrentTabIndex(), 0 /* offset */, 0 /* pixelOffset */);
1258 onPageSelected(mListsFragment.getCurrentTabIndex());
1259
1260 final FragmentTransaction transaction = getFragmentManager().beginTransaction();
1261 if (mSmartDialSearchFragment != null) {
1262 transaction.remove(mSmartDialSearchFragment);
1263 }
1264 if (mRegularSearchFragment != null) {
1265 transaction.remove(mRegularSearchFragment);
1266 }
Eric Erfanian2ca43182017-08-31 06:57:16 -07001267 if (mNewSearchFragment != null) {
1268 transaction.remove(mNewSearchFragment);
1269 }
Eric Erfanianccca3152017-02-22 16:32:36 -08001270 transaction.commit();
1271
1272 Assert.isNotNull(mListsFragment.getView()).animate().alpha(1).withLayer();
1273
1274 if (mDialpadFragment == null || !mDialpadFragment.isVisible()) {
1275 // If the dialpad fragment wasn't previously visible, then send a screen view because
1276 // we are exiting regular search. Otherwise, the screen view will be sent by
1277 // {@link #hideDialpadFragment}.
1278 mListsFragment.sendScreenViewForCurrentPosition();
1279 mListsFragment.setUserVisibleHint(true);
1280 }
1281
1282 mActionBarController.onSearchUiExited();
1283 }
1284
1285 @Override
1286 public void onBackPressed() {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001287 PerformanceReport.recordClick(UiAction.Type.PRESS_ANDROID_BACK_BUTTON);
1288
Eric Erfanianccca3152017-02-22 16:32:36 -08001289 if (mStateSaved) {
1290 return;
1291 }
1292 if (mIsDialpadShown) {
calderwoodraed4e11d2017-09-05 18:24:47 -07001293 hideDialpadFragment(true, false);
calderwoodraecb04562017-09-16 07:34:05 -07001294 if (TextUtils.isEmpty(mDialpadQuery)) {
1295 exitSearchUi();
1296 }
calderwoodraed4e11d2017-09-05 18:24:47 -07001297 } else if (isInSearchUi()) {
calderwoodra50715262017-09-08 09:20:08 -07001298 if (mIsKeyboardOpen) {
1299 DialerUtils.hideInputMethod(mParentLayout);
1300 PerformanceReport.recordClick(UiAction.Type.HIDE_KEYBOARD_IN_SEARCH);
1301 } else {
1302 exitSearchUi();
1303 }
Eric Erfanianccca3152017-02-22 16:32:36 -08001304 } else {
1305 super.onBackPressed();
1306 }
1307 }
1308
calderwoodra50715262017-09-08 09:20:08 -07001309 @Override
1310 public void onConfigurationChanged(Configuration configuration) {
1311 super.onConfigurationChanged(configuration);
1312 // Checks whether a hardware keyboard is available
1313 if (configuration.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO) {
1314 mIsKeyboardOpen = true;
1315 } else if (configuration.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_YES) {
1316 mIsKeyboardOpen = false;
1317 }
1318 }
1319
Eric Erfanianccca3152017-02-22 16:32:36 -08001320 private void maybeEnterSearchUi() {
1321 if (!isInSearchUi()) {
1322 enterSearchUi(true /* isSmartDial */, mSearchQuery, false);
1323 }
1324 }
1325
Eric Erfanianccca3152017-02-22 16:32:36 -08001326 @Override
1327 public void onDialpadQueryChanged(String query) {
1328 mDialpadQuery = query;
1329 if (mSmartDialSearchFragment != null) {
1330 mSmartDialSearchFragment.setAddToContactNumber(query);
1331 }
1332 final String normalizedQuery =
1333 SmartDialNameMatcher.normalizeNumber(query, SmartDialNameMatcher.LATIN_SMART_DIAL_MAP);
1334
1335 if (!TextUtils.equals(mSearchView.getText(), normalizedQuery)) {
1336 if (DEBUG) {
1337 LogUtil.v("DialtactsActivity.onDialpadQueryChanged", "new query: " + query);
1338 }
1339 if (mDialpadFragment == null || !mDialpadFragment.isVisible()) {
1340 // This callback can happen if the dialpad fragment is recreated because of
1341 // activity destruction. In that case, don't update the search view because
1342 // that would bring the user back to the search fragment regardless of the
1343 // previous state of the application. Instead, just return here and let the
1344 // fragment manager correctly figure out whatever fragment was last displayed.
1345 if (!TextUtils.isEmpty(normalizedQuery)) {
1346 mPendingSearchViewQuery = normalizedQuery;
1347 }
1348 return;
1349 }
1350 mSearchView.setText(normalizedQuery);
1351 }
1352
1353 try {
1354 if (mDialpadFragment != null && mDialpadFragment.isVisible()) {
1355 mDialpadFragment.process_quote_emergency_unquote(normalizedQuery);
1356 }
1357 } catch (Exception ignored) {
1358 // Skip any exceptions for this piece of code
1359 }
1360 }
1361
1362 @Override
1363 public boolean onDialpadSpacerTouchWithEmptyQuery() {
1364 if (mInDialpadSearch
1365 && mSmartDialSearchFragment != null
1366 && !mSmartDialSearchFragment.isShowingPermissionRequest()) {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001367 PerformanceReport.recordClick(UiAction.Type.CLOSE_DIALPAD);
Eric Erfanianccca3152017-02-22 16:32:36 -08001368 hideDialpadFragment(true /* animate */, true /* clearDialpad */);
1369 return true;
1370 }
1371 return false;
1372 }
1373
1374 @Override
1375 public void onListFragmentScrollStateChange(int scrollState) {
Eric Erfanian2ca43182017-08-31 06:57:16 -07001376 PerformanceReport.recordScrollStateChange(scrollState);
Eric Erfanianccca3152017-02-22 16:32:36 -08001377 if (scrollState == OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
1378 hideDialpadFragment(true, false);
1379 DialerUtils.hideInputMethod(mParentLayout);
1380 }
1381 }
1382
1383 @Override
1384 public void onListFragmentScroll(int firstVisibleItem, int visibleItemCount, int totalItemCount) {
1385 // TODO: No-op for now. This should eventually show/hide the actionBar based on
1386 // interactions with the ListsFragments.
1387 }
1388
1389 private boolean phoneIsInUse() {
wangqifff6fcf2017-08-31 15:30:58 -07001390 return TelecomUtil.isInManagedCall(this);
Eric Erfanianccca3152017-02-22 16:32:36 -08001391 }
1392
1393 private boolean canIntentBeHandled(Intent intent) {
1394 final PackageManager packageManager = getPackageManager();
1395 final List<ResolveInfo> resolveInfo =
1396 packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
1397 return resolveInfo != null && resolveInfo.size() > 0;
1398 }
1399
1400 /** Called when the user has long-pressed a contact tile to start a drag operation. */
1401 @Override
1402 public void onDragStarted(int x, int y, PhoneFavoriteSquareTileView view) {
1403 mListsFragment.showRemoveView(true);
1404 }
1405
1406 @Override
1407 public void onDragHovered(int x, int y, PhoneFavoriteSquareTileView view) {}
1408
1409 /** Called when the user has released a contact tile after long-pressing it. */
1410 @Override
1411 public void onDragFinished(int x, int y) {
1412 mListsFragment.showRemoveView(false);
1413 }
1414
1415 @Override
1416 public void onDroppedOnRemove() {}
1417
1418 /**
1419 * Allows the SpeedDialFragment to attach the drag controller to mRemoveViewContainer once it has
1420 * been attached to the activity.
1421 */
1422 @Override
1423 public void setDragDropController(DragDropController dragController) {
1424 mDragDropController = dragController;
1425 mListsFragment.getRemoveView().setDragDropController(dragController);
1426 }
1427
Eric Erfaniand8046e52017-04-06 09:41:50 -07001428 /** Implemented to satisfy {@link OldSpeedDialFragment.HostInterface} */
Eric Erfanianccca3152017-02-22 16:32:36 -08001429 @Override
1430 public void showAllContactsTab() {
1431 if (mListsFragment != null) {
Eric Erfaniand8046e52017-04-06 09:41:50 -07001432 mListsFragment.showTab(DialtactsPagerAdapter.TAB_INDEX_ALL_CONTACTS);
Eric Erfanianccca3152017-02-22 16:32:36 -08001433 }
1434 }
1435
1436 /** Implemented to satisfy {@link CallLogFragment.HostInterface} */
1437 @Override
1438 public void showDialpad() {
1439 showDialpadFragment(true);
1440 }
1441
1442 @Override
1443 public void enableFloatingButton(boolean enabled) {
1444 LogUtil.d("DialtactsActivity.enableFloatingButton", "enable: %b", enabled);
1445 // Floating button shouldn't be enabled when dialpad is shown.
1446 if (!isDialpadShown() || !enabled) {
1447 mFloatingActionButtonController.setVisible(enabled);
1448 }
1449 }
1450
1451 @Override
1452 public void onPickDataUri(
1453 Uri dataUri, boolean isVideoCall, CallSpecificAppData callSpecificAppData) {
1454 mClearSearchOnPause = true;
1455 PhoneNumberInteraction.startInteractionForPhoneCall(
1456 DialtactsActivity.this, dataUri, isVideoCall, callSpecificAppData);
1457 }
1458
1459 @Override
1460 public void onPickPhoneNumber(
1461 String phoneNumber, boolean isVideoCall, CallSpecificAppData callSpecificAppData) {
1462 if (phoneNumber == null) {
1463 // Invalid phone number, but let the call go through so that InCallUI can show
1464 // an error message.
1465 phoneNumber = "";
1466 }
1467
1468 Intent intent =
1469 new CallIntentBuilder(phoneNumber, callSpecificAppData).setIsVideoCall(isVideoCall).build();
1470
Eric Erfanian2ca43182017-08-31 06:57:16 -07001471 if (callSpecificAppData.getAllowAssistedDialing()) {
1472 Bundle extras = new Bundle();
1473 extras.putBoolean(TelephonyManagerCompat.ALLOW_ASSISTED_DIAL, true);
1474 intent.putExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, extras);
1475 }
1476
Eric Erfanianccca3152017-02-22 16:32:36 -08001477 DialerUtils.startActivityWithErrorToast(this, intent);
1478 mClearSearchOnPause = true;
1479 }
1480
1481 @Override
1482 public void onHomeInActionBarSelected() {
1483 exitSearchUi();
1484 }
1485
1486 @Override
1487 public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
1488 int tabIndex = mListsFragment.getCurrentTabIndex();
1489
1490 // Scroll the button from center to end when moving from the Speed Dial to Call History tab.
1491 // In RTL, scroll when the current tab is Call History instead, since the order of the tabs
1492 // is reversed and the ViewPager returns the left tab position during scroll.
1493 boolean isRtl = ViewUtil.isRtl();
Eric Erfaniand8046e52017-04-06 09:41:50 -07001494 if (!isRtl && tabIndex == DialtactsPagerAdapter.TAB_INDEX_SPEED_DIAL && !mIsLandscape) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001495 mFloatingActionButtonController.onPageScrolled(positionOffset);
Eric Erfaniand8046e52017-04-06 09:41:50 -07001496 } else if (isRtl && tabIndex == DialtactsPagerAdapter.TAB_INDEX_HISTORY && !mIsLandscape) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001497 mFloatingActionButtonController.onPageScrolled(1 - positionOffset);
Eric Erfaniand8046e52017-04-06 09:41:50 -07001498 } else if (tabIndex != DialtactsPagerAdapter.TAB_INDEX_SPEED_DIAL) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001499 mFloatingActionButtonController.onPageScrolled(1);
1500 }
1501 }
1502
1503 @Override
1504 public void onPageSelected(int position) {
1505 updateMissedCalls();
1506 int tabIndex = mListsFragment.getCurrentTabIndex();
1507 mPreviouslySelectedTabIndex = tabIndex;
1508 mFloatingActionButtonController.setVisible(true);
Eric Erfaniand8046e52017-04-06 09:41:50 -07001509 timeTabSelected = SystemClock.elapsedRealtime();
Eric Erfanianccca3152017-02-22 16:32:36 -08001510 }
1511
1512 @Override
1513 public void onPageScrollStateChanged(int state) {}
1514
1515 @Override
1516 public boolean isActionBarShowing() {
1517 return mActionBarController.isActionBarShowing();
1518 }
1519
1520 @Override
Eric Erfanianccca3152017-02-22 16:32:36 -08001521 public boolean isDialpadShown() {
1522 return mIsDialpadShown;
1523 }
1524
1525 @Override
1526 public int getDialpadHeight() {
1527 if (mDialpadFragment != null) {
1528 return mDialpadFragment.getDialpadHeight();
1529 }
1530 return 0;
1531 }
1532
1533 @Override
Eric Erfanianccca3152017-02-22 16:32:36 -08001534 public void setActionBarHideOffset(int offset) {
1535 getActionBarSafely().setHideOffset(offset);
1536 }
1537
1538 @Override
1539 public int getActionBarHeight() {
1540 return mActionBarHeight;
1541 }
1542
1543 private int getFabAlignment() {
1544 if (!mIsLandscape
1545 && !isInSearchUi()
Eric Erfaniand8046e52017-04-06 09:41:50 -07001546 && mListsFragment.getCurrentTabIndex() == DialtactsPagerAdapter.TAB_INDEX_SPEED_DIAL) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001547 return FloatingActionButtonController.ALIGN_MIDDLE;
1548 }
1549 return FloatingActionButtonController.ALIGN_END;
1550 }
1551
1552 private void updateMissedCalls() {
Eric Erfaniand8046e52017-04-06 09:41:50 -07001553 if (mPreviouslySelectedTabIndex == DialtactsPagerAdapter.TAB_INDEX_HISTORY) {
Eric Erfanianccca3152017-02-22 16:32:36 -08001554 mListsFragment.markMissedCallsAsReadAndRemoveNotifications();
1555 }
1556 }
1557
1558 @Override
1559 public void onDisambigDialogDismissed() {
1560 // Don't do anything; the app will remain open with favorites tiles displayed.
1561 }
1562
1563 @Override
1564 public void interactionError(@InteractionErrorCode int interactionErrorCode) {
1565 switch (interactionErrorCode) {
1566 case InteractionErrorCode.USER_LEAVING_ACTIVITY:
1567 // This is expected to happen if the user exits the activity before the interaction occurs.
1568 return;
1569 case InteractionErrorCode.CONTACT_NOT_FOUND:
1570 case InteractionErrorCode.CONTACT_HAS_NO_NUMBER:
1571 case InteractionErrorCode.OTHER_ERROR:
1572 default:
1573 // All other error codes are unexpected. For example, it should be impossible to start an
1574 // interaction with an invalid contact from the Dialtacts activity.
1575 Assert.fail("PhoneNumberInteraction error: " + interactionErrorCode);
1576 }
1577 }
1578
1579 @Override
1580 public void onRequestPermissionsResult(
1581 int requestCode, String[] permissions, int[] grantResults) {
1582 // This should never happen; it should be impossible to start an interaction without the
1583 // contacts permission from the Dialtacts activity.
1584 Assert.fail(
1585 String.format(
1586 Locale.US,
1587 "Permissions requested unexpectedly: %d/%s/%s",
1588 requestCode,
1589 Arrays.toString(permissions),
1590 Arrays.toString(grantResults)));
1591 }
1592
Eric Erfanian2ca43182017-08-31 06:57:16 -07001593 @Override
1594 public void onActionModeStateChanged(boolean isEnabled) {
1595 isMultiSelectModeEnabled = isEnabled;
1596 }
1597
1598 @Override
1599 public boolean isActionModeStateEnabled() {
1600 return isMultiSelectModeEnabled;
1601 }
1602
1603 private CallInitiationType.Type getCallInitiationType() {
1604 return mIsDialpadShown
1605 ? CallInitiationType.Type.DIALPAD
1606 : CallInitiationType.Type.REGULAR_SEARCH;
1607 }
1608
1609 /** Popup menu accessible from the search bar */
Eric Erfanianccca3152017-02-22 16:32:36 -08001610 protected class OptionsPopupMenu extends PopupMenu {
1611
1612 public OptionsPopupMenu(Context context, View anchor) {
1613 super(context, anchor, Gravity.END);
1614 }
1615
1616 @Override
1617 public void show() {
Eric Erfaniand8046e52017-04-06 09:41:50 -07001618 Menu menu = getMenu();
1619 MenuItem clearFrequents = menu.findItem(R.id.menu_clear_frequents);
Eric Erfanianccca3152017-02-22 16:32:36 -08001620 clearFrequents.setVisible(
Eric Erfanianc857f902017-05-15 14:05:33 -07001621 PermissionsUtil.hasContactsReadPermissions(DialtactsActivity.this)
Eric Erfaniand8046e52017-04-06 09:41:50 -07001622 && mListsFragment != null
1623 && mListsFragment.hasFrequents());
Eric Erfanianccca3152017-02-22 16:32:36 -08001624
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001625 menu.findItem(R.id.menu_history)
Eric Erfanianccca3152017-02-22 16:32:36 -08001626 .setVisible(PermissionsUtil.hasPhonePermissions(DialtactsActivity.this));
Eric Erfaniand5e47f62017-03-15 14:41:07 -07001627
1628 Context context = DialtactsActivity.this.getApplicationContext();
1629 MenuItem simulatorMenuItem = menu.findItem(R.id.menu_simulator_submenu);
1630 Simulator simulator = SimulatorComponent.get(context).getSimulator();
1631 if (simulator.shouldShow()) {
1632 simulatorMenuItem.setVisible(true);
1633 simulatorMenuItem.setActionProvider(simulator.getActionProvider(context));
1634 } else {
1635 simulatorMenuItem.setVisible(false);
1636 }
1637
Eric Erfanian2ca43182017-08-31 06:57:16 -07001638 Main dialtacts = MainComponent.get(context).getMain();
1639 menu.findItem(R.id.menu_new_ui_launcher_shortcut)
1640 .setVisible(dialtacts.isNewUiEnabled(context));
1641
Eric Erfanianccca3152017-02-22 16:32:36 -08001642 super.show();
1643 }
1644 }
1645
1646 /**
1647 * Listener that listens to drag events and sends their x and y coordinates to a {@link
1648 * DragDropController}.
1649 */
1650 private class LayoutOnDragListener implements OnDragListener {
1651
1652 @Override
1653 public boolean onDrag(View v, DragEvent event) {
1654 if (event.getAction() == DragEvent.ACTION_DRAG_LOCATION) {
1655 mDragDropController.handleDragHovered(v, (int) event.getX(), (int) event.getY());
1656 }
1657 return true;
1658 }
1659 }
1660}