blob: f9ef29c4fee365a0faceca66a67f526fb069bd6a [file] [log] [blame]
/*
* Copyright (C) 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.tuner.sample.dvb.setup;
import android.app.FragmentManager;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.media.tv.TvInputInfo;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
import android.view.KeyEvent;
import com.android.tv.common.feature.CommonFeatures;
import com.android.tv.common.singletons.HasSingletons;
import com.android.tv.common.ui.setup.SetupFragment;
import com.android.tv.common.ui.setup.SetupMultiPaneFragment;
import com.android.tv.common.util.PostalCodeUtils;
import com.android.tv.tuner.sample.dvb.R;
import com.android.tv.tuner.setup.BaseTunerSetupActivity;
import com.android.tv.tuner.setup.ConnectionTypeFragment;
import com.android.tv.tuner.setup.LineupFragment;
import com.android.tv.tuner.setup.LocationFragment;
import com.android.tv.tuner.setup.PostalCodeFragment;
import com.android.tv.tuner.setup.ScanFragment;
import com.android.tv.tuner.setup.ScanResultFragment;
import com.android.tv.tuner.setup.WelcomeFragment;
import com.android.tv.tuner.singletons.TunerSingletons;
import com.google.android.tv.partner.support.EpgContract;
import com.google.android.tv.partner.support.EpgInput;
import com.google.android.tv.partner.support.EpgInputs;
import com.google.android.tv.partner.support.Lineup;
import com.google.android.tv.partner.support.Lineups;
import com.google.android.tv.partner.support.TunerSetupUtils;
import dagger.android.ContributesAndroidInjector;
import java.util.ArrayList;
import java.util.List;
/** An activity that serves Live TV tuner setup process. */
public class SampleDvbTunerSetupActivity extends BaseTunerSetupActivity {
private static final String TAG = "SampleDvbTunerSetupActivity";
private static final boolean DEBUG = false;
private static final int FETCH_LINEUP_TIMEOUT_MS = 10000; // 10 seconds
private static final int FETCH_LINEUP_RETRY_TIMEOUT_MS = 20000; // 20 seconds
private static final String OTAD_PREFIX = "OTAD";
private static final String STRING_BROADCAST_DIGITAL = "Broadcast Digital";
private LineupFragment currentLineupFragment;
private List<String> channelNumbers;
private List<Lineup> lineups;
private Lineup selectedLineup;
private List<Pair<Lineup, Integer>> lineupMatchCountPair;
private FetchLineupTask fetchLineupTask;
private EpgInput epgInput;
private String postalCode;
private final Handler handler = new Handler();
private final Runnable cancelFetchLineupTaskRunnable = this::cancelFetchLineup;
private String embeddedInputId;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (DEBUG) {
Log.d(TAG, "onCreate");
}
embeddedInputId =
HasSingletons.get(TunerSingletons.class, getApplicationContext())
.getEmbeddedTunerInputId();
new QueryEpgInputTask(embeddedInputId).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
@Override
protected void executeGetTunerTypeAndCountAsyncTask() {
new AsyncTask<Void, Void, Integer>() {
@Override
protected Integer doInBackground(Void... arg0) {
return mTunerFactory.getTunerTypeAndCount(SampleDvbTunerSetupActivity.this).first;
}
@Override
protected void onPostExecute(Integer result) {
if (!SampleDvbTunerSetupActivity.this.isDestroyed()) {
mTunerType = result;
if (result == null) {
finish();
} else if (!mActivityStopped) {
showInitialFragment();
} else {
mPendingShowInitialFragment = true;
}
}
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
@Override
protected boolean executeAction(String category, int actionId, Bundle params) {
switch (category) {
case WelcomeFragment.ACTION_CATEGORY:
switch (actionId) {
case SetupMultiPaneFragment.ACTION_DONE:
super.executeAction(category, actionId, params);
break;
default:
String postalCode = PostalCodeUtils.getLastPostalCode(this);
boolean needLocation =
CommonFeatures.ENABLE_CLOUD_EPG_REGION.isEnabled(
getApplicationContext())
&& TextUtils.isEmpty(postalCode);
if (needLocation
&& checkSelfPermission(
android.Manifest.permission.ACCESS_COARSE_LOCATION)
!= PackageManager.PERMISSION_GRANTED) {
showLocationFragment();
} else if (mNeedToShowPostalCodeFragment || needLocation) {
// 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 {
lineups = null;
selectedLineup = null;
this.postalCode = postalCode;
restartFetchLineupTask();
showConnectionTypeFragment();
}
break;
}
return true;
case LocationFragment.ACTION_CATEGORY:
switch (actionId) {
case LocationFragment.ACTION_ALLOW_PERMISSION:
String postalCode =
params == null
? null
: params.getString(LocationFragment.KEY_POSTAL_CODE);
if (postalCode == null) {
showPostalCodeFragment();
} else {
this.postalCode = postalCode;
restartFetchLineupTask();
showConnectionTypeFragment();
}
break;
default:
cancelFetchLineup();
showConnectionTypeFragment();
}
return true;
case PostalCodeFragment.ACTION_CATEGORY:
lineups = null;
selectedLineup = null;
switch (actionId) {
case SetupMultiPaneFragment.ACTION_DONE:
String postalCode = params.getString(PostalCodeFragment.KEY_POSTAL_CODE);
if (postalCode != null) {
this.postalCode = postalCode;
restartFetchLineupTask();
}
// fall through
case SetupMultiPaneFragment.ACTION_SKIP:
showConnectionTypeFragment();
break;
default: // fall out
}
return true;
case ConnectionTypeFragment.ACTION_CATEGORY:
channelNumbers = null;
lineupMatchCountPair = null;
return super.executeAction(category, actionId, params);
case ScanFragment.ACTION_CATEGORY:
switch (actionId) {
case ScanFragment.ACTION_CANCEL:
getFragmentManager().popBackStack();
return true;
case ScanFragment.ACTION_FINISH:
clearTunerHal();
channelNumbers =
params.getStringArrayList(ScanFragment.KEY_CHANNEL_NUMBERS);
selectedLineup = null;
if (CommonFeatures.ENABLE_CLOUD_EPG_REGION.isEnabled(
getApplicationContext())
&& channelNumbers != null
&& !channelNumbers.isEmpty()
&& !TextUtils.isEmpty(this.postalCode)) {
showLineupFragment();
} else {
showScanResultFragment();
}
return true;
default: // fall out
}
break;
case LineupFragment.ACTION_CATEGORY:
switch (actionId) {
case LineupFragment.ACTION_SKIP:
selectedLineup = null;
currentLineupFragment = null;
showScanResultFragment();
break;
case LineupFragment.ACTION_ID_RETRY:
currentLineupFragment.onRetry();
restartFetchLineupTask();
handler.postDelayed(
cancelFetchLineupTaskRunnable, FETCH_LINEUP_RETRY_TIMEOUT_MS);
break;
default:
if (actionId >= 0 && actionId < lineupMatchCountPair.size()) {
if (DEBUG) {
if (selectedLineup != null) {
Log.d(
TAG,
"Lineup " + selectedLineup.getName() + " is selected.");
}
}
selectedLineup = lineupMatchCountPair.get(actionId).first;
}
currentLineupFragment = null;
showScanResultFragment();
break;
}
return true;
case ScanResultFragment.ACTION_CATEGORY:
switch (actionId) {
case SetupMultiPaneFragment.ACTION_DONE:
new InsertOrModifyEpgInputTask(selectedLineup, embeddedInputId)
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
break;
default:
// scan again
if (lineups == null || lineups.isEmpty()) {
lineups = null;
restartFetchLineupTask();
}
super.executeAction(category, actionId, params);
break;
}
return true;
default: // fall out
}
return false;
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
FragmentManager manager = getFragmentManager();
int count = manager.getBackStackEntryCount();
if (count > 0) {
String lastTag = manager.getBackStackEntryAt(count - 1).getName();
if (LineupFragment.class.getCanonicalName().equals(lastTag) && count >= 2) {
// Pops fragment including ScanFragment.
manager.popBackStack(
manager.getBackStackEntryAt(count - 2).getName(),
FragmentManager.POP_BACK_STACK_INCLUSIVE);
return true;
}
if (ScanResultFragment.class.getCanonicalName().equals(lastTag) && count >= 2) {
String secondLastTag = manager.getBackStackEntryAt(count - 2).getName();
if (ScanFragment.class.getCanonicalName().equals(secondLastTag)) {
// Pops fragment including ScanFragment.
manager.popBackStack(
secondLastTag, FragmentManager.POP_BACK_STACK_INCLUSIVE);
return true;
}
if (LineupFragment.class.getCanonicalName().equals(secondLastTag)) {
currentLineupFragment =
(LineupFragment) manager.findFragmentByTag(secondLastTag);
if (lineups == null || lineups.isEmpty()) {
lineups = null;
restartFetchLineupTask();
}
}
} else if (ScanFragment.class.getCanonicalName().equals(lastTag)) {
mLastScanFragment.finishScan(true);
return true;
}
}
}
return super.onKeyUp(keyCode, event);
}
private void showLineupFragment() {
if (lineupMatchCountPair == null && lineups != null) {
lineupMatchCountPair = TunerSetupUtils.lineupChannelMatchCount(lineups, channelNumbers);
}
currentLineupFragment = new LineupFragment();
currentLineupFragment.setArguments(getArgsForLineupFragment());
currentLineupFragment.setShortDistance(
SetupFragment.FRAGMENT_ENTER_TRANSITION | SetupFragment.FRAGMENT_RETURN_TRANSITION);
handler.removeCallbacksAndMessages(null);
showFragment(currentLineupFragment, true);
handler.postDelayed(cancelFetchLineupTaskRunnable, FETCH_LINEUP_TIMEOUT_MS);
}
private Bundle getArgsForLineupFragment() {
Bundle args = new Bundle();
if (lineupMatchCountPair == null) {
return args;
}
ArrayList<String> lineupNames = new ArrayList<>(lineupMatchCountPair.size());
ArrayList<Integer> matchNumbers = new ArrayList<>(lineupMatchCountPair.size());
int defaultLineupIndex = 0;
for (Pair<Lineup, Integer> pair : lineupMatchCountPair) {
Lineup lineup = pair.first;
String name;
if (!TextUtils.isEmpty(lineup.getName())) {
name = lineup.getName();
} else {
name = lineup.getId();
}
if (name.equals(OTAD_PREFIX + postalCode) || name.equals(STRING_BROADCAST_DIGITAL)) {
// rename OTA / antenna lineups
name = getString(R.string.ut_lineup_name_antenna);
}
lineupNames.add(name);
matchNumbers.add(pair.second);
if (epgInput != null && TextUtils.equals(lineup.getId(), epgInput.getLineupId())) {
// The last index is the current one.
defaultLineupIndex = lineupNames.size() - 1;
}
}
args.putStringArrayList(LineupFragment.KEY_LINEUP_NAMES, lineupNames);
args.putIntegerArrayList(LineupFragment.KEY_MATCH_NUMBERS, matchNumbers);
args.putInt(LineupFragment.KEY_DEFAULT_LINEUP, defaultLineupIndex);
return args;
}
private void cancelFetchLineup() {
if (fetchLineupTask == null) {
return;
}
AsyncTask.Status status = fetchLineupTask.getStatus();
if (status == AsyncTask.Status.RUNNING || status == AsyncTask.Status.PENDING) {
fetchLineupTask.cancel(true);
fetchLineupTask = null;
if (currentLineupFragment != null) {
currentLineupFragment.onLineupNotFound();
}
}
}
private void restartFetchLineupTask() {
if (!CommonFeatures.ENABLE_CLOUD_EPG_REGION.isEnabled(getApplicationContext())
|| TextUtils.isEmpty(postalCode)
|| checkSelfPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION)
!= PackageManager.PERMISSION_GRANTED) {
return;
}
if (fetchLineupTask != null) {
fetchLineupTask.cancel(true);
}
handler.removeCallbacksAndMessages(null);
fetchLineupTask = new FetchLineupTask(getContentResolver(), postalCode);
fetchLineupTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
private class FetchLineupTask extends AsyncTask<Void, Void, List<Lineup>> {
private final ContentResolver contentResolver;
private final String postalCode;
FetchLineupTask(ContentResolver contentResolver, String postalCode) {
this.contentResolver = contentResolver;
this.postalCode = postalCode;
}
@Override
protected List<Lineup> doInBackground(Void... args) {
if (contentResolver == null || TextUtils.isEmpty(postalCode)) {
return new ArrayList<>();
}
return new ArrayList<>(Lineups.query(contentResolver, postalCode));
}
@Override
protected void onPostExecute(List<Lineup> lineups) {
if (DEBUG) {
if (lineups != null) {
Log.d(TAG, "FetchLineupTask fetched " + lineups.size() + " lineups");
} else {
Log.d(TAG, "FetchLineupTask returned null");
}
}
SampleDvbTunerSetupActivity.this.lineups = lineups;
if (currentLineupFragment != null) {
if (lineups == null || lineups.isEmpty()) {
currentLineupFragment.onLineupNotFound();
} else {
lineupMatchCountPair =
TunerSetupUtils.lineupChannelMatchCount(
SampleDvbTunerSetupActivity.this.lineups, channelNumbers);
currentLineupFragment.onLineupFound(getArgsForLineupFragment());
}
}
}
}
private class InsertOrModifyEpgInputTask extends AsyncTask<Void, Void, Void> {
private final Lineup lineup;
private final String inputId;
InsertOrModifyEpgInputTask(@Nullable Lineup lineup, String inputId) {
this.lineup = lineup;
this.inputId = inputId;
}
@Override
protected Void doInBackground(Void... args) {
if (lineup == null
|| (SampleDvbTunerSetupActivity.this.epgInput != null
&& TextUtils.equals(
lineup.getId(),
SampleDvbTunerSetupActivity.this.epgInput.getLineupId()))) {
return null;
}
ContentValues values = new ContentValues();
values.put(EpgContract.EpgInputs.COLUMN_INPUT_ID, inputId);
values.put(EpgContract.EpgInputs.COLUMN_LINEUP_ID, lineup.getId());
ContentResolver contentResolver = getContentResolver();
if (SampleDvbTunerSetupActivity.this.epgInput != null) {
values.put(
EpgContract.EpgInputs.COLUMN_ID,
SampleDvbTunerSetupActivity.this.epgInput.getId());
EpgInputs.update(contentResolver, EpgInput.createEpgChannel(values));
return null;
}
EpgInput epgInput = EpgInputs.queryEpgInput(contentResolver, inputId);
if (epgInput == null) {
contentResolver.insert(EpgContract.EpgInputs.CONTENT_URI, values);
} else {
values.put(EpgContract.EpgInputs.COLUMN_ID, epgInput.getId());
EpgInputs.update(contentResolver, EpgInput.createEpgChannel(values));
}
return null;
}
@Override
protected void onPostExecute(Void result) {
Intent data = new Intent();
data.putExtra(TvInputInfo.EXTRA_INPUT_ID, inputId);
data.putExtra(EpgContract.EXTRA_USE_CLOUD_EPG, true);
setResult(RESULT_OK, data);
finish();
}
}
/**
* Exports {@link SampleDvbTunerSetupActivity} for Dagger codegen to create the appropriate
* injector.
*/
@dagger.Module
public abstract static class Module {
@ContributesAndroidInjector
abstract SampleDvbTunerSetupActivity contributeSampleDvbTunerSetupActivityInjector();
}
private class QueryEpgInputTask extends AsyncTask<Void, Void, EpgInput> {
private final String inputId;
QueryEpgInputTask(String inputId) {
this.inputId = inputId;
}
@Override
protected EpgInput doInBackground(Void... args) {
ContentResolver contentResolver = getContentResolver();
return EpgInputs.queryEpgInput(contentResolver, inputId);
}
@Override
protected void onPostExecute(EpgInput result) {
epgInput = result;
}
}
}