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