diff options
author | Treehugger Robot <treehugger-gerrit@google.com> | 2017-11-30 22:24:47 +0000 |
---|---|---|
committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2017-11-30 22:24:47 +0000 |
commit | 1d18e69c85005698aea49509e5a587b7585985e8 (patch) | |
tree | 6c9b41286d91b802c169e734aba430eca815da1a /java/com/android/dialer | |
parent | 49842c4701b9aa9f998fdc92e0ec505d1d99e777 (diff) | |
parent | 1632cfe1e3f3f94295289a62c69ec5a0489d8f13 (diff) |
Merge "*** Reason for rollback ***"
Diffstat (limited to 'java/com/android/dialer')
8 files changed, 257 insertions, 46 deletions
diff --git a/java/com/android/dialer/app/res/values/styles.xml b/java/com/android/dialer/app/res/values/styles.xml index c26821023..2d47eaf94 100644 --- a/java/com/android/dialer/app/res/values/styles.xml +++ b/java/com/android/dialer/app/res/values/styles.xml @@ -21,7 +21,7 @@ <item name="android:colorAccent">@color/dialtacts_theme_color</item> </style> - <style name="DialtactsTheme" parent="DialerThemeBase"> + <style name="DialtactsThemeBase" parent="DialerThemeBase"> <!-- Styles that require AppCompat compatibility, remember to update both sets --> <item name="android:windowActionBarOverlay">true</item> @@ -77,6 +77,9 @@ <item name="dialpad_style">@style/Dialpad.Light</item> </style> + <style name="DialtactsTheme" parent="DialtactsThemeBase"> + </style> + <!-- Action bar overflow menu icon. White with no shadow. --> <style name="DialtactsActionBarOverflowWhite" parent="@android:style/Widget.Material.Light.ActionButton.Overflow"> diff --git a/java/com/android/dialer/calllog/RefreshAnnotatedCallLogWorker.java b/java/com/android/dialer/calllog/RefreshAnnotatedCallLogWorker.java index 72f73cdaf..d9924b23f 100644 --- a/java/com/android/dialer/calllog/RefreshAnnotatedCallLogWorker.java +++ b/java/com/android/dialer/calllog/RefreshAnnotatedCallLogWorker.java @@ -29,39 +29,74 @@ import com.android.dialer.calllog.datasources.CallLogMutations; import com.android.dialer.calllog.datasources.DataSources; import com.android.dialer.common.Assert; import com.android.dialer.common.LogUtil; -import com.android.dialer.common.concurrent.DialerExecutor.Worker; +import com.android.dialer.common.concurrent.Annotations.UiSerial; import com.android.dialer.inject.ApplicationContext; import com.android.dialer.storage.Unencrypted; +import com.google.common.util.concurrent.ListenableScheduledFuture; +import com.google.common.util.concurrent.ListeningScheduledExecutorService; +import com.google.common.util.concurrent.MoreExecutors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; import javax.inject.Inject; -/** - * Worker which brings the annotated call log up to date, if necessary. - * - * <p>Accepts a boolean which indicates if the dirty check should be skipped. - */ -public class RefreshAnnotatedCallLogWorker implements Worker<Boolean, Void> { +/** Brings the annotated call log up to date, if necessary. */ +public class RefreshAnnotatedCallLogWorker { + + /* + * This is a reasonable time that it might take between related call log writes, that also + * shouldn't slow down single-writes too much. For example, when populating the database using + * the simulator, using this value results in ~6 refresh cycles (on a release build) to write 120 + * call log entries. + */ + private static final long WAIT_MILLIS = 100L; private final Context appContext; private final DataSources dataSources; private final SharedPreferences sharedPreferences; + private final ListeningScheduledExecutorService listeningScheduledExecutorService; + private ListenableScheduledFuture<Void> scheduledFuture; @Inject RefreshAnnotatedCallLogWorker( @ApplicationContext Context appContext, DataSources dataSources, - @Unencrypted SharedPreferences sharedPreferences) { + @Unencrypted SharedPreferences sharedPreferences, + @UiSerial ScheduledExecutorService serialUiExecutorService) { this.appContext = appContext; this.dataSources = dataSources; this.sharedPreferences = sharedPreferences; + this.listeningScheduledExecutorService = + MoreExecutors.listeningDecorator(serialUiExecutorService); } - @Override - public Void doInBackground(Boolean skipDirtyCheck) + /** Checks if the annotated call log is dirty and refreshes it if necessary. */ + public ListenableScheduledFuture<Void> refreshWithDirtyCheck() { + return refresh(true); + } + + /** Refreshes the annotated call log, bypassing dirty checks. */ + public ListenableScheduledFuture<Void> refreshWithoutDirtyCheck() { + return refresh(false); + } + + private ListenableScheduledFuture<Void> refresh(boolean checkDirty) { + if (scheduledFuture != null) { + LogUtil.i("RefreshAnnotatedCallLogWorker.refresh", "cancelling waiting task"); + scheduledFuture.cancel(false /* mayInterrupt */); + } + scheduledFuture = + listeningScheduledExecutorService.schedule( + () -> doInBackground(checkDirty), WAIT_MILLIS, TimeUnit.MILLISECONDS); + return scheduledFuture; + } + + @WorkerThread + private Void doInBackground(boolean checkDirty) throws RemoteException, OperationApplicationException { LogUtil.enterBlock("RefreshAnnotatedCallLogWorker.doInBackground"); long startTime = System.currentTimeMillis(); - checkDirtyAndRebuildIfNecessary(appContext, skipDirtyCheck); + checkDirtyAndRebuildIfNecessary(appContext, checkDirty); LogUtil.i( "RefreshAnnotatedCallLogWorker.doInBackground", "took %dms", @@ -70,7 +105,7 @@ public class RefreshAnnotatedCallLogWorker implements Worker<Boolean, Void> { } @WorkerThread - private void checkDirtyAndRebuildIfNecessary(Context appContext, boolean skipDirtyCheck) + private void checkDirtyAndRebuildIfNecessary(Context appContext, boolean checkDirty) throws RemoteException, OperationApplicationException { Assert.isWorkerThread(); @@ -86,7 +121,7 @@ public class RefreshAnnotatedCallLogWorker implements Worker<Boolean, Void> { "annotated call log has been marked dirty or does not exist"); } - boolean isDirty = skipDirtyCheck || forceRebuildPrefValue || isDirty(appContext); + boolean isDirty = !checkDirty || forceRebuildPrefValue || isDirty(appContext); LogUtil.i( "RefreshAnnotatedCallLogWorker.checkDirtyAndRebuildIfNecessary", diff --git a/java/com/android/dialer/calllog/ui/NewCallLogFragment.java b/java/com/android/dialer/calllog/ui/NewCallLogFragment.java index ab7381347..6833452c6 100644 --- a/java/com/android/dialer/calllog/ui/NewCallLogFragment.java +++ b/java/com/android/dialer/calllog/ui/NewCallLogFragment.java @@ -28,24 +28,18 @@ import android.view.ViewGroup; import com.android.dialer.calllog.CallLogComponent; import com.android.dialer.calllog.CallLogFramework; import com.android.dialer.calllog.CallLogFramework.CallLogUi; +import com.android.dialer.calllog.RefreshAnnotatedCallLogWorker; import com.android.dialer.common.LogUtil; -import com.android.dialer.common.concurrent.DialerExecutor; import com.android.dialer.common.concurrent.DialerExecutorComponent; -import com.android.dialer.common.concurrent.DialerExecutorFactory; +import com.android.dialer.common.concurrent.UiListener; +import com.google.common.util.concurrent.ListenableScheduledFuture; /** The "new" call log fragment implementation, which is built on top of the annotated call log. */ public final class NewCallLogFragment extends Fragment implements CallLogUi, LoaderCallbacks<Cursor> { - /* - * This is a reasonable time that it might take between related call log writes, that also - * shouldn't slow down single-writes too much. For example, when populating the database using - * the simulator, using this value results in ~6 refresh cycles (on a release build) to write 120 - * call log entries. - */ - private static final long WAIT_MILLIS = 100L; - - private DialerExecutor<Boolean> refreshAnnotatedCallLogTask; + private RefreshAnnotatedCallLogWorker refreshAnnotatedCallLogWorker; + private UiListener<Void> refreshAnnotatedCallLogListener; private RecyclerView recyclerView; public NewCallLogFragment() { @@ -62,17 +56,12 @@ public final class NewCallLogFragment extends Fragment CallLogFramework callLogFramework = component.callLogFramework(); callLogFramework.attachUi(this); - DialerExecutorFactory dialerExecutorFactory = - DialerExecutorComponent.get(getContext()).dialerExecutorFactory(); - // TODO(zachh): Use support fragment manager and add support for them in executors library. - refreshAnnotatedCallLogTask = - dialerExecutorFactory - .createUiTaskBuilder( - getActivity().getFragmentManager(), - "NewCallLogFragment.refreshAnnotatedCallLog", - component.getRefreshAnnotatedCallLogWorker()) - .build(); + refreshAnnotatedCallLogListener = + DialerExecutorComponent.get(getContext()) + .createUiListener( + getActivity().getFragmentManager(), "NewCallLogFragment.refreshAnnotatedCallLog"); + refreshAnnotatedCallLogWorker = component.getRefreshAnnotatedCallLogWorker(); } @Override @@ -120,13 +109,16 @@ public final class NewCallLogFragment extends Fragment private void checkAnnotatedCallLogDirtyAndRefreshIfNecessary() { LogUtil.enterBlock("NewCallLogFragment.checkAnnotatedCallLogDirtyAndRefreshIfNecessary"); - refreshAnnotatedCallLogTask.executeSerialWithWait(false /* skipDirtyCheck */, WAIT_MILLIS); + ListenableScheduledFuture<Void> future = refreshAnnotatedCallLogWorker.refreshWithDirtyCheck(); + refreshAnnotatedCallLogListener.listen(future, unused -> {}, RuntimeException::new); } @Override public void invalidateUi() { LogUtil.enterBlock("NewCallLogFragment.invalidateUi"); - refreshAnnotatedCallLogTask.executeSerialWithWait(true /* skipDirtyCheck */, WAIT_MILLIS); + ListenableScheduledFuture<Void> future = + refreshAnnotatedCallLogWorker.refreshWithoutDirtyCheck(); + refreshAnnotatedCallLogListener.listen(future, unused -> {}, RuntimeException::new); } @Override diff --git a/java/com/android/dialer/common/concurrent/DialerExecutorComponent.java b/java/com/android/dialer/common/concurrent/DialerExecutorComponent.java index 346fdda56..7ee30a083 100644 --- a/java/com/android/dialer/common/concurrent/DialerExecutorComponent.java +++ b/java/com/android/dialer/common/concurrent/DialerExecutorComponent.java @@ -16,9 +16,12 @@ package com.android.dialer.common.concurrent; +import android.app.FragmentManager; import android.content.Context; import com.android.dialer.common.concurrent.Annotations.NonUiParallel; +import com.android.dialer.common.concurrent.Annotations.Ui; import com.android.dialer.inject.HasRootComponent; +import com.google.common.util.concurrent.ListeningExecutorService; import dagger.Subcomponent; import java.util.concurrent.ExecutorService; @@ -28,6 +31,14 @@ public abstract class DialerExecutorComponent { public abstract DialerExecutorFactory dialerExecutorFactory(); + @Ui + public abstract ListeningExecutorService uiExecutorService(); + + public <OutputT> UiListener<OutputT> createUiListener( + FragmentManager fragmentManager, String taskId) { + return UiListener.create(uiExecutorService(), fragmentManager, taskId); + } + @NonUiParallel public abstract ExecutorService lowPriorityThreadPool(); diff --git a/java/com/android/dialer/common/concurrent/UiListener.java b/java/com/android/dialer/common/concurrent/UiListener.java new file mode 100644 index 000000000..11302d299 --- /dev/null +++ b/java/com/android/dialer/common/concurrent/UiListener.java @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2017 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.dialer.common.concurrent; + +import android.app.Fragment; +import android.app.FragmentManager; +import android.os.Bundle; +import android.support.annotation.MainThread; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import com.android.dialer.common.Assert; +import com.android.dialer.common.LogUtil; +import com.android.dialer.common.concurrent.DialerExecutor.FailureListener; +import com.android.dialer.common.concurrent.DialerExecutor.SuccessListener; +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import java.util.concurrent.Executor; + +/** + * A headless fragment for use in UI components that interact with ListenableFutures. + * + * <p>Callbacks are only executed if the UI component is still alive. + * + * <p>Example usage: <code><pre> + * public class MyActivity extends Activity { + * + * private UiListener<MyOutputType> uiListener; + * + * public void onCreate(Bundle bundle) { + * super.onCreate(bundle); + * + * // Must be called in onCreate! + * uiListener = DialerExecutorComponent.get(context).createUiListener(fragmentManager, taskId); + * } + * + * private void onSuccess(MyOutputType output) { ... } + * private void onFailure(Throwable throwable) { ... } + * + * private void userDidSomething() { + * ListenableFuture<MyOutputType> future = callSomeMethodReturningListenableFuture(input); + * uiListener.listen(future, this::onSuccess, this::onFailure); + * } + * } + * </pre></code> + */ +public class UiListener<OutputT> extends Fragment { + + private Executor uiThreadExecutor; + private CallbackWrapper<OutputT> callbackWrapper; + + @MainThread + static <OutputT> UiListener<OutputT> create( + Executor uiThreadExecutor, FragmentManager fragmentManager, String taskId) { + @SuppressWarnings("unchecked") + UiListener<OutputT> uiListener = + (UiListener<OutputT>) fragmentManager.findFragmentByTag(taskId); + + if (uiListener == null) { + LogUtil.i("UiListener.create", "creating new UiListener for " + taskId); + uiListener = new UiListener<>(); + uiListener.uiThreadExecutor = uiThreadExecutor; + fragmentManager.beginTransaction().add(uiListener, taskId).commit(); + } + return uiListener; + } + + /** + * Adds the specified listeners to the provided future. + * + * <p>The listeners are not called if the UI component this {@link UiListener} is declared in is + * dead. + */ + @MainThread + public void listen( + @NonNull ListenableFuture<OutputT> future, + @NonNull SuccessListener<OutputT> successListener, + @NonNull FailureListener failureListener) { + callbackWrapper = + new CallbackWrapper<>(Assert.isNotNull(successListener), Assert.isNotNull(failureListener)); + Futures.addCallback(Assert.isNotNull(future), callbackWrapper, uiThreadExecutor); + } + + private static class CallbackWrapper<OutputT> implements FutureCallback<OutputT> { + private SuccessListener<OutputT> successListener; + private FailureListener failureListener; + + private CallbackWrapper( + SuccessListener<OutputT> successListener, FailureListener failureListener) { + this.successListener = successListener; + this.failureListener = failureListener; + } + + @Override + public void onSuccess(@Nullable OutputT output) { + if (successListener == null) { + LogUtil.i("UiListener.runTask", "task succeeded but UI is dead"); + } else { + successListener.onSuccess(output); + } + } + + @Override + public void onFailure(Throwable throwable) { + LogUtil.e("UiListener.runTask", "task failed", throwable); + if (failureListener == null) { + LogUtil.i("UiListener.runTask", "task failed but UI is dead"); + } else { + failureListener.onFailure(throwable); + } + } + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setRetainInstance(true); + } + + @Override + public void onDetach() { + super.onDetach(); + LogUtil.enterBlock("UiListener.onDetach"); + if (callbackWrapper != null) { + callbackWrapper.successListener = null; + callbackWrapper.failureListener = null; + } + } +} diff --git a/java/com/android/dialer/common/concurrent/UiThreadExecutor.java b/java/com/android/dialer/common/concurrent/UiThreadExecutor.java index 7b95d70ea..ec51ed334 100644 --- a/java/com/android/dialer/common/concurrent/UiThreadExecutor.java +++ b/java/com/android/dialer/common/concurrent/UiThreadExecutor.java @@ -21,6 +21,7 @@ import com.google.common.util.concurrent.SettableFuture; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.TimeUnit; +import javax.inject.Inject; /** * An ExecutorService that delegates to the UI thread. Rejects attempts to shut down, and all @@ -29,6 +30,9 @@ import java.util.concurrent.TimeUnit; */ public class UiThreadExecutor extends AbstractListeningExecutorService { + @Inject + UiThreadExecutor() {} + @Override public void shutdown() { throw new UnsupportedOperationException(); diff --git a/java/com/android/dialer/dialpadview/DialpadFragment.java b/java/com/android/dialer/dialpadview/DialpadFragment.java index 69186d87c..4673d9eca 100644 --- a/java/com/android/dialer/dialpadview/DialpadFragment.java +++ b/java/com/android/dialer/dialpadview/DialpadFragment.java @@ -49,6 +49,7 @@ import android.telephony.PhoneNumberFormattingTextWatcher; import android.telephony.PhoneNumberUtils; import android.telephony.TelephonyManager; import android.text.Editable; +import android.text.Selection; import android.text.TextUtils; import android.text.TextWatcher; import android.util.AttributeSet; @@ -89,6 +90,7 @@ import com.android.dialer.telecom.TelecomUtil; import com.android.dialer.util.CallUtil; import com.android.dialer.util.PermissionsUtil; import com.android.dialer.widget.FloatingActionButtonController; +import com.google.common.base.Ascii; import com.google.common.base.Optional; import java.util.HashSet; import java.util.List; @@ -1882,13 +1884,21 @@ public class DialpadFragment extends Fragment @Override public synchronized void afterTextChanged(Editable s) { - super.afterTextChanged(s); - - if (!"AR".equals(countryCode)) { + // When the country code is NOT "AR", Android telephony's PhoneNumberFormattingTextWatcher can + // correctly handle the input so we will let it do its job. + if (!Ascii.toUpperCase(countryCode).equals("AR")) { + super.afterTextChanged(s); return; } + // When the country code is "AR", PhoneNumberFormattingTextWatcher can also format the input + // correctly if the number is NOT for a domestic call to a mobile phone. String rawNumber = getRawNumber(s); + Matcher matcher = AR_DOMESTIC_CALL_MOBILE_NUMBER_PATTERN.matcher(rawNumber); + if (!matcher.matches()) { + super.afterTextChanged(s); + return; + } // As modifying the input will trigger another call to afterTextChanged(Editable), we must // check whether the input's format has already been removed and return if it has @@ -1897,11 +1907,16 @@ public class DialpadFragment extends Fragment return; } - Matcher matcher = AR_DOMESTIC_CALL_MOBILE_NUMBER_PATTERN.matcher(rawNumber); - if (matcher.matches()) { - s.replace(0, s.length(), rawNumber); - PhoneNumberUtils.addTtsSpan(s, 0 /* start */, s.length() /* endExclusive */); - } + // If we reach this point, the country code must be "AR" and variable "s" represents a number + // for a domestic call to a mobile phone. "s" is incorrectly formatted by Android telephony's + // PhoneNumberFormattingTextWatcher so we remove its format by replacing it with the raw + // number. + s.replace(0, s.length(), rawNumber); + + // Make sure the cursor is at the end of the text. + Selection.setSelection(s, s.length()); + + PhoneNumberUtils.addTtsSpan(s, 0 /* start */, s.length() /* endExclusive */); } private static String getRawNumber(Editable s) { diff --git a/java/com/android/dialer/precall/externalreceiver/LaunchPreCallActivity.java b/java/com/android/dialer/precall/externalreceiver/LaunchPreCallActivity.java index f79546e54..6096d5192 100644 --- a/java/com/android/dialer/precall/externalreceiver/LaunchPreCallActivity.java +++ b/java/com/android/dialer/precall/externalreceiver/LaunchPreCallActivity.java @@ -21,6 +21,8 @@ import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.support.annotation.Nullable; +import android.telecom.PhoneAccountHandle; +import android.telecom.TelecomManager; import com.android.dialer.callintent.CallInitiationType.Type; import com.android.dialer.callintent.CallIntentBuilder; import com.android.dialer.configprovider.ConfigProvider; @@ -56,8 +58,14 @@ public class LaunchPreCallActivity extends Activity { ConfigProvider configProvider = ConfigProviderBindings.get(getApplicationContext()); Intent intent = getIntent(); CallIntentBuilder builder = new CallIntentBuilder(intent.getData(), Type.EXTERNAL_INITIATION); + + PhoneAccountHandle phoneAccountHandle = intent.getParcelableExtra(EXTRA_PHONE_ACCOUNT_HANDLE); + if (phoneAccountHandle == null) { + phoneAccountHandle = intent.getParcelableExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE); + } + builder - .setPhoneAccountHandle(intent.getParcelableExtra(EXTRA_PHONE_ACCOUNT_HANDLE)) + .setPhoneAccountHandle(phoneAccountHandle) .setIsVideoCall(intent.getBooleanExtra(EXTRA_IS_VIDEO_CALL, false)) .setCallSubject(intent.getStringExtra(EXTRA_CALL_SUBJECT)) .setAllowAssistedDial( |