diff options
Diffstat (limited to 'java/com/android/dialer/util')
-rw-r--r-- | java/com/android/dialer/util/AndroidManifest.xml | 3 | ||||
-rw-r--r-- | java/com/android/dialer/util/CallUtil.java | 135 | ||||
-rw-r--r-- | java/com/android/dialer/util/DialerUtils.java | 246 | ||||
-rw-r--r-- | java/com/android/dialer/util/DrawableConverter.java | 97 | ||||
-rw-r--r-- | java/com/android/dialer/util/ExpirableCache.java | 269 | ||||
-rw-r--r-- | java/com/android/dialer/util/IntentUtil.java | 78 | ||||
-rw-r--r-- | java/com/android/dialer/util/MoreStrings.java | 64 | ||||
-rw-r--r-- | java/com/android/dialer/util/OrientationUtil.java | 30 | ||||
-rw-r--r-- | java/com/android/dialer/util/PermissionsUtil.java | 121 | ||||
-rw-r--r-- | java/com/android/dialer/util/SettingsUtil.java | 95 | ||||
-rw-r--r-- | java/com/android/dialer/util/TouchPointManager.java | 60 | ||||
-rw-r--r-- | java/com/android/dialer/util/TransactionSafeActivity.java | 64 | ||||
-rw-r--r-- | java/com/android/dialer/util/ViewUtil.java | 129 | ||||
-rw-r--r-- | java/com/android/dialer/util/res/values/strings.xml | 42 |
14 files changed, 1433 insertions, 0 deletions
diff --git a/java/com/android/dialer/util/AndroidManifest.xml b/java/com/android/dialer/util/AndroidManifest.xml new file mode 100644 index 000000000..499df9b4e --- /dev/null +++ b/java/com/android/dialer/util/AndroidManifest.xml @@ -0,0 +1,3 @@ +<manifest + package="com.android.dialer.util"> +</manifest> diff --git a/java/com/android/dialer/util/CallUtil.java b/java/com/android/dialer/util/CallUtil.java new file mode 100644 index 000000000..81a4bb21e --- /dev/null +++ b/java/com/android/dialer/util/CallUtil.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2015 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.util; + +import android.content.Context; +import android.net.Uri; +import android.telecom.PhoneAccount; +import android.telecom.PhoneAccountHandle; +import android.telecom.TelecomManager; +import com.android.dialer.compat.CompatUtils; +import com.android.dialer.phonenumberutil.PhoneNumberHelper; +import java.util.List; + +/** Utilities related to calls that can be used by non system apps. */ +public class CallUtil { + + /** Indicates that the video calling is not available. */ + public static final int VIDEO_CALLING_DISABLED = 0; + + /** Indicates that video calling is enabled, regardless of presence status. */ + public static final int VIDEO_CALLING_ENABLED = 1; + + /** + * Indicates that video calling is enabled, but the availability of video call affordances is + * determined by the presence status associated with contacts. + */ + public static final int VIDEO_CALLING_PRESENCE = 2; + + /** Return Uri with an appropriate scheme, accepting both SIP and usual phone call numbers. */ + public static Uri getCallUri(String number) { + if (PhoneNumberHelper.isUriNumber(number)) { + return Uri.fromParts(PhoneAccount.SCHEME_SIP, number, null); + } + return Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null); + } + + /** @return Uri that directly dials a user's voicemail inbox. */ + public static Uri getVoicemailUri() { + return Uri.fromParts(PhoneAccount.SCHEME_VOICEMAIL, "", null); + } + + /** + * Determines if video calling is available, and if so whether presence checking is available as + * well. + * + * <p>Returns a bitmask with {@link #VIDEO_CALLING_ENABLED} to indicate that video calling is + * available, and {@link #VIDEO_CALLING_PRESENCE} if presence indication is also available. + * + * @param context The context + * @return A bit-mask describing the current video capabilities. + */ + public static int getVideoCallingAvailability(Context context) { + if (!PermissionsUtil.hasPermission(context, android.Manifest.permission.READ_PHONE_STATE) + || !CompatUtils.isVideoCompatible()) { + return VIDEO_CALLING_DISABLED; + } + TelecomManager telecommMgr = (TelecomManager) context.getSystemService(Context.TELECOM_SERVICE); + if (telecommMgr == null) { + return VIDEO_CALLING_DISABLED; + } + + List<PhoneAccountHandle> accountHandles = telecommMgr.getCallCapablePhoneAccounts(); + for (PhoneAccountHandle accountHandle : accountHandles) { + PhoneAccount account = telecommMgr.getPhoneAccount(accountHandle); + if (account != null) { + if (account.hasCapabilities(PhoneAccount.CAPABILITY_VIDEO_CALLING)) { + // Builds prior to N do not have presence support. + if (!CompatUtils.isVideoPresenceCompatible()) { + return VIDEO_CALLING_ENABLED; + } + + int videoCapabilities = VIDEO_CALLING_ENABLED; + if (account.hasCapabilities(PhoneAccount.CAPABILITY_VIDEO_CALLING_RELIES_ON_PRESENCE)) { + videoCapabilities |= VIDEO_CALLING_PRESENCE; + } + return videoCapabilities; + } + } + } + return VIDEO_CALLING_DISABLED; + } + + /** + * Determines if one of the call capable phone accounts defined supports video calling. + * + * @param context The context. + * @return {@code true} if one of the call capable phone accounts supports video calling, {@code + * false} otherwise. + */ + public static boolean isVideoEnabled(Context context) { + return (getVideoCallingAvailability(context) & VIDEO_CALLING_ENABLED) != 0; + } + + /** + * Determines if one of the call capable phone accounts defined supports calling with a subject + * specified. + * + * @param context The context. + * @return {@code true} if one of the call capable phone accounts supports calling with a subject + * specified, {@code false} otherwise. + */ + public static boolean isCallWithSubjectSupported(Context context) { + if (!PermissionsUtil.hasPermission(context, android.Manifest.permission.READ_PHONE_STATE) + || !CompatUtils.isCallSubjectCompatible()) { + return false; + } + TelecomManager telecommMgr = (TelecomManager) context.getSystemService(Context.TELECOM_SERVICE); + if (telecommMgr == null) { + return false; + } + + List<PhoneAccountHandle> accountHandles = telecommMgr.getCallCapablePhoneAccounts(); + for (PhoneAccountHandle accountHandle : accountHandles) { + PhoneAccount account = telecommMgr.getPhoneAccount(accountHandle); + if (account != null && account.hasCapabilities(PhoneAccount.CAPABILITY_CALL_SUBJECT)) { + return true; + } + } + return false; + } +} diff --git a/java/com/android/dialer/util/DialerUtils.java b/java/com/android/dialer/util/DialerUtils.java new file mode 100644 index 000000000..63f870e73 --- /dev/null +++ b/java/com/android/dialer/util/DialerUtils.java @@ -0,0 +1,246 @@ +/* + * Copyright (C) 2014 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.util; + +import android.app.AlertDialog; +import android.content.ActivityNotFoundException; +import android.content.Context; +import android.content.DialogInterface; +import android.content.DialogInterface.OnClickListener; +import android.content.Intent; +import android.content.SharedPreferences; +import android.graphics.Point; +import android.os.Build.VERSION; +import android.os.Build.VERSION_CODES; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.support.annotation.NonNull; +import android.support.v4.content.ContextCompat; +import android.telecom.TelecomManager; +import android.telephony.TelephonyManager; +import android.text.BidiFormatter; +import android.text.TextDirectionHeuristics; +import android.view.View; +import android.view.inputmethod.InputMethodManager; +import android.widget.Toast; +import com.android.dialer.common.Assert; +import com.android.dialer.common.LogUtil; +import com.android.dialer.telecom.TelecomUtil; +import java.io.File; +import java.util.Iterator; +import java.util.Random; + +/** General purpose utility methods for the Dialer. */ +public class DialerUtils { + + /** + * Prefix on a dialed number that indicates that the call should be placed through the Wireless + * Priority Service. + */ + private static final String WPS_PREFIX = "*272"; + + public static final String FILE_PROVIDER_CACHE_DIR = "my_cache"; + + private static final Random RANDOM = new Random(); + + /** + * Attempts to start an activity and displays a toast with the default error message if the + * activity is not found, instead of throwing an exception. + * + * @param context to start the activity with. + * @param intent to start the activity with. + */ + public static void startActivityWithErrorToast(Context context, Intent intent) { + startActivityWithErrorToast(context, intent, R.string.activity_not_available); + } + + /** + * Attempts to start an activity and displays a toast with a provided error message if the + * activity is not found, instead of throwing an exception. + * + * @param context to start the activity with. + * @param intent to start the activity with. + * @param msgId Resource ID of the string to display in an error message if the activity is not + * found. + */ + public static void startActivityWithErrorToast( + final Context context, final Intent intent, int msgId) { + try { + if ((Intent.ACTION_CALL.equals(intent.getAction()))) { + // All dialer-initiated calls should pass the touch point to the InCallUI + Point touchPoint = TouchPointManager.getInstance().getPoint(); + if (touchPoint.x != 0 || touchPoint.y != 0) { + Bundle extras; + // Make sure to not accidentally clobber any existing extras + if (intent.hasExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS)) { + extras = intent.getParcelableExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS); + } else { + extras = new Bundle(); + } + extras.putParcelable(TouchPointManager.TOUCH_POINT, touchPoint); + intent.putExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, extras); + } + + if (shouldWarnForOutgoingWps(context, intent.getData().getSchemeSpecificPart())) { + LogUtil.i( + "DialUtils.startActivityWithErrorToast", + "showing outgoing WPS dialog before placing call"); + AlertDialog.Builder builder = new AlertDialog.Builder(context); + builder.setMessage(R.string.outgoing_wps_warning); + builder.setPositiveButton( + R.string.dialog_continue, + new OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + placeCallOrMakeToast(context, intent); + } + }); + builder.setNegativeButton(android.R.string.cancel, null); + builder.create().show(); + } else { + placeCallOrMakeToast(context, intent); + } + } else { + context.startActivity(intent); + } + } catch (ActivityNotFoundException e) { + Toast.makeText(context, msgId, Toast.LENGTH_SHORT).show(); + } + } + + private static void placeCallOrMakeToast(Context context, Intent intent) { + final boolean hasCallPermission = TelecomUtil.placeCall(context, intent); + if (!hasCallPermission) { + // TODO: Make calling activity show request permission dialog and handle + // callback results appropriately. + Toast.makeText(context, "Cannot place call without Phone permission", Toast.LENGTH_SHORT) + .show(); + } + } + + /** + * Returns whether the user should be warned about an outgoing WPS call. This checks if there is a + * currently active call over LTE. Regardless of the country or carrier, the radio will drop an + * active LTE call if a WPS number is dialed, so this warning is necessary. + */ + private static boolean shouldWarnForOutgoingWps(Context context, String number) { + if (number != null && number.startsWith(WPS_PREFIX)) { + TelephonyManager telephonyManager = context.getSystemService(TelephonyManager.class); + boolean isOnVolte = + VERSION.SDK_INT >= VERSION_CODES.N + && telephonyManager.getVoiceNetworkType() == TelephonyManager.NETWORK_TYPE_LTE; + boolean hasCurrentActiveCall = + telephonyManager.getCallState() == TelephonyManager.CALL_STATE_OFFHOOK; + return isOnVolte && hasCurrentActiveCall; + } + return false; + } + + /** + * Closes an {@link AutoCloseable}, silently ignoring any checked exceptions. Does nothing if + * null. + * + * @param closeable to close. + */ + public static void closeQuietly(AutoCloseable closeable) { + if (closeable != null) { + try { + closeable.close(); + } catch (RuntimeException rethrown) { + throw rethrown; + } catch (Exception ignored) { + } + } + } + + /** + * Joins a list of {@link CharSequence} into a single {@link CharSequence} seperated by ", ". + * + * @param list List of char sequences to join. + * @return Joined char sequences. + */ + public static CharSequence join(Iterable<CharSequence> list) { + StringBuilder sb = new StringBuilder(); + final BidiFormatter formatter = BidiFormatter.getInstance(); + final CharSequence separator = ", "; + + Iterator<CharSequence> itr = list.iterator(); + boolean firstTime = true; + while (itr.hasNext()) { + if (firstTime) { + firstTime = false; + } else { + sb.append(separator); + } + // Unicode wrap the elements of the list to respect RTL for individual strings. + sb.append( + formatter.unicodeWrap(itr.next().toString(), TextDirectionHeuristics.FIRSTSTRONG_LTR)); + } + + // Unicode wrap the joined value, to respect locale's RTL ordering for the whole list. + return formatter.unicodeWrap(sb.toString()); + } + + public static void showInputMethod(View view) { + final InputMethodManager imm = + (InputMethodManager) view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + if (imm != null) { + imm.showSoftInput(view, 0); + } + } + + public static void hideInputMethod(View view) { + final InputMethodManager imm = + (InputMethodManager) view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + if (imm != null) { + imm.hideSoftInputFromWindow(view.getWindowToken(), 0); + } + } + + /** + * Create a File in the cache directory that Dialer's FileProvider knows about so they can be + * shared to other apps. + */ + public static File createShareableFile(Context context) { + long fileId = Math.abs(RANDOM.nextLong()); + File parentDir = new File(context.getCacheDir(), FILE_PROVIDER_CACHE_DIR); + if (!parentDir.exists()) { + parentDir.mkdirs(); + } + return new File(parentDir, String.valueOf(fileId)); + } + + /** + * Returns default preference for context accessing device protected storage. This is used when + * directBoot is enabled (before device unlocked after boot) since the default shared preference + * used normally is not available at this moment for N devices. Returns regular default shared + * preference for pre-N devices. + */ + @NonNull + public static SharedPreferences getDefaultSharedPreferenceForDeviceProtectedStorageContext( + @NonNull Context context) { + Assert.isNotNull(context); + Context deviceProtectedContext = + ContextCompat.isDeviceProtectedStorage(context) + ? context + : ContextCompat.createDeviceProtectedStorageContext(context); + // ContextCompat.createDeviceProtectedStorageContext(context) returns null on pre-N, thus fall + // back to regular default shared preference for pre-N devices since devices protected context + // is not available. + return PreferenceManager.getDefaultSharedPreferences( + deviceProtectedContext != null ? deviceProtectedContext : context); + } +} diff --git a/java/com/android/dialer/util/DrawableConverter.java b/java/com/android/dialer/util/DrawableConverter.java new file mode 100644 index 000000000..5670315c9 --- /dev/null +++ b/java/com/android/dialer/util/DrawableConverter.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2012 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.util; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.graphics.drawable.RoundedBitmapDrawable; +import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory; +import com.android.dialer.common.LogUtil; + +/** Provides utilities for bitmaps and drawables. */ +public class DrawableConverter { + + private DrawableConverter() {} + + /** Converts the provided drawable to a bitmap using the drawable's intrinsic width and height. */ + @Nullable + public static Bitmap drawableToBitmap(@Nullable Drawable drawable) { + return drawableToBitmap(drawable, 0, 0); + } + + /** + * Converts the provided drawable to a bitmap with the specified width and height. + * + * <p>If both width and height are 0, the drawable's intrinsic width and height are used (but in + * that case {@link #drawableToBitmap(Drawable)} should be used). + */ + @Nullable + public static Bitmap drawableToBitmap(@Nullable Drawable drawable, int width, int height) { + if (drawable == null) { + return null; + } + + Bitmap bitmap; + if (drawable instanceof BitmapDrawable) { + bitmap = ((BitmapDrawable) drawable).getBitmap(); + } else { + if (width > 0 || height > 0) { + bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + } else if (drawable.getIntrinsicWidth() <= 0 || drawable.getIntrinsicHeight() <= 0) { + // Needed for drawables that are just a colour. + bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888); + } else { + bitmap = + Bitmap.createBitmap( + drawable.getIntrinsicWidth(), + drawable.getIntrinsicHeight(), + Bitmap.Config.ARGB_8888); + } + + LogUtil.i( + "DrawableConverter.drawableToBitmap", + "created bitmap with width: %d, height: %d", + bitmap.getWidth(), + bitmap.getHeight()); + + Canvas canvas = new Canvas(bitmap); + drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); + drawable.draw(canvas); + } + return bitmap; + } + + @Nullable + public static Drawable getRoundedDrawable( + @NonNull Context context, @Nullable Drawable photo, int width, int height) { + Bitmap bitmap = drawableToBitmap(photo); + if (bitmap != null) { + Bitmap scaledBitmap = Bitmap.createScaledBitmap(bitmap, width, height, false); + RoundedBitmapDrawable drawable = + RoundedBitmapDrawableFactory.create(context.getResources(), scaledBitmap); + drawable.setAntiAlias(true); + drawable.setCornerRadius(drawable.getIntrinsicHeight() / 2); + return drawable; + } + return null; + } +} diff --git a/java/com/android/dialer/util/ExpirableCache.java b/java/com/android/dialer/util/ExpirableCache.java new file mode 100644 index 000000000..2778a572c --- /dev/null +++ b/java/com/android/dialer/util/ExpirableCache.java @@ -0,0 +1,269 @@ +/* + * 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.util; + +import android.util.LruCache; +import java.util.concurrent.atomic.AtomicInteger; +import javax.annotation.concurrent.Immutable; +import javax.annotation.concurrent.ThreadSafe; + +/** + * An LRU cache in which all items can be marked as expired at a given time and it is possible to + * query whether a particular cached value is expired or not. + * + * <p>A typical use case for this is caching of values which are expensive to compute but which are + * still useful when out of date. + * + * <p>Consider a cache for contact information: + * + * <pre>{@code + * private ExpirableCache<String, Contact> mContactCache; + * }</pre> + * + * which stores the contact information for a given phone number. + * + * <p>When we need to store contact information for a given phone number, we can look up the info in + * the cache: + * + * <pre>{@code + * CachedValue<Contact> cachedContact = mContactCache.getCachedValue(phoneNumber); + * }</pre> + * + * We might also want to fetch the contact information again if the item is expired. + * + * <pre> + * if (cachedContact.isExpired()) { + * fetchContactForNumber(phoneNumber, + * new FetchListener() { + * @Override + * public void onFetched(Contact contact) { + * mContactCache.put(phoneNumber, contact); + * } + * }); + * }</pre> + * + * and insert it back into the cache when the fetch completes. + * + * <p>At a certain point we want to expire the content of the cache because we know the content may + * no longer be up-to-date, for instance, when resuming the activity this is shown into: + * + * <pre> + * @Override + * protected onResume() { + * // We were paused for some time, the cached value might no longer be up to date. + * mContactCache.expireAll(); + * super.onResume(); + * } + * </pre> + * + * The values will be still available from the cache, but they will be expired. + * + * <p>If interested only in the value itself, not whether it is expired or not, one should use the + * {@link #getPossiblyExpired(Object)} method. If interested only in non-expired values, one should + * use the {@link #get(Object)} method instead. + * + * <p>This class wraps around an {@link LruCache} instance: it follows the {@link LruCache} behavior + * for evicting items when the cache is full. It is possible to supply your own subclass of LruCache + * by using the {@link #create(LruCache)} method, which can define a custom expiration policy. Since + * the underlying cache maps keys to cached values it can determine which items are expired and + * which are not, allowing for an implementation that evicts expired items before non expired ones. + * + * <p>This class is thread-safe. + * + * @param <K> the type of the keys + * @param <V> the type of the values + */ +@ThreadSafe +public class ExpirableCache<K, V> { + + /** + * The current generation of items added to the cache. + * + * <p>Items in the cache can belong to a previous generation, but in that case they would be + * expired. + * + * @see ExpirableCache.CachedValue#isExpired() + */ + private final AtomicInteger mGeneration; + /** The underlying cache used to stored the cached values. */ + private LruCache<K, CachedValue<V>> mCache; + + private ExpirableCache(LruCache<K, CachedValue<V>> cache) { + mCache = cache; + mGeneration = new AtomicInteger(0); + } + + /** + * Creates a new {@link ExpirableCache} that wraps the given {@link LruCache}. + * + * <p>The created cache takes ownership of the cache passed in as an argument. + * + * @param <K> the type of the keys + * @param <V> the type of the values + * @param cache the cache to store the value in + * @return the newly created expirable cache + * @throws IllegalArgumentException if the cache is not empty + */ + public static <K, V> ExpirableCache<K, V> create(LruCache<K, CachedValue<V>> cache) { + return new ExpirableCache<K, V>(cache); + } + + /** + * Creates a new {@link ExpirableCache} with the given maximum size. + * + * @param <K> the type of the keys + * @param <V> the type of the values + * @return the newly created expirable cache + */ + public static <K, V> ExpirableCache<K, V> create(int maxSize) { + return create(new LruCache<K, CachedValue<V>>(maxSize)); + } + + /** + * Returns the cached value for the given key, or null if no value exists. + * + * <p>The cached value gives access both to the value associated with the key and whether it is + * expired or not. + * + * <p>If not interested in whether the value is expired, use {@link #getPossiblyExpired(Object)} + * instead. + * + * <p>If only wants values that are not expired, use {@link #get(Object)} instead. + * + * @param key the key to look up + */ + public CachedValue<V> getCachedValue(K key) { + return mCache.get(key); + } + + /** + * Returns the value for the given key, or null if no value exists. + * + * <p>When using this method, it is not possible to determine whether the value is expired or not. + * Use {@link #getCachedValue(Object)} to achieve that instead. However, if using {@link + * #getCachedValue(Object)} to determine if an item is expired, one should use the item within the + * {@link CachedValue} and not call {@link #getPossiblyExpired(Object)} to get the value + * afterwards, since that is not guaranteed to return the same value or that the newly returned + * value is in the same state. + * + * @param key the key to look up + */ + public V getPossiblyExpired(K key) { + CachedValue<V> cachedValue = getCachedValue(key); + return cachedValue == null ? null : cachedValue.getValue(); + } + + /** + * Returns the value for the given key only if it is not expired, or null if no value exists or is + * expired. + * + * <p>This method will return null if either there is no value associated with this key or if the + * associated value is expired. + * + * @param key the key to look up + */ + public V get(K key) { + CachedValue<V> cachedValue = getCachedValue(key); + return cachedValue == null || cachedValue.isExpired() ? null : cachedValue.getValue(); + } + + /** + * Puts an item in the cache. + * + * <p>Newly added item will not be expired until {@link #expireAll()} is next called. + * + * @param key the key to look up + * @param value the value to associate with the key + */ + public void put(K key, V value) { + mCache.put(key, newCachedValue(value)); + } + + /** + * Mark all items currently in the cache as expired. + * + * <p>Newly added items after this call will be marked as not expired. + * + * <p>Expiring the items in the cache does not imply they will be evicted. + */ + public void expireAll() { + mGeneration.incrementAndGet(); + } + + /** + * Creates a new {@link CachedValue} instance to be stored in this cache. + * + * <p>Implementation of {@link LruCache#create(K)} can use this method to create a new entry. + */ + public CachedValue<V> newCachedValue(V value) { + return new GenerationalCachedValue<V>(value, mGeneration); + } + + /** + * A cached value stored inside the cache. + * + * <p>It provides access to the value stored in the cache but also allows to check whether the + * value is expired. + * + * @param <V> the type of value stored in the cache + */ + public interface CachedValue<V> { + + /** Returns the value stored in the cache for a given key. */ + V getValue(); + + /** + * Checks whether the value, while still being present in the cache, is expired. + * + * @return true if the value is expired + */ + boolean isExpired(); + } + + /** Cached values storing the generation at which they were added. */ + @Immutable + private static class GenerationalCachedValue<V> implements ExpirableCache.CachedValue<V> { + + /** The value stored in the cache. */ + public final V mValue; + /** The generation at which the value was added to the cache. */ + private final int mGeneration; + /** The atomic integer storing the current generation of the cache it belongs to. */ + private final AtomicInteger mCacheGeneration; + + /** + * @param cacheGeneration the atomic integer storing the generation of the cache in which this + * value will be stored + */ + public GenerationalCachedValue(V value, AtomicInteger cacheGeneration) { + mValue = value; + mCacheGeneration = cacheGeneration; + // Snapshot the current generation. + mGeneration = mCacheGeneration.get(); + } + + @Override + public V getValue() { + return mValue; + } + + @Override + public boolean isExpired() { + return mGeneration != mCacheGeneration.get(); + } + } +} diff --git a/java/com/android/dialer/util/IntentUtil.java b/java/com/android/dialer/util/IntentUtil.java new file mode 100644 index 000000000..2f265b5a7 --- /dev/null +++ b/java/com/android/dialer/util/IntentUtil.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2012 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.util; + +import android.content.Intent; +import android.net.Uri; +import android.provider.ContactsContract; + +/** Utilities for creation of intents in Dialer. */ +public class IntentUtil { + + private static final String SMS_URI_PREFIX = "sms:"; + private static final int NO_PHONE_TYPE = -1; + + public static Intent getSendSmsIntent(CharSequence phoneNumber) { + return new Intent(Intent.ACTION_SENDTO, Uri.parse(SMS_URI_PREFIX + phoneNumber)); + } + + public static Intent getNewContactIntent() { + return new Intent(Intent.ACTION_INSERT, ContactsContract.Contacts.CONTENT_URI); + } + + public static Intent getNewContactIntent(CharSequence phoneNumber) { + return getNewContactIntent(null /* name */, phoneNumber /* phoneNumber */, NO_PHONE_TYPE); + } + + public static Intent getNewContactIntent( + CharSequence name, CharSequence phoneNumber, int phoneNumberType) { + Intent intent = getNewContactIntent(); + populateContactIntent(intent, name, phoneNumber, phoneNumberType); + return intent; + } + + public static Intent getAddToExistingContactIntent() { + Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT); + intent.setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE); + return intent; + } + + public static Intent getAddToExistingContactIntent(CharSequence phoneNumber) { + return getAddToExistingContactIntent( + null /* name */, phoneNumber /* phoneNumber */, NO_PHONE_TYPE); + } + + public static Intent getAddToExistingContactIntent( + CharSequence name, CharSequence phoneNumber, int phoneNumberType) { + Intent intent = getAddToExistingContactIntent(); + populateContactIntent(intent, name, phoneNumber, phoneNumberType); + return intent; + } + + private static void populateContactIntent( + Intent intent, CharSequence name, CharSequence phoneNumber, int phoneNumberType) { + if (phoneNumber != null) { + intent.putExtra(ContactsContract.Intents.Insert.PHONE, phoneNumber); + } + if (name != null) { + intent.putExtra(ContactsContract.Intents.Insert.NAME, name); + } + if (phoneNumberType != NO_PHONE_TYPE) { + intent.putExtra(ContactsContract.Intents.Insert.PHONE_TYPE, phoneNumberType); + } + } +} diff --git a/java/com/android/dialer/util/MoreStrings.java b/java/com/android/dialer/util/MoreStrings.java new file mode 100644 index 000000000..5a43b1d10 --- /dev/null +++ b/java/com/android/dialer/util/MoreStrings.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2013 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.util; + +import android.support.annotation.Nullable; +import android.text.TextUtils; + +/** Static utility methods for Strings. */ +public class MoreStrings { + + /** + * Returns the given string if it is non-null; the empty string otherwise. + * + * @param string the string to test and possibly return + * @return {@code string} itself if it is non-null; {@code ""} if it is null + */ + public static String nullToEmpty(@Nullable String string) { + return (string == null) ? "" : string; + } + + /** + * Returns the given string if it is nonempty; {@code null} otherwise. + * + * @param string the string to test and possibly return + * @return {@code string} itself if it is nonempty; {@code null} if it is empty or null + */ + @Nullable + public static String emptyToNull(@Nullable String string) { + return TextUtils.isEmpty(string) ? null : string; + } + + public static String toSafeString(String value) { + if (value == null) { + return null; + } + + // Do exactly same thing as Uri#toSafeString() does, which will enable us to compare + // sanitized phone numbers. + final StringBuilder builder = new StringBuilder(); + for (int i = 0; i < value.length(); i++) { + final char c = value.charAt(i); + if (c == '-' || c == '@' || c == '.') { + builder.append(c); + } else { + builder.append('x'); + } + } + return builder.toString(); + } +} diff --git a/java/com/android/dialer/util/OrientationUtil.java b/java/com/android/dialer/util/OrientationUtil.java new file mode 100644 index 000000000..5a8d1ae0f --- /dev/null +++ b/java/com/android/dialer/util/OrientationUtil.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2012 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.util; + +import android.content.Context; +import android.content.res.Configuration; + +/** Static methods related to device orientation. */ +public class OrientationUtil { + + /** @return if the context is in landscape orientation. */ + public static boolean isLandscape(Context context) { + return context.getResources().getConfiguration().orientation + == Configuration.ORIENTATION_LANDSCAPE; + } +} diff --git a/java/com/android/dialer/util/PermissionsUtil.java b/java/com/android/dialer/util/PermissionsUtil.java new file mode 100644 index 000000000..70b96dfe1 --- /dev/null +++ b/java/com/android/dialer/util/PermissionsUtil.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2015 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.util; + +import android.Manifest.permission; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.support.v4.content.ContextCompat; +import android.support.v4.content.LocalBroadcastManager; +import com.android.dialer.common.LogUtil; + +/** Utility class to help with runtime permissions. */ +public class PermissionsUtil { + + private static final String PERMISSION_PREFERENCE = "dialer_permissions"; + + public static boolean hasPhonePermissions(Context context) { + return hasPermission(context, permission.CALL_PHONE); + } + + public static boolean hasContactsPermissions(Context context) { + return hasPermission(context, permission.READ_CONTACTS); + } + + public static boolean hasLocationPermissions(Context context) { + return hasPermission(context, permission.ACCESS_FINE_LOCATION); + } + + public static boolean hasCameraPermissions(Context context) { + return hasPermission(context, permission.CAMERA); + } + + public static boolean hasPermission(Context context, String permission) { + return ContextCompat.checkSelfPermission(context, permission) + == PackageManager.PERMISSION_GRANTED; + } + + /** + * Checks {@link android.content.SharedPreferences} if a permission has been requested before. + * + * <p>It is important to note that this method only works if you call {@link + * PermissionsUtil#permissionRequested(Context, String)} in {@link + * android.app.Activity#onRequestPermissionsResult(int, String[], int[])}. + */ + public static boolean isFirstRequest(Context context, String permission) { + return context + .getSharedPreferences(PERMISSION_PREFERENCE, Context.MODE_PRIVATE) + .getBoolean(permission, true); + } + + /** + * Records in {@link android.content.SharedPreferences} that the specified permission has been + * requested at least once. + * + * <p>This method should be called in {@link android.app.Activity#onRequestPermissionsResult(int, + * String[], int[])}. + */ + public static void permissionRequested(Context context, String permission) { + context + .getSharedPreferences(PERMISSION_PREFERENCE, Context.MODE_PRIVATE) + .edit() + .putBoolean(permission, false) + .apply(); + } + + /** + * Rudimentary methods wrapping the use of a LocalBroadcastManager to simplify the process of + * notifying other classes when a particular fragment is notified that a permission is granted. + * + * <p>To be notified when a permission has been granted, create a new broadcast receiver and + * register it using {@link #registerPermissionReceiver(Context, BroadcastReceiver, String)} + * + * <p>E.g. + * + * <p>final BroadcastReceiver receiver = new BroadcastReceiver() { @Override public void + * onReceive(Context context, Intent intent) { refreshContactsView(); } } + * + * <p>PermissionsUtil.registerPermissionReceiver(getActivity(), receiver, READ_CONTACTS); + * + * <p>If you register to listen for multiple permissions, you can identify which permission was + * granted by inspecting {@link Intent#getAction()}. + * + * <p>In the fragment that requests for the permission, be sure to call {@link + * #notifyPermissionGranted(Context, String)} when the permission is granted so that any + * interested listeners are notified of the change. + */ + public static void registerPermissionReceiver( + Context context, BroadcastReceiver receiver, String permission) { + LogUtil.i("PermissionsUtil.registerPermissionReceiver", permission); + final IntentFilter filter = new IntentFilter(permission); + LocalBroadcastManager.getInstance(context).registerReceiver(receiver, filter); + } + + public static void unregisterPermissionReceiver(Context context, BroadcastReceiver receiver) { + LogUtil.i("PermissionsUtil.unregisterPermissionReceiver", null); + LocalBroadcastManager.getInstance(context).unregisterReceiver(receiver); + } + + public static void notifyPermissionGranted(Context context, String permission) { + LogUtil.i("PermissionsUtil.notifyPermissionGranted", permission); + final Intent intent = new Intent(permission); + LocalBroadcastManager.getInstance(context).sendBroadcast(intent); + } +} diff --git a/java/com/android/dialer/util/SettingsUtil.java b/java/com/android/dialer/util/SettingsUtil.java new file mode 100644 index 000000000..c61c09b6c --- /dev/null +++ b/java/com/android/dialer/util/SettingsUtil.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2014 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.util; + +import android.content.Context; +import android.content.SharedPreferences; +import android.database.sqlite.SQLiteException; +import android.media.Ringtone; +import android.media.RingtoneManager; +import android.net.Uri; +import android.os.Handler; +import android.preference.PreferenceManager; +import android.provider.Settings; +import android.text.TextUtils; + +public class SettingsUtil { + + private static final String DEFAULT_NOTIFICATION_URI_STRING = + Settings.System.DEFAULT_NOTIFICATION_URI.toString(); + + /** + * Queries for a ringtone name, and sets the name using a handler. This is a method was originally + * copied from com.android.settings.SoundSettings. + * + * @param context The application context. + * @param handler The handler, which takes the name of the ringtone as a String as a parameter. + * @param type The type of sound. + * @param key The key to the shared preferences entry being updated. + * @param msg An integer identifying the message sent to the handler. + */ + public static void updateRingtoneName( + Context context, Handler handler, int type, String key, int msg) { + final Uri ringtoneUri; + boolean defaultRingtone = false; + if (type == RingtoneManager.TYPE_RINGTONE) { + // For ringtones, we can just lookup the system default because changing the settings + // in Call Settings changes the system default. + ringtoneUri = RingtoneManager.getActualDefaultRingtoneUri(context, type); + } else { + final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + // For voicemail notifications, we use the value saved in Phone's shared preferences. + String uriString = prefs.getString(key, DEFAULT_NOTIFICATION_URI_STRING); + if (TextUtils.isEmpty(uriString)) { + // silent ringtone + ringtoneUri = null; + } else { + if (uriString.equals(DEFAULT_NOTIFICATION_URI_STRING)) { + // If it turns out that the voicemail notification is set to the system + // default notification, we retrieve the actual URI to prevent it from showing + // up as "Unknown Ringtone". + defaultRingtone = true; + ringtoneUri = RingtoneManager.getActualDefaultRingtoneUri(context, type); + } else { + ringtoneUri = Uri.parse(uriString); + } + } + } + CharSequence summary = context.getString(R.string.ringtone_unknown); + // Is it a silent ringtone? + if (ringtoneUri == null) { + summary = context.getString(R.string.ringtone_silent); + } else { + // Fetch the ringtone title from the media provider + final Ringtone ringtone = RingtoneManager.getRingtone(context, ringtoneUri); + if (ringtone != null) { + try { + final String title = ringtone.getTitle(context); + if (!TextUtils.isEmpty(title)) { + summary = title; + } + } catch (SQLiteException sqle) { + // Unknown title for the ringtone + } + } + } + if (defaultRingtone) { + summary = context.getString(R.string.default_notification_description, summary); + } + handler.sendMessage(handler.obtainMessage(msg, summary)); + } +} diff --git a/java/com/android/dialer/util/TouchPointManager.java b/java/com/android/dialer/util/TouchPointManager.java new file mode 100644 index 000000000..74f87c477 --- /dev/null +++ b/java/com/android/dialer/util/TouchPointManager.java @@ -0,0 +1,60 @@ +/* + * 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.util; + +import android.graphics.Point; + +/** + * Singleton class to keep track of where the user last touched the screen. + * + * <p>Used to pass on to the InCallUI for animation. + */ +public class TouchPointManager { + + public static final String TOUCH_POINT = "touchPoint"; + + private static TouchPointManager sInstance = new TouchPointManager(); + + private Point mPoint = new Point(); + + /** Private constructor. Instance should only be acquired through getInstance(). */ + private TouchPointManager() {} + + public static TouchPointManager getInstance() { + return sInstance; + } + + public Point getPoint() { + return mPoint; + } + + public void setPoint(int x, int y) { + mPoint.set(x, y); + } + + /** + * When a point is initialized, its value is (0,0). Since it is highly unlikely a user will touch + * at that exact point, if the point in TouchPointManager is (0,0), it is safe to assume that the + * TouchPointManager has not yet collected a touch. + * + * @return True if there is a valid point saved. Define a valid point as any point that is not + * (0,0). + */ + public boolean hasValidPoint() { + return mPoint.x != 0 || mPoint.y != 0; + } +} diff --git a/java/com/android/dialer/util/TransactionSafeActivity.java b/java/com/android/dialer/util/TransactionSafeActivity.java new file mode 100644 index 000000000..9b5e92ba8 --- /dev/null +++ b/java/com/android/dialer/util/TransactionSafeActivity.java @@ -0,0 +1,64 @@ +/* + * 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.util; + +import android.app.Activity; +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; + +/** + * A common superclass that keeps track of whether an {@link Activity} has saved its state yet or + * not. + */ +public abstract class TransactionSafeActivity extends AppCompatActivity { + + private boolean mIsSafeToCommitTransactions; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mIsSafeToCommitTransactions = true; + } + + @Override + protected void onStart() { + super.onStart(); + mIsSafeToCommitTransactions = true; + } + + @Override + protected void onResume() { + super.onResume(); + mIsSafeToCommitTransactions = true; + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + mIsSafeToCommitTransactions = false; + } + + /** + * Returns true if it is safe to commit {@link FragmentTransaction}s at this time, based on + * whether {@link Activity#onSaveInstanceState} has been called or not. + * + * <p>Make sure that the current activity calls into {@link super.onSaveInstanceState(Bundle + * outState)} (if that method is overridden), so the flag is properly set. + */ + public boolean isSafeToCommitTransactions() { + return mIsSafeToCommitTransactions; + } +} diff --git a/java/com/android/dialer/util/ViewUtil.java b/java/com/android/dialer/util/ViewUtil.java new file mode 100644 index 000000000..de08e41a7 --- /dev/null +++ b/java/com/android/dialer/util/ViewUtil.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2012 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.util; + +import android.content.ContentResolver; +import android.content.Context; +import android.graphics.Paint; +import android.os.PowerManager; +import android.provider.Settings; +import android.provider.Settings.Global; +import android.support.annotation.NonNull; +import android.text.TextUtils; +import android.util.TypedValue; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewTreeObserver.OnPreDrawListener; +import android.widget.TextView; +import java.util.Locale; + +/** Provides static functions to work with views */ +public class ViewUtil { + + private ViewUtil() {} + + /** Similar to {@link Runnable} but takes a View parameter to operate on */ + public interface ViewRunnable { + void run(@NonNull View view); + } + + /** + * Returns the width as specified in the LayoutParams + * + * @throws IllegalStateException Thrown if the view's width is unknown before a layout pass s + */ + public static int getConstantPreLayoutWidth(View view) { + // We haven't been layed out yet, so get the size from the LayoutParams + final ViewGroup.LayoutParams p = view.getLayoutParams(); + if (p.width < 0) { + throw new IllegalStateException( + "Expecting view's width to be a constant rather " + "than a result of the layout pass"); + } + return p.width; + } + + /** + * Returns a boolean indicating whether or not the view's layout direction is RTL + * + * @param view - A valid view + * @return True if the view's layout direction is RTL + */ + public static boolean isViewLayoutRtl(View view) { + return view.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; + } + + public static boolean isRtl() { + return TextUtils.getLayoutDirectionFromLocale(Locale.getDefault()) == View.LAYOUT_DIRECTION_RTL; + } + + public static void resizeText(TextView textView, int originalTextSize, int minTextSize) { + final Paint paint = textView.getPaint(); + final int width = textView.getWidth(); + if (width == 0) { + return; + } + textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, originalTextSize); + float ratio = width / paint.measureText(textView.getText().toString()); + if (ratio <= 1.0f) { + textView.setTextSize( + TypedValue.COMPLEX_UNIT_PX, Math.max(minTextSize, originalTextSize * ratio)); + } + } + + /** Runs a piece of code just before the next draw, after layout and measurement */ + public static void doOnPreDraw( + @NonNull final View view, final boolean drawNextFrame, final Runnable runnable) { + view.getViewTreeObserver() + .addOnPreDrawListener( + new OnPreDrawListener() { + @Override + public boolean onPreDraw() { + view.getViewTreeObserver().removeOnPreDrawListener(this); + runnable.run(); + return drawNextFrame; + } + }); + } + + public static void doOnPreDraw( + @NonNull final View view, final boolean drawNextFrame, final ViewRunnable runnable) { + view.getViewTreeObserver() + .addOnPreDrawListener( + new OnPreDrawListener() { + @Override + public boolean onPreDraw() { + view.getViewTreeObserver().removeOnPreDrawListener(this); + runnable.run(view); + return drawNextFrame; + } + }); + } + + /** + * Returns {@code true} if animations should be disabled. + * + * <p>Animations should be disabled if {@link + * android.provider.Settings.Global#ANIMATOR_DURATION_SCALE} is set to 0 through system settings + * or the device is in power save mode. + */ + public static boolean areAnimationsDisabled(Context context) { + ContentResolver contentResolver = context.getContentResolver(); + PowerManager powerManager = context.getSystemService(PowerManager.class); + return Settings.Global.getFloat(contentResolver, Global.ANIMATOR_DURATION_SCALE, 1.0f) == 0 + || powerManager.isPowerSaveMode(); + } +} diff --git a/java/com/android/dialer/util/res/values/strings.xml b/java/com/android/dialer/util/res/values/strings.xml new file mode 100644 index 000000000..43ea6e31a --- /dev/null +++ b/java/com/android/dialer/util/res/values/strings.xml @@ -0,0 +1,42 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2012 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 + --> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + + <!-- The string used to describe a notification if it is the default one in the system. For + example, if the user selects the default notification, it will appear as something like + Default sound(Capella) in the notification summary. + [CHAR LIMIT=40] --> + <string name="default_notification_description">Default sound (<xliff:g id="default_sound_title">%1$s</xliff:g>)</string> + + <!-- Choice in the ringtone picker. If chosen, there will be silence instead of a ringtone played. --> + <string name="ringtone_silent">None</string> + + <!-- If there is ever a ringtone set for some setting, but that ringtone can no longer be resolved, this is shown instead. For example, if the ringtone was on a SD card and it had been removed, this would be shown for ringtones on that SD card. --> + <string name="ringtone_unknown">Unknown ringtone</string> + + <!-- Message displayed when there is no application available to handle a particular action. + [CHAR LIMIT=NONE] --> + <string name="activity_not_available">No app for that on this device</string> + + <!-- Text of warning to be shown when the user attempts to make an outgoing Wireless + Preferred Service call when there is an VoLTE call in progress --> + <string name="outgoing_wps_warning">Placing a WPS call will disconnect your existing call.</string> + + <!-- Text for button which indicates that the user wants to proceed with an action. --> + <string name="dialog_continue">Continue</string> + +</resources> |