Don't preload AsYouTypeFormatter
- This took 200ms of startup time at least, and a lot more under heavy load,
especially when the flash is busy. (200ms is mostly disk I/O.)
- Instead, make sure we always use AsyncTask to create
PhoneNumberFormattingTextWatcher, which wass the only thing that uses
AsYouTypeFormatter.
- DialpadFragment already had an AsnycTask. Moved it to the new class UiUtils
and use it in TextFieldsEditorView, which is the only other callsite.
- Also improved the logging for account loading. We used to log only CPU
time, but what we really care is the actual wall time. Because
account loading involves a lot of file access (e.g. loading 3rd party
apks), only measuring CPU time is not too useful.
(In fact, on my phone, loading accounts takes only 50ms CPU time but
>500ms wall time.)
Bug 5195464
Change-Id: I2b51e864d75831bdbb9e424aa846133d49d6ef94
diff --git a/src/com/android/contacts/dialpad/DialpadFragment.java b/src/com/android/contacts/dialpad/DialpadFragment.java
index e3e8875..1ff16d2 100644
--- a/src/com/android/contacts/dialpad/DialpadFragment.java
+++ b/src/com/android/contacts/dialpad/DialpadFragment.java
@@ -21,6 +21,7 @@
import com.android.contacts.SpecialCharSequenceMgr;
import com.android.contacts.activities.DialtactsActivity;
import com.android.contacts.activities.DialtactsActivity.ViewPagerVisibilityListener;
+import com.android.contacts.util.PhoneNumberFormatter;
import com.android.internal.telephony.ITelephony;
import com.android.phone.CallLogAsync;
import com.android.phone.HapticFeedback;
@@ -150,33 +151,6 @@
private String mCurrentCountryIso;
- /**
- * May be null for a moment and filled by AsyncTask. Must not be touched outside UI thread.
- */
- private PhoneNumberFormattingTextWatcher mTextWatcher;
-
- /**
- * Delays {@link PhoneNumberFormattingTextWatcher} creation as it may cause disk read operation.
- */
- private final AsyncTask<Void, Void, Void> mTextWatcherLoadAsyncTask =
- new AsyncTask<Void, Void, Void>() {
-
- private PhoneNumberFormattingTextWatcher mTemporaryWatcher;
-
- @Override
- protected Void doInBackground(Void... params) {
- mTemporaryWatcher = new PhoneNumberFormattingTextWatcher(mCurrentCountryIso);
- return null;
- }
-
- @Override
- protected void onPostExecute(Void result) {
- // Should be in UI thread.
- mTextWatcher = mTemporaryWatcher;
- mDigits.addTextChangedListener(mTextWatcher);
- }
- };
-
private final PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
/**
* Listen for phone state changes so that we can take down the
@@ -261,14 +235,7 @@
mDigits.setOnKeyListener(this);
mDigits.addTextChangedListener(this);
- if (mTextWatcher == null) {
- if (mTextWatcherLoadAsyncTask.getStatus() == AsyncTask.Status.PENDING) {
- // Start loading text watcher for phone number, which requires disk read.
- mTextWatcherLoadAsyncTask.execute();
- }
- } else {
- mDigits.addTextChangedListener(mTextWatcher);
- }
+ PhoneNumberFormatter.setPhoneNumberFormattingTextWatcher(getActivity(), mDigits);
// Soft menu button should appear only when there's no hardware menu button.
final View overflowMenuButton = fragmentView.findViewById(R.id.overflow_menu);
diff --git a/src/com/android/contacts/editor/TextFieldsEditorView.java b/src/com/android/contacts/editor/TextFieldsEditorView.java
index a448f4e..a4ad53a 100644
--- a/src/com/android/contacts/editor/TextFieldsEditorView.java
+++ b/src/com/android/contacts/editor/TextFieldsEditorView.java
@@ -23,6 +23,7 @@
import com.android.contacts.model.EntityDelta;
import com.android.contacts.model.EntityDelta.ValuesDelta;
import com.android.contacts.util.NameConverter;
+import com.android.contacts.util.PhoneNumberFormatter;
import android.content.Context;
import android.content.Entity;
@@ -185,8 +186,7 @@
int inputType = field.inputType;
fieldView.setInputType(inputType);
if (inputType == InputType.TYPE_CLASS_PHONE) {
- fieldView.addTextChangedListener(new PhoneNumberFormattingTextWatcher(
- ContactsUtils.getCurrentCountryIso(mContext)));
+ PhoneNumberFormatter.setPhoneNumberFormattingTextWatcher(mContext, fieldView);
}
fieldView.setMinLines(field.minLines);
diff --git a/src/com/android/contacts/model/AccountTypeManager.java b/src/com/android/contacts/model/AccountTypeManager.java
index 65af3ee..5cd500f 100644
--- a/src/com/android/contacts/model/AccountTypeManager.java
+++ b/src/com/android/contacts/model/AccountTypeManager.java
@@ -45,6 +45,7 @@
import android.provider.ContactsContract;
import android.text.TextUtils;
import android.util.Log;
+import android.util.TimingLogger;
import java.util.Collection;
import java.util.Collections;
@@ -275,7 +276,9 @@
if (Log.isLoggable(Constants.PERFORMANCE_TAG, Log.DEBUG)) {
Log.d(Constants.PERFORMANCE_TAG, "AccountTypeManager.loadAccountsInBackground start");
}
- long startTime = SystemClock.currentThreadTimeMillis();
+ TimingLogger timings = new TimingLogger(TAG, "loadAccountsInBackground");
+ final long startTime = SystemClock.currentThreadTimeMillis();
+ final long startTimeWall = SystemClock.elapsedRealtime();
// Account types, keyed off the account type and data set concatenation.
Map<AccountTypeWithDataSet, AccountType> accountTypesByTypeAndDataSet = Maps.newHashMap();
@@ -371,6 +374,7 @@
} catch (RemoteException e) {
Log.w(TAG, "Problem loading accounts: " + e.toString());
}
+ timings.addSplit("Loaded account types");
// Map in accounts to associate the account names with each account type entry.
Account[] accounts = mAccountManager.getAccounts();
@@ -402,11 +406,7 @@
Collections.sort(allAccounts, ACCOUNT_COMPARATOR);
Collections.sort(writableAccounts, ACCOUNT_COMPARATOR);
- // The UI will need a phone number formatter. We can preload meta data for the
- // current locale to prevent a delay later on.
- PhoneNumberUtil.getInstance().getAsYouTypeFormatter(Locale.getDefault().getCountry());
-
- long endTime = SystemClock.currentThreadTimeMillis();
+ timings.addSplit("Loaded accounts");
synchronized (this) {
mAccountTypesWithDataSets = accountTypesByTypeAndDataSet;
@@ -416,8 +416,13 @@
mContext, allAccounts, accountTypesByTypeAndDataSet);
}
+ timings.dumpToLog();
+ final long endTimeWall = SystemClock.elapsedRealtime();
+ final long endTime = SystemClock.currentThreadTimeMillis();
+
Log.i(TAG, "Loaded meta-data for " + mAccountTypesWithDataSets.size() + " account types, "
- + mAccounts.size() + " accounts in " + (endTime - startTime) + "ms");
+ + mAccounts.size() + " accounts in " + (endTimeWall - startTimeWall) + "ms(wall) "
+ + (endTime - startTime) + "ms(cpu)");
if (mInitializationLatch != null) {
mInitializationLatch.countDown();
diff --git a/src/com/android/contacts/util/PhoneNumberFormatter.java b/src/com/android/contacts/util/PhoneNumberFormatter.java
new file mode 100644
index 0000000..6e63aac
--- /dev/null
+++ b/src/com/android/contacts/util/PhoneNumberFormatter.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.contacts.util;
+
+import com.android.contacts.ContactsUtils;
+
+import android.content.Context;
+import android.os.AsyncTask;
+import android.telephony.PhoneNumberFormattingTextWatcher;
+import android.widget.TextView;
+
+public final class PhoneNumberFormatter {
+ private PhoneNumberFormatter() {}
+
+ /**
+ * Load {@link TextWatcherLoadAsyncTask} in a worker thread and set it to a {@link TextView}.
+ */
+ private static class TextWatcherLoadAsyncTask extends
+ AsyncTask<Void, Void, PhoneNumberFormattingTextWatcher> {
+ private final String mCountryCode;
+ private final TextView mTextView;
+
+ public TextWatcherLoadAsyncTask(String countryCode, TextView textView) {
+ mCountryCode = countryCode;
+ mTextView = textView;
+ }
+
+ @Override
+ protected PhoneNumberFormattingTextWatcher doInBackground(Void... params) {
+ return new PhoneNumberFormattingTextWatcher(mCountryCode);
+ }
+
+ @Override
+ protected void onPostExecute(PhoneNumberFormattingTextWatcher watcher) {
+ if (watcher == null || isCancelled()) {
+ return; // May happen if we cancel the task.
+ }
+ if (mTextView.getHandler() == null) {
+ return; // View is already detached.
+ }
+ mTextView.addTextChangedListener(watcher);
+
+ // Note changes the user made before onPostExecute() will not be formatted, but
+ // once they type the next letter we format the entire text, so it's not a big deal.
+ // (And loading PhoneNumberFormattingTextWatcher is usually fast enough.)
+ // We could use watcher.afterTextChanged(mTextView.getEditableText()) to force format
+ // the existing content here, but that could cause unwanted results.
+ // (e.g. the contact editor thinks the user changed the content, and would save
+ // when closed even when the user didn't make other changes.)
+ }
+ }
+
+ /**
+ * Delay-set {@link PhoneNumberFormattingTextWatcher} to a {@link TextView}.
+ */
+ public static final void setPhoneNumberFormattingTextWatcher(Context context,
+ TextView textView) {
+ new TextWatcherLoadAsyncTask(ContactsUtils.getCurrentCountryIso(context), textView)
+ .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
+ }
+}