1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.tv;
18 
19 import android.annotation.TargetApi;
20 import android.app.Activity;
21 import android.content.ComponentName;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.SharedPreferences;
25 import android.content.pm.PackageInfo;
26 import android.content.pm.PackageManager;
27 import android.media.tv.TvContract.Programs;
28 import android.media.tv.TvInputInfo;
29 import android.media.tv.TvInputManager;
30 import android.media.tv.TvInputManager.TvInputCallback;
31 import android.os.Build;
32 import android.os.Bundle;
33 import android.support.annotation.Nullable;
34 import android.text.TextUtils;
35 import android.util.Log;
36 import android.view.KeyEvent;
37 import android.widget.Toast;
38 
39 import com.android.tv.common.BaseApplication;
40 import com.android.tv.common.feature.CommonFeatures;
41 import com.android.tv.common.recording.RecordingStorageStatusManager;
42 import com.android.tv.common.ui.setup.animation.SetupAnimationHelper;
43 import com.android.tv.common.util.Debug;
44 import com.android.tv.common.util.SharedPreferencesUtils;
45 import com.android.tv.data.ChannelDataManager;
46 import com.android.tv.data.PreviewDataManager;
47 import com.android.tv.data.ProgramDataManager;
48 import com.android.tv.data.epg.EpgFetcher;
49 import com.android.tv.data.epg.EpgReader;
50 import com.android.tv.dvr.DvrDataManager;
51 import com.android.tv.dvr.DvrManager;
52 import com.android.tv.dvr.DvrScheduleManager;
53 import com.android.tv.dvr.DvrStorageStatusManager;
54 import com.android.tv.dvr.DvrWatchedPositionManager;
55 import com.android.tv.dvr.recorder.RecordingScheduler;
56 import com.android.tv.dvr.ui.browse.DvrBrowseActivity;
57 import com.android.tv.features.TvFeatures;
58 import com.android.tv.perf.PerformanceMonitor;
59 import com.android.tv.perf.StartupMeasure;
60 import com.android.tv.perf.StartupMeasureFactory;
61 import com.android.tv.recommendation.ChannelPreviewUpdater;
62 import com.android.tv.recommendation.RecordedProgramPreviewUpdater;
63 import com.android.tv.tunerinputcontroller.BuiltInTunerManager;
64 import com.android.tv.tunerinputcontroller.TunerInputController;
65 import com.android.tv.util.AsyncDbTask.DbExecutor;
66 import com.android.tv.util.SetupUtils;
67 import com.android.tv.util.TvInputManagerHelper;
68 import com.android.tv.util.Utils;
69 
70 import com.google.common.base.Optional;
71 
72 import dagger.Lazy;
73 
74 import com.android.tv.common.flags.CloudEpgFlags;
75 import com.android.tv.common.flags.LegacyFlags;
76 
77 import java.util.List;
78 import java.util.concurrent.Executor;
79 
80 import javax.inject.Inject;
81 
82 /**
83  * TV application.
84  *
85  * <p>This includes all the Google specific hooks.
86  */
87 public abstract class TvApplication extends BaseApplication implements TvSingletons, Starter {
88 
89     protected static final StartupMeasure STARTUP_MEASURE = StartupMeasureFactory.create();
90     private static final String TAG = "TvApplication";
91     private static final boolean DEBUG = false;
92 
93     /**
94      * Broadcast Action: The user has updated LC to a new version that supports tuner input. {@link
95      * TunerInputController} will receive this intent to check the existence of tuner input when the
96      * new version is first launched.
97      */
98     public static final String ACTION_APPLICATION_FIRST_LAUNCHED =
99             " com.android.tv.action.APPLICATION_FIRST_LAUNCHED";
100 
101     private static final String PREFERENCE_IS_FIRST_LAUNCH = "is_first_launch";
102 
103     private String mVersionName = "";
104 
105     private final MainActivityWrapper mMainActivityWrapper = new MainActivityWrapper();
106 
107     private SelectInputActivity mSelectInputActivity;
108     @Inject Lazy<ChannelDataManager> mChannelDataManager;
109     private volatile ProgramDataManager mProgramDataManager;
110     private PreviewDataManager mPreviewDataManager;
111     @Inject Lazy<DvrManager> mDvrManager;
112     private DvrScheduleManager mDvrScheduleManager;
113     @Inject Lazy<DvrDataManager> mDvrDataManager;
114     private DvrWatchedPositionManager mDvrWatchedPositionManager;
115     private RecordingScheduler mRecordingScheduler;
116     private RecordingStorageStatusManager mDvrStorageStatusManager;
117     @Nullable private InputSessionManager mInputSessionManager;
118     // STOP-SHIP: Remove this variable when Tuner Process is split to another application.
119     // When this variable is null, we don't know in which process TvApplication runs.
120     private Boolean mRunningInMainProcess;
121     @Inject Lazy<TvInputManagerHelper> mLazyTvInputManagerHelper;
122     private boolean mStarted;
123     @Inject EpgFetcher mEpgFetcher;
124 
125     @Inject Optional<BuiltInTunerManager> mOptionalBuiltInTunerManager;
126     @Inject SetupUtils mSetupUtils;
127     @Inject @DbExecutor Executor mDbExecutor;
128     @Inject Lazy<EpgReader> mEpgReader;
129     @Inject BuildType mBuildType;
130     @Inject CloudEpgFlags mCloudEpgFlags;
131     @Inject LegacyFlags mLegacyFlags;
132     @Inject PerformanceMonitor mPerformanceMonitor;
133 
134     @Override
onCreate()135     public void onCreate() {
136         if (getSystemService(TvInputManager.class) == null) {
137             String msg = "Not an Android TV device.";
138             Toast.makeText(this, msg, Toast.LENGTH_LONG);
139             Log.wtf(TAG, msg);
140             throw new IllegalStateException(msg);
141         }
142         super.onCreate();
143         mPerformanceMonitor.startMemoryMonitor();
144         mPerformanceMonitor.startCrashMonitor();
145         SharedPreferencesUtils.initialize(
146                 this,
147                 () -> {
148                     if (mRunningInMainProcess != null && mRunningInMainProcess) {
149                         checkTunerServiceOnFirstLaunch();
150                     }
151                 });
152         try {
153             PackageInfo pInfo = getPackageManager().getPackageInfo(getPackageName(), 0);
154             mVersionName = pInfo.versionName;
155         } catch (PackageManager.NameNotFoundException e) {
156             Log.w(TAG, "Unable to find package '" + getPackageName() + "'.", e);
157             mVersionName = "";
158         }
159         Log.i(TAG, "Starting TV app " + getVersionName());
160 
161         // In SetupFragment, transitions are set in the constructor. Because the fragment can be
162         // created in Activity.onCreate() by the framework, SetupAnimationHelper should be
163         // initialized here before Activity.onCreate() is called.
164         SetupAnimationHelper.initialize(this);
165         getTvInputManagerHelper();
166 
167         Log.i(TAG, "Started TV app " + mVersionName);
168         Debug.getTimer(Debug.TAG_START_UP_TIMER).log("finish TvApplication.onCreate");
169     }
170 
171     /** Initializes application. It is a noop if called twice. */
172     @Override
start()173     public void start() {
174         if (mStarted) {
175             return;
176         }
177         mStarted = true;
178         mRunningInMainProcess = true;
179         Debug.getTimer(Debug.TAG_START_UP_TIMER).log("start TvApplication.start");
180         if (mRunningInMainProcess) {
181             getTvInputManagerHelper()
182                     .addCallback(
183                             new TvInputCallback() {
184                                 @Override
185                                 public void onInputAdded(String inputId) {
186                                     if (mOptionalBuiltInTunerManager.isPresent()) {
187                                         BuiltInTunerManager builtInTunerManager =
188                                                 mOptionalBuiltInTunerManager.get();
189                                         if (TextUtils.equals(
190                                                 inputId,
191                                                 builtInTunerManager.getEmbeddedTunerInputId())) {
192 
193                                             builtInTunerManager
194                                                     .getTunerInputController()
195                                                     .updateTunerInputInfo(TvApplication.this);
196                                         }
197                                         handleInputCountChanged();
198                                     }
199                                 }
200 
201                                 @Override
202                                 public void onInputRemoved(String inputId) {
203                                     handleInputCountChanged();
204                                 }
205                             });
206             if (mOptionalBuiltInTunerManager.isPresent()) {
207                 // If the tuner input service is added before the app is started, we need to
208                 // handle it here.
209                 mOptionalBuiltInTunerManager
210                         .get()
211                         .getTunerInputController()
212                         .updateTunerInputInfo(TvApplication.this);
213             }
214             if (CommonFeatures.DVR.isEnabled(this)) {
215                 mDvrScheduleManager = new DvrScheduleManager(this);
216                 mRecordingScheduler = RecordingScheduler.createScheduler(this);
217             }
218             mEpgFetcher.startRoutineService();
219             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
220                 ChannelPreviewUpdater.getInstance(this).startRoutineService();
221                 if (CommonFeatures.DVR.isEnabled(this)) {
222                     RecordedProgramPreviewUpdater.getInstance(this)
223                             .updatePreviewDataForRecordedPrograms();
224                 }
225             }
226         }
227         Debug.getTimer(Debug.TAG_START_UP_TIMER).log("finish TvApplication.start");
228     }
229 
checkTunerServiceOnFirstLaunch()230     private void checkTunerServiceOnFirstLaunch() {
231         SharedPreferences sharedPreferences =
232                 this.getSharedPreferences(
233                         SharedPreferencesUtils.SHARED_PREF_FEATURES, Context.MODE_PRIVATE);
234         boolean isFirstLaunch = sharedPreferences.getBoolean(PREFERENCE_IS_FIRST_LAUNCH, true);
235         if (isFirstLaunch) {
236             if (DEBUG) Log.d(TAG, "Congratulations, it's the first launch!");
237             if (mOptionalBuiltInTunerManager.isPresent()) {
238                 mOptionalBuiltInTunerManager
239                         .get()
240                         .getTunerInputController()
241                         .onCheckingUsbTunerStatus(this, ACTION_APPLICATION_FIRST_LAUNCHED);
242             }
243             SharedPreferences.Editor editor = sharedPreferences.edit();
244             editor.putBoolean(PREFERENCE_IS_FIRST_LAUNCH, false);
245             editor.apply();
246         }
247     }
248 
249     @Override
getSetupUtils()250     public synchronized SetupUtils getSetupUtils() {
251         return mSetupUtils;
252     }
253 
254     /** Returns the {@link DvrManager}. */
255     @Override
256     @Nullable
getDvrManager()257     public DvrManager getDvrManager() {
258         return (CommonFeatures.DVR.isEnabled(this) && mDvrScheduleManager != null)
259                 ? mDvrManager.get()
260                 : null;
261     }
262 
263     /** Returns the {@link DvrScheduleManager}. */
264     @Override
getDvrScheduleManager()265     public DvrScheduleManager getDvrScheduleManager() {
266         return mDvrScheduleManager;
267     }
268 
269     /** Returns the {@link RecordingScheduler}. */
270     @Override
271     @Nullable
getRecordingScheduler()272     public RecordingScheduler getRecordingScheduler() {
273         return mRecordingScheduler;
274     }
275 
276     /** Returns the {@link DvrWatchedPositionManager}. */
277     @Override
getDvrWatchedPositionManager()278     public DvrWatchedPositionManager getDvrWatchedPositionManager() {
279         if (mDvrWatchedPositionManager == null) {
280             mDvrWatchedPositionManager = new DvrWatchedPositionManager(this);
281         }
282         return mDvrWatchedPositionManager;
283     }
284 
285     @Override
286     @TargetApi(Build.VERSION_CODES.N)
getInputSessionManager()287     public InputSessionManager getInputSessionManager() {
288         if (mInputSessionManager == null) {
289             mInputSessionManager = new InputSessionManager(this);
290         }
291         return mInputSessionManager;
292     }
293 
294     /** Returns {@link ChannelDataManager}. */
295     @Override
getChannelDataManager()296     public ChannelDataManager getChannelDataManager() {
297         return mChannelDataManager.get();
298     }
299 
300     /** Returns {@link ProgramDataManager}. */
301     @Override
getProgramDataManager()302     public ProgramDataManager getProgramDataManager() {
303         if (mProgramDataManager != null) {
304             return mProgramDataManager;
305         }
306         Utils.runInMainThreadAndWait(
307                 () -> {
308                     if (mProgramDataManager == null) {
309                         mProgramDataManager = new ProgramDataManager(TvApplication.this);
310                         mProgramDataManager.start();
311                     }
312                 });
313         return mProgramDataManager;
314     }
315 
316     /** Returns {@link PreviewDataManager}. */
317     @TargetApi(Build.VERSION_CODES.O)
318     @Override
getPreviewDataManager()319     public PreviewDataManager getPreviewDataManager() {
320         if (mPreviewDataManager == null) {
321             mPreviewDataManager = new PreviewDataManager(this);
322             mPreviewDataManager.start();
323         }
324         return mPreviewDataManager;
325     }
326 
327     /** Returns {@link DvrDataManager}. */
328     @TargetApi(Build.VERSION_CODES.N)
329     @Override
getDvrDataManager()330     public DvrDataManager getDvrDataManager() {
331         return mDvrDataManager.get();
332     }
333 
334     @Override
335     @TargetApi(Build.VERSION_CODES.N)
getRecordingStorageStatusManager()336     public RecordingStorageStatusManager getRecordingStorageStatusManager() {
337         if (mDvrStorageStatusManager == null) {
338             mDvrStorageStatusManager = new DvrStorageStatusManager(this);
339         }
340         return mDvrStorageStatusManager;
341     }
342 
343     @Override
getPerformanceMonitor()344     public PerformanceMonitor getPerformanceMonitor() {
345         return mPerformanceMonitor;
346     }
347 
348     /** Returns the main activity information. */
349     @Override
getMainActivityWrapper()350     public MainActivityWrapper getMainActivityWrapper() {
351         return mMainActivityWrapper;
352     }
353 
354     /** Returns {@link TvInputManagerHelper}. */
355     @Override
getTvInputManagerHelper()356     public TvInputManagerHelper getTvInputManagerHelper() {
357         return mLazyTvInputManagerHelper.get();
358     }
359 
360     @Override
isRunningInMainProcess()361     public boolean isRunningInMainProcess() {
362         return mRunningInMainProcess != null && mRunningInMainProcess;
363     }
364 
365     /**
366      * SelectInputActivity is set in {@link SelectInputActivity#onCreate} and cleared in {@link
367      * SelectInputActivity#onDestroy}.
368      */
setSelectInputActivity(SelectInputActivity activity)369     public void setSelectInputActivity(SelectInputActivity activity) {
370         mSelectInputActivity = activity;
371     }
372 
handleGuideKey()373     public void handleGuideKey() {
374         if (!mMainActivityWrapper.isResumed()) {
375             startActivity(
376                     new Intent(Intent.ACTION_VIEW, Programs.CONTENT_URI)
377                             .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
378         } else {
379             mMainActivityWrapper.getMainActivity().getOverlayManager().toggleProgramGuide();
380         }
381     }
382 
383     /** Handles the global key KEYCODE_TV. */
handleTvKey()384     public void handleTvKey() {
385         if (!mMainActivityWrapper.isResumed()) {
386             startMainActivity(null);
387         }
388     }
389 
390     /** Handles the global key KEYCODE_DVR. */
handleDvrKey()391     public void handleDvrKey() {
392         startActivity(
393                 new Intent(this, DvrBrowseActivity.class).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
394     }
395 
396     /** Handles the global key KEYCODE_TV_INPUT. */
handleTvInputKey()397     public void handleTvInputKey() {
398         TvInputManager tvInputManager = (TvInputManager) getSystemService(Context.TV_INPUT_SERVICE);
399         List<TvInputInfo> tvInputs = tvInputManager.getTvInputList();
400         int inputCount = 0;
401         boolean hasTunerInput = false;
402         for (TvInputInfo input : tvInputs) {
403             if (input.isPassthroughInput()) {
404                 if (!input.isHidden(this)) {
405                     ++inputCount;
406                 }
407             } else if (!hasTunerInput) {
408                 hasTunerInput = true;
409                 ++inputCount;
410             }
411         }
412 
413         if (inputCount < 1) {
414             Log.w(TAG, "No available input to be selected.");
415             return;
416         }
417 
418         if (mMainActivityWrapper.isResumed() && inputCount < 2) {
419             // if no other inputs can be switched to, keep in the same input
420             return;
421         }
422 
423         Activity activityToHandle =
424                 mMainActivityWrapper.isResumed()
425                         ? mMainActivityWrapper.getMainActivity()
426                         : mSelectInputActivity;
427         if (activityToHandle != null) {
428             // If startActivity is called, MainActivity.onPause is unnecessarily called. To
429             // prevent it, MainActivity.dispatchKeyEvent is directly called.
430             activityToHandle.dispatchKeyEvent(
431                     new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_TV_INPUT));
432             activityToHandle.dispatchKeyEvent(
433                     new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_TV_INPUT));
434         } else if (mMainActivityWrapper.isStarted()) {
435             Bundle extras = new Bundle();
436             extras.putString(Utils.EXTRA_KEY_ACTION, Utils.EXTRA_ACTION_SHOW_TV_INPUT);
437             startMainActivity(extras);
438         } else {
439             startActivity(
440                     new Intent(this, SelectInputActivity.class)
441                             .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
442         }
443     }
444 
startMainActivity(Bundle extras)445     private void startMainActivity(Bundle extras) {
446         // The use of FLAG_ACTIVITY_NEW_TASK enables arbitrary applications to access the intent
447         // sent to the root activity. Having said that, we should be fine here since such an intent
448         // does not carry any important user data.
449         Intent intent =
450                 new Intent(this, MainActivity.class).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
451         if (extras != null) {
452             intent.putExtras(extras);
453         }
454         startActivity(intent);
455     }
456 
457     /**
458      * Returns the version name of the TV app.
459      *
460      * @see PackageInfo#versionName
461      */
getVersionName()462     public String getVersionName() {
463         return mVersionName;
464     }
465 
466     /**
467      * Checks the input counts and enable/disable TvActivity. Also upda162 the input list in {@link
468      * SetupUtils}.
469      */
470     @Override
handleInputCountChanged()471     public void handleInputCountChanged() {
472         handleInputCountChanged(false, false, false);
473     }
474 
475     /**
476      * Checks the input counts and enable/disable TvActivity. Also updates the input list in {@link
477      * SetupUtils}.
478      *
479      * @param calledByTunerServiceChanged true if it is called when BaseTunerTvInputService is
480      *     enabled or disabled.
481      * @param tunerServiceEnabled it's available only when calledByTunerServiceChanged is true.
482      * @param dontKillApp when TvActivity is enabled or disabled by this method, the app restarts by
483      *     default. But, if dontKillApp is true, the app won't restart.
484      */
handleInputCountChanged( boolean calledByTunerServiceChanged, boolean tunerServiceEnabled, boolean dontKillApp)485     public void handleInputCountChanged(
486             boolean calledByTunerServiceChanged, boolean tunerServiceEnabled, boolean dontKillApp) {
487         TvInputManager inputManager = (TvInputManager) getSystemService(Context.TV_INPUT_SERVICE);
488         boolean enable =
489                 (calledByTunerServiceChanged && tunerServiceEnabled)
490                         || TvFeatures.UNHIDE.isEnabled(TvApplication.this);
491         if (!enable) {
492             List<TvInputInfo> inputs = inputManager.getTvInputList();
493             boolean skipTunerInputCheck = false;
494             Optional<String> optionalEmbeddedTunerInputId =
495                     mOptionalBuiltInTunerManager.transform(
496                             BuiltInTunerManager::getEmbeddedTunerInputId);
497             // If there is only play movies trailer input, we don't handle input count change.
498             final String playMoviesInputIdPrefix = "com.google.android.videos/";
499             int tunerInputCount = 0;
500             boolean hasPlayMoviesInput = false;
501             for (TvInputInfo input : inputs) {
502                 if (calledByTunerServiceChanged
503                         && !tunerServiceEnabled
504                         && optionalEmbeddedTunerInputId.isPresent()
505                         && optionalEmbeddedTunerInputId.get().equals(input.getId())) {
506                     continue;
507                 }
508                 if (input.getType() == TvInputInfo.TYPE_TUNER) {
509                     if (DEBUG) Log.d(TAG, "Tuner input: " + input.getId());
510                     ++tunerInputCount;
511                     if (input.getId().startsWith(playMoviesInputIdPrefix)) {
512                         hasPlayMoviesInput = true;
513                     }
514                 }
515             }
516             if (DEBUG) {
517                 Log.d(
518                         TAG,
519                         "Input count: "
520                                 + tunerInputCount
521                                 + " hasPlayMoviesChannel: "
522                                 + hasPlayMoviesInput);
523             }
524             if (tunerInputCount == 1 && hasPlayMoviesInput) {
525                 if (DEBUG) Log.d(TAG, "There is only play movies input");
526                 skipTunerInputCheck = true;
527             }
528             // Enable the TvActivity only if there is at least one tuner type input.
529             if (!skipTunerInputCheck) {
530                 for (TvInputInfo input : inputs) {
531                     if (calledByTunerServiceChanged
532                             && !tunerServiceEnabled
533                             && optionalEmbeddedTunerInputId.isPresent()
534                             && optionalEmbeddedTunerInputId.get().equals(input.getId())) {
535                         continue;
536                     }
537                     if (input.getType() == TvInputInfo.TYPE_TUNER) {
538                         enable = true;
539                         break;
540                     }
541                 }
542             }
543             if (DEBUG) Log.d(TAG, "Enable MainActivity: " + enable);
544         }
545         PackageManager packageManager = getPackageManager();
546         ComponentName name = new ComponentName(this, TvActivity.class);
547         int newState =
548                 enable
549                         ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
550                         : PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
551         if (packageManager.getComponentEnabledSetting(name) != newState) {
552             packageManager.setComponentEnabledSetting(
553                     name, newState, dontKillApp ? PackageManager.DONT_KILL_APP : 0);
554             Log.i(TAG, (enable ? "Un-hide" : "Hide") + " TV app.");
555         }
556         mSetupUtils.onInputListUpdated(inputManager);
557     }
558 
559     @Override
560     @DbExecutor
getDbExecutor()561     public Executor getDbExecutor() {
562         return mDbExecutor;
563     }
564 
565     @Override
providesEpgReader()566     public Lazy<EpgReader> providesEpgReader() {
567         return mEpgReader;
568     }
569 
570     @Override
getBuildType()571     public BuildType getBuildType() {
572         return mBuildType;
573     }
574 }
575