diff options
Diffstat (limited to 'common/src/com/android/tv/common/util')
15 files changed, 1197 insertions, 0 deletions
diff --git a/common/src/com/android/tv/common/util/AutoCloseableUtils.java b/common/src/com/android/tv/common/util/AutoCloseableUtils.java new file mode 100644 index 00000000..605715ef --- /dev/null +++ b/common/src/com/android/tv/common/util/AutoCloseableUtils.java @@ -0,0 +1,34 @@ +/* + * 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.tv.common.util; + +import android.util.Log; + +/** Static utilities for AutoCloseable. */ +public class AutoCloseableUtils { + private static final String TAG = "AutoCloseableUtils"; + + private AutoCloseableUtils() {} + + public static void closeQuietly(AutoCloseable closeable) { + try { + closeable.close(); + } catch (Exception ex) { + Log.e(TAG, "Error closing " + closeable, ex); + } + } +} diff --git a/common/src/com/android/tv/common/util/Clock.java b/common/src/com/android/tv/common/util/Clock.java new file mode 100644 index 00000000..cd6ede86 --- /dev/null +++ b/common/src/com/android/tv/common/util/Clock.java @@ -0,0 +1,78 @@ +/* + * 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.tv.common.util; + +import android.os.SystemClock; + +/** + * An interface through which system clocks can be read. The {@link #SYSTEM} implementation must be + * used for all non-test cases. + */ +public interface Clock { + /** + * Returns the current time in milliseconds since January 1, 1970 00:00:00.0 UTC. + * + * @see System#currentTimeMillis(). + */ + long currentTimeMillis(); + + /** + * Returns milliseconds since boot, including time spent in sleep. + * + * @see SystemClock#elapsedRealtime() + */ + long elapsedRealtime(); + + /** + * Returns milliseconds since boot, not counting time spent in deep sleep. + * + * @return milliseconds of non-sleep uptime since boot. + * @see SystemClock#uptimeMillis() + */ + long uptimeMillis(); + + /** + * Waits a given number of milliseconds (of uptimeMillis) before returning. + * + * @param ms to sleep before returning, in milliseconds of uptime. + * @see SystemClock#sleep(long) + */ + void sleep(long ms); + + /** The default implementation of Clock. */ + Clock SYSTEM = + new Clock() { + @Override + public long currentTimeMillis() { + return System.currentTimeMillis(); + } + + @Override + public long elapsedRealtime() { + return SystemClock.elapsedRealtime(); + } + + @Override + public void sleep(long ms) { + SystemClock.sleep(ms); + } + + @Override + public long uptimeMillis() { + return SystemClock.uptimeMillis(); + } + }; +} diff --git a/common/src/com/android/tv/common/util/CollectionUtils.java b/common/src/com/android/tv/common/util/CollectionUtils.java new file mode 100644 index 00000000..8ca7e3cc --- /dev/null +++ b/common/src/com/android/tv/common/util/CollectionUtils.java @@ -0,0 +1,95 @@ +/* + * 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.tv.common.util; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +/** Static utilities for collections */ +public class CollectionUtils { + + /** + * Returns an array with the arrays concatenated together. + * + * @see <a href="http://stackoverflow.com/a/784842/1122089">Stackoverflow answer</a> by <a + * href="http://stackoverflow.com/users/40342/joachim-sauer">Joachim Sauer</a> + */ + public static <T> T[] concatAll(T[] first, T[]... rest) { + int totalLength = first.length; + for (T[] array : rest) { + totalLength += array.length; + } + T[] result = Arrays.copyOf(first, totalLength); + int offset = first.length; + for (T[] array : rest) { + System.arraycopy(array, 0, result, offset, array.length); + offset += array.length; + } + return result; + } + + /** + * Unions the two collections and returns the unified list. + * + * <p>The elements is not compared with hashcode() or equals(). Comparator is used for the + * equality check. + */ + public static <T> List<T> union( + Collection<T> originals, Collection<T> toAdds, Comparator<T> comparator) { + List<T> result = new ArrayList<>(originals); + Collections.sort(result, comparator); + List<T> resultToAdd = new ArrayList<>(); + for (T toAdd : toAdds) { + if (Collections.binarySearch(result, toAdd, comparator) < 0) { + resultToAdd.add(toAdd); + } + } + result.addAll(resultToAdd); + return result; + } + + /** Subtracts the elements from the original collection. */ + public static <T> List<T> subtract( + Collection<T> originals, T[] toSubtracts, Comparator<T> comparator) { + List<T> result = new ArrayList<>(originals); + Collections.sort(result, comparator); + for (T toSubtract : toSubtracts) { + int index = Collections.binarySearch(result, toSubtract, comparator); + if (index >= 0) { + result.remove(index); + } + } + return result; + } + + /** Returns {@code true} if the two specified collections have common elements. */ + public static <T> boolean containsAny( + Collection<T> c1, Collection<T> c2, Comparator<T> comparator) { + List<T> contains = new ArrayList<>(c1); + Collections.sort(contains, comparator); + for (T iterate : c2) { + if (Collections.binarySearch(contains, iterate, comparator) >= 0) { + return true; + } + } + return false; + } +} diff --git a/common/src/com/android/tv/common/util/CommonUtils.java b/common/src/com/android/tv/common/util/CommonUtils.java new file mode 100644 index 00000000..305431d3 --- /dev/null +++ b/common/src/com/android/tv/common/util/CommonUtils.java @@ -0,0 +1,154 @@ +/* + * Copyright 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.tv.common.util; + +import android.content.Context; +import android.content.Intent; +import android.media.tv.TvInputInfo; +import android.os.Build; +import android.util.ArraySet; +import android.util.Log; +import com.android.tv.common.BuildConfig; +import com.android.tv.common.CommonConstants; +import com.android.tv.common.actions.InputSetupActionUtils; +import com.android.tv.common.experiments.Experiments; +import java.io.File; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.Set; + +/** Util class for common use in TV app and inputs. */ +@SuppressWarnings("AndroidApiChecker") // TODO(b/32513850) remove when error prone is updated +public final class CommonUtils { + private static final String TAG = "CommonUtils"; + private static final ThreadLocal<SimpleDateFormat> ISO_8601 = + new ThreadLocal() { + private final SimpleDateFormat value = + new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.US); + + @Override + protected SimpleDateFormat initialValue() { + return value; + } + }; + // Hardcoded list for known bundled inputs not written by OEM/SOCs. + // Bundled (system) inputs not in the list will get the high priority + // so they and their channels come first in the UI. + private static final Set<String> BUNDLED_PACKAGE_SET = new ArraySet<>(); + + static { + BUNDLED_PACKAGE_SET.add("com.android.tv"); + } + + private static Boolean sRunningInTest; + + private CommonUtils() {} + + /** + * Returns an intent to start the setup activity for the TV input using {@link + * InputSetupActionUtils#INTENT_ACTION_INPUT_SETUP}. + */ + public static Intent createSetupIntent(Intent originalSetupIntent, String inputId) { + if (originalSetupIntent == null) { + return null; + } + Intent setupIntent = new Intent(originalSetupIntent); + if (!InputSetupActionUtils.hasInputSetupAction(originalSetupIntent)) { + Intent intentContainer = new Intent(InputSetupActionUtils.INTENT_ACTION_INPUT_SETUP); + intentContainer.putExtra(InputSetupActionUtils.EXTRA_SETUP_INTENT, originalSetupIntent); + intentContainer.putExtra(InputSetupActionUtils.EXTRA_INPUT_ID, inputId); + setupIntent = intentContainer; + } + return setupIntent; + } + + /** + * Returns an intent to start the setup activity for this TV input using {@link + * InputSetupActionUtils#INTENT_ACTION_INPUT_SETUP}. + */ + public static Intent createSetupIntent(TvInputInfo input) { + return createSetupIntent(input.createSetupIntent(), input.getId()); + } + + /** + * Checks if this application is running in tests. + * + * <p>{@link android.app.ActivityManager#isRunningInTestHarness} doesn't return {@code true} for + * the usual devices even the application is running in tests. We need to figure it out by + * checking whether the class in tv-tests-common module can be loaded or not. + */ + public static synchronized boolean isRunningInTest() { + if (sRunningInTest == null) { + try { + Class.forName("com.android.tv.testing.utils.Utils"); + Log.i( + TAG, + "Assumed to be running in a test because" + + " com.android.tv.testing.utils.Utils is found"); + sRunningInTest = true; + } catch (ClassNotFoundException e) { + sRunningInTest = false; + } + } + return sRunningInTest; + } + + /** Checks whether a given package is in our bundled package set. */ + public static boolean isInBundledPackageSet(String packageName) { + return BUNDLED_PACKAGE_SET.contains(packageName); + } + + /** Checks whether a given input is a bundled input. */ + public static boolean isBundledInput(String inputId) { + for (String prefix : BUNDLED_PACKAGE_SET) { + if (inputId.startsWith(prefix + "/")) { + return true; + } + } + return false; + } + + /** Returns true if the application is packaged with Live TV. */ + public static boolean isPackagedWithLiveChannels(Context context) { + return (CommonConstants.BASE_PACKAGE.equals(context.getPackageName())); + } + + /** Returns true if the current user is a developer. */ + public static boolean isDeveloper() { + return BuildConfig.ENG || Experiments.ENABLE_DEVELOPER_FEATURES.get(); + } + + /** Converts time in milliseconds to a ISO 8061 string. */ + public static String toIsoDateTimeString(long timeMillis) { + return ISO_8601.get().format(new Date(timeMillis)); + } + + /** Deletes a file or a directory. */ + public static void deleteDirOrFile(File fileOrDirectory) { + if (fileOrDirectory.isDirectory()) { + for (File child : fileOrDirectory.listFiles()) { + deleteDirOrFile(child); + } + } + fileOrDirectory.delete(); + } + + public static boolean isRoboTest() { + return "robolectric".equals(Build.FINGERPRINT); + } +} diff --git a/common/src/com/android/tv/common/util/ContentUriUtils.java b/common/src/com/android/tv/common/util/ContentUriUtils.java new file mode 100644 index 00000000..6cbe5e12 --- /dev/null +++ b/common/src/com/android/tv/common/util/ContentUriUtils.java @@ -0,0 +1,45 @@ +/* + * Copyright 2018 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.tv.common.util; + +import android.content.ContentUris; +import android.net.Uri; +import android.util.Log; + +/** Static utils for{@link android.content.ContentUris}. */ +public class ContentUriUtils { + private static final String TAG = "ContentUriUtils"; + + /** + * Converts the last path segment to a long. + * + * <p>This supports a common convention for content URIs where an ID is stored in the last + * segment. + * + * @return the long conversion of the last segment or -1 if the path is empty or there is any + * error + * @see ContentUris#parseId(Uri) + */ + public static long safeParseId(Uri uri) { + try { + return ContentUris.parseId(uri); + } catch (Exception e) { + Log.d(TAG, "Error parsing " + uri, e); + return -1; + } + } +} diff --git a/common/src/com/android/tv/common/util/Debug.java b/common/src/com/android/tv/common/util/Debug.java new file mode 100644 index 00000000..ab908741 --- /dev/null +++ b/common/src/com/android/tv/common/util/Debug.java @@ -0,0 +1,50 @@ +/* + * 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.tv.common.util; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +/** A class only for help developers. */ +public class Debug { + /** + * A threshold of start up time, when the start up time of Live TV is more than it, a + * warning will show to the developer. + */ + public static final long TIME_START_UP_DURATION_THRESHOLD = TimeUnit.SECONDS.toMillis(6); + /** Tag for measuring start up time of Live TV. */ + public static final String TAG_START_UP_TIMER = "start_up_timer"; + + /** A global map for duration timers. */ + private static final Map<String, DurationTimer> sTimerMap = new HashMap<>(); + + /** Returns the global duration timer by tag. */ + public static DurationTimer getTimer(String tag) { + if (sTimerMap.get(tag) != null) { + return sTimerMap.get(tag); + } + DurationTimer timer = new DurationTimer(tag, true); + sTimerMap.put(tag, timer); + return timer; + } + + /** Removes the global duration timer by tag. */ + public static DurationTimer removeTimer(String tag) { + return sTimerMap.remove(tag); + } +} diff --git a/common/src/com/android/tv/common/util/DurationTimer.java b/common/src/com/android/tv/common/util/DurationTimer.java new file mode 100644 index 00000000..91581ad5 --- /dev/null +++ b/common/src/com/android/tv/common/util/DurationTimer.java @@ -0,0 +1,80 @@ +/* + * 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.tv.common.util; + +import android.os.SystemClock; +import android.util.Log; +import com.android.tv.common.BuildConfig; + +/** Times a duration. */ +public final class DurationTimer { + private static final String TAG = "DurationTimer"; + public static final long TIME_NOT_SET = -1; + + private long mStartTimeMs = TIME_NOT_SET; + private String mTag = TAG; + private boolean mLogEngOnly; + + public DurationTimer() {} + + public DurationTimer(String tag, boolean logEngOnly) { + mTag = tag; + mLogEngOnly = logEngOnly; + } + + /** Returns true if the timer is running. */ + public boolean isRunning() { + return mStartTimeMs != TIME_NOT_SET; + } + + /** Start the timer. */ + public void start() { + mStartTimeMs = SystemClock.elapsedRealtime(); + } + + /** Returns true if timer is started. */ + public boolean isStarted() { + return mStartTimeMs != TIME_NOT_SET; + } + + /** + * Returns the current duration in milliseconds or {@link #TIME_NOT_SET} if the timer is not + * running. + */ + public long getDuration() { + return isRunning() ? SystemClock.elapsedRealtime() - mStartTimeMs : TIME_NOT_SET; + } + + /** + * Stops the timer and resets its value to {@link #TIME_NOT_SET}. + * + * @return the current duration in milliseconds or {@link #TIME_NOT_SET} if the timer is not + * running. + */ + public long reset() { + long duration = getDuration(); + mStartTimeMs = TIME_NOT_SET; + return duration; + } + + /** Adds information and duration time to the log. */ + public void log(String message) { + if (isRunning() && (!mLogEngOnly || BuildConfig.ENG)) { + Log.i(mTag, message + " : " + getDuration() + "ms"); + } + } +} diff --git a/common/src/com/android/tv/common/util/LocationUtils.java b/common/src/com/android/tv/common/util/LocationUtils.java new file mode 100644 index 00000000..53155298 --- /dev/null +++ b/common/src/com/android/tv/common/util/LocationUtils.java @@ -0,0 +1,143 @@ +/* + * 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.tv.common.util; + +import android.Manifest; +import android.content.Context; +import android.content.pm.PackageManager; +import android.location.Address; +import android.location.Geocoder; +import android.location.Location; +import android.location.LocationListener; +import android.location.LocationManager; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.text.TextUtils; +import android.util.Log; +import com.android.tv.common.BuildConfig; + + + + +import java.io.IOException; +import java.util.List; +import java.util.Locale; + +/** A utility class to get the current location. */ +public class LocationUtils { + private static final String TAG = "LocationUtils"; + private static final boolean DEBUG = false; + + private static Context sApplicationContext; + private static Address sAddress; + private static String sCountry; + private static IOException sError; + + /** Checks the current location. */ + public static synchronized Address getCurrentAddress(Context context) + throws IOException, SecurityException { + if (sAddress != null) { + return sAddress; + } + if (sError != null) { + throw sError; + } + if (sApplicationContext == null) { + sApplicationContext = context.getApplicationContext(); + } + LocationUtilsHelper.startLocationUpdates(); + return null; + } + + /** Returns the current country. */ + @NonNull + public static synchronized String getCurrentCountry(Context context) { + if (sCountry != null) { + return sCountry; + } + if (TextUtils.isEmpty(sCountry)) { + sCountry = context.getResources().getConfiguration().locale.getCountry(); + } + return sCountry; + } + + private static void updateAddress(Location location) { + if (DEBUG) Log.d(TAG, "Updating address with " + location); + if (location == null) { + return; + } + Geocoder geocoder = new Geocoder(sApplicationContext, Locale.getDefault()); + try { + List<Address> addresses = + geocoder.getFromLocation(location.getLatitude(), location.getLongitude(), 1); + if (addresses != null && !addresses.isEmpty()) { + sAddress = addresses.get(0); + if (DEBUG) Log.d(TAG, "Got " + sAddress); + try { + PostalCodeUtils.updatePostalCode(sApplicationContext); + } catch (Exception e) { + // Do nothing + } + } else { + if (DEBUG) Log.d(TAG, "No address returned"); + } + sError = null; + } catch (IOException e) { + Log.w(TAG, "Error in updating address", e); + sError = e; + } + } + + private LocationUtils() {} + + private static class LocationUtilsHelper { + private static final LocationListener LOCATION_LISTENER = + new LocationListener() { + @Override + public void onLocationChanged(Location location) { + updateAddress(location); + } + + @Override + public void onStatusChanged(String provider, int status, Bundle extras) {} + + @Override + public void onProviderEnabled(String provider) {} + + @Override + public void onProviderDisabled(String provider) {} + }; + + private static LocationManager sLocationManager; + + public static void startLocationUpdates() { + if (sLocationManager == null) { + sLocationManager = + (LocationManager) + sApplicationContext.getSystemService(Context.LOCATION_SERVICE); + try { + sLocationManager.requestLocationUpdates( + LocationManager.NETWORK_PROVIDER, 1000, 10, LOCATION_LISTENER, null); + } catch (SecurityException e) { + // Enables requesting the location updates again. + sLocationManager = null; + throw e; + } + } + } + } +} diff --git a/common/src/com/android/tv/common/util/NetworkTrafficTags.java b/common/src/com/android/tv/common/util/NetworkTrafficTags.java new file mode 100644 index 00000000..91f2bcd1 --- /dev/null +++ b/common/src/com/android/tv/common/util/NetworkTrafficTags.java @@ -0,0 +1,63 @@ +/* + * 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.tv.common.util; + +import android.net.TrafficStats; +import android.support.annotation.NonNull; +import java.util.concurrent.Executor; + +/** Constants for tagging network traffic in the Live channels app. */ +public final class NetworkTrafficTags { + + public static final int DEFAULT_LIVE_CHANNELS = 1; + public static final int LOGO_FETCHER = 2; + public static final int HDHOMERUN = 3; + public static final int EPG_FETCH = 4; + + /** + * An executor which simply wraps a provided delegate executor, but calls {@link + * TrafficStats#setThreadStatsTag(int)} before executing any task. + */ + public static class TrafficStatsTaggingExecutor implements Executor { + private final Executor delegateExecutor; + private final int tag; + + public TrafficStatsTaggingExecutor(Executor delegateExecutor, int tag) { + this.delegateExecutor = delegateExecutor; + this.tag = tag; + } + + @Override + public void execute(final @NonNull Runnable command) { + // TODO(b/62038127): robolectric does not support lamdas in unbundled apps + delegateExecutor.execute( + new Runnable() { + @Override + public void run() { + TrafficStats.setThreadStatsTag(tag); + try { + command.run(); + } finally { + TrafficStats.clearThreadStatsTag(); + } + } + }); + } + } + + private NetworkTrafficTags() {} +} diff --git a/common/src/com/android/tv/common/util/PermissionUtils.java b/common/src/com/android/tv/common/util/PermissionUtils.java new file mode 100644 index 00000000..8d409e50 --- /dev/null +++ b/common/src/com/android/tv/common/util/PermissionUtils.java @@ -0,0 +1,68 @@ +/* + * 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.tv.common.util; + +import android.content.Context; +import android.content.pm.PackageManager; + +/** Util class to handle permissions. */ +public class PermissionUtils { + /** Permission to read the TV listings. */ + public static final String PERMISSION_READ_TV_LISTINGS = "android.permission.READ_TV_LISTINGS"; + + private static Boolean sHasAccessAllEpgPermission; + private static Boolean sHasAccessWatchedHistoryPermission; + private static Boolean sHasModifyParentalControlsPermission; + + public static boolean hasAccessAllEpg(Context context) { + if (sHasAccessAllEpgPermission == null) { + sHasAccessAllEpgPermission = + context.checkSelfPermission( + "com.android.providers.tv.permission.ACCESS_ALL_EPG_DATA") + == PackageManager.PERMISSION_GRANTED; + } + return sHasAccessAllEpgPermission; + } + + public static boolean hasAccessWatchedHistory(Context context) { + if (sHasAccessWatchedHistoryPermission == null) { + sHasAccessWatchedHistoryPermission = + context.checkSelfPermission( + "com.android.providers.tv.permission.ACCESS_WATCHED_PROGRAMS") + == PackageManager.PERMISSION_GRANTED; + } + return sHasAccessWatchedHistoryPermission; + } + + public static boolean hasModifyParentalControls(Context context) { + if (sHasModifyParentalControlsPermission == null) { + sHasModifyParentalControlsPermission = + context.checkSelfPermission("android.permission.MODIFY_PARENTAL_CONTROLS") + == PackageManager.PERMISSION_GRANTED; + } + return sHasModifyParentalControlsPermission; + } + + public static boolean hasReadTvListings(Context context) { + return context.checkSelfPermission(PERMISSION_READ_TV_LISTINGS) + == PackageManager.PERMISSION_GRANTED; + } + + public static boolean hasInternet(Context context) { + return context.checkSelfPermission("android.permission.INTERNET") + == PackageManager.PERMISSION_GRANTED; + } +} diff --git a/common/src/com/android/tv/common/util/PostalCodeUtils.java b/common/src/com/android/tv/common/util/PostalCodeUtils.java new file mode 100644 index 00000000..c0917af2 --- /dev/null +++ b/common/src/com/android/tv/common/util/PostalCodeUtils.java @@ -0,0 +1,137 @@ +/* + * 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.tv.common.util; + +import android.content.Context; +import android.location.Address; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.text.TextUtils; +import android.util.Log; +import com.android.tv.common.CommonPreferences; +import java.io.IOException; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.regex.Pattern; + +/** A utility class to update, get, and set the last known postal or zip code. */ +public class PostalCodeUtils { + private static final String TAG = "PostalCodeUtils"; + + // Postcode formats, where A signifies a letter and 9 a digit: + // US zip code format: 99999 + private static final String POSTCODE_REGEX_US = "^(\\d{5})"; + // UK postcode district formats: A9, A99, AA9, AA99 + // Full UK postcode format: Postcode District + space + 9AA + // Should be able to handle both postcode district and full postcode + private static final String POSTCODE_REGEX_GB = + "^([A-Z][A-Z]?[0-9][0-9A-Z]?)( ?[0-9][A-Z]{2})?$"; + private static final String POSTCODE_REGEX_GB_GIR = "^GIR( ?0AA)?$"; // special UK postcode + + private static final Map<String, Pattern> REGION_PATTERN = new HashMap<>(); + private static final Map<String, Integer> REGION_MAX_LENGTH = new HashMap<>(); + + static { + REGION_PATTERN.put(Locale.US.getCountry(), Pattern.compile(POSTCODE_REGEX_US)); + REGION_PATTERN.put( + Locale.UK.getCountry(), + Pattern.compile(POSTCODE_REGEX_GB + "|" + POSTCODE_REGEX_GB_GIR)); + REGION_MAX_LENGTH.put(Locale.US.getCountry(), 5); + REGION_MAX_LENGTH.put(Locale.UK.getCountry(), 8); + } + + // The longest postcode number is 10-character-long. + // Use a larger number to accommodate future changes. + private static final int DEFAULT_MAX_LENGTH = 16; + + /** Returns {@code true} if postal code has been changed */ + public static boolean updatePostalCode(Context context) + throws IOException, SecurityException, NoPostalCodeException { + String postalCode = getPostalCode(context); + String lastPostalCode = getLastPostalCode(context); + if (TextUtils.isEmpty(postalCode)) { + if (TextUtils.isEmpty(lastPostalCode)) { + throw new NoPostalCodeException(); + } + } else if (!TextUtils.equals(postalCode, lastPostalCode)) { + setLastPostalCode(context, postalCode); + return true; + } + return false; + } + + /** + * Gets the last stored postal or zip code, which might be decided by {@link LocationUtils} or + * input by users. + */ + public static String getLastPostalCode(Context context) { + return CommonPreferences.getLastPostalCode(context); + } + + /** + * Sets the last stored postal or zip code. This method will overwrite the value written by + * calling {@link #updatePostalCode(Context)}. + */ + public static void setLastPostalCode(Context context, String postalCode) { + Log.i(TAG, "Set Postal Code:" + postalCode); + CommonPreferences.setLastPostalCode(context, postalCode); + } + + @Nullable + private static String getPostalCode(Context context) throws IOException, SecurityException { + Address address = LocationUtils.getCurrentAddress(context); + if (address != null) { + Log.i( + TAG, + "Current country and postal code is " + + address.getCountryName() + + ", " + + address.getPostalCode()); + return address.getPostalCode(); + } + return null; + } + + /** An {@link java.lang.Exception} class to notify no valid postal or zip code is available. */ + public static class NoPostalCodeException extends Exception { + public NoPostalCodeException() {} + } + + /** + * Checks whether a postcode matches the format of the specific region. + * + * @return {@code false} if the region is supported and the postcode doesn't match; {@code true} + * otherwise + */ + public static boolean matches(@NonNull CharSequence postcode, @NonNull String region) { + Pattern pattern = REGION_PATTERN.get(region.toUpperCase()); + return pattern == null || pattern.matcher(postcode).matches(); + } + + /** + * Gets the largest possible postcode length in the region. + * + * @return maximum postcode length if the region is supported; {@link #DEFAULT_MAX_LENGTH} + * otherwise + */ + public static int getRegionMaxLength(Context context) { + Integer maxLength = + REGION_MAX_LENGTH.get(LocationUtils.getCurrentCountry(context).toUpperCase()); + return maxLength == null ? DEFAULT_MAX_LENGTH : maxLength; + } +} diff --git a/common/src/com/android/tv/common/util/SharedPreferencesUtils.java b/common/src/com/android/tv/common/util/SharedPreferencesUtils.java new file mode 100644 index 00000000..e8bfe61b --- /dev/null +++ b/common/src/com/android/tv/common/util/SharedPreferencesUtils.java @@ -0,0 +1,84 @@ +/* + * 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.tv.common.util; + +import android.content.Context; +import android.os.AsyncTask; +import android.preference.PreferenceManager; +import com.android.tv.common.CommonConstants; + +/** Static utilities for {@link android.content.SharedPreferences} */ +public final class SharedPreferencesUtils { + // Note that changing the preference name will reset the preference values. + public static final String SHARED_PREF_FEATURES = "sharePreferencesFeatures"; + public static final String SHARED_PREF_BROWSABLE = "browsable_shared_preference"; + public static final String SHARED_PREF_WATCHED_HISTORY = "watched_history_shared_preference"; + public static final String SHARED_PREF_DVR_WATCHED_POSITION = + "dvr_watched_position_shared_preference"; + public static final String SHARED_PREF_AUDIO_CAPABILITIES = + CommonConstants.BASE_PACKAGE + ".audio_capabilities"; + public static final String SHARED_PREF_RECURRING_RUNNER = "sharedPreferencesRecurringRunner"; + public static final String SHARED_PREF_EPG = "epg_preferences"; + public static final String SHARED_PREF_SERIES_RECORDINGS = "seriesRecordings"; + /** No need to pre-initialize. It's used only on the worker thread. */ + public static final String SHARED_PREF_CHANNEL_LOGO_URIS = "channelLogoUris"; + /** Stores the UI related settings */ + public static final String SHARED_PREF_UI_SETTINGS = "ui_settings"; + + public static final String SHARED_PREF_PREVIEW_PROGRAMS = "previewPrograms"; + + private static boolean sInitializeCalled; + + /** + * {@link android.content.SharedPreferences} loads the preference file when {@link + * Context#getSharedPreferences(String, int)} is called for the first time. Call {@link + * Context#getSharedPreferences(String, int)} as early as possible to avoid the ANR due to the + * file loading. + */ + public static synchronized void initialize(final Context context, final Runnable postTask) { + if (!sInitializeCalled) { + sInitializeCalled = true; + new AsyncTask<Void, Void, Void>() { + @Override + protected Void doInBackground(Void... params) { + PreferenceManager.getDefaultSharedPreferences(context); + context.getSharedPreferences(SHARED_PREF_FEATURES, Context.MODE_PRIVATE); + context.getSharedPreferences(SHARED_PREF_BROWSABLE, Context.MODE_PRIVATE); + context.getSharedPreferences(SHARED_PREF_WATCHED_HISTORY, Context.MODE_PRIVATE); + context.getSharedPreferences( + SHARED_PREF_DVR_WATCHED_POSITION, Context.MODE_PRIVATE); + context.getSharedPreferences( + SHARED_PREF_AUDIO_CAPABILITIES, Context.MODE_PRIVATE); + context.getSharedPreferences( + SHARED_PREF_RECURRING_RUNNER, Context.MODE_PRIVATE); + context.getSharedPreferences(SHARED_PREF_EPG, Context.MODE_PRIVATE); + context.getSharedPreferences( + SHARED_PREF_SERIES_RECORDINGS, Context.MODE_PRIVATE); + context.getSharedPreferences(SHARED_PREF_UI_SETTINGS, Context.MODE_PRIVATE); + return null; + } + + @Override + protected void onPostExecute(Void result) { + postTask.run(); + } + }.execute(); + } + } + + private SharedPreferencesUtils() {} +} diff --git a/common/src/com/android/tv/common/util/StringUtils.java b/common/src/com/android/tv/common/util/StringUtils.java new file mode 100644 index 00000000..b9461426 --- /dev/null +++ b/common/src/com/android/tv/common/util/StringUtils.java @@ -0,0 +1,34 @@ +/* + * 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.tv.common.util; + +/** Utility class for handling {@link String}. */ +public final class StringUtils { + + private StringUtils() {} + + /** Returns compares two strings lexicographically and handles null values quietly. */ + public static int compare(String a, String b) { + if (a == null) { + return b == null ? 0 : -1; + } + if (b == null) { + return 1; + } + return a.compareTo(b); + } +} diff --git a/common/src/com/android/tv/common/util/SystemProperties.java b/common/src/com/android/tv/common/util/SystemProperties.java new file mode 100644 index 00000000..a9f18d4b --- /dev/null +++ b/common/src/com/android/tv/common/util/SystemProperties.java @@ -0,0 +1,53 @@ +/* + * 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.tv.common.util; + +import com.android.tv.common.BooleanSystemProperty; + +/** A convenience class for getting TV related system properties. */ +public final class SystemProperties { + + /** Allow Google Analytics for eng builds. */ + public static final BooleanSystemProperty ALLOW_ANALYTICS_IN_ENG = + new BooleanSystemProperty("tv_allow_analytics_in_eng", false); + + /** Allow Strict mode for debug builds. */ + public static final BooleanSystemProperty ALLOW_STRICT_MODE = + new BooleanSystemProperty("tv_allow_strict_mode", true); + + /** When true {@link android.view.KeyEvent}s are logged. Defaults to false. */ + public static final BooleanSystemProperty LOG_KEYEVENT = + new BooleanSystemProperty("tv_log_keyevent", false); + /** When true debug keys are used. Defaults to false. */ + public static final BooleanSystemProperty USE_DEBUG_KEYS = + new BooleanSystemProperty("tv_use_debug_keys", false); + + /** Send {@link com.android.tv.analytics.Tracker} information. Defaults to {@code true}. */ + public static final BooleanSystemProperty USE_TRACKER = + new BooleanSystemProperty("tv_use_tracker", true); + + static { + updateSystemProperties(); + } + + private SystemProperties() {} + + /** Update the TV related system properties. */ + public static void updateSystemProperties() { + BooleanSystemProperty.resetAll(); + } +} diff --git a/common/src/com/android/tv/common/util/SystemPropertiesProxy.java b/common/src/com/android/tv/common/util/SystemPropertiesProxy.java new file mode 100644 index 00000000..a3ffd0fa --- /dev/null +++ b/common/src/com/android/tv/common/util/SystemPropertiesProxy.java @@ -0,0 +1,79 @@ +/* + * 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.tv.common.util; + +import android.util.Log; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** + * Proxy class that gives an access to a hidden API {@link android.os.SystemProperties#getBoolean}. + */ +public class SystemPropertiesProxy { + private static final String TAG = "SystemPropertiesProxy"; + + private SystemPropertiesProxy() {} + + public static boolean getBoolean(String key, boolean def) throws IllegalArgumentException { + try { + Class SystemPropertiesClass = Class.forName("android.os.SystemProperties"); + Method getBooleanMethod = + SystemPropertiesClass.getDeclaredMethod( + "getBoolean", String.class, boolean.class); + getBooleanMethod.setAccessible(true); + return (boolean) getBooleanMethod.invoke(SystemPropertiesClass, key, def); + } catch (InvocationTargetException + | IllegalAccessException + | NoSuchMethodException + | ClassNotFoundException e) { + Log.e(TAG, "Failed to invoke SystemProperties.getBoolean()", e); + } + return def; + } + + public static int getInt(String key, int def) throws IllegalArgumentException { + try { + Class SystemPropertiesClass = Class.forName("android.os.SystemProperties"); + Method getIntMethod = + SystemPropertiesClass.getDeclaredMethod("getInt", String.class, int.class); + getIntMethod.setAccessible(true); + return (int) getIntMethod.invoke(SystemPropertiesClass, key, def); + } catch (InvocationTargetException + | IllegalAccessException + | NoSuchMethodException + | ClassNotFoundException e) { + Log.e(TAG, "Failed to invoke SystemProperties.getInt()", e); + } + return def; + } + + public static String getString(String key, String def) throws IllegalArgumentException { + try { + Class SystemPropertiesClass = Class.forName("android.os.SystemProperties"); + Method getIntMethod = + SystemPropertiesClass.getDeclaredMethod("get", String.class, String.class); + getIntMethod.setAccessible(true); + return (String) getIntMethod.invoke(SystemPropertiesClass, key, def); + } catch (InvocationTargetException + | IllegalAccessException + | NoSuchMethodException + | ClassNotFoundException e) { + Log.e(TAG, "Failed to invoke SystemProperties.get()", e); + } + return def; + } +} |