blob: 826317b948a9f652dc0290298631933c4ccb9605 [file] [log] [blame]
/*
* 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;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.media.tv.TvContract;
import android.media.tv.TvInputInfo;
import android.media.tv.TvInputManager;
import android.media.tv.TvInputManager.TvInputCallback;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.Log;
import android.view.KeyEvent;
import com.android.tv.common.BaseApplication;
import com.android.tv.common.concurrent.NamedThreadFactory;
import com.android.tv.common.feature.CommonFeatures;
import com.android.tv.common.recording.RecordingStorageStatusManager;
import com.android.tv.common.ui.setup.animation.SetupAnimationHelper;
import com.android.tv.common.util.Clock;
import com.android.tv.common.util.Debug;
import com.android.tv.common.util.SharedPreferencesUtils;
import com.android.tv.data.ChannelDataManager;
import com.android.tv.data.PreviewDataManager;
import com.android.tv.data.ProgramDataManager;
import com.android.tv.data.epg.EpgFetcher;
import com.android.tv.data.epg.EpgFetcherImpl;
import com.android.tv.dvr.DvrDataManager;
import com.android.tv.dvr.DvrDataManagerImpl;
import com.android.tv.dvr.DvrManager;
import com.android.tv.dvr.DvrScheduleManager;
import com.android.tv.dvr.DvrStorageStatusManager;
import com.android.tv.dvr.DvrWatchedPositionManager;
import com.android.tv.dvr.recorder.RecordingScheduler;
import com.android.tv.dvr.ui.browse.DvrBrowseActivity;
import com.android.tv.recommendation.ChannelPreviewUpdater;
import com.android.tv.recommendation.RecordedProgramPreviewUpdater;
import com.android.tv.tuner.TunerInputController;
import com.android.tv.tuner.util.TunerInputInfoUtils;
import com.android.tv.util.SetupUtils;
import com.android.tv.util.TvInputManagerHelper;
import com.android.tv.util.Utils;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Live TV application.
*
* <p>This includes all the Google specific hooks.
*/
public abstract class TvApplication extends BaseApplication implements TvSingletons, Starter {
private static final String TAG = "TvApplication";
private static final boolean DEBUG = false;
/** Namespace for LiveChannels configs. LiveChannels configs are kept in piper. */
public static final String CONFIGNS_P4 = "configns:p4";
/**
* Broadcast Action: The user has updated LC to a new version that supports tuner input. {@link
* TunerInputController} will receive this intent to check the existence of tuner input when the
* new version is first launched.
*/
public static final String ACTION_APPLICATION_FIRST_LAUNCHED =
" com.android.tv.action.APPLICATION_FIRST_LAUNCHED";
private static final String PREFERENCE_IS_FIRST_LAUNCH = "is_first_launch";
private static final NamedThreadFactory THREAD_FACTORY = new NamedThreadFactory("tv-app-db");
private static final ExecutorService DB_EXECUTOR =
Executors.newSingleThreadExecutor(THREAD_FACTORY);
private String mVersionName = "";
private final MainActivityWrapper mMainActivityWrapper = new MainActivityWrapper();
private SelectInputActivity mSelectInputActivity;
private ChannelDataManager mChannelDataManager;
private volatile ProgramDataManager mProgramDataManager;
private PreviewDataManager mPreviewDataManager;
private DvrManager mDvrManager;
private DvrScheduleManager mDvrScheduleManager;
private DvrDataManager mDvrDataManager;
private DvrWatchedPositionManager mDvrWatchedPositionManager;
private RecordingScheduler mRecordingScheduler;
private RecordingStorageStatusManager mDvrStorageStatusManager;
@Nullable private InputSessionManager mInputSessionManager;
// STOP-SHIP: Remove this variable when Tuner Process is split to another application.
// When this variable is null, we don't know in which process TvApplication runs.
private Boolean mRunningInMainProcess;
private TvInputManagerHelper mTvInputManagerHelper;
private boolean mStarted;
private EpgFetcher mEpgFetcher;
private TunerInputController mTunerInputController;
@Override
public void onCreate() {
super.onCreate();
SharedPreferencesUtils.initialize(
this,
new Runnable() {
@Override
public void run() {
if (mRunningInMainProcess != null && mRunningInMainProcess) {
checkTunerServiceOnFirstLaunch();
}
}
});
try {
PackageInfo pInfo = getPackageManager().getPackageInfo(getPackageName(), 0);
mVersionName = pInfo.versionName;
} catch (PackageManager.NameNotFoundException e) {
Log.w(TAG, "Unable to find package '" + getPackageName() + "'.", e);
mVersionName = "";
}
Log.i(TAG, "Starting Live TV " + getVersionName());
// In SetupFragment, transitions are set in the constructor. Because the fragment can be
// created in Activity.onCreate() by the framework, SetupAnimationHelper should be
// initialized here before Activity.onCreate() is called.
mEpgFetcher = EpgFetcherImpl.create(this);
SetupAnimationHelper.initialize(this);
getTvInputManagerHelper();
Log.i(TAG, "Started Live TV " + mVersionName);
Debug.getTimer(Debug.TAG_START_UP_TIMER).log("finish TvApplication.onCreate");
}
/** Initializes application. It is a noop if called twice. */
@Override
public void start() {
if (mStarted) {
return;
}
mStarted = true;
mRunningInMainProcess = true;
Debug.getTimer(Debug.TAG_START_UP_TIMER).log("start TvApplication.start");
if (mRunningInMainProcess) {
getTvInputManagerHelper()
.addCallback(
new TvInputCallback() {
@Override
public void onInputAdded(String inputId) {
if (TvFeatures.TUNER.isEnabled(TvApplication.this)
&& TextUtils.equals(
inputId, getEmbeddedTunerInputId())) {
TunerInputInfoUtils.updateTunerInputInfo(
TvApplication.this);
}
handleInputCountChanged();
}
@Override
public void onInputRemoved(String inputId) {
handleInputCountChanged();
}
});
if (TvFeatures.TUNER.isEnabled(this)) {
// If the tuner input service is added before the app is started, we need to
// handle it here.
TunerInputInfoUtils.updateTunerInputInfo(TvApplication.this);
}
if (CommonFeatures.DVR.isEnabled(this)) {
mDvrScheduleManager = new DvrScheduleManager(this);
mDvrManager = new DvrManager(this);
mRecordingScheduler = RecordingScheduler.createScheduler(this);
}
mEpgFetcher.startRoutineService();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
ChannelPreviewUpdater.getInstance(this).startRoutineService();
RecordedProgramPreviewUpdater.getInstance(this)
.updatePreviewDataForRecordedPrograms();
}
}
Debug.getTimer(Debug.TAG_START_UP_TIMER).log("finish TvApplication.start");
}
private void checkTunerServiceOnFirstLaunch() {
SharedPreferences sharedPreferences =
this.getSharedPreferences(
SharedPreferencesUtils.SHARED_PREF_FEATURES, Context.MODE_PRIVATE);
boolean isFirstLaunch = sharedPreferences.getBoolean(PREFERENCE_IS_FIRST_LAUNCH, true);
if (isFirstLaunch) {
if (DEBUG) Log.d(TAG, "Congratulations, it's the first launch!");
getTunerInputController()
.onCheckingUsbTunerStatus(this, ACTION_APPLICATION_FIRST_LAUNCHED);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putBoolean(PREFERENCE_IS_FIRST_LAUNCH, false);
editor.apply();
}
}
@Override
public EpgFetcher getEpgFetcher() {
return mEpgFetcher;
}
@Override
public synchronized SetupUtils getSetupUtils() {
return SetupUtils.createForTvSingletons(this);
}
/** Returns the {@link DvrManager}. */
@Override
public DvrManager getDvrManager() {
return mDvrManager;
}
/** Returns the {@link DvrScheduleManager}. */
@Override
public DvrScheduleManager getDvrScheduleManager() {
return mDvrScheduleManager;
}
/** Returns the {@link RecordingScheduler}. */
@Override
@Nullable
public RecordingScheduler getRecordingScheduler() {
return mRecordingScheduler;
}
/** Returns the {@link DvrWatchedPositionManager}. */
@Override
public DvrWatchedPositionManager getDvrWatchedPositionManager() {
if (mDvrWatchedPositionManager == null) {
mDvrWatchedPositionManager = new DvrWatchedPositionManager(this);
}
return mDvrWatchedPositionManager;
}
@Override
@TargetApi(Build.VERSION_CODES.N)
public InputSessionManager getInputSessionManager() {
if (mInputSessionManager == null) {
mInputSessionManager = new InputSessionManager(this);
}
return mInputSessionManager;
}
/** Returns {@link ChannelDataManager}. */
@Override
public ChannelDataManager getChannelDataManager() {
if (mChannelDataManager == null) {
mChannelDataManager = new ChannelDataManager(this, getTvInputManagerHelper());
mChannelDataManager.start();
}
return mChannelDataManager;
}
@Override
public boolean isChannelDataManagerLoadFinished() {
return mChannelDataManager != null && mChannelDataManager.isDbLoadFinished();
}
/** Returns {@link ProgramDataManager}. */
@Override
public ProgramDataManager getProgramDataManager() {
if (mProgramDataManager != null) {
return mProgramDataManager;
}
Utils.runInMainThreadAndWait(
new Runnable() {
@Override
public void run() {
if (mProgramDataManager == null) {
mProgramDataManager = new ProgramDataManager(TvApplication.this);
mProgramDataManager.start();
}
}
});
return mProgramDataManager;
}
@Override
public boolean isProgramDataManagerCurrentProgramsLoadFinished() {
return mProgramDataManager != null && mProgramDataManager.isCurrentProgramsLoadFinished();
}
/** Returns {@link PreviewDataManager}. */
@TargetApi(Build.VERSION_CODES.O)
@Override
public PreviewDataManager getPreviewDataManager() {
if (mPreviewDataManager == null) {
mPreviewDataManager = new PreviewDataManager(this);
mPreviewDataManager.start();
}
return mPreviewDataManager;
}
/** Returns {@link DvrDataManager}. */
@TargetApi(Build.VERSION_CODES.N)
@Override
public DvrDataManager getDvrDataManager() {
if (mDvrDataManager == null) {
DvrDataManagerImpl dvrDataManager = new DvrDataManagerImpl(this, Clock.SYSTEM);
mDvrDataManager = dvrDataManager;
dvrDataManager.start();
}
return mDvrDataManager;
}
@Override
@TargetApi(Build.VERSION_CODES.N)
public RecordingStorageStatusManager getRecordingStorageStatusManager() {
if (mDvrStorageStatusManager == null) {
mDvrStorageStatusManager = new DvrStorageStatusManager(this);
}
return mDvrStorageStatusManager;
}
/** Returns the main activity information. */
@Override
public MainActivityWrapper getMainActivityWrapper() {
return mMainActivityWrapper;
}
/** Returns {@link TvInputManagerHelper}. */
@Override
public TvInputManagerHelper getTvInputManagerHelper() {
if (mTvInputManagerHelper == null) {
mTvInputManagerHelper = new TvInputManagerHelper(this);
mTvInputManagerHelper.start();
}
return mTvInputManagerHelper;
}
@Override
public synchronized TunerInputController getTunerInputController() {
if (mTunerInputController == null) {
mTunerInputController =
new TunerInputController(
ComponentName.unflattenFromString(getEmbeddedTunerInputId()));
}
return mTunerInputController;
}
@Override
public boolean isRunningInMainProcess() {
return mRunningInMainProcess != null && mRunningInMainProcess;
}
/**
* SelectInputActivity is set in {@link SelectInputActivity#onCreate} and cleared in {@link
* SelectInputActivity#onDestroy}.
*/
public void setSelectInputActivity(SelectInputActivity activity) {
mSelectInputActivity = activity;
}
public void handleGuideKey() {
if (!mMainActivityWrapper.isResumed()) {
startActivity(new Intent(Intent.ACTION_VIEW, TvContract.Programs.CONTENT_URI));
} else {
mMainActivityWrapper.getMainActivity().getOverlayManager().toggleProgramGuide();
}
}
/** Handles the global key KEYCODE_TV. */
public void handleTvKey() {
if (!mMainActivityWrapper.isResumed()) {
startMainActivity(null);
}
}
/** Handles the global key KEYCODE_DVR. */
public void handleDvrKey() {
startActivity(new Intent(this, DvrBrowseActivity.class));
}
/** Handles the global key KEYCODE_TV_INPUT. */
public void handleTvInputKey() {
TvInputManager tvInputManager = (TvInputManager) getSystemService(Context.TV_INPUT_SERVICE);
List<TvInputInfo> tvInputs = tvInputManager.getTvInputList();
int inputCount = 0;
boolean hasTunerInput = false;
for (TvInputInfo input : tvInputs) {
if (input.isPassthroughInput()) {
if (!input.isHidden(this)) {
++inputCount;
}
} else if (!hasTunerInput) {
hasTunerInput = true;
++inputCount;
}
}
if (inputCount < 2) {
return;
}
Activity activityToHandle =
mMainActivityWrapper.isResumed()
? mMainActivityWrapper.getMainActivity()
: mSelectInputActivity;
if (activityToHandle != null) {
// If startActivity is called, MainActivity.onPause is unnecessarily called. To
// prevent it, MainActivity.dispatchKeyEvent is directly called.
activityToHandle.dispatchKeyEvent(
new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_TV_INPUT));
activityToHandle.dispatchKeyEvent(
new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_TV_INPUT));
} else if (mMainActivityWrapper.isStarted()) {
Bundle extras = new Bundle();
extras.putString(Utils.EXTRA_KEY_ACTION, Utils.EXTRA_ACTION_SHOW_TV_INPUT);
startMainActivity(extras);
} else {
startActivity(
new Intent(this, SelectInputActivity.class)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
}
}
private void startMainActivity(Bundle extras) {
// The use of FLAG_ACTIVITY_NEW_TASK enables arbitrary applications to access the intent
// sent to the root activity. Having said that, we should be fine here since such an intent
// does not carry any important user data.
Intent intent =
new Intent(this, MainActivity.class).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if (extras != null) {
intent.putExtras(extras);
}
startActivity(intent);
}
/**
* Returns the version name of the live channels.
*
* @see PackageInfo#versionName
*/
public String getVersionName() {
return mVersionName;
}
/**
* Checks the input counts and enable/disable TvActivity. Also upda162 the input list in {@link
* SetupUtils}.
*/
@Override
public void handleInputCountChanged() {
handleInputCountChanged(false, false, false);
}
/**
* Checks the input counts and enable/disable TvActivity. Also updates the input list in {@link
* SetupUtils}.
*
* @param calledByTunerServiceChanged true if it is called when BaseTunerTvInputService is
* enabled or disabled.
* @param tunerServiceEnabled it's available only when calledByTunerServiceChanged is true.
* @param dontKillApp when TvActivity is enabled or disabled by this method, the app restarts by
* default. But, if dontKillApp is true, the app won't restart.
*/
public void handleInputCountChanged(
boolean calledByTunerServiceChanged, boolean tunerServiceEnabled, boolean dontKillApp) {
TvInputManager inputManager = (TvInputManager) getSystemService(Context.TV_INPUT_SERVICE);
boolean enable =
(calledByTunerServiceChanged && tunerServiceEnabled)
|| TvFeatures.UNHIDE.isEnabled(TvApplication.this);
if (!enable) {
List<TvInputInfo> inputs = inputManager.getTvInputList();
boolean skipTunerInputCheck = false;
// Enable the TvActivity only if there is at least one tuner type input.
if (!skipTunerInputCheck) {
for (TvInputInfo input : inputs) {
if (calledByTunerServiceChanged
&& !tunerServiceEnabled
&& getEmbeddedTunerInputId().equals(input.getId())) {
continue;
}
if (input.getType() == TvInputInfo.TYPE_TUNER) {
enable = true;
break;
}
}
}
if (DEBUG) Log.d(TAG, "Enable MainActivity: " + enable);
}
PackageManager packageManager = getPackageManager();
ComponentName name = new ComponentName(this, TvActivity.class);
int newState =
enable
? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
: PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
if (packageManager.getComponentEnabledSetting(name) != newState) {
packageManager.setComponentEnabledSetting(
name, newState, dontKillApp ? PackageManager.DONT_KILL_APP : 0);
Log.i(TAG, (enable ? "Un-hide" : "Hide") + " Live TV.");
}
getSetupUtils().onInputListUpdated(inputManager);
}
@Override
public Executor getDbExecutor() {
return DB_EXECUTOR;
}
}