aboutsummaryrefslogtreecommitdiff
path: root/src/com/android/tv/tuner/setup/TunerSetupActivity.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/tv/tuner/setup/TunerSetupActivity.java')
-rw-r--r--src/com/android/tv/tuner/setup/TunerSetupActivity.java375
1 files changed, 318 insertions, 57 deletions
diff --git a/src/com/android/tv/tuner/setup/TunerSetupActivity.java b/src/com/android/tv/tuner/setup/TunerSetupActivity.java
index 78121bc5..e9f3baa7 100644
--- a/src/com/android/tv/tuner/setup/TunerSetupActivity.java
+++ b/src/com/android/tv/tuner/setup/TunerSetupActivity.java
@@ -19,6 +19,7 @@ package com.android.tv.tuner.setup;
import android.app.Fragment;
import android.app.FragmentManager;
import android.app.Notification;
+import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.ComponentName;
@@ -29,49 +30,98 @@ import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.tv.TvContract;
+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.view.KeyEvent;
import android.widget.Toast;
+import com.android.tv.Features;
import com.android.tv.TvApplication;
+import com.android.tv.common.AutoCloseableUtils;
+import com.android.tv.common.SoftPreconditions;
import com.android.tv.common.TvCommonConstants;
import com.android.tv.common.TvCommonUtils;
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.experiments.Experiments;
import com.android.tv.tuner.R;
import com.android.tv.tuner.TunerHal;
import com.android.tv.tuner.TunerPreferences;
import com.android.tv.tuner.tvinput.TunerTvInputService;
-import com.android.tv.tuner.util.TunerInputInfoUtils;
+import com.android.tv.tuner.util.PostalCodeUtils;
+
+import java.util.concurrent.Executor;
/**
* An activity that serves tuner setup process.
*/
public class TunerSetupActivity extends SetupActivity {
- private final String TAG = "TunerSetupActivity";
- // For the recommendation card
+ private static final String TAG = "TunerSetupActivity";
+ 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.
private static final String TV_ACTIVITY_CLASS_NAME = "com.android.tv.TvActivity";
+ private static final String TUNER_SET_UP_NOTIFICATION_CHANNEL_ID = "tuner_setup_channel";
private static final String NOTIFY_TAG = "TunerSetup";
private static final int NOTIFY_ID = 1000;
private static final String TAG_DRAWABLE = "drawable";
private static final String TAG_ICON = "ic_launcher_s";
+ private static final int PERMISSIONS_REQUEST_ACCESS_COARSE_LOCATION = 1;
private 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_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
+ };
private ScanFragment mLastScanFragment;
+ private Integer mTunerType;
+ private TunerHalFactory mTunerHalFactory;
+ private boolean mNeedToShowPostalCodeFragment;
+ private String mPreviousPostalCode;
@Override
protected void onCreate(Bundle savedInstanceState) {
+ if (DEBUG) Log.d(TAG, "onCreate");
+ new AsyncTask<Void, Void, Integer>() {
+ @Override
+ protected Integer doInBackground(Void... arg0) {
+ return TunerHal.getTunerTypeAndCount(TunerSetupActivity.this).first;
+ }
+
+ @Override
+ protected void onPostExecute(Integer result) {
+ if (!TunerSetupActivity.this.isDestroyed()) {
+ mTunerType = result;
+ if (result == null) {
+ finish();
+ } else {
+ showInitialFragment();
+ }
+ }
+ }
+ }.execute();
TvApplication.setCurrentRunningProcess(this, false);
super.onCreate(savedInstanceState);
// TODO: check {@link shouldShowRequestPermissionRationale}.
@@ -79,16 +129,52 @@ public class TunerSetupActivity extends SetupActivity {
!= PackageManager.PERMISSION_GRANTED) {
// No need to check the request result.
requestPermissions(new String[] {android.Manifest.permission.ACCESS_COARSE_LOCATION},
- 0);
+ PERMISSIONS_REQUEST_ACCESS_COARSE_LOCATION);
+ }
+ mTunerHalFactory = new TunerHalFactory(getApplicationContext());
+ 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);
+ }
+ }
+
+ @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() {
- SetupFragment fragment = new WelcomeFragment();
- fragment.setShortDistance(SetupFragment.FRAGMENT_EXIT_TRANSITION
- | SetupFragment.FRAGMENT_REENTER_TRANSITION);
- return fragment;
+ 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
@@ -101,34 +187,42 @@ public class TunerSetupActivity extends SetupActivity {
setResult(mLastScanFragment == null ? RESULT_CANCELED : RESULT_OK);
finish();
break;
- default: {
- SetupFragment fragment = new ConnectionTypeFragment();
- fragment.setShortDistance(SetupFragment.FRAGMENT_ENTER_TRANSITION
- | SetupFragment.FRAGMENT_RETURN_TRANSITION);
- showFragment(fragment, true);
+ default:
+ if (mNeedToShowPostalCodeFragment
+ || Features.ENABLE_CLOUD_EPG_REGION.isEnabled(
+ getApplicationContext())
+ && TextUtils.isEmpty(
+ PostalCodeUtils.getLastPostalCode(this))) {
+ // 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:
+ if (actionId == SetupMultiPaneFragment.ACTION_DONE
+ || actionId == SetupMultiPaneFragment.ACTION_SKIP) {
+ showConnectionTypeFragment();
}
return true;
case ConnectionTypeFragment.ACTION_CATEGORY:
- TunerHal hal = TunerHal.createInstance(getApplicationContext());
- if (hal == null) {
+ if (mTunerHalFactory.getOrCreate() == null) {
finish();
Toast.makeText(getApplicationContext(),
R.string.ut_channel_scan_tuner_unavailable,Toast.LENGTH_LONG).show();
return true;
}
- try {
- hal.close();
- } catch (Exception e) {
- Log.e(TAG, "Tuner hal close failed", e);
- return true;
- }
mLastScanFragment = new ScanFragment();
- Bundle args = new Bundle();
- args.putInt(ScanFragment.EXTRA_FOR_CHANNEL_SCAN_FILE,
+ Bundle args1 = new Bundle();
+ args1.putInt(ScanFragment.EXTRA_FOR_CHANNEL_SCAN_FILE,
CHANNEL_MAP_SCAN_FILE[actionId]);
- mLastScanFragment.setArguments(args);
+ args1.putInt(KEY_TUNER_TYPE, mTunerType);
+ mLastScanFragment.setArguments(args1);
showFragment(mLastScanFragment, true);
return true;
case ScanFragment.ACTION_CATEGORY:
@@ -137,7 +231,11 @@ public class TunerSetupActivity extends SetupActivity {
getFragmentManager().popBackStack();
return true;
case ScanFragment.ACTION_FINISH:
+ mTunerHalFactory.clear();
SetupFragment fragment = new ScanResultFragment();
+ Bundle args2 = new Bundle();
+ args2.putInt(KEY_TUNER_TYPE, mTunerType);
+ fragment.setArguments(args2);
fragment.setShortDistance(SetupFragment.FRAGMENT_EXIT_TRANSITION
| SetupFragment.FRAGMENT_REENTER_TRANSITION);
showFragment(fragment, true);
@@ -183,6 +281,14 @@ public class TunerSetupActivity extends SetupActivity {
return super.onKeyUp(keyCode, event);
}
+ @Override
+ public void onDestroy() {
+ if (mPreviousPostalCode != null && PostalCodeUtils.getLastPostalCode(this) == null) {
+ PostalCodeUtils.setLastPostalCode(this, mPreviousPostalCode);
+ }
+ super.onDestroy();
+ }
+
/**
* A callback to be invoked when the TvInputService is enabled or disabled.
*
@@ -190,17 +296,17 @@ public class TunerSetupActivity extends SetupActivity {
* @param enabled {@code true} for the {@link TunerTvInputService} to be enabled;
* otherwise {@code false}
*/
- public static void onTvInputEnabled(Context context, boolean enabled) {
- // Send a recommendation card for tuner setup if there's no channels and the tuner TV input
+ 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);
- sendRecommendationCard(context);
+ sendNotification(context, tunerType);
} else {
TunerPreferences.setShouldShowSetupActivity(context, false);
- cancelRecommendationCard(context);
+ cancelNotification(context);
}
}
@@ -213,7 +319,7 @@ public class TunerSetupActivity extends SetupActivity {
String inputId = TvContract.buildInputId(new ComponentName(context.getPackageName(),
TunerTvInputService.class.getName()));
- // Make an intent to launch the setup activity of USB tuner TV input.
+ // Make an intent to launch the setup activity of TV tuner input.
Intent intent = TvCommonUtils.createSetupIntent(
new Intent(context, TunerSetupActivity.class), inputId);
intent.putExtra(TvCommonConstants.EXTRA_INPUT_ID, inputId);
@@ -224,6 +330,27 @@ public class TunerSetupActivity extends SetupActivity {
}
/**
+ * Gets the currently used tuner HAL.
+ */
+ TunerHal getTunerHal() {
+ return mTunerHalFactory.getOrCreate();
+ }
+
+ /**
+ * Generates tuner HAL.
+ */
+ void generateTunerHal() {
+ mTunerHalFactory.generate();
+ }
+
+ /**
+ * Clears the currently used tuner HAL.
+ */
+ void clearTunerHal() {
+ mTunerHalFactory.clear();
+ }
+
+ /**
* Returns a {@link PendingIntent} to launch the tuner TV input service.
*
* @param context a {@link Context} instance
@@ -233,34 +360,53 @@ public class TunerSetupActivity extends SetupActivity {
PendingIntent.FLAG_UPDATE_CURRENT);
}
+ 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;
+ }
+ 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);
+ }
+ }
+
/**
* Sends the recommendation card to start the tuner TV input setup activity.
*
* @param context a {@link Context} instance
*/
- private static void sendRecommendationCard(Context context) {
- Resources resources = context.getResources();
- String focusedTitle = resources.getString(
- R.string.ut_setup_recommendation_card_focused_title);
- String title;
- if (TunerInputInfoUtils.isBuiltInTuner(context)) {
- title = resources.getString(R.string.bt_setup_recommendation_card_title);
- } else {
- title = resources.getString(R.string.ut_setup_recommendation_card_title);
- }
- Bitmap largeIcon = BitmapFactory.decodeResource(resources,
- R.drawable.recommendation_antenna);
-
+ 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(focusedTitle)
- .setContentText(title)
- .setContentInfo(title)
+ .setContentTitle(contentTitle)
+ .setContentText(contentText)
+ .setContentInfo(contentText)
.setCategory(Notification.CATEGORY_RECOMMENDATION)
.setLargeIcon(largeIcon)
- .setSmallIcon(resources.getIdentifier(
+ .setSmallIcon(context.getResources().getIdentifier(
TAG_ICON, TAG_DRAWABLE, context.getPackageName()))
.setContentIntent(createPendingIntentForSetupActivity(context)))
.build();
@@ -269,14 +415,129 @@ public class TunerSetupActivity extends SetupActivity {
notificationManager.notify(NOTIFY_TAG, NOTIFY_ID, notification);
}
+ 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);
+ }
+
+ private void showPostalCodeFragment() {
+ SetupFragment fragment = new PostalCodeFragment();
+ fragment.setShortDistance(SetupFragment.FRAGMENT_ENTER_TRANSITION
+ | SetupFragment.FRAGMENT_RETURN_TRANSITION);
+ showFragment(fragment, true);
+ }
+
+ private void showConnectionTypeFragment() {
+ SetupFragment fragment = new ConnectionTypeFragment();
+ fragment.setShortDistance(SetupFragment.FRAGMENT_ENTER_TRANSITION
+ | SetupFragment.FRAGMENT_RETURN_TRANSITION);
+ showFragment(fragment, true);
+ }
+
/**
- * Cancels the previously shown recommendation card.
+ * Cancels the previously shown notification.
*
* @param context a {@link Context} instance
*/
- public static void cancelRecommendationCard(Context context) {
+ public static void cancelNotification(Context context) {
NotificationManager notificationManager = (NotificationManager) context
.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.cancel(NOTIFY_TAG, NOTIFY_ID);
}
-}
+
+ @VisibleForTesting
+ static class TunerHalFactory {
+ private Context mContext;
+ @VisibleForTesting
+ TunerHal mTunerHal;
+ private 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 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;
+ }
+ }
+ }
+} \ No newline at end of file