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
17package com.android.tv;
18
19import android.annotation.TargetApi;
20import android.app.Activity;
21import android.app.Application;
22import android.content.ComponentName;
23import android.content.Context;
24import android.content.Intent;
25import android.content.IntentFilter;
26import android.content.SharedPreferences;
27import android.content.pm.PackageInfo;
28import android.content.pm.PackageManager;
29import android.media.tv.TvContract;
30import android.media.tv.TvInputInfo;
31import android.media.tv.TvInputManager;
32import android.media.tv.TvInputManager.TvInputCallback;
33import android.os.AsyncTask;
34import android.os.Build;
35import android.os.Bundle;
36import android.os.StrictMode;
37import android.support.annotation.Nullable;
38import android.support.annotation.VisibleForTesting;
39import android.text.TextUtils;
40import android.util.Log;
41import android.view.KeyEvent;
42
43import com.android.tv.analytics.Analytics;
44import com.android.tv.analytics.StubAnalytics;
45import com.android.tv.analytics.StubAnalytics;
46import com.android.tv.analytics.Tracker;
47import com.android.tv.common.BuildConfig;
48import com.android.tv.common.SharedPreferencesUtils;
49import com.android.tv.common.SoftPreconditions;
50import com.android.tv.common.TvCommonUtils;
51import com.android.tv.common.feature.CommonFeatures;
52import com.android.tv.common.ui.setup.animation.SetupAnimationHelper;
53import com.android.tv.config.DefaultConfigManager;
54import com.android.tv.config.RemoteConfig;
55import com.android.tv.data.ChannelDataManager;
56import com.android.tv.data.PreviewDataManager;
57import com.android.tv.data.ProgramDataManager;
58import com.android.tv.data.epg.EpgFetcher;
59import com.android.tv.dvr.DvrDataManager;
60import com.android.tv.dvr.DvrDataManagerImpl;
61import com.android.tv.dvr.DvrManager;
62import com.android.tv.dvr.DvrScheduleManager;
63import com.android.tv.dvr.DvrStorageStatusManager;
64import com.android.tv.dvr.DvrWatchedPositionManager;
65import com.android.tv.dvr.recorder.RecordingScheduler;
66import com.android.tv.perf.EventNames;
67import com.android.tv.perf.PerformanceMonitor;
68import com.android.tv.perf.StubPerformanceMonitor;
69import com.android.tv.perf.TimerEvent;
70import com.android.tv.recommendation.ChannelPreviewUpdater;
71import com.android.tv.recommendation.RecordedProgramPreviewUpdater;
72import com.android.tv.tuner.TunerInputController;
73import com.android.tv.tuner.TunerPreferences;
74import com.android.tv.tuner.tvinput.TunerTvInputService;
75import com.android.tv.tuner.util.TunerInputInfoUtils;
76import com.android.tv.util.AccountHelper;
77import com.android.tv.util.Clock;
78import com.android.tv.util.Debug;
79import com.android.tv.util.PermissionUtils;
80import com.android.tv.util.SetupUtils;
81import com.android.tv.util.SystemProperties;
82import com.android.tv.util.TvInputManagerHelper;
83import com.android.tv.util.Utils;
84
85import java.util.List;
86
87public class TvApplication extends Application implements ApplicationSingletons {
88    private static final String TAG = "TvApplication";
89    private static final boolean DEBUG = false;
90    private static final TimerEvent sAppStartTimer = StubPerformanceMonitor.startBootstrapTimer();
91
92    /**
93     * An instance of {@link ApplicationSingletons}. Note that this can be set directly only for the
94     * test purpose.
95     */
96    @VisibleForTesting
97    public static ApplicationSingletons sAppSingletons;
98
99    /**
100     * Broadcast Action: The user has updated LC to a new version that supports tuner input.
101     * {@link com.android.tv.tuner.TunerInputController} will recevice this intent to check
102     * the existence of tuner input when the new version is first launched.
103     */
104    public static final String ACTION_APPLICATION_FIRST_LAUNCHED =
105            "com.android.tv.action.APPLICATION_FIRST_LAUNCHED";
106    private static final String PREFERENCE_IS_FIRST_LAUNCH = "is_first_launch";
107
108    private RemoteConfig mRemoteConfig;
109    private String mVersionName = "";
110
111    private final MainActivityWrapper mMainActivityWrapper = new MainActivityWrapper();
112
113    private SelectInputActivity mSelectInputActivity;
114    private Analytics mAnalytics;
115    private Tracker mTracker;
116    private TvInputManagerHelper mTvInputManagerHelper;
117    private ChannelDataManager mChannelDataManager;
118    private volatile ProgramDataManager mProgramDataManager;
119    private PreviewDataManager mPreviewDataManager;
120    private DvrManager mDvrManager;
121    private DvrScheduleManager mDvrScheduleManager;
122    private DvrDataManager mDvrDataManager;
123    private DvrStorageStatusManager mDvrStorageStatusManager;
124    private DvrWatchedPositionManager mDvrWatchedPositionManager;
125    private RecordingScheduler mRecordingScheduler;
126    @Nullable
127    private InputSessionManager mInputSessionManager;
128    private AccountHelper mAccountHelper;
129    // When this variable is null, we don't know in which process TvApplication runs.
130    private Boolean mRunningInMainProcess;
131    private PerformanceMonitor mPerformanceMonitor;
132
133    @Override
134    public void onCreate() {
135        super.onCreate();
136        if (!PermissionUtils.hasInternet(this)) {
137            // When an isolated process starts, just skip all the initialization.
138            return;
139        }
140        Debug.getTimer(Debug.TAG_START_UP_TIMER).start();
141        Debug.getTimer(Debug.TAG_START_UP_TIMER).log("Start TvApplication.onCreate");
142        SharedPreferencesUtils.initialize(this, new Runnable() {
143            @Override
144            public void run() {
145                if (mRunningInMainProcess != null && mRunningInMainProcess) {
146                    checkTunerServiceOnFirstLaunch();
147                }
148            }
149        });
150        // TunerPreferences is used to enable/disable the tuner input even when TUNER feature is
151        // disabled.
152        TunerPreferences.initialize(this);
153        try {
154            PackageInfo pInfo = getPackageManager().getPackageInfo(getPackageName(), 0);
155            mVersionName = pInfo.versionName;
156        } catch (PackageManager.NameNotFoundException e) {
157            Log.w(TAG, "Unable to find package '" + getPackageName() + "'.", e);
158            mVersionName = "";
159        }
160        Log.i(TAG, "Starting Live TV " + getVersionName());
161
162        // Only set StrictMode for ENG builds because the build server only produces userdebug
163        // builds.
164        if (BuildConfig.ENG && SystemProperties.ALLOW_STRICT_MODE.getValue()) {
165            StrictMode.ThreadPolicy.Builder threadPolicyBuilder =
166                    new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog();
167            StrictMode.VmPolicy.Builder vmPolicyBuilder =
168                    new StrictMode.VmPolicy.Builder().detectAll().penaltyDeath();
169            if (!TvCommonUtils.isRunningInTest()) {
170                threadPolicyBuilder.penaltyDialog();
171            }
172            StrictMode.setThreadPolicy(threadPolicyBuilder.build());
173            StrictMode.setVmPolicy(vmPolicyBuilder.build());
174        }
175        if (BuildConfig.ENG && !SystemProperties.ALLOW_ANALYTICS_IN_ENG.getValue()) {
176            mAnalytics = StubAnalytics.getInstance(this);
177        } else {
178            mAnalytics = StubAnalytics.getInstance(this);
179        }
180        mTracker = mAnalytics.getDefaultTracker();
181        getTvInputManagerHelper();
182        // In SetupFragment, transitions are set in the constructor. Because the fragment can be
183        // created in Activity.onCreate() by the framework, SetupAnimationHelper should be
184        // initialized here before Activity.onCreate() is called.
185        SetupAnimationHelper.initialize(this);
186
187
188        Log.i(TAG, "Started Live TV " + mVersionName);
189        Debug.getTimer(Debug.TAG_START_UP_TIMER).log("finish TvApplication.onCreate");
190        getPerformanceMonitor().stopTimer(sAppStartTimer, EventNames.APPLICATION_ONCREATE);
191    }
192
193    private void setCurrentRunningProcess(boolean isMainProcess) {
194        if (mRunningInMainProcess != null) {
195            SoftPreconditions.checkState(isMainProcess == mRunningInMainProcess);
196            return;
197        }
198        Debug.getTimer(Debug.TAG_START_UP_TIMER).log(
199                "start TvApplication.setCurrentRunningProcess");
200        mRunningInMainProcess = isMainProcess;
201        if (CommonFeatures.DVR.isEnabled(this)) {
202            mDvrStorageStatusManager = new DvrStorageStatusManager(this, mRunningInMainProcess);
203        }
204        new AsyncTask<Void, Void, Void>() {
205            @Override
206            protected Void doInBackground(Void... params) {
207                // Fetch remote config
208                getRemoteConfig().fetch(null);
209                return null;
210            }
211        }.execute();
212        if (mRunningInMainProcess) {
213            getTvInputManagerHelper().addCallback(new TvInputCallback() {
214                @Override
215                public void onInputAdded(String inputId) {
216                    if (Features.TUNER.isEnabled(TvApplication.this) && TextUtils.equals(inputId,
217                            TunerTvInputService.getInputId(TvApplication.this))) {
218                        TunerInputInfoUtils.updateTunerInputInfo(TvApplication.this);
219                    }
220                    handleInputCountChanged();
221                }
222
223                @Override
224                public void onInputRemoved(String inputId) {
225                    handleInputCountChanged();
226                }
227            });
228            if (Features.TUNER.isEnabled(this)) {
229                // If the tuner input service is added before the app is started, we need to
230                // handle it here.
231                TunerInputInfoUtils.updateTunerInputInfo(TvApplication.this);
232            }
233            if (CommonFeatures.DVR.isEnabled(this)) {
234                mDvrScheduleManager = new DvrScheduleManager(this);
235                mDvrManager = new DvrManager(this);
236                mRecordingScheduler = RecordingScheduler.createScheduler(this);
237            }
238            EpgFetcher.getInstance(this).startRoutineService();
239            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
240                ChannelPreviewUpdater.getInstance(this).startRoutineService();
241                RecordedProgramPreviewUpdater.getInstance(this)
242                        .updatePreviewDataForRecordedPrograms();
243            }
244        }
245        Debug.getTimer(Debug.TAG_START_UP_TIMER).log(
246                "finish TvApplication.setCurrentRunningProcess");
247    }
248
249    private void checkTunerServiceOnFirstLaunch() {
250        SharedPreferences sharedPreferences = this.getSharedPreferences(
251                SharedPreferencesUtils.SHARED_PREF_FEATURES, Context.MODE_PRIVATE);
252        boolean isFirstLaunch = sharedPreferences.getBoolean(PREFERENCE_IS_FIRST_LAUNCH, true);
253        if (isFirstLaunch) {
254            if (DEBUG) Log.d(TAG, "Congratulations, it's the first launch!");
255            TunerInputController.onCheckingUsbTunerStatus(this, ACTION_APPLICATION_FIRST_LAUNCHED);
256            SharedPreferences.Editor editor = sharedPreferences.edit();
257            editor.putBoolean(PREFERENCE_IS_FIRST_LAUNCH, false);
258            editor.apply();
259        }
260    }
261
262    /**
263     * Returns the {@link DvrManager}.
264     */
265    @Override
266    public DvrManager getDvrManager() {
267        return mDvrManager;
268    }
269
270    /**
271     * Returns the {@link DvrScheduleManager}.
272     */
273    @Override
274    public DvrScheduleManager getDvrScheduleManager() {
275        return mDvrScheduleManager;
276    }
277
278    /**
279     * Returns the {@link RecordingScheduler}.
280     */
281    @Override
282    @Nullable
283    public RecordingScheduler getRecordingScheduler() {
284        return mRecordingScheduler;
285    }
286
287    /**
288     * Returns the {@link DvrWatchedPositionManager}.
289     */
290    @Override
291    public DvrWatchedPositionManager getDvrWatchedPositionManager() {
292        if (mDvrWatchedPositionManager == null) {
293            mDvrWatchedPositionManager = new DvrWatchedPositionManager(this);
294        }
295        return mDvrWatchedPositionManager;
296    }
297
298    @Override
299    @TargetApi(Build.VERSION_CODES.N)
300    public InputSessionManager getInputSessionManager() {
301        if (mInputSessionManager == null) {
302            mInputSessionManager = new InputSessionManager(this);
303        }
304        return mInputSessionManager;
305    }
306
307    /**
308     * Returns the {@link Analytics}.
309     */
310    @Override
311    public Analytics getAnalytics() {
312        return mAnalytics;
313    }
314
315    /**
316     * Returns the default tracker.
317     */
318    @Override
319    public Tracker getTracker() {
320        return mTracker;
321    }
322
323    /**
324     * Returns {@link ChannelDataManager}.
325     */
326    @Override
327    public ChannelDataManager getChannelDataManager() {
328        if (mChannelDataManager == null) {
329            mChannelDataManager = new ChannelDataManager(this, getTvInputManagerHelper());
330            mChannelDataManager.start();
331        }
332        return mChannelDataManager;
333    }
334
335    @Override
336    public boolean isChannelDataManagerLoadFinished() {
337        return mChannelDataManager != null && mChannelDataManager.isDbLoadFinished();
338    }
339
340    /**
341     * Returns {@link ProgramDataManager}.
342     */
343    @Override
344    public ProgramDataManager getProgramDataManager() {
345        if (mProgramDataManager != null) {
346            return mProgramDataManager;
347        }
348        Utils.runInMainThreadAndWait(new Runnable() {
349            @Override
350            public void run() {
351                if (mProgramDataManager == null) {
352                    mProgramDataManager = new ProgramDataManager(TvApplication.this);
353                    mProgramDataManager.start();
354                }
355            }
356        });
357        return mProgramDataManager;
358    }
359
360    @Override
361    public boolean isProgramDataManagerCurrentProgramsLoadFinished() {
362        return mProgramDataManager != null && mProgramDataManager.isCurrentProgramsLoadFinished();
363    }
364
365    /**
366     * Returns {@link PreviewDataManager}.
367     */
368    @TargetApi(Build.VERSION_CODES.O)
369    @Override
370    public PreviewDataManager getPreviewDataManager() {
371        if (mPreviewDataManager == null) {
372            mPreviewDataManager = new PreviewDataManager(this);
373            mPreviewDataManager.start();
374        }
375        return mPreviewDataManager;
376    }
377
378    /**
379     * Returns {@link DvrDataManager}.
380     */
381    @TargetApi(Build.VERSION_CODES.N)
382    @Override
383    public DvrDataManager getDvrDataManager() {
384        if (mDvrDataManager == null) {
385            DvrDataManagerImpl dvrDataManager = new DvrDataManagerImpl(this, Clock.SYSTEM);
386            mDvrDataManager = dvrDataManager;
387            dvrDataManager.start();
388        }
389        return mDvrDataManager;
390    }
391
392    /**
393     * Returns {@link DvrStorageStatusManager}.
394     */
395    @TargetApi(Build.VERSION_CODES.N)
396    @Override
397    public DvrStorageStatusManager getDvrStorageStatusManager() {
398        return mDvrStorageStatusManager;
399    }
400
401    /**
402     * Returns {@link TvInputManagerHelper}.
403     */
404    @Override
405    public TvInputManagerHelper getTvInputManagerHelper() {
406        if (mTvInputManagerHelper == null) {
407            mTvInputManagerHelper = new TvInputManagerHelper(this);
408            mTvInputManagerHelper.start();
409        }
410        return mTvInputManagerHelper;
411    }
412
413    /**
414     * Returns the main activity information.
415     */
416    @Override
417    public MainActivityWrapper getMainActivityWrapper() {
418        return mMainActivityWrapper;
419    }
420
421    /**
422     * Returns the {@link AccountHelper}.
423     */
424    @Override
425    public AccountHelper getAccountHelper() {
426        if (mAccountHelper == null) {
427            mAccountHelper = new AccountHelper(getApplicationContext());
428        }
429        return mAccountHelper;
430    }
431
432    @Override
433    public RemoteConfig getRemoteConfig() {
434        if (mRemoteConfig == null) {
435            // No need to synchronize this, it does not hurt to create two and throw one away.
436            mRemoteConfig = DefaultConfigManager.createInstance(this).getRemoteConfig();
437        }
438        return mRemoteConfig;
439    }
440
441    @Override
442    public boolean isRunningInMainProcess() {
443        return mRunningInMainProcess != null && mRunningInMainProcess;
444    }
445
446    @Override
447    public PerformanceMonitor getPerformanceMonitor() {
448        if (mPerformanceMonitor == null) {
449            mPerformanceMonitor = StubPerformanceMonitor.initialize(this);
450        }
451        return mPerformanceMonitor;
452    }
453
454    /**
455     * SelectInputActivity is set in {@link SelectInputActivity#onCreate} and cleared in
456     * {@link SelectInputActivity#onDestroy}.
457     */
458    public void setSelectInputActivity(SelectInputActivity activity) {
459        mSelectInputActivity = activity;
460    }
461
462    public void handleGuideKey() {
463        if (!mMainActivityWrapper.isResumed()) {
464            startActivity(new Intent(Intent.ACTION_VIEW, TvContract.Programs.CONTENT_URI));
465        } else {
466            mMainActivityWrapper.getMainActivity().getOverlayManager().toggleProgramGuide();
467        }
468    }
469
470    /**
471     * Handles the global key KEYCODE_TV.
472     */
473    public void handleTvKey() {
474        if (!mMainActivityWrapper.isResumed()) {
475            startMainActivity(null);
476        }
477    }
478
479    /**
480     * Handles the global key KEYCODE_TV_INPUT.
481     */
482    public void handleTvInputKey() {
483        TvInputManager tvInputManager = (TvInputManager) getSystemService(Context.TV_INPUT_SERVICE);
484        List<TvInputInfo> tvInputs = tvInputManager.getTvInputList();
485        int inputCount = 0;
486        boolean hasTunerInput = false;
487        for (TvInputInfo input : tvInputs) {
488            if (input.isPassthroughInput()) {
489                if (!input.isHidden(this)) {
490                    ++inputCount;
491                }
492            } else if (!hasTunerInput) {
493                hasTunerInput = true;
494                ++inputCount;
495            }
496        }
497        if (inputCount < 2) {
498            return;
499        }
500        Activity activityToHandle = mMainActivityWrapper.isResumed()
501                ? mMainActivityWrapper.getMainActivity() : mSelectInputActivity;
502        if (activityToHandle != null) {
503            // If startActivity is called, MainActivity.onPause is unnecessarily called. To
504            // prevent it, MainActivity.dispatchKeyEvent is directly called.
505            activityToHandle.dispatchKeyEvent(
506                    new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_TV_INPUT));
507            activityToHandle.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP,
508                    KeyEvent.KEYCODE_TV_INPUT));
509        } else if (mMainActivityWrapper.isStarted()) {
510            Bundle extras = new Bundle();
511            extras.putString(Utils.EXTRA_KEY_ACTION, Utils.EXTRA_ACTION_SHOW_TV_INPUT);
512            startMainActivity(extras);
513        } else {
514            startActivity(new Intent(this, SelectInputActivity.class).setFlags(
515                    Intent.FLAG_ACTIVITY_NEW_TASK));
516        }
517    }
518
519    private void startMainActivity(Bundle extras) {
520        // The use of FLAG_ACTIVITY_NEW_TASK enables arbitrary applications to access the intent
521        // sent to the root activity. Having said that, we should be fine here since such an intent
522        // does not carry any important user data.
523        Intent intent = new Intent(this, MainActivity.class)
524                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
525        if (extras != null) {
526            intent.putExtras(extras);
527        }
528        startActivity(intent);
529    }
530
531    /**
532     * Returns the version name of the live channels.
533     *
534     * @see PackageInfo#versionName
535     */
536    public String getVersionName() {
537        return mVersionName;
538    }
539
540    /**
541     * Checks the input counts and enable/disable TvActivity. Also updates the input list in
542     * {@link SetupUtils}.
543     */
544    public void handleInputCountChanged() {
545        handleInputCountChanged(false, false, false);
546    }
547
548    /**
549     * Checks the input counts and enable/disable TvActivity. Also updates the input list in
550     * {@link SetupUtils}.
551     *
552     * @param calledByTunerServiceChanged true if it is called when TunerTvInputService
553     *        is enabled or disabled.
554     * @param tunerServiceEnabled it's available only when calledByTunerServiceChanged is true.
555     * @param dontKillApp when TvActivity is enabled or disabled by this method, the app restarts
556     *        by default. But, if dontKillApp is true, the app won't restart.
557     */
558    public void handleInputCountChanged(boolean calledByTunerServiceChanged,
559            boolean tunerServiceEnabled, boolean dontKillApp) {
560        TvInputManager inputManager = (TvInputManager) getSystemService(Context.TV_INPUT_SERVICE);
561        boolean enable = (calledByTunerServiceChanged && tunerServiceEnabled)
562                || Features.UNHIDE.isEnabled(TvApplication.this);
563        if (!enable) {
564            List<TvInputInfo> inputs = inputManager.getTvInputList();
565            boolean skipTunerInputCheck = false;
566            // Enable the TvActivity only if there is at least one tuner type input.
567            if (!skipTunerInputCheck) {
568                for (TvInputInfo input : inputs) {
569                    if (calledByTunerServiceChanged && !tunerServiceEnabled
570                            && TunerTvInputService.getInputId(this).equals(input.getId())) {
571                        continue;
572                    }
573                    if (input.getType() == TvInputInfo.TYPE_TUNER) {
574                        enable = true;
575                        break;
576                    }
577                }
578            }
579            if (DEBUG) Log.d(TAG, "Enable MainActivity: " + enable);
580        }
581        PackageManager packageManager = getPackageManager();
582        ComponentName name = new ComponentName(this, TvActivity.class);
583        int newState = enable ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED :
584                PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
585        if (packageManager.getComponentEnabledSetting(name) != newState) {
586            packageManager.setComponentEnabledSetting(name, newState,
587                    dontKillApp ? PackageManager.DONT_KILL_APP : 0);
588            Log.i(TAG, (enable ? "Un-hide" : "Hide") + " Live TV.");
589        }
590        SetupUtils.getInstance(TvApplication.this).onInputListUpdated(inputManager);
591    }
592
593    /**
594     * Returns the @{@link ApplicationSingletons} using the application context.
595     */
596    public static ApplicationSingletons getSingletons(Context context) {
597        // No need to be "synchronized" because this doesn't create any instance.
598        if (sAppSingletons == null) {
599            sAppSingletons = (ApplicationSingletons) context.getApplicationContext();
600        }
601        return sAppSingletons;
602    }
603
604    /**
605     * Sets true, if TvApplication is running on the main process. If TvApplication runs on
606     * tuner process or other process, it sets false.
607     *
608     * Note: it should be called at the beginning of Service.onCreate Activity.onCreate, or
609     * BroadcastReceiver.onCreate. When it is firstly called after launch, it runs process
610     * specific initializations.
611     */
612    public static void setCurrentRunningProcess(Context context, boolean isMainProcess) {
613        // TODO(b/63064354) TvApplication should not have to know if it is "the main process"
614        if (context.getApplicationContext() instanceof TvApplication) {
615            TvApplication tvApplication = (TvApplication) context.getApplicationContext();
616            tvApplication.setCurrentRunningProcess(isMainProcess);
617        } else {
618            // Application context can be MockTvApplication.
619            Log.w(TAG, "It is not a context of TvApplication");
620        }
621    }
622}
623