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)) ? mDvrManager.get() : null;
259     }
260 
261     /** Returns the {@link DvrScheduleManager}. */
262     @Override
getDvrScheduleManager()263     public DvrScheduleManager getDvrScheduleManager() {
264         return mDvrScheduleManager;
265     }
266 
267     /** Returns the {@link RecordingScheduler}. */
268     @Override
269     @Nullable
getRecordingScheduler()270     public RecordingScheduler getRecordingScheduler() {
271         return mRecordingScheduler;
272     }
273 
274     /** Returns the {@link DvrWatchedPositionManager}. */
275     @Override
getDvrWatchedPositionManager()276     public DvrWatchedPositionManager getDvrWatchedPositionManager() {
277         if (mDvrWatchedPositionManager == null) {
278             mDvrWatchedPositionManager = new DvrWatchedPositionManager(this);
279         }
280         return mDvrWatchedPositionManager;
281     }
282 
283     @Override
284     @TargetApi(Build.VERSION_CODES.N)
getInputSessionManager()285     public InputSessionManager getInputSessionManager() {
286         if (mInputSessionManager == null) {
287             mInputSessionManager = new InputSessionManager(this);
288         }
289         return mInputSessionManager;
290     }
291 
292     /** Returns {@link ChannelDataManager}. */
293     @Override
getChannelDataManager()294     public ChannelDataManager getChannelDataManager() {
295         return mChannelDataManager.get();
296     }
297 
298     /** Returns {@link ProgramDataManager}. */
299     @Override
getProgramDataManager()300     public ProgramDataManager getProgramDataManager() {
301         if (mProgramDataManager != null) {
302             return mProgramDataManager;
303         }
304         Utils.runInMainThreadAndWait(
305                 () -> {
306                     if (mProgramDataManager == null) {
307                         mProgramDataManager = new ProgramDataManager(TvApplication.this);
308                         mProgramDataManager.start();
309                     }
310                 });
311         return mProgramDataManager;
312     }
313 
314     /** Returns {@link PreviewDataManager}. */
315     @TargetApi(Build.VERSION_CODES.O)
316     @Override
getPreviewDataManager()317     public PreviewDataManager getPreviewDataManager() {
318         if (mPreviewDataManager == null) {
319             mPreviewDataManager = new PreviewDataManager(this);
320             mPreviewDataManager.start();
321         }
322         return mPreviewDataManager;
323     }
324 
325     /** Returns {@link DvrDataManager}. */
326     @TargetApi(Build.VERSION_CODES.N)
327     @Override
getDvrDataManager()328     public DvrDataManager getDvrDataManager() {
329         return mDvrDataManager.get();
330     }
331 
332     @Override
333     @TargetApi(Build.VERSION_CODES.N)
getRecordingStorageStatusManager()334     public RecordingStorageStatusManager getRecordingStorageStatusManager() {
335         if (mDvrStorageStatusManager == null) {
336             mDvrStorageStatusManager = new DvrStorageStatusManager(this);
337         }
338         return mDvrStorageStatusManager;
339     }
340 
341     @Override
getPerformanceMonitor()342     public PerformanceMonitor getPerformanceMonitor() {
343         return mPerformanceMonitor;
344     }
345 
346     /** Returns the main activity information. */
347     @Override
getMainActivityWrapper()348     public MainActivityWrapper getMainActivityWrapper() {
349         return mMainActivityWrapper;
350     }
351 
352     /** Returns {@link TvInputManagerHelper}. */
353     @Override
getTvInputManagerHelper()354     public TvInputManagerHelper getTvInputManagerHelper() {
355         return mLazyTvInputManagerHelper.get();
356     }
357 
358     @Override
isRunningInMainProcess()359     public boolean isRunningInMainProcess() {
360         return mRunningInMainProcess != null && mRunningInMainProcess;
361     }
362 
363     /**
364      * SelectInputActivity is set in {@link SelectInputActivity#onCreate} and cleared in {@link
365      * SelectInputActivity#onDestroy}.
366      */
setSelectInputActivity(SelectInputActivity activity)367     public void setSelectInputActivity(SelectInputActivity activity) {
368         mSelectInputActivity = activity;
369     }
370 
handleGuideKey()371     public void handleGuideKey() {
372         if (!mMainActivityWrapper.isResumed()) {
373             startActivity(
374                     new Intent(Intent.ACTION_VIEW, Programs.CONTENT_URI)
375                             .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
376         } else {
377             mMainActivityWrapper.getMainActivity().getOverlayManager().toggleProgramGuide();
378         }
379     }
380 
381     /** Handles the global key KEYCODE_TV. */
handleTvKey()382     public void handleTvKey() {
383         if (!mMainActivityWrapper.isResumed()) {
384             startMainActivity(null);
385         }
386     }
387 
388     /** Handles the global key KEYCODE_DVR. */
handleDvrKey()389     public void handleDvrKey() {
390         startActivity(
391                 new Intent(this, DvrBrowseActivity.class).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
392     }
393 
394     /** Handles the global key KEYCODE_TV_INPUT. */
handleTvInputKey()395     public void handleTvInputKey() {
396         TvInputManager tvInputManager = (TvInputManager) getSystemService(Context.TV_INPUT_SERVICE);
397         List<TvInputInfo> tvInputs = tvInputManager.getTvInputList();
398         int inputCount = 0;
399         boolean hasTunerInput = false;
400         for (TvInputInfo input : tvInputs) {
401             if (input.isPassthroughInput()) {
402                 if (!input.isHidden(this)) {
403                     ++inputCount;
404                 }
405             } else if (!hasTunerInput) {
406                 hasTunerInput = true;
407                 ++inputCount;
408             }
409         }
410         if (inputCount < 2) {
411             return;
412         }
413         Activity activityToHandle =
414                 mMainActivityWrapper.isResumed()
415                         ? mMainActivityWrapper.getMainActivity()
416                         : mSelectInputActivity;
417         if (activityToHandle != null) {
418             // If startActivity is called, MainActivity.onPause is unnecessarily called. To
419             // prevent it, MainActivity.dispatchKeyEvent is directly called.
420             activityToHandle.dispatchKeyEvent(
421                     new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_TV_INPUT));
422             activityToHandle.dispatchKeyEvent(
423                     new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_TV_INPUT));
424         } else if (mMainActivityWrapper.isStarted()) {
425             Bundle extras = new Bundle();
426             extras.putString(Utils.EXTRA_KEY_ACTION, Utils.EXTRA_ACTION_SHOW_TV_INPUT);
427             startMainActivity(extras);
428         } else {
429             startActivity(
430                     new Intent(this, SelectInputActivity.class)
431                             .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
432         }
433     }
434 
startMainActivity(Bundle extras)435     private void startMainActivity(Bundle extras) {
436         // The use of FLAG_ACTIVITY_NEW_TASK enables arbitrary applications to access the intent
437         // sent to the root activity. Having said that, we should be fine here since such an intent
438         // does not carry any important user data.
439         Intent intent =
440                 new Intent(this, MainActivity.class).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
441         if (extras != null) {
442             intent.putExtras(extras);
443         }
444         startActivity(intent);
445     }
446 
447     /**
448      * Returns the version name of the TV app.
449      *
450      * @see PackageInfo#versionName
451      */
getVersionName()452     public String getVersionName() {
453         return mVersionName;
454     }
455 
456     /**
457      * Checks the input counts and enable/disable TvActivity. Also upda162 the input list in {@link
458      * SetupUtils}.
459      */
460     @Override
handleInputCountChanged()461     public void handleInputCountChanged() {
462         handleInputCountChanged(false, false, false);
463     }
464 
465     /**
466      * Checks the input counts and enable/disable TvActivity. Also updates the input list in {@link
467      * SetupUtils}.
468      *
469      * @param calledByTunerServiceChanged true if it is called when BaseTunerTvInputService is
470      *     enabled or disabled.
471      * @param tunerServiceEnabled it's available only when calledByTunerServiceChanged is true.
472      * @param dontKillApp when TvActivity is enabled or disabled by this method, the app restarts by
473      *     default. But, if dontKillApp is true, the app won't restart.
474      */
handleInputCountChanged( boolean calledByTunerServiceChanged, boolean tunerServiceEnabled, boolean dontKillApp)475     public void handleInputCountChanged(
476             boolean calledByTunerServiceChanged, boolean tunerServiceEnabled, boolean dontKillApp) {
477         TvInputManager inputManager = (TvInputManager) getSystemService(Context.TV_INPUT_SERVICE);
478         boolean enable =
479                 (calledByTunerServiceChanged && tunerServiceEnabled)
480                         || TvFeatures.UNHIDE.isEnabled(TvApplication.this);
481         if (!enable) {
482             List<TvInputInfo> inputs = inputManager.getTvInputList();
483             boolean skipTunerInputCheck = false;
484             Optional<String> optionalEmbeddedTunerInputId =
485                     mOptionalBuiltInTunerManager.transform(
486                             BuiltInTunerManager::getEmbeddedTunerInputId);
487             // If there is only play movies trailer input, we don't handle input count change.
488             final String playMoviesInputIdPrefix = "com.google.android.videos/";
489             int tunerInputCount = 0;
490             boolean hasPlayMoviesInput = false;
491             for (TvInputInfo input : inputs) {
492                 if (calledByTunerServiceChanged
493                         && !tunerServiceEnabled
494                         && optionalEmbeddedTunerInputId.isPresent()
495                         && optionalEmbeddedTunerInputId.get().equals(input.getId())) {
496                     continue;
497                 }
498                 if (input.getType() == TvInputInfo.TYPE_TUNER) {
499                     if (DEBUG) Log.d(TAG, "Tuner input: " + input.getId());
500                     ++tunerInputCount;
501                     if (input.getId().startsWith(playMoviesInputIdPrefix)) {
502                         hasPlayMoviesInput = true;
503                     }
504                 }
505             }
506             if (DEBUG) {
507                 Log.d(
508                         TAG,
509                         "Input count: "
510                                 + tunerInputCount
511                                 + " hasPlayMoviesChannel: "
512                                 + hasPlayMoviesInput);
513             }
514             if (tunerInputCount == 1 && hasPlayMoviesInput) {
515                 if (DEBUG) Log.d(TAG, "There is only play movies input");
516                 skipTunerInputCheck = true;
517             }
518             // Enable the TvActivity only if there is at least one tuner type input.
519             if (!skipTunerInputCheck) {
520                 for (TvInputInfo input : inputs) {
521                     if (calledByTunerServiceChanged
522                             && !tunerServiceEnabled
523                             && optionalEmbeddedTunerInputId.isPresent()
524                             && optionalEmbeddedTunerInputId.get().equals(input.getId())) {
525                         continue;
526                     }
527                     if (input.getType() == TvInputInfo.TYPE_TUNER) {
528                         enable = true;
529                         break;
530                     }
531                 }
532             }
533             if (DEBUG) Log.d(TAG, "Enable MainActivity: " + enable);
534         }
535         PackageManager packageManager = getPackageManager();
536         ComponentName name = new ComponentName(this, TvActivity.class);
537         int newState =
538                 enable
539                         ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
540                         : PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
541         if (packageManager.getComponentEnabledSetting(name) != newState) {
542             packageManager.setComponentEnabledSetting(
543                     name, newState, dontKillApp ? PackageManager.DONT_KILL_APP : 0);
544             Log.i(TAG, (enable ? "Un-hide" : "Hide") + " TV app.");
545         }
546         mSetupUtils.onInputListUpdated(inputManager);
547     }
548 
549     @Override
550     @DbExecutor
getDbExecutor()551     public Executor getDbExecutor() {
552         return mDbExecutor;
553     }
554 
555     @Override
providesEpgReader()556     public Lazy<EpgReader> providesEpgReader() {
557         return mEpgReader;
558     }
559 
560     @Override
getBuildType()561     public BuildType getBuildType() {
562         return mBuildType;
563     }
564 }
565