aboutsummaryrefslogtreecommitdiff
path: root/common/src/com/android/tv/common/util
diff options
context:
space:
mode:
Diffstat (limited to 'common/src/com/android/tv/common/util')
-rw-r--r--common/src/com/android/tv/common/util/AutoCloseableUtils.java34
-rw-r--r--common/src/com/android/tv/common/util/Clock.java78
-rw-r--r--common/src/com/android/tv/common/util/CollectionUtils.java95
-rw-r--r--common/src/com/android/tv/common/util/CommonUtils.java154
-rw-r--r--common/src/com/android/tv/common/util/ContentUriUtils.java45
-rw-r--r--common/src/com/android/tv/common/util/Debug.java50
-rw-r--r--common/src/com/android/tv/common/util/DurationTimer.java80
-rw-r--r--common/src/com/android/tv/common/util/LocationUtils.java143
-rw-r--r--common/src/com/android/tv/common/util/NetworkTrafficTags.java63
-rw-r--r--common/src/com/android/tv/common/util/PermissionUtils.java68
-rw-r--r--common/src/com/android/tv/common/util/PostalCodeUtils.java137
-rw-r--r--common/src/com/android/tv/common/util/SharedPreferencesUtils.java84
-rw-r--r--common/src/com/android/tv/common/util/StringUtils.java34
-rw-r--r--common/src/com/android/tv/common/util/SystemProperties.java53
-rw-r--r--common/src/com/android/tv/common/util/SystemPropertiesProxy.java79
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;
+ }
+}