aboutsummaryrefslogtreecommitdiff
path: root/tuner/src/com/android/tv/tuner/setup/BaseTunerSetupActivity.java
diff options
context:
space:
mode:
Diffstat (limited to 'tuner/src/com/android/tv/tuner/setup/BaseTunerSetupActivity.java')
-rw-r--r--tuner/src/com/android/tv/tuner/setup/BaseTunerSetupActivity.java516
1 files changed, 516 insertions, 0 deletions
diff --git a/tuner/src/com/android/tv/tuner/setup/BaseTunerSetupActivity.java b/tuner/src/com/android/tv/tuner/setup/BaseTunerSetupActivity.java
new file mode 100644
index 00000000..1be4e1c2
--- /dev/null
+++ b/tuner/src/com/android/tv/tuner/setup/BaseTunerSetupActivity.java
@@ -0,0 +1,516 @@
+/*
+ * 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.tuner.setup;
+
+import android.app.Fragment;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.os.AsyncTask;
+import android.os.Build;
+import android.os.Bundle;
+import android.support.annotation.MainThread;
+import android.support.annotation.NonNull;
+import android.support.annotation.VisibleForTesting;
+import android.support.annotation.WorkerThread;
+import android.support.v4.app.NotificationCompat;
+import android.text.TextUtils;
+import android.util.Log;
+import android.widget.Toast;
+import com.android.tv.common.BaseApplication;
+import com.android.tv.common.SoftPreconditions;
+import com.android.tv.common.experiments.Experiments;
+import com.android.tv.common.feature.CommonFeatures;
+import com.android.tv.common.ui.setup.SetupActivity;
+import com.android.tv.common.ui.setup.SetupFragment;
+import com.android.tv.common.ui.setup.SetupMultiPaneFragment;
+import com.android.tv.common.util.AutoCloseableUtils;
+import com.android.tv.common.util.PostalCodeUtils;
+import com.android.tv.tuner.R;
+import com.android.tv.tuner.TunerHal;
+import com.android.tv.tuner.TunerPreferences;
+import java.util.concurrent.Executor;
+
+/** The base setup activity class for tuner. */
+public class BaseTunerSetupActivity extends SetupActivity {
+ private static final String TAG = "BaseTunerSetupActivity";
+ private static final boolean DEBUG = false;
+
+ /** Key for passing tuner type to sub-fragments. */
+ public static final String KEY_TUNER_TYPE = "TunerSetupActivity.tunerType";
+
+ // For the notification.
+ protected static final String TUNER_SET_UP_NOTIFICATION_CHANNEL_ID = "tuner_setup_channel";
+ protected static final String NOTIFY_TAG = "TunerSetup";
+ protected static final int NOTIFY_ID = 1000;
+ protected static final String TAG_DRAWABLE = "drawable";
+ protected static final String TAG_ICON = "ic_launcher_s";
+ protected static final int PERMISSIONS_REQUEST_ACCESS_COARSE_LOCATION = 1;
+
+ protected static final int[] CHANNEL_MAP_SCAN_FILE = {
+ R.raw.ut_us_atsc_center_frequencies_8vsb,
+ R.raw.ut_us_cable_standard_center_frequencies_qam256,
+ R.raw.ut_us_all,
+ R.raw.ut_kr_atsc_center_frequencies_8vsb,
+ R.raw.ut_kr_cable_standard_center_frequencies_qam256,
+ R.raw.ut_kr_all,
+ R.raw.ut_kr_dev_cj_cable_center_frequencies_qam256,
+ R.raw.ut_euro_dvbt_all,
+ R.raw.ut_euro_dvbt_all,
+ R.raw.ut_euro_dvbt_all
+ };
+
+ protected ScanFragment mLastScanFragment;
+ protected Integer mTunerType;
+ protected boolean mNeedToShowPostalCodeFragment;
+ protected String mPreviousPostalCode;
+ protected boolean mActivityStopped;
+ protected boolean mPendingShowInitialFragment;
+
+ private TunerHalFactory mTunerHalFactory;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ if (DEBUG) {
+ Log.d(TAG, "onCreate");
+ }
+ mActivityStopped = false;
+ executeGetTunerTypeAndCountAsyncTask();
+ mTunerHalFactory =
+ new TunerHalFactory(getApplicationContext(), AsyncTask.THREAD_POOL_EXECUTOR);
+ super.onCreate(savedInstanceState);
+ // TODO: check {@link shouldShowRequestPermissionRationale}.
+ if (checkSelfPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION)
+ != PackageManager.PERMISSION_GRANTED) {
+ // No need to check the request result.
+ requestPermissions(
+ new String[] {android.Manifest.permission.ACCESS_COARSE_LOCATION},
+ PERMISSIONS_REQUEST_ACCESS_COARSE_LOCATION);
+ }
+ try {
+ // Updating postal code takes time, therefore we called it here for "warm-up".
+ mPreviousPostalCode = PostalCodeUtils.getLastPostalCode(this);
+ PostalCodeUtils.setLastPostalCode(this, null);
+ PostalCodeUtils.updatePostalCode(this);
+ } catch (Exception e) {
+ // Do nothing. If the last known postal code is null, we'll show guided fragment to
+ // prompt users to input postal code before ConnectionTypeFragment is shown.
+ Log.i(TAG, "Can't get postal code:" + e);
+ }
+ }
+
+ protected void executeGetTunerTypeAndCountAsyncTask() {}
+
+ @Override
+ protected void onStop() {
+ mActivityStopped = true;
+ super.onStop();
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ mActivityStopped = false;
+ if (mPendingShowInitialFragment) {
+ showInitialFragment();
+ mPendingShowInitialFragment = false;
+ }
+ }
+
+ @Override
+ public void onRequestPermissionsResult(
+ int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+ if (requestCode == PERMISSIONS_REQUEST_ACCESS_COARSE_LOCATION) {
+ if (grantResults.length > 0
+ && grantResults[0] == PackageManager.PERMISSION_GRANTED
+ && Experiments.CLOUD_EPG.get()) {
+ try {
+ // Updating postal code takes time, therefore we should update postal code
+ // right after the permission is granted, so that the subsequent operations,
+ // especially EPG fetcher, could get the newly updated postal code.
+ PostalCodeUtils.updatePostalCode(this);
+ } catch (Exception e) {
+ // Do nothing
+ }
+ }
+ }
+ }
+
+ @Override
+ protected Fragment onCreateInitialFragment() {
+ if (mTunerType != null) {
+ SetupFragment fragment = new WelcomeFragment();
+ Bundle args = new Bundle();
+ args.putInt(KEY_TUNER_TYPE, mTunerType);
+ fragment.setArguments(args);
+ fragment.setShortDistance(
+ SetupFragment.FRAGMENT_EXIT_TRANSITION
+ | SetupFragment.FRAGMENT_REENTER_TRANSITION);
+ return fragment;
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ protected boolean executeAction(String category, int actionId, Bundle params) {
+ switch (category) {
+ case WelcomeFragment.ACTION_CATEGORY:
+ switch (actionId) {
+ case SetupMultiPaneFragment.ACTION_DONE:
+ // If the scan was performed, then the result should be OK.
+ setResult(mLastScanFragment == null ? RESULT_CANCELED : RESULT_OK);
+ finish();
+ break;
+ default:
+ String postalCode = PostalCodeUtils.getLastPostalCode(this);
+ if (mNeedToShowPostalCodeFragment
+ || (CommonFeatures.ENABLE_CLOUD_EPG_REGION.isEnabled(
+ getApplicationContext())
+ && TextUtils.isEmpty(postalCode))) {
+ // We cannot get postal code automatically. Postal code input fragment
+ // should always be shown even if users have input some valid postal
+ // code in this activity before.
+ mNeedToShowPostalCodeFragment = true;
+ showPostalCodeFragment();
+ } else {
+ showConnectionTypeFragment();
+ }
+ break;
+ }
+ return true;
+ case PostalCodeFragment.ACTION_CATEGORY:
+ switch (actionId) {
+ case SetupMultiPaneFragment.ACTION_DONE:
+ // fall through
+ case SetupMultiPaneFragment.ACTION_SKIP:
+ showConnectionTypeFragment();
+ break;
+ default: // fall out
+ }
+ return true;
+ case ConnectionTypeFragment.ACTION_CATEGORY:
+ if (mTunerHalFactory.getOrCreate() == null) {
+ finish();
+ Toast.makeText(
+ getApplicationContext(),
+ R.string.ut_channel_scan_tuner_unavailable,
+ Toast.LENGTH_LONG)
+ .show();
+ return true;
+ }
+ mLastScanFragment = new ScanFragment();
+ Bundle args1 = new Bundle();
+ args1.putInt(
+ ScanFragment.EXTRA_FOR_CHANNEL_SCAN_FILE, CHANNEL_MAP_SCAN_FILE[actionId]);
+ args1.putInt(KEY_TUNER_TYPE, mTunerType);
+ mLastScanFragment.setArguments(args1);
+ showFragment(mLastScanFragment, true);
+ return true;
+ case ScanFragment.ACTION_CATEGORY:
+ switch (actionId) {
+ case ScanFragment.ACTION_CANCEL:
+ getFragmentManager().popBackStack();
+ return true;
+ case ScanFragment.ACTION_FINISH:
+ mTunerHalFactory.clear();
+ showScanResultFragment();
+ return true;
+ default: // fall out
+ }
+ break;
+ case ScanResultFragment.ACTION_CATEGORY:
+ switch (actionId) {
+ case SetupMultiPaneFragment.ACTION_DONE:
+ setResult(RESULT_OK);
+ finish();
+ break;
+ default:
+ // scan again
+ SetupFragment fragment = new ConnectionTypeFragment();
+ fragment.setShortDistance(
+ SetupFragment.FRAGMENT_ENTER_TRANSITION
+ | SetupFragment.FRAGMENT_RETURN_TRANSITION);
+ showFragment(fragment, true);
+ break;
+ }
+ return true;
+ default: // fall out
+ }
+ return false;
+ }
+
+ @Override
+ public void onDestroy() {
+ if (mPreviousPostalCode != null && PostalCodeUtils.getLastPostalCode(this) == null) {
+ PostalCodeUtils.setLastPostalCode(this, mPreviousPostalCode);
+ }
+ super.onDestroy();
+ }
+
+ /** Gets the currently used tuner HAL. */
+ TunerHal getTunerHal() {
+ return mTunerHalFactory.getOrCreate();
+ }
+
+ /** Generates tuner HAL. */
+ void generateTunerHal() {
+ mTunerHalFactory.generate();
+ }
+
+ /** Clears the currently used tuner HAL. */
+ protected void clearTunerHal() {
+ mTunerHalFactory.clear();
+ }
+
+ protected void showPostalCodeFragment() {
+ SetupFragment fragment = new PostalCodeFragment();
+ fragment.setShortDistance(
+ SetupFragment.FRAGMENT_ENTER_TRANSITION | SetupFragment.FRAGMENT_RETURN_TRANSITION);
+ showFragment(fragment, true);
+ }
+
+ protected void showConnectionTypeFragment() {
+ SetupFragment fragment = new ConnectionTypeFragment();
+ fragment.setShortDistance(
+ SetupFragment.FRAGMENT_ENTER_TRANSITION | SetupFragment.FRAGMENT_RETURN_TRANSITION);
+ showFragment(fragment, true);
+ }
+
+ protected void showScanResultFragment() {
+ SetupFragment scanResultFragment = new ScanResultFragment();
+ Bundle args2 = new Bundle();
+ args2.putInt(KEY_TUNER_TYPE, mTunerType);
+ scanResultFragment.setShortDistance(
+ SetupFragment.FRAGMENT_EXIT_TRANSITION | SetupFragment.FRAGMENT_REENTER_TRANSITION);
+ showFragment(scanResultFragment, true);
+ }
+
+ /**
+ * Cancels the previously shown notification.
+ *
+ * @param context a {@link Context} instance
+ */
+ public static void cancelNotification(Context context) {
+ NotificationManager notificationManager =
+ (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+ notificationManager.cancel(NOTIFY_TAG, NOTIFY_ID);
+ }
+
+ /**
+ * A callback to be invoked when the TvInputService is enabled or disabled.
+ *
+ * @param context a {@link Context} instance
+ * @param enabled {@code true} for the {@link TunerTvInputService} to be enabled; otherwise
+ * {@code false}
+ */
+ public static void onTvInputEnabled(Context context, boolean enabled, Integer tunerType) {
+ // Send a notification for tuner setup if there's no channels and the tuner TV input
+ // setup has been not done.
+ boolean channelScanDoneOnPreference = TunerPreferences.isScanDone(context);
+ int channelCountOnPreference = TunerPreferences.getScannedChannelCount(context);
+ if (enabled && !channelScanDoneOnPreference && channelCountOnPreference == 0) {
+ TunerPreferences.setShouldShowSetupActivity(context, true);
+ sendNotification(context, tunerType);
+ } else {
+ TunerPreferences.setShouldShowSetupActivity(context, false);
+ cancelNotification(context);
+ }
+ }
+
+ private static void sendNotification(Context context, Integer tunerType) {
+ SoftPreconditions.checkState(
+ tunerType != null, TAG, "tunerType is null when send notification");
+ if (tunerType == null) {
+ return;
+ }
+ Resources resources = context.getResources();
+ String contentTitle = resources.getString(R.string.ut_setup_notification_content_title);
+ int contentTextId = 0;
+ switch (tunerType) {
+ case TunerHal.TUNER_TYPE_BUILT_IN:
+ contentTextId = R.string.bt_setup_notification_content_text;
+ break;
+ case TunerHal.TUNER_TYPE_USB:
+ contentTextId = R.string.ut_setup_notification_content_text;
+ break;
+ case TunerHal.TUNER_TYPE_NETWORK:
+ contentTextId = R.string.nt_setup_notification_content_text;
+ break;
+ default: // fall out
+ }
+ String contentText = resources.getString(contentTextId);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ sendNotificationInternal(context, contentTitle, contentText);
+ } else {
+ Bitmap largeIcon =
+ BitmapFactory.decodeResource(resources, R.drawable.recommendation_antenna);
+ sendRecommendationCard(context, contentTitle, contentText, largeIcon);
+ }
+ }
+
+ private static void sendNotificationInternal(
+ Context context, String contentTitle, String contentText) {
+ NotificationManager notificationManager =
+ (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+ notificationManager.createNotificationChannel(
+ new NotificationChannel(
+ TUNER_SET_UP_NOTIFICATION_CHANNEL_ID,
+ context.getResources()
+ .getString(R.string.ut_setup_notification_channel_name),
+ NotificationManager.IMPORTANCE_HIGH));
+ Notification notification =
+ new Notification.Builder(context, TUNER_SET_UP_NOTIFICATION_CHANNEL_ID)
+ .setContentTitle(contentTitle)
+ .setContentText(contentText)
+ .setSmallIcon(
+ context.getResources()
+ .getIdentifier(
+ TAG_ICON, TAG_DRAWABLE, context.getPackageName()))
+ .setContentIntent(createPendingIntentForSetupActivity(context))
+ .setVisibility(Notification.VISIBILITY_PUBLIC)
+ .extend(new Notification.TvExtender())
+ .build();
+ notificationManager.notify(NOTIFY_TAG, NOTIFY_ID, notification);
+ }
+
+ /**
+ * Sends the recommendation card to start the tuner TV input setup activity.
+ *
+ * @param context a {@link Context} instance
+ */
+ private static void sendRecommendationCard(
+ Context context, String contentTitle, String contentText, Bitmap largeIcon) {
+ // Build and send the notification.
+ Notification notification =
+ new NotificationCompat.BigPictureStyle(
+ new NotificationCompat.Builder(context)
+ .setAutoCancel(false)
+ .setContentTitle(contentTitle)
+ .setContentText(contentText)
+ .setContentInfo(contentText)
+ .setCategory(Notification.CATEGORY_RECOMMENDATION)
+ .setLargeIcon(largeIcon)
+ .setSmallIcon(
+ context.getResources()
+ .getIdentifier(
+ TAG_ICON,
+ TAG_DRAWABLE,
+ context.getPackageName()))
+ .setContentIntent(
+ createPendingIntentForSetupActivity(context)))
+ .build();
+ NotificationManager notificationManager =
+ (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+ notificationManager.notify(NOTIFY_TAG, NOTIFY_ID, notification);
+ }
+
+ /**
+ * Returns a {@link PendingIntent} to launch the tuner TV input service.
+ *
+ * @param context a {@link Context} instance
+ */
+ private static PendingIntent createPendingIntentForSetupActivity(Context context) {
+ return PendingIntent.getActivity(
+ context,
+ 0,
+ BaseApplication.getSingletons(context).getTunerSetupIntent(context),
+ PendingIntent.FLAG_UPDATE_CURRENT);
+ }
+
+ /** A static factory for {@link TunerHal} instances * */
+ @VisibleForTesting
+ protected static class TunerHalFactory {
+ private Context mContext;
+ @VisibleForTesting TunerHal mTunerHal;
+ private TunerHalFactory.GenerateTunerHalTask mGenerateTunerHalTask;
+ private final Executor mExecutor;
+
+ TunerHalFactory(Context context) {
+ this(context, AsyncTask.SERIAL_EXECUTOR);
+ }
+
+ TunerHalFactory(Context context, Executor executor) {
+ mContext = context;
+ mExecutor = executor;
+ }
+
+ /**
+ * Returns tuner HAL currently used. If it's {@code null} and tuner HAL is not generated
+ * before, tries to generate it synchronously.
+ */
+ @WorkerThread
+ TunerHal getOrCreate() {
+ if (mGenerateTunerHalTask != null
+ && mGenerateTunerHalTask.getStatus() != AsyncTask.Status.FINISHED) {
+ try {
+ return mGenerateTunerHalTask.get();
+ } catch (Exception e) {
+ Log.e(TAG, "Cannot get Tuner HAL: " + e);
+ }
+ } else if (mGenerateTunerHalTask == null && mTunerHal == null) {
+ mTunerHal = createInstance();
+ }
+ return mTunerHal;
+ }
+
+ /** Generates tuner hal for scanning with asynchronous tasks. */
+ @MainThread
+ void generate() {
+ if (mGenerateTunerHalTask == null && mTunerHal == null) {
+ mGenerateTunerHalTask = new TunerHalFactory.GenerateTunerHalTask();
+ mGenerateTunerHalTask.executeOnExecutor(mExecutor);
+ }
+ }
+
+ /** Clears the currently used tuner hal. */
+ @MainThread
+ void clear() {
+ if (mGenerateTunerHalTask != null) {
+ mGenerateTunerHalTask.cancel(true);
+ mGenerateTunerHalTask = null;
+ }
+ if (mTunerHal != null) {
+ AutoCloseableUtils.closeQuietly(mTunerHal);
+ mTunerHal = null;
+ }
+ }
+
+ @WorkerThread
+ protected TunerHal createInstance() {
+ return TunerHal.createInstance(mContext);
+ }
+
+ class GenerateTunerHalTask extends AsyncTask<Void, Void, TunerHal> {
+ @Override
+ protected TunerHal doInBackground(Void... args) {
+ return createInstance();
+ }
+
+ @Override
+ protected void onPostExecute(TunerHal tunerHal) {
+ mTunerHal = tunerHal;
+ }
+ }
+ }
+}