diff options
Diffstat (limited to 'java/com/android/dialer/common')
16 files changed, 1262 insertions, 0 deletions
diff --git a/java/com/android/dialer/common/AndroidManifest.xml b/java/com/android/dialer/common/AndroidManifest.xml new file mode 100644 index 000000000..ae43d6693 --- /dev/null +++ b/java/com/android/dialer/common/AndroidManifest.xml @@ -0,0 +1,3 @@ +<manifest + package="com.android.dialer.common"> +</manifest> diff --git a/java/com/android/dialer/common/Assert.java b/java/com/android/dialer/common/Assert.java new file mode 100644 index 000000000..00b4f2595 --- /dev/null +++ b/java/com/android/dialer/common/Assert.java @@ -0,0 +1,185 @@ +/* + * Copyright (C) 2016 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; + +import android.os.Looper; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +/** Assertions which will result in program termination unless disabled by flags. */ +public class Assert { + + private static boolean areThreadAssertsEnabled = true; + + public static void setAreThreadAssertsEnabled(boolean areThreadAssertsEnabled) { + Assert.areThreadAssertsEnabled = areThreadAssertsEnabled; + } + + /** + * Called when a truly exceptional case occurs. + * + * @throws AssertionError + */ + public static void fail() { + throw new AssertionError("Fail"); + } + + /** + * Called when a truly exceptional case occurs. + * + * @param reason the optional reason to supply as the exception message + * @throws AssertionError + */ + public static void fail(String reason) { + throw new AssertionError(reason); + } + + /** + * Ensures the truth of an expression involving one or more parameters to the calling method. + * + * @param expression a boolean expression + * @throws IllegalArgumentException if {@code expression} is false + */ + public static void checkArgument(boolean expression) { + checkArgument(expression, null); + } + + /** + * Ensures the truth of an expression involving one or more parameters to the calling method. + * + * @param expression a boolean expression + * @param messageTemplate the message to log, possible with format arguments. + * @param args optional arguments to be used in the formatted string. + * @throws IllegalArgumentException if {@code expression} is false + */ + public static void checkArgument( + boolean expression, @Nullable String messageTemplate, Object... args) { + if (!expression) { + throw new IllegalArgumentException(format(messageTemplate, args)); + } + } + + /** + * Ensures the truth of an expression involving the state of the calling instance, but not + * involving any parameters to the calling method. + * + * @param expression a boolean expression + * @throws IllegalStateException if {@code expression} is false + */ + public static void checkState(boolean expression) { + checkState(expression, null); + } + + /** + * Ensures the truth of an expression involving the state of the calling instance, but not + * involving any parameters to the calling method. + * + * @param expression a boolean expression + * @param messageTemplate the message to log, possible with format arguments. + * @param args optional arguments to be used in the formatted string. + * @throws IllegalStateException if {@code expression} is false + */ + public static void checkState( + boolean expression, @Nullable String messageTemplate, Object... args) { + if (!expression) { + throw new IllegalStateException(format(messageTemplate, args)); + } + } + + /** + * Ensures that an object reference passed as a parameter to the calling method is not null. + * + * @param reference an object reference + * @return the non-null reference that was validated + * @throws NullPointerException if {@code reference} is null + */ + @NonNull + public static <T> T isNotNull(@Nullable T reference) { + return isNotNull(reference, null); + } + + /** + * Ensures that an object reference passed as a parameter to the calling method is not null. + * + * @param reference an object reference + * @param messageTemplate the message to log, possible with format arguments. + * @param args optional arguments to be used in the formatted string. + * @return the non-null reference that was validated + * @throws NullPointerException if {@code reference} is null + */ + @NonNull + public static <T> T isNotNull( + @Nullable T reference, @Nullable String messageTemplate, Object... args) { + if (reference == null) { + throw new NullPointerException(format(messageTemplate, args)); + } + return reference; + } + + /** + * Ensures that the current thread is the main thread. + * + * @throws IllegalStateException if called on a background thread + */ + public static void isMainThread() { + isMainThread(null); + } + + /** + * Ensures that the current thread is the main thread. + * + * @param messageTemplate the message to log, possible with format arguments. + * @param args optional arguments to be used in the formatted string. + * @throws IllegalStateException if called on a background thread + */ + public static void isMainThread(@Nullable String messageTemplate, Object... args) { + if (!areThreadAssertsEnabled) { + return; + } + checkState(Looper.getMainLooper().equals(Looper.myLooper()), messageTemplate, args); + } + + /** + * Ensures that the current thread is a worker thread. + * + * @throws IllegalStateException if called on the main thread + */ + public static void isWorkerThread() { + isWorkerThread(null); + } + + /** + * Ensures that the current thread is a worker thread. + * + * @param messageTemplate the message to log, possible with format arguments. + * @param args optional arguments to be used in the formatted string. + * @throws IllegalStateException if called on the main thread + */ + public static void isWorkerThread(@Nullable String messageTemplate, Object... args) { + if (!areThreadAssertsEnabled) { + return; + } + checkState(!Looper.getMainLooper().equals(Looper.myLooper()), messageTemplate, args); + } + + private static String format(@Nullable String messageTemplate, Object... args) { + if (messageTemplate == null) { + return null; + } + return String.format(messageTemplate, args); + } +} diff --git a/java/com/android/dialer/common/AsyncTaskExecutor.java b/java/com/android/dialer/common/AsyncTaskExecutor.java new file mode 100644 index 000000000..caadfe7ce --- /dev/null +++ b/java/com/android/dialer/common/AsyncTaskExecutor.java @@ -0,0 +1,51 @@ +/* + * 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.dialer.common; + +import android.os.AsyncTask; +import android.support.annotation.MainThread; +import java.util.concurrent.Executor; + +/** + * Interface used to submit {@link AsyncTask} objects to run in the background. + * + * <p>This interface has a direct parallel with the {@link Executor} interface. It exists to + * decouple the mechanics of AsyncTask submission from the description of how that AsyncTask will + * execute. + * + * <p>One immediate benefit of this approach is that testing becomes much easier, since it is easy + * to introduce a mock or fake AsyncTaskExecutor in unit/integration tests, and thus inspect which + * tasks have been submitted and control their execution in an orderly manner. + * + * <p>Another benefit in due course will be the management of the submitted tasks. An extension to + * this interface is planned to allow Activities to easily cancel all the submitted tasks that are + * still pending in the onDestroy() method of the Activity. + */ +public interface AsyncTaskExecutor { + + /** + * Executes the given AsyncTask with the default Executor. + * + * <p>This method <b>must only be called from the ui thread</b>. + * + * <p>The identifier supplied is any Object that can be used to identify the task later. Most + * commonly this will be an enum which the tests can also refer to. {@code null} is also accepted, + * though of course this won't help in identifying the task later. + */ + @MainThread + <T> AsyncTask<T, ?, ?> submit(Object identifier, AsyncTask<T, ?, ?> task, T... params); +} diff --git a/java/com/android/dialer/common/AsyncTaskExecutors.java b/java/com/android/dialer/common/AsyncTaskExecutors.java new file mode 100644 index 000000000..77bebdb36 --- /dev/null +++ b/java/com/android/dialer/common/AsyncTaskExecutors.java @@ -0,0 +1,91 @@ +/* + * 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.dialer.common; + +import android.os.AsyncTask; +import android.support.annotation.MainThread; +import java.util.concurrent.Executor; + +/** + * Factory methods for creating AsyncTaskExecutors. + * + * <p>All of the factory methods on this class check first to see if you have set a static {@link + * AsyncTaskExecutorFactory} set through the {@link #setFactoryForTest(AsyncTaskExecutorFactory)} + * method, and if so delegate to that instead, which is one way of injecting dependencies for + * testing classes whose construction cannot be controlled such as {@link android.app.Activity}. + */ +public final class AsyncTaskExecutors { + + /** + * A single instance of the {@link AsyncTaskExecutorFactory}, to which we delegate if it is + * non-null, for injecting when testing. + */ + private static AsyncTaskExecutorFactory mInjectedAsyncTaskExecutorFactory = null; + + /** + * Creates an AsyncTaskExecutor that submits tasks to run with {@link AsyncTask#SERIAL_EXECUTOR}. + */ + public static AsyncTaskExecutor createAsyncTaskExecutor() { + synchronized (AsyncTaskExecutors.class) { + if (mInjectedAsyncTaskExecutorFactory != null) { + return mInjectedAsyncTaskExecutorFactory.createAsyncTaskExeuctor(); + } + return new SimpleAsyncTaskExecutor(AsyncTask.SERIAL_EXECUTOR); + } + } + + /** + * Creates an AsyncTaskExecutor that submits tasks to run with {@link + * AsyncTask#THREAD_POOL_EXECUTOR}. + */ + public static AsyncTaskExecutor createThreadPoolExecutor() { + synchronized (AsyncTaskExecutors.class) { + if (mInjectedAsyncTaskExecutorFactory != null) { + return mInjectedAsyncTaskExecutorFactory.createAsyncTaskExeuctor(); + } + return new SimpleAsyncTaskExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + } + + public static void setFactoryForTest(AsyncTaskExecutorFactory factory) { + synchronized (AsyncTaskExecutors.class) { + mInjectedAsyncTaskExecutorFactory = factory; + } + } + + /** Interface for creating AsyncTaskExecutor objects. */ + public interface AsyncTaskExecutorFactory { + + AsyncTaskExecutor createAsyncTaskExeuctor(); + } + + private static class SimpleAsyncTaskExecutor implements AsyncTaskExecutor { + + private final Executor mExecutor; + + public SimpleAsyncTaskExecutor(Executor executor) { + mExecutor = executor; + } + + @Override + @MainThread + public <T> AsyncTask<T, ?, ?> submit(Object identifer, AsyncTask<T, ?, ?> task, T... params) { + Assert.isMainThread(); + return task.executeOnExecutor(mExecutor, params); + } + } +} diff --git a/java/com/android/dialer/common/AutoValue_FallibleAsyncTask_FallibleTaskResult.java b/java/com/android/dialer/common/AutoValue_FallibleAsyncTask_FallibleTaskResult.java new file mode 100644 index 000000000..f9d7cea90 --- /dev/null +++ b/java/com/android/dialer/common/AutoValue_FallibleAsyncTask_FallibleTaskResult.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2016 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; + +import android.support.annotation.Nullable; +import javax.annotation.Generated; + + + final class AutoValue_FallibleAsyncTask_FallibleTaskResult<ResultT> extends FallibleAsyncTask.FallibleTaskResult<ResultT> { + + private final Throwable throwable; + private final ResultT result; + + AutoValue_FallibleAsyncTask_FallibleTaskResult( + @Nullable Throwable throwable, + @Nullable ResultT result) { + this.throwable = throwable; + this.result = result; + } + + @Nullable + @Override + public Throwable getThrowable() { + return throwable; + } + + @Nullable + @Override + public ResultT getResult() { + return result; + } + + @Override + public String toString() { + return "FallibleTaskResult{" + + "throwable=" + throwable + ", " + + "result=" + result + + "}"; + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + if (o instanceof FallibleAsyncTask.FallibleTaskResult) { + FallibleAsyncTask.FallibleTaskResult<?> that = (FallibleAsyncTask.FallibleTaskResult<?>) o; + return ((this.throwable == null) ? (that.getThrowable() == null) : this.throwable.equals(that.getThrowable())) + && ((this.result == null) ? (that.getResult() == null) : this.result.equals(that.getResult())); + } + return false; + } + + @Override + public int hashCode() { + int h = 1; + h *= 1000003; + h ^= (throwable == null) ? 0 : this.throwable.hashCode(); + h *= 1000003; + h ^= (result == null) ? 0 : this.result.hashCode(); + return h; + } + +} + diff --git a/java/com/android/dialer/common/ConfigProvider.java b/java/com/android/dialer/common/ConfigProvider.java new file mode 100644 index 000000000..c0791e979 --- /dev/null +++ b/java/com/android/dialer/common/ConfigProvider.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2016 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; + +/** Gets config values from the container application. */ +public interface ConfigProvider { + + String getString(String key, String defaultValue); + + long getLong(String key, long defaultValue); + + boolean getBoolean(String key, boolean defaultValue); +} diff --git a/java/com/android/dialer/common/ConfigProviderBindings.java b/java/com/android/dialer/common/ConfigProviderBindings.java new file mode 100644 index 000000000..92e6cc3ff --- /dev/null +++ b/java/com/android/dialer/common/ConfigProviderBindings.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2016 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; + +import android.content.Context; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.VisibleForTesting; + +/** Accessor for getting a {@link ConfigProvider}. */ +public class ConfigProviderBindings { + + private static ConfigProvider configProvider; + + public static ConfigProvider get(@NonNull Context context) { + Assert.isNotNull(context); + if (configProvider != null) { + return configProvider; + } + + Context application = context.getApplicationContext(); + if (application instanceof ConfigProviderFactory) { + configProvider = ((ConfigProviderFactory) application).getConfigProvider(); + } + + if (configProvider == null) { + configProvider = new ConfigProviderStub(); + } + + return configProvider; + } + + @VisibleForTesting + public static void setForTesting(@Nullable ConfigProvider configProviderForTesting) { + configProvider = configProviderForTesting; + } + + private static class ConfigProviderStub implements ConfigProvider { + @Override + public String getString(String key, String defaultValue) { + return defaultValue; + } + + @Override + public long getLong(String key, long defaultValue) { + return defaultValue; + } + + @Override + public boolean getBoolean(String key, boolean defaultValue) { + return defaultValue; + } + } +} diff --git a/java/com/android/dialer/common/ConfigProviderFactory.java b/java/com/android/dialer/common/ConfigProviderFactory.java new file mode 100644 index 000000000..aeb4f303a --- /dev/null +++ b/java/com/android/dialer/common/ConfigProviderFactory.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2016 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; + +/** + * This interface should be implementated by the Application subclass. It allows dialer code to get + * references to a config provider. + */ +public interface ConfigProviderFactory { + + ConfigProvider getConfigProvider(); +} diff --git a/java/com/android/dialer/common/DpUtil.java b/java/com/android/dialer/common/DpUtil.java new file mode 100644 index 000000000..0388824cd --- /dev/null +++ b/java/com/android/dialer/common/DpUtil.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2016 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; + +import android.content.Context; + +/** Utility for dp to px conversion */ +public class DpUtil { + + public static float pxToDp(Context context, float px) { + return px / context.getResources().getDisplayMetrics().density; + } + + public static float dpToPx(Context context, float dp) { + return dp * context.getResources().getDisplayMetrics().density; + } +} diff --git a/java/com/android/dialer/common/FallibleAsyncTask.java b/java/com/android/dialer/common/FallibleAsyncTask.java new file mode 100644 index 000000000..fbdbda75f --- /dev/null +++ b/java/com/android/dialer/common/FallibleAsyncTask.java @@ -0,0 +1,94 @@ +/* + * 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; + +import android.os.AsyncTask; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import com.android.dialer.common.FallibleAsyncTask.FallibleTaskResult; + + +/** + * A task that runs work in the background, passing Throwables from {@link + * #doInBackground(Object[])} to {@link #onPostExecute(Object)} through a {@link + * FallibleTaskResult}. + * + * @param <ParamsT> the type of the parameters sent to the task upon execution + * @param <ProgressT> the type of the progress units published during the background computation + * @param <ResultT> the type of the result of the background computation + */ +public abstract class FallibleAsyncTask<ParamsT, ProgressT, ResultT> + extends AsyncTask<ParamsT, ProgressT, FallibleTaskResult<ResultT>> { + + @Override + protected final FallibleTaskResult<ResultT> doInBackground(ParamsT... params) { + try { + return FallibleTaskResult.createSuccessResult(doInBackgroundFallible(params)); + } catch (Throwable t) { + return FallibleTaskResult.createFailureResult(t); + } + } + + /** Performs background work that may result in a Throwable. */ + @Nullable + protected abstract ResultT doInBackgroundFallible(ParamsT... params) throws Throwable; + + /** + * Holds the result of processing from {@link #doInBackground(Object[])}. + * + * @param <ResultT> the type of the result of the background computation + */ + + protected abstract static class FallibleTaskResult<ResultT> { + + /** Creates an instance of FallibleTaskResult for the given throwable. */ + private static <ResultT> FallibleTaskResult<ResultT> createFailureResult(@NonNull Throwable t) { + return new AutoValue_FallibleAsyncTask_FallibleTaskResult<>(t, null); + } + + /** Creates an instance of FallibleTaskResult for the given result. */ + private static <ResultT> FallibleTaskResult<ResultT> createSuccessResult( + @Nullable ResultT result) { + return new AutoValue_FallibleAsyncTask_FallibleTaskResult<>(null, result); + } + + /** + * Returns the Throwable thrown in {@link #doInBackground(Object[])}, or {@code null} if + * background work completed without throwing. + */ + @Nullable + public abstract Throwable getThrowable(); + + /** + * Returns the result of {@link #doInBackground(Object[])}, which may be {@code null}, or {@code + * null} if the background work threw a Throwable. + * + * <p>Use {@link #isFailure()} to determine if a {@code null} return is the result of a + * Throwable from the background work. + */ + @Nullable + public abstract ResultT getResult(); + + /** + * Returns {@code true} if this object is the result of background work that threw a Throwable. + */ + public boolean isFailure() { + //noinspection ThrowableResultOfMethodCallIgnored + return getThrowable() != null; + } + } +} diff --git a/java/com/android/dialer/common/FragmentUtils.java b/java/com/android/dialer/common/FragmentUtils.java new file mode 100644 index 000000000..cb036959d --- /dev/null +++ b/java/com/android/dialer/common/FragmentUtils.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2016 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; + +import android.support.annotation.CheckResult; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.VisibleForTesting; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentActivity; + +/** Utility methods for working with Fragments */ +public class FragmentUtils { + + private static Object parentForTesting; + + @VisibleForTesting(otherwise = VisibleForTesting.NONE) + public static void setParentForTesting(Object parentForTesting) { + FragmentUtils.parentForTesting = parentForTesting; + } + + /** + * @return The parent of frag that implements the callbackInterface or null if no such parent can + * be found + */ + @CheckResult(suggest = "#checkParent(Fragment, Class)}") + @Nullable + public static <T> T getParent(@NonNull Fragment fragment, @NonNull Class<T> callbackInterface) { + if (callbackInterface.isInstance(parentForTesting)) { + @SuppressWarnings("unchecked") // Casts are checked using runtime methods + T parent = (T) parentForTesting; + return parent; + } + + Fragment parentFragment = fragment.getParentFragment(); + if (callbackInterface.isInstance(parentFragment)) { + @SuppressWarnings("unchecked") // Casts are checked using runtime methods + T parent = (T) parentFragment; + return parent; + } else { + FragmentActivity activity = fragment.getActivity(); + if (callbackInterface.isInstance(activity)) { + @SuppressWarnings("unchecked") // Casts are checked using runtime methods + T parent = (T) activity; + return parent; + } + } + return null; + } + + /** Returns the parent or throws. Should perform check elsewhere(e.g. onAttach, newInstance). */ + @NonNull + public static <T> T getParentUnsafe( + @NonNull Fragment fragment, @NonNull Class<T> callbackInterface) { + return Assert.isNotNull(getParent(fragment, callbackInterface)); + } + + /** + * Ensures fragment has a parent that implements the corresponding interface + * + * @param frag The Fragment whose parents are to be checked + * @param callbackInterface The interface class that a parent should implement + * @throws IllegalStateException if no parents are found that implement callbackInterface + */ + public static void checkParent(@NonNull Fragment frag, @NonNull Class<?> callbackInterface) + throws IllegalStateException { + if (parentForTesting != null) { + return; + } + if (FragmentUtils.getParent(frag, callbackInterface) == null) { + String parent = + frag.getParentFragment() == null + ? frag.getActivity().getClass().getName() + : frag.getParentFragment().getClass().getName(); + throw new IllegalStateException( + frag.getClass().getName() + + " must be added to a parent" + + " that implements " + + callbackInterface.getName() + + ". Instead found " + + parent); + } + } +} diff --git a/java/com/android/dialer/common/LogUtil.java b/java/com/android/dialer/common/LogUtil.java new file mode 100644 index 000000000..32d7b960b --- /dev/null +++ b/java/com/android/dialer/common/LogUtil.java @@ -0,0 +1,214 @@ +/* + * Copyright (C) 2016 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; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.telephony.PhoneNumberUtils; +import android.text.TextUtils; + +/** Provides logging functions. */ +public class LogUtil { + + public static final String TAG = "Dialer"; + private static final String SEPARATOR = " - "; + + private LogUtil() {} + + /** + * Log at a verbose level. Verbose logs should generally be filtered out, but may be useful when + * additional information is needed (e.g. to see how a particular flow evolved). These logs will + * not generally be available on production builds. + * + * @param tag An identifier to allow searching for related logs. Generally of the form + * 'Class.method'. + * @param msg The message you would like logged, possibly with format arguments. + * @param args Optional arguments to be used in the formatted string. + * @see {@link String#format(String, Object...)} + * @see {@link android.util.Log#v(String, String)} + */ + public static void v(@NonNull String tag, @Nullable String msg, @Nullable Object... args) { + println(android.util.Log.VERBOSE, TAG, tag, msg, args); + } + + /** + * Log at a debug level. Debug logs should provide known-useful information to aid in + * troubleshooting or evaluating flow. These logs will not generally be available on production + * builds. + * + * @param tag An identifier to allow searching for related logs. Generally of the form + * 'Class.method' + * @param msg The message you would like logged, possibly with format arguments + * @param args Optional arguments to be used in the formatted string + * @see {@link String#format(String, Object...)} + * @see {@link android.util.Log#d(String, String)} + */ + public static void d(@NonNull String tag, @Nullable String msg, @Nullable Object... args) { + println(android.util.Log.DEBUG, TAG, tag, msg, args); + } + + /** + * Log at an info level. Info logs provide information that would be useful to have on production + * builds for troubleshooting. + * + * @param tag An identifier to allow searching for related logs. Generally of the form + * 'Class.method'. + * @param msg The message you would like logged, possibly with format arguments. + * @param args Optional arguments to be used in the formatted string. + * @see {@link String#format(String, Object...)} + * @see {@link android.util.Log#i(String, String)} + */ + public static void i(@NonNull String tag, @Nullable String msg, @Nullable Object... args) { + println(android.util.Log.INFO, TAG, tag, msg, args); + } + + /** + * Log entry into a method at the info level. + * + * @param tag An identifier to allow searching for related logs. Generally of the form + * 'Class.method'. + */ + public static void enterBlock(String tag) { + println(android.util.Log.INFO, TAG, tag, "enter"); + } + + /** + * Log at a warn level. Warn logs indicate a possible error (e.g. a default switch branch was hit, + * or a null object was expected to be non-null), but recovery is possible. This may be used when + * it is not guaranteed that an indeterminate or bad state was entered, just that something may + * have gone wrong. + * + * @param tag An identifier to allow searching for related logs. Generally of the form + * 'Class.method'. + * @param msg The message you would like logged, possibly with format arguments. + * @param args Optional arguments to be used in the formatted string. + * @see {@link String#format(String, Object...)} + * @see {@link android.util.Log#w(String, String)} + */ + public static void w(@NonNull String tag, @Nullable String msg, @Nullable Object... args) { + println(android.util.Log.WARN, TAG, tag, msg, args); + } + + /** + * Log at an error level. Error logs are used when it is known that an error occurred and is + * possibly fatal. This is used to log information that will be useful for troubleshooting a crash + * or other severe condition (e.g. error codes, state values, etc.). + * + * @param tag An identifier to allow searching for related logs. Generally of the form + * 'Class.method'. + * @param msg The message you would like logged, possibly with format arguments. + * @param args Optional arguments to be used in the formatted string. + * @see {@link String#format(String, Object...)} + * @see {@link android.util.Log#e(String, String)} + */ + public static void e(@NonNull String tag, @Nullable String msg, @Nullable Object... args) { + println(android.util.Log.ERROR, TAG, tag, msg, args); + } + + /** + * Log an exception at an error level. Error logs are used when it is known that an error occurred + * and is possibly fatal. This is used to log information that will be useful for troubleshooting + * a crash or other severe condition (e.g. error codes, state values, etc.). + * + * @param tag An identifier to allow searching for related logs. Generally of the form + * 'Class.method'. + * @param msg The message you would like logged. + * @param throwable The exception to log. + * @see {@link String#format(String, Object...)} + * @see {@link android.util.Log#e(String, String)} + */ + public static void e(@NonNull String tag, @Nullable String msg, @NonNull Throwable throwable) { + if (!TextUtils.isEmpty(msg)) { + println(android.util.Log.ERROR, TAG, tag, msg); + } + println(android.util.Log.ERROR, TAG, tag, android.util.Log.getStackTraceString(throwable)); + } + + /** + * Used for log statements where we don't want to log various strings (e.g., usernames) with + * default logging to avoid leaking PII in logcat. + * + * @return text as is if {@value #TAG}'s log level is set to DEBUG or VERBOSE or on non-release + * builds; returns a redacted version otherwise. + */ + public static String sanitizePii(@Nullable Object object) { + if (object == null) { + return "null"; + } + if (isDebugEnabled()) { + return object.toString(); + } + return "Redacted-" + object.toString().length() + "-chars"; + } + + /** Anonymizes char to prevent logging personally identifiable information. */ + public static char sanitizeDialPadChar(char ch) { + if (isDebugEnabled()) { + return ch; + } + if (is12Key(ch)) { + return '*'; + } + return ch; + } + + /** Anonymizes the phone number to prevent logging personally identifiable information. */ + public static String sanitizePhoneNumber(@Nullable String phoneNumber) { + if (isDebugEnabled()) { + return phoneNumber; + } + if (phoneNumber == null) { + return null; + } + StringBuilder stringBuilder = new StringBuilder(phoneNumber.length()); + for (char c : phoneNumber.toCharArray()) { + stringBuilder.append(sanitizeDialPadChar(c)); + } + return stringBuilder.toString(); + } + + public static boolean isVerboseEnabled() { + return android.util.Log.isLoggable(TAG, android.util.Log.VERBOSE); + } + + public static boolean isDebugEnabled() { + return android.util.Log.isLoggable(TAG, android.util.Log.DEBUG); + } + + private static boolean is12Key(char ch) { + return PhoneNumberUtils.is12Key(ch); + } + + private static void println( + int level, + @NonNull String tag, + @NonNull String localTag, + @Nullable String msg, + @Nullable Object... args) { + // Formatted message is computed lazily if required. + String formattedMsg; + // Either null is passed as a single argument or more than one argument is passed. + boolean hasArgs = args == null || args.length > 0; + if ((level >= android.util.Log.INFO) || android.util.Log.isLoggable(tag, level)) { + formattedMsg = localTag; + if (!TextUtils.isEmpty(msg)) { + formattedMsg += SEPARATOR + (hasArgs ? String.format(msg, args) : msg); + } + android.util.Log.println(level, tag, formattedMsg); + } + } +} diff --git a/java/com/android/dialer/common/MathUtil.java b/java/com/android/dialer/common/MathUtil.java new file mode 100644 index 000000000..e811a46e2 --- /dev/null +++ b/java/com/android/dialer/common/MathUtil.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2016 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; + +/** Utility class for common math operations */ +public class MathUtil { + + /** + * Interpolates between two integer values based on percentage. + * + * @param begin Begin value + * @param end End value + * @param percent Percentage value, between 0 and 1 + * @return Interpolated result + */ + public static int lerp(int begin, int end, float percent) { + return (int) (begin * (1 - percent) + end * percent); + } + + /** + * Interpolates between two float values based on percentage. + * + * @param begin Begin value + * @param end End value + * @param percent Percentage value, between 0 and 1 + * @return Interpolated result + */ + public static float lerp(float begin, float end, float percent) { + return begin * (1 - percent) + end * percent; + } + + /** + * Clamps a value between two bounds inclusively. + * + * @param value Value to be clamped + * @param min Lower bound + * @param max Upper bound + * @return Clamped value + */ + public static float clamp(float value, float min, float max) { + return Math.max(min, Math.min(value, max)); + } +} diff --git a/java/com/android/dialer/common/NetworkUtil.java b/java/com/android/dialer/common/NetworkUtil.java new file mode 100644 index 000000000..47d84243e --- /dev/null +++ b/java/com/android/dialer/common/NetworkUtil.java @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2016 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; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.net.wifi.WifiInfo; +import android.net.wifi.WifiManager; +import android.support.annotation.Nullable; +import android.support.annotation.RequiresPermission; +import android.support.annotation.StringDef; +import android.telephony.TelephonyManager; +import android.text.TextUtils; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Objects; + +/** Utility class for dealing with network */ +public class NetworkUtil { + + /* Returns the current network type. */ + @RequiresPermission("android.permission.ACCESS_NETWORK_STATE") + @NetworkType + public static String getCurrentNetworkType(@Nullable Context context) { + if (context == null) { + return NetworkType.NONE; + } + ConnectivityManager connectivityManager = + (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + return getNetworkType(connectivityManager.getActiveNetworkInfo()); + } + + /* Returns the current network info. */ + @Nullable + @RequiresPermission("android.permission.ACCESS_NETWORK_STATE") + public static NetworkInfo getCurrentNetworkInfo(@Nullable Context context) { + if (context == null) { + return null; + } + ConnectivityManager connectivityManager = + (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + return connectivityManager.getActiveNetworkInfo(); + } + + /** + * Returns the current network type as a string. For mobile network types the subtype name of the + * network is appended. + */ + @RequiresPermission("android.permission.ACCESS_NETWORK_STATE") + public static String getCurrentNetworkTypeName(@Nullable Context context) { + if (context == null) { + return NetworkType.NONE; + } + ConnectivityManager connectivityManager = + (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo netInfo = connectivityManager.getActiveNetworkInfo(); + @NetworkType String networkType = getNetworkType(netInfo); + if (isNetworkTypeMobile(networkType)) { + return networkType + " (" + netInfo.getSubtypeName() + ")"; + } + return networkType; + } + + @NetworkType + public static String getNetworkType(@Nullable NetworkInfo netInfo) { + if (netInfo == null || !netInfo.isConnected()) { + return NetworkType.NONE; + } + switch (netInfo.getType()) { + case ConnectivityManager.TYPE_WIFI: + return NetworkType.WIFI; + case ConnectivityManager.TYPE_MOBILE: + return getMobileNetworkType(netInfo.getSubtype()); + default: + return NetworkType.UNKNOWN; + } + } + + public static boolean isNetworkTypeMobile(@NetworkType String networkType) { + return Objects.equals(networkType, NetworkType.MOBILE_2G) + || Objects.equals(networkType, NetworkType.MOBILE_3G) + || Objects.equals(networkType, NetworkType.MOBILE_4G); + } + + @RequiresPermission("android.permission.ACCESS_NETWORK_STATE") + public static String getCurrentNetworkName(Context context) { + @NetworkType String networkType = getCurrentNetworkType(context); + switch (networkType) { + case NetworkType.WIFI: + return getWifiNetworkName(context); + case NetworkType.MOBILE_2G: + case NetworkType.MOBILE_3G: + case NetworkType.MOBILE_4G: + case NetworkType.MOBILE_UNKNOWN: + return getMobileNetworkName(context); + default: + return ""; + } + } + + private static String getWifiNetworkName(Context context) { + WifiManager wifiMgr = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); + String name = null; + if (context.checkSelfPermission("android.permission.ACCESS_WIFI_STATE") + == PackageManager.PERMISSION_GRANTED) { + //noinspection MissingPermission + WifiInfo wifiInfo = wifiMgr.getConnectionInfo(); + if (wifiInfo == null) { + return ""; + } + name = wifiInfo.getSSID(); + } + return TextUtils.isEmpty(name) + ? context.getString(R.string.network_name_wifi) + : name.replaceAll("\"", ""); + } + + private static String getMobileNetworkName(Context context) { + TelephonyManager telephonyMgr = + (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); + String name = telephonyMgr.getNetworkOperatorName(); + return TextUtils.isEmpty(name) + ? context.getString(R.string.network_name_mobile) + : name.replaceAll("\"", ""); + } + + @NetworkType + private static String getMobileNetworkType(int networkSubtype) { + switch (networkSubtype) { + case TelephonyManager.NETWORK_TYPE_1xRTT: + case TelephonyManager.NETWORK_TYPE_CDMA: + case TelephonyManager.NETWORK_TYPE_EDGE: + case TelephonyManager.NETWORK_TYPE_GPRS: + case TelephonyManager.NETWORK_TYPE_IDEN: + return NetworkType.MOBILE_2G; + case TelephonyManager.NETWORK_TYPE_EHRPD: + case TelephonyManager.NETWORK_TYPE_EVDO_0: + case TelephonyManager.NETWORK_TYPE_EVDO_A: + case TelephonyManager.NETWORK_TYPE_EVDO_B: + case TelephonyManager.NETWORK_TYPE_HSDPA: + case TelephonyManager.NETWORK_TYPE_HSPA: + case TelephonyManager.NETWORK_TYPE_HSPAP: + case TelephonyManager.NETWORK_TYPE_HSUPA: + case TelephonyManager.NETWORK_TYPE_UMTS: + return NetworkType.MOBILE_3G; + case TelephonyManager.NETWORK_TYPE_LTE: + return NetworkType.MOBILE_4G; + default: + return NetworkType.MOBILE_UNKNOWN; + } + } + + /** Network types. */ + @Retention(RetentionPolicy.SOURCE) + @StringDef( + value = { + NetworkType.NONE, + NetworkType.WIFI, + NetworkType.MOBILE_2G, + NetworkType.MOBILE_3G, + NetworkType.MOBILE_4G, + NetworkType.MOBILE_UNKNOWN, + NetworkType.UNKNOWN + } + ) + public @interface NetworkType { + + String NONE = "NONE"; + String WIFI = "WIFI"; + String MOBILE_2G = "MOBILE_2G"; + String MOBILE_3G = "MOBILE_3G"; + String MOBILE_4G = "MOBILE_4G"; + String MOBILE_UNKNOWN = "MOBILE_UNKNOWN"; + String UNKNOWN = "UNKNOWN"; + } +} diff --git a/java/com/android/dialer/common/UiUtil.java b/java/com/android/dialer/common/UiUtil.java new file mode 100644 index 000000000..4c4ebea11 --- /dev/null +++ b/java/com/android/dialer/common/UiUtil.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2016 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; + +import android.app.Activity; +import android.content.Context; +import android.view.View; +import android.view.inputmethod.InputMethodManager; + +/** Utility class for commons functions used with Android UI. */ +public class UiUtil { + + /** Hides the android keyboard. */ + public static void hideKeyboardFrom(Context context, View view) { + InputMethodManager imm = + (InputMethodManager) context.getSystemService(Activity.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(view.getWindowToken(), 0); + } + + /** Opens the android keyboard. */ + public static void openKeyboardFrom(Context context, View view) { + InputMethodManager inputMethodManager = + (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); + inputMethodManager.toggleSoftInputFromWindow( + view.getApplicationWindowToken(), InputMethodManager.SHOW_FORCED, 0); + } +} diff --git a/java/com/android/dialer/common/res/values/strings.xml b/java/com/android/dialer/common/res/values/strings.xml new file mode 100644 index 000000000..8e9616178 --- /dev/null +++ b/java/com/android/dialer/common/res/values/strings.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <string name="network_name_wifi">Wifi</string> + <string name="network_name_mobile">Mobile</string> +</resources> |