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