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; 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 com.android.tv.common.BaseApplication; 38 import com.android.tv.common.concurrent.NamedThreadFactory; 39 import com.android.tv.common.feature.CommonFeatures; 40 import com.android.tv.common.recording.RecordingStorageStatusManager; 41 import com.android.tv.common.ui.setup.animation.SetupAnimationHelper; 42 import com.android.tv.common.util.Clock; 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.EpgFetcherImpl; 50 import com.android.tv.dvr.DvrDataManager; 51 import com.android.tv.dvr.DvrDataManagerImpl; 52 import com.android.tv.dvr.DvrManager; 53 import com.android.tv.dvr.DvrScheduleManager; 54 import com.android.tv.dvr.DvrStorageStatusManager; 55 import com.android.tv.dvr.DvrWatchedPositionManager; 56 import com.android.tv.dvr.recorder.RecordingScheduler; 57 import com.android.tv.dvr.ui.browse.DvrBrowseActivity; 58 import com.android.tv.recommendation.ChannelPreviewUpdater; 59 import com.android.tv.recommendation.RecordedProgramPreviewUpdater; 60 import com.android.tv.tuner.TunerInputController; 61 import com.android.tv.tuner.util.TunerInputInfoUtils; 62 import com.android.tv.util.SetupUtils; 63 import com.android.tv.util.TvInputManagerHelper; 64 import com.android.tv.util.Utils; 65 import java.util.List; 66 import java.util.concurrent.Executor; 67 import java.util.concurrent.ExecutorService; 68 import java.util.concurrent.Executors; 69 70 /** 71 * Live TV application. 72 * 73 * <p>This includes all the Google specific hooks. 74 */ 75 public abstract class TvApplication extends BaseApplication implements TvSingletons, Starter { 76 private static final String TAG = "TvApplication"; 77 private static final boolean DEBUG = false; 78 79 /** Namespace for LiveChannels configs. LiveChannels configs are kept in piper. */ 80 public static final String CONFIGNS_P4 = "configns:p4"; 81 82 /** 83 * Broadcast Action: The user has updated LC to a new version that supports tuner input. {@link 84 * TunerInputController} will receive this intent to check the existence of tuner input when the 85 * new version is first launched. 86 */ 87 public static final String ACTION_APPLICATION_FIRST_LAUNCHED = 88 " com.android.tv.action.APPLICATION_FIRST_LAUNCHED"; 89 90 private static final String PREFERENCE_IS_FIRST_LAUNCH = "is_first_launch"; 91 92 private static final NamedThreadFactory THREAD_FACTORY = new NamedThreadFactory("tv-app-db"); 93 private static final ExecutorService DB_EXECUTOR = 94 Executors.newSingleThreadExecutor(THREAD_FACTORY); 95 96 private String mVersionName = ""; 97 98 private final MainActivityWrapper mMainActivityWrapper = new MainActivityWrapper(); 99 100 private SelectInputActivity mSelectInputActivity; 101 private ChannelDataManager mChannelDataManager; 102 private volatile ProgramDataManager mProgramDataManager; 103 private PreviewDataManager mPreviewDataManager; 104 private DvrManager mDvrManager; 105 private DvrScheduleManager mDvrScheduleManager; 106 private DvrDataManager mDvrDataManager; 107 private DvrWatchedPositionManager mDvrWatchedPositionManager; 108 private RecordingScheduler mRecordingScheduler; 109 private RecordingStorageStatusManager mDvrStorageStatusManager; 110 @Nullable private InputSessionManager mInputSessionManager; 111 // STOP-SHIP: Remove this variable when Tuner Process is split to another application. 112 // When this variable is null, we don't know in which process TvApplication runs. 113 private Boolean mRunningInMainProcess; 114 private TvInputManagerHelper mTvInputManagerHelper; 115 private boolean mStarted; 116 private EpgFetcher mEpgFetcher; 117 private TunerInputController mTunerInputController; 118 119 @Override 120 public void onCreate() { 121 super.onCreate(); 122 SharedPreferencesUtils.initialize( 123 this, 124 new Runnable() { 125 @Override 126 public void run() { 127 if (mRunningInMainProcess != null && mRunningInMainProcess) { 128 checkTunerServiceOnFirstLaunch(); 129 } 130 } 131 }); 132 try { 133 PackageInfo pInfo = getPackageManager().getPackageInfo(getPackageName(), 0); 134 mVersionName = pInfo.versionName; 135 } catch (PackageManager.NameNotFoundException e) { 136 Log.w(TAG, "Unable to find package '" + getPackageName() + "'.", e); 137 mVersionName = ""; 138 } 139 Log.i(TAG, "Starting Live TV " + getVersionName()); 140 141 // In SetupFragment, transitions are set in the constructor. Because the fragment can be 142 // created in Activity.onCreate() by the framework, SetupAnimationHelper should be 143 // initialized here before Activity.onCreate() is called. 144 mEpgFetcher = EpgFetcherImpl.create(this); 145 SetupAnimationHelper.initialize(this); 146 getTvInputManagerHelper(); 147 148 Log.i(TAG, "Started Live TV " + mVersionName); 149 Debug.getTimer(Debug.TAG_START_UP_TIMER).log("finish TvApplication.onCreate"); 150 } 151 152 /** Initializes application. It is a noop if called twice. */ 153 @Override 154 public void start() { 155 if (mStarted) { 156 return; 157 } 158 mStarted = true; 159 mRunningInMainProcess = true; 160 Debug.getTimer(Debug.TAG_START_UP_TIMER).log("start TvApplication.start"); 161 if (mRunningInMainProcess) { 162 getTvInputManagerHelper() 163 .addCallback( 164 new TvInputCallback() { 165 @Override 166 public void onInputAdded(String inputId) { 167 if (TvFeatures.TUNER.isEnabled(TvApplication.this) 168 && TextUtils.equals( 169 inputId, getEmbeddedTunerInputId())) { 170 TunerInputInfoUtils.updateTunerInputInfo( 171 TvApplication.this); 172 } 173 handleInputCountChanged(); 174 } 175 176 @Override 177 public void onInputRemoved(String inputId) { 178 handleInputCountChanged(); 179 } 180 }); 181 if (TvFeatures.TUNER.isEnabled(this)) { 182 // If the tuner input service is added before the app is started, we need to 183 // handle it here. 184 TunerInputInfoUtils.updateTunerInputInfo(TvApplication.this); 185 } 186 if (CommonFeatures.DVR.isEnabled(this)) { 187 mDvrScheduleManager = new DvrScheduleManager(this); 188 mDvrManager = new DvrManager(this); 189 mRecordingScheduler = RecordingScheduler.createScheduler(this); 190 } 191 mEpgFetcher.startRoutineService(); 192 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 193 ChannelPreviewUpdater.getInstance(this).startRoutineService(); 194 RecordedProgramPreviewUpdater.getInstance(this) 195 .updatePreviewDataForRecordedPrograms(); 196 } 197 } 198 Debug.getTimer(Debug.TAG_START_UP_TIMER).log("finish TvApplication.start"); 199 } 200 201 private void checkTunerServiceOnFirstLaunch() { 202 SharedPreferences sharedPreferences = 203 this.getSharedPreferences( 204 SharedPreferencesUtils.SHARED_PREF_FEATURES, Context.MODE_PRIVATE); 205 boolean isFirstLaunch = sharedPreferences.getBoolean(PREFERENCE_IS_FIRST_LAUNCH, true); 206 if (isFirstLaunch) { 207 if (DEBUG) Log.d(TAG, "Congratulations, it's the first launch!"); 208 getTunerInputController() 209 .onCheckingUsbTunerStatus(this, ACTION_APPLICATION_FIRST_LAUNCHED); 210 SharedPreferences.Editor editor = sharedPreferences.edit(); 211 editor.putBoolean(PREFERENCE_IS_FIRST_LAUNCH, false); 212 editor.apply(); 213 } 214 } 215 216 @Override 217 public EpgFetcher getEpgFetcher() { 218 return mEpgFetcher; 219 } 220 221 @Override 222 public synchronized SetupUtils getSetupUtils() { 223 return SetupUtils.createForTvSingletons(this); 224 } 225 226 /** Returns the {@link DvrManager}. */ 227 @Override 228 public DvrManager getDvrManager() { 229 return mDvrManager; 230 } 231 232 /** Returns the {@link DvrScheduleManager}. */ 233 @Override 234 public DvrScheduleManager getDvrScheduleManager() { 235 return mDvrScheduleManager; 236 } 237 238 /** Returns the {@link RecordingScheduler}. */ 239 @Override 240 @Nullable 241 public RecordingScheduler getRecordingScheduler() { 242 return mRecordingScheduler; 243 } 244 245 /** Returns the {@link DvrWatchedPositionManager}. */ 246 @Override 247 public DvrWatchedPositionManager getDvrWatchedPositionManager() { 248 if (mDvrWatchedPositionManager == null) { 249 mDvrWatchedPositionManager = new DvrWatchedPositionManager(this); 250 } 251 return mDvrWatchedPositionManager; 252 } 253 254 @Override 255 @TargetApi(Build.VERSION_CODES.N) 256 public InputSessionManager getInputSessionManager() { 257 if (mInputSessionManager == null) { 258 mInputSessionManager = new InputSessionManager(this); 259 } 260 return mInputSessionManager; 261 } 262 263 /** Returns {@link ChannelDataManager}. */ 264 @Override 265 public ChannelDataManager getChannelDataManager() { 266 if (mChannelDataManager == null) { 267 mChannelDataManager = new ChannelDataManager(this, getTvInputManagerHelper()); 268 mChannelDataManager.start(); 269 } 270 return mChannelDataManager; 271 } 272 273 @Override 274 public boolean isChannelDataManagerLoadFinished() { 275 return mChannelDataManager != null && mChannelDataManager.isDbLoadFinished(); 276 } 277 278 /** Returns {@link ProgramDataManager}. */ 279 @Override 280 public ProgramDataManager getProgramDataManager() { 281 if (mProgramDataManager != null) { 282 return mProgramDataManager; 283 } 284 Utils.runInMainThreadAndWait( 285 new Runnable() { 286 @Override 287 public void run() { 288 if (mProgramDataManager == null) { 289 mProgramDataManager = new ProgramDataManager(TvApplication.this); 290 mProgramDataManager.start(); 291 } 292 } 293 }); 294 return mProgramDataManager; 295 } 296 297 @Override 298 public boolean isProgramDataManagerCurrentProgramsLoadFinished() { 299 return mProgramDataManager != null && mProgramDataManager.isCurrentProgramsLoadFinished(); 300 } 301 302 /** Returns {@link PreviewDataManager}. */ 303 @TargetApi(Build.VERSION_CODES.O) 304 @Override 305 public PreviewDataManager getPreviewDataManager() { 306 if (mPreviewDataManager == null) { 307 mPreviewDataManager = new PreviewDataManager(this); 308 mPreviewDataManager.start(); 309 } 310 return mPreviewDataManager; 311 } 312 313 /** Returns {@link DvrDataManager}. */ 314 @TargetApi(Build.VERSION_CODES.N) 315 @Override 316 public DvrDataManager getDvrDataManager() { 317 if (mDvrDataManager == null) { 318 DvrDataManagerImpl dvrDataManager = new DvrDataManagerImpl(this, Clock.SYSTEM); 319 mDvrDataManager = dvrDataManager; 320 dvrDataManager.start(); 321 } 322 return mDvrDataManager; 323 } 324 325 @Override 326 @TargetApi(Build.VERSION_CODES.N) 327 public RecordingStorageStatusManager getRecordingStorageStatusManager() { 328 if (mDvrStorageStatusManager == null) { 329 mDvrStorageStatusManager = new DvrStorageStatusManager(this); 330 } 331 return mDvrStorageStatusManager; 332 } 333 334 /** Returns the main activity information. */ 335 @Override 336 public MainActivityWrapper getMainActivityWrapper() { 337 return mMainActivityWrapper; 338 } 339 340 /** Returns {@link TvInputManagerHelper}. */ 341 @Override 342 public TvInputManagerHelper getTvInputManagerHelper() { 343 if (mTvInputManagerHelper == null) { 344 mTvInputManagerHelper = new TvInputManagerHelper(this); 345 mTvInputManagerHelper.start(); 346 } 347 return mTvInputManagerHelper; 348 } 349 350 @Override 351 public synchronized TunerInputController getTunerInputController() { 352 if (mTunerInputController == null) { 353 mTunerInputController = 354 new TunerInputController( 355 ComponentName.unflattenFromString(getEmbeddedTunerInputId())); 356 } 357 return mTunerInputController; 358 } 359 360 @Override 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 */ 369 public void setSelectInputActivity(SelectInputActivity activity) { 370 mSelectInputActivity = activity; 371 } 372 373 public void handleGuideKey() { 374 if (!mMainActivityWrapper.isResumed()) { 375 startActivity(new Intent(Intent.ACTION_VIEW, TvContract.Programs.CONTENT_URI)); 376 } else { 377 mMainActivityWrapper.getMainActivity().getOverlayManager().toggleProgramGuide(); 378 } 379 } 380 381 /** Handles the global key KEYCODE_TV. */ 382 public void handleTvKey() { 383 if (!mMainActivityWrapper.isResumed()) { 384 startMainActivity(null); 385 } 386 } 387 388 /** Handles the global key KEYCODE_DVR. */ 389 public void handleDvrKey() { 390 startActivity(new Intent(this, DvrBrowseActivity.class)); 391 } 392 393 /** Handles the global key KEYCODE_TV_INPUT. */ 394 public void handleTvInputKey() { 395 TvInputManager tvInputManager = (TvInputManager) getSystemService(Context.TV_INPUT_SERVICE); 396 List<TvInputInfo> tvInputs = tvInputManager.getTvInputList(); 397 int inputCount = 0; 398 boolean hasTunerInput = false; 399 for (TvInputInfo input : tvInputs) { 400 if (input.isPassthroughInput()) { 401 if (!input.isHidden(this)) { 402 ++inputCount; 403 } 404 } else if (!hasTunerInput) { 405 hasTunerInput = true; 406 ++inputCount; 407 } 408 } 409 if (inputCount < 2) { 410 return; 411 } 412 Activity activityToHandle = 413 mMainActivityWrapper.isResumed() 414 ? mMainActivityWrapper.getMainActivity() 415 : mSelectInputActivity; 416 if (activityToHandle != null) { 417 // If startActivity is called, MainActivity.onPause is unnecessarily called. To 418 // prevent it, MainActivity.dispatchKeyEvent is directly called. 419 activityToHandle.dispatchKeyEvent( 420 new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_TV_INPUT)); 421 activityToHandle.dispatchKeyEvent( 422 new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_TV_INPUT)); 423 } else if (mMainActivityWrapper.isStarted()) { 424 Bundle extras = new Bundle(); 425 extras.putString(Utils.EXTRA_KEY_ACTION, Utils.EXTRA_ACTION_SHOW_TV_INPUT); 426 startMainActivity(extras); 427 } else { 428 startActivity( 429 new Intent(this, SelectInputActivity.class) 430 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); 431 } 432 } 433 434 private void startMainActivity(Bundle extras) { 435 // The use of FLAG_ACTIVITY_NEW_TASK enables arbitrary applications to access the intent 436 // sent to the root activity. Having said that, we should be fine here since such an intent 437 // does not carry any important user data. 438 Intent intent = 439 new Intent(this, MainActivity.class).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 440 if (extras != null) { 441 intent.putExtras(extras); 442 } 443 startActivity(intent); 444 } 445 446 /** 447 * Returns the version name of the live channels. 448 * 449 * @see PackageInfo#versionName 450 */ 451 public String getVersionName() { 452 return mVersionName; 453 } 454 455 /** 456 * Checks the input counts and enable/disable TvActivity. Also upda162 the input list in {@link 457 * SetupUtils}. 458 */ 459 @Override 460 public void handleInputCountChanged() { 461 handleInputCountChanged(false, false, false); 462 } 463 464 /** 465 * Checks the input counts and enable/disable TvActivity. Also updates the input list in {@link 466 * SetupUtils}. 467 * 468 * @param calledByTunerServiceChanged true if it is called when BaseTunerTvInputService is 469 * enabled or disabled. 470 * @param tunerServiceEnabled it's available only when calledByTunerServiceChanged is true. 471 * @param dontKillApp when TvActivity is enabled or disabled by this method, the app restarts by 472 * default. But, if dontKillApp is true, the app won't restart. 473 */ 474 public void handleInputCountChanged( 475 boolean calledByTunerServiceChanged, boolean tunerServiceEnabled, boolean dontKillApp) { 476 TvInputManager inputManager = (TvInputManager) getSystemService(Context.TV_INPUT_SERVICE); 477 boolean enable = 478 (calledByTunerServiceChanged && tunerServiceEnabled) 479 || TvFeatures.UNHIDE.isEnabled(TvApplication.this); 480 if (!enable) { 481 List<TvInputInfo> inputs = inputManager.getTvInputList(); 482 boolean skipTunerInputCheck = false; 483 // Enable the TvActivity only if there is at least one tuner type input. 484 if (!skipTunerInputCheck) { 485 for (TvInputInfo input : inputs) { 486 if (calledByTunerServiceChanged 487 && !tunerServiceEnabled 488 && getEmbeddedTunerInputId().equals(input.getId())) { 489 continue; 490 } 491 if (input.getType() == TvInputInfo.TYPE_TUNER) { 492 enable = true; 493 break; 494 } 495 } 496 } 497 if (DEBUG) Log.d(TAG, "Enable MainActivity: " + enable); 498 } 499 PackageManager packageManager = getPackageManager(); 500 ComponentName name = new ComponentName(this, TvActivity.class); 501 int newState = 502 enable 503 ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED 504 : PackageManager.COMPONENT_ENABLED_STATE_DISABLED; 505 if (packageManager.getComponentEnabledSetting(name) != newState) { 506 packageManager.setComponentEnabledSetting( 507 name, newState, dontKillApp ? PackageManager.DONT_KILL_APP : 0); 508 Log.i(TAG, (enable ? "Un-hide" : "Hide") + " Live TV."); 509 } 510 getSetupUtils().onInputListUpdated(inputManager); 511 } 512 513 @Override 514 public Executor getDbExecutor() { 515 return DB_EXECUTOR; 516 } 517 } 518