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