| /* |
| * 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. |
| * |
| * <p>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<ChannelDataManager> mChannelDataManager; |
| private volatile ProgramDataManager mProgramDataManager; |
| private PreviewDataManager mPreviewDataManager; |
| @Inject Lazy<DvrManager> mDvrManager; |
| private DvrScheduleManager mDvrScheduleManager; |
| @Inject Lazy<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; |
| @Inject Lazy<TvInputManagerHelper> mLazyTvInputManagerHelper; |
| private boolean mStarted; |
| @Inject EpgFetcher mEpgFetcher; |
| |
| @Inject Optional<BuiltInTunerManager> mOptionalBuiltInTunerManager; |
| @Inject SetupUtils mSetupUtils; |
| @Inject @DbExecutor Executor mDbExecutor; |
| @Inject Lazy<EpgReader> 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<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 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<TvInputInfo> inputs = inputManager.getTvInputList(); |
| boolean skipTunerInputCheck = false; |
| Optional<String> 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<EpgReader> providesEpgReader() { |
| return mEpgReader; |
| } |
| |
| @Override |
| public BuildType getBuildType() { |
| return mBuildType; |
| } |
| } |