/*
* 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.Programs;
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 android.widget.Toast;
import com.android.tv.common.BaseApplication;
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.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.EpgReader;
import com.android.tv.dvr.DvrDataManager;
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.features.TvFeatures;
import com.android.tv.perf.PerformanceMonitor;
import com.android.tv.perf.StartupMeasure;
import com.android.tv.perf.StartupMeasureFactory;
import com.android.tv.recommendation.ChannelPreviewUpdater;
import com.android.tv.recommendation.RecordedProgramPreviewUpdater;
import com.android.tv.tunerinputcontroller.BuiltInTunerManager;
import com.android.tv.tunerinputcontroller.TunerInputController;
import com.android.tv.util.AsyncDbTask.DbExecutor;
import com.android.tv.util.SetupUtils;
import com.android.tv.util.TvInputManagerHelper;
import com.android.tv.util.Utils;
import com.google.common.base.Optional;
import dagger.Lazy;
import com.android.tv.common.flags.CloudEpgFlags;
import com.android.tv.common.flags.LegacyFlags;
import java.util.List;
import java.util.concurrent.Executor;
import javax.inject.Inject;
/**
* TV application.
*
*
This includes all the Google specific hooks.
*/
public abstract class TvApplication extends BaseApplication implements TvSingletons, Starter {
protected static final StartupMeasure STARTUP_MEASURE = StartupMeasureFactory.create();
private static final String TAG = "TvApplication";
private static final boolean DEBUG = false;
/**
* 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 String mVersionName = "";
private final MainActivityWrapper mMainActivityWrapper = new MainActivityWrapper();
private SelectInputActivity mSelectInputActivity;
@Inject Lazy mChannelDataManager;
private volatile ProgramDataManager mProgramDataManager;
private PreviewDataManager mPreviewDataManager;
@Inject Lazy mDvrManager;
private DvrScheduleManager mDvrScheduleManager;
@Inject Lazy 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;
@Inject Lazy mLazyTvInputManagerHelper;
private boolean mStarted;
@Inject EpgFetcher mEpgFetcher;
@Inject Optional mOptionalBuiltInTunerManager;
@Inject SetupUtils mSetupUtils;
@Inject @DbExecutor Executor mDbExecutor;
@Inject Lazy mEpgReader;
@Inject BuildType mBuildType;
@Inject CloudEpgFlags mCloudEpgFlags;
@Inject LegacyFlags mLegacyFlags;
@Inject PerformanceMonitor mPerformanceMonitor;
@Override
public void onCreate() {
if (getSystemService(TvInputManager.class) == null) {
String msg = "Not an Android TV device.";
Toast.makeText(this, msg, Toast.LENGTH_LONG);
Log.wtf(TAG, msg);
throw new IllegalStateException(msg);
}
super.onCreate();
mPerformanceMonitor.startMemoryMonitor();
mPerformanceMonitor.startCrashMonitor();
SharedPreferencesUtils.initialize(
this,
() -> {
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 TV app " + 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.
SetupAnimationHelper.initialize(this);
getTvInputManagerHelper();
Log.i(TAG, "Started TV app " + 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 (mOptionalBuiltInTunerManager.isPresent()) {
BuiltInTunerManager builtInTunerManager =
mOptionalBuiltInTunerManager.get();
if (TextUtils.equals(
inputId,
builtInTunerManager.getEmbeddedTunerInputId())) {
builtInTunerManager
.getTunerInputController()
.updateTunerInputInfo(TvApplication.this);
}
handleInputCountChanged();
}
}
@Override
public void onInputRemoved(String inputId) {
handleInputCountChanged();
}
});
if (mOptionalBuiltInTunerManager.isPresent()) {
// If the tuner input service is added before the app is started, we need to
// handle it here.
mOptionalBuiltInTunerManager
.get()
.getTunerInputController()
.updateTunerInputInfo(TvApplication.this);
}
if (CommonFeatures.DVR.isEnabled(this)) {
mDvrScheduleManager = new DvrScheduleManager(this);
mRecordingScheduler = RecordingScheduler.createScheduler(this);
}
mEpgFetcher.startRoutineService();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
ChannelPreviewUpdater.getInstance(this).startRoutineService();
if (CommonFeatures.DVR.isEnabled(this)) {
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!");
if (mOptionalBuiltInTunerManager.isPresent()) {
mOptionalBuiltInTunerManager
.get()
.getTunerInputController()
.onCheckingUsbTunerStatus(this, ACTION_APPLICATION_FIRST_LAUNCHED);
}
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putBoolean(PREFERENCE_IS_FIRST_LAUNCH, false);
editor.apply();
}
}
@Override
public synchronized SetupUtils getSetupUtils() {
return mSetupUtils;
}
/** Returns the {@link DvrManager}. */
@Override
@Nullable
public DvrManager getDvrManager() {
return (CommonFeatures.DVR.isEnabled(this)) ? mDvrManager.get() : null;
}
/** 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() {
return mChannelDataManager.get();
}
/** Returns {@link ProgramDataManager}. */
@Override
public ProgramDataManager getProgramDataManager() {
if (mProgramDataManager != null) {
return mProgramDataManager;
}
Utils.runInMainThreadAndWait(
() -> {
if (mProgramDataManager == null) {
mProgramDataManager = new ProgramDataManager(TvApplication.this);
mProgramDataManager.start();
}
});
return mProgramDataManager;
}
/** 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() {
return mDvrDataManager.get();
}
@Override
@TargetApi(Build.VERSION_CODES.N)
public RecordingStorageStatusManager getRecordingStorageStatusManager() {
if (mDvrStorageStatusManager == null) {
mDvrStorageStatusManager = new DvrStorageStatusManager(this);
}
return mDvrStorageStatusManager;
}
@Override
public PerformanceMonitor getPerformanceMonitor() {
return mPerformanceMonitor;
}
/** Returns the main activity information. */
@Override
public MainActivityWrapper getMainActivityWrapper() {
return mMainActivityWrapper;
}
/** Returns {@link TvInputManagerHelper}. */
@Override
public TvInputManagerHelper getTvInputManagerHelper() {
return mLazyTvInputManagerHelper.get();
}
@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, Programs.CONTENT_URI)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
} 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).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
}
/** Handles the global key KEYCODE_TV_INPUT. */
public void handleTvInputKey() {
TvInputManager tvInputManager = (TvInputManager) getSystemService(Context.TV_INPUT_SERVICE);
List 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 TV app.
*
* @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 inputs = inputManager.getTvInputList();
boolean skipTunerInputCheck = false;
Optional optionalEmbeddedTunerInputId =
mOptionalBuiltInTunerManager.transform(
BuiltInTunerManager::getEmbeddedTunerInputId);
// If there is only play movies trailer input, we don't handle input count change.
final String playMoviesInputIdPrefix = "com.google.android.videos/";
int tunerInputCount = 0;
boolean hasPlayMoviesInput = false;
for (TvInputInfo input : inputs) {
if (calledByTunerServiceChanged
&& !tunerServiceEnabled
&& optionalEmbeddedTunerInputId.isPresent()
&& optionalEmbeddedTunerInputId.get().equals(input.getId())) {
continue;
}
if (input.getType() == TvInputInfo.TYPE_TUNER) {
if (DEBUG) Log.d(TAG, "Tuner input: " + input.getId());
++tunerInputCount;
if (input.getId().startsWith(playMoviesInputIdPrefix)) {
hasPlayMoviesInput = true;
}
}
}
if (DEBUG) {
Log.d(
TAG,
"Input count: "
+ tunerInputCount
+ " hasPlayMoviesChannel: "
+ hasPlayMoviesInput);
}
if (tunerInputCount == 1 && hasPlayMoviesInput) {
if (DEBUG) Log.d(TAG, "There is only play movies input");
skipTunerInputCheck = true;
}
// Enable the TvActivity only if there is at least one tuner type input.
if (!skipTunerInputCheck) {
for (TvInputInfo input : inputs) {
if (calledByTunerServiceChanged
&& !tunerServiceEnabled
&& optionalEmbeddedTunerInputId.isPresent()
&& optionalEmbeddedTunerInputId.get().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") + " TV app.");
}
mSetupUtils.onInputListUpdated(inputManager);
}
@Override
@DbExecutor
public Executor getDbExecutor() {
return mDbExecutor;
}
@Override
public Lazy providesEpgReader() {
return mEpgReader;
}
@Override
public BuildType getBuildType() {
return mBuildType;
}
}