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.app.Activity; 20 import android.content.ActivityNotFoundException; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.media.tv.TvInputInfo; 24 import android.os.Bundle; 25 import android.os.Handler; 26 import android.os.Looper; 27 import android.support.annotation.MainThread; 28 import android.util.Log; 29 import com.android.tv.common.SoftPreconditions; 30 import com.android.tv.common.actions.InputSetupActionUtils; 31 import com.android.tv.common.experiments.Experiments; 32 import com.android.tv.data.ChannelDataManager; 33 import com.android.tv.data.ChannelDataManager.Listener; 34 import com.android.tv.data.epg.EpgFetcher; 35 import com.android.tv.data.epg.EpgInputWhiteList; 36 import com.android.tv.util.SetupUtils; 37 import com.android.tv.util.TvInputManagerHelper; 38 import com.android.tv.util.Utils; 39 import com.google.android.tv.partner.support.EpgContract; 40 import java.util.concurrent.TimeUnit; 41 42 /** 43 * An activity to launch a TV input setup activity. 44 * 45 * <p>After setup activity is finished, all channels will be browsable. 46 */ 47 public class SetupPassthroughActivity extends Activity { 48 private static final String TAG = "SetupPassthroughAct"; 49 private static final boolean DEBUG = false; 50 51 private static final int REQUEST_START_SETUP_ACTIVITY = 200; 52 53 private static ScanTimeoutMonitor sScanTimeoutMonitor; 54 55 private TvInputInfo mTvInputInfo; 56 private Intent mActivityAfterCompletion; 57 private boolean mEpgFetcherDuringScan; 58 private EpgInputWhiteList mEpgInputWhiteList; 59 60 @Override onCreate(Bundle savedInstanceState)61 public void onCreate(Bundle savedInstanceState) { 62 if (DEBUG) Log.d(TAG, "onCreate"); 63 super.onCreate(savedInstanceState); 64 TvSingletons tvSingletons = TvSingletons.getSingletons(this); 65 TvInputManagerHelper inputManager = tvSingletons.getTvInputManagerHelper(); 66 Intent intent = getIntent(); 67 String inputId = intent.getStringExtra(InputSetupActionUtils.EXTRA_INPUT_ID); 68 mTvInputInfo = inputManager.getTvInputInfo(inputId); 69 mEpgInputWhiteList = new EpgInputWhiteList(tvSingletons.getRemoteConfig()); 70 mActivityAfterCompletion = InputSetupActionUtils.getExtraActivityAfter(intent); 71 boolean needToFetchEpg = 72 mTvInputInfo != null 73 && Utils.isInternalTvInput(this, mTvInputInfo.getId()) 74 && Experiments.CLOUD_EPG.get(); 75 if (needToFetchEpg) { 76 // In case when the activity is restored, this flag should be restored as well. 77 mEpgFetcherDuringScan = true; 78 } 79 if (savedInstanceState == null) { 80 SoftPreconditions.checkArgument( 81 InputSetupActionUtils.hasInputSetupAction(intent), 82 TAG, 83 "Unsupported action %s", 84 intent.getAction()); 85 if (DEBUG) Log.d(TAG, "TvInputId " + inputId + " / TvInputInfo " + mTvInputInfo); 86 if (mTvInputInfo == null) { 87 Log.w(TAG, "There is no input with the ID " + inputId + "."); 88 finish(); 89 return; 90 } 91 if (intent.getExtras() == null) { 92 Log.w(TAG, "There is no extra info in the intent"); 93 finish(); 94 return; 95 } 96 Intent setupIntent = InputSetupActionUtils.getExtraSetupIntent(intent); 97 if (DEBUG) Log.d(TAG, "Setup activity launch intent: " + setupIntent); 98 if (setupIntent == null) { 99 Log.w(TAG, "The input (" + mTvInputInfo.getId() + ") doesn't have setup."); 100 finish(); 101 return; 102 } 103 SetupUtils.grantEpgPermission(this, mTvInputInfo.getServiceInfo().packageName); 104 if (DEBUG) Log.d(TAG, "Activity after completion " + mActivityAfterCompletion); 105 // If EXTRA_SETUP_INTENT is not removed, an infinite recursion happens during 106 // setupIntent.putExtras(intent.getExtras()). 107 Bundle extras = intent.getExtras(); 108 InputSetupActionUtils.removeSetupIntent(extras); 109 setupIntent.putExtras(extras); 110 try { 111 startActivityForResult(setupIntent, REQUEST_START_SETUP_ACTIVITY); 112 } catch (ActivityNotFoundException e) { 113 Log.e(TAG, "Can't find activity: " + setupIntent.getComponent()); 114 finish(); 115 return; 116 } 117 if (needToFetchEpg) { 118 if (sScanTimeoutMonitor == null) { 119 sScanTimeoutMonitor = new ScanTimeoutMonitor(this); 120 } 121 sScanTimeoutMonitor.startMonitoring(); 122 TvSingletons.getSingletons(this).getEpgFetcher().onChannelScanStarted(); 123 } 124 } 125 } 126 127 @Override onActivityResult(int requestCode, final int resultCode, final Intent data)128 public void onActivityResult(int requestCode, final int resultCode, final Intent data) { 129 if (DEBUG) 130 Log.d(TAG, "onActivityResult(" + requestCode + ", " + resultCode + ", " + data + ")"); 131 if (sScanTimeoutMonitor != null) { 132 sScanTimeoutMonitor.stopMonitoring(); 133 } 134 // Note: It's not guaranteed that this method is always called after scanning. 135 boolean setupComplete = 136 requestCode == REQUEST_START_SETUP_ACTIVITY && resultCode == Activity.RESULT_OK; 137 // Tells EpgFetcher that channel source setup is finished. 138 EpgFetcher epgFetcher = TvSingletons.getSingletons(this).getEpgFetcher(); 139 if (mEpgFetcherDuringScan) { 140 epgFetcher.onChannelScanFinished(); 141 } 142 if (!setupComplete) { 143 setResult(resultCode, data); 144 finish(); 145 return; 146 } 147 TvSingletons.getSingletons(this) 148 .getSetupUtils() 149 .onTvInputSetupFinished( 150 mTvInputInfo.getId(), 151 new Runnable() { 152 @Override 153 public void run() { 154 if (mActivityAfterCompletion != null) { 155 try { 156 startActivity(mActivityAfterCompletion); 157 } catch (ActivityNotFoundException e) { 158 Log.w(TAG, "Activity launch failed", e); 159 } 160 } 161 setResult(resultCode, data); 162 finish(); 163 } 164 }); 165 } 166 167 /** 168 * Monitors the scan progress and notifies the timeout of the scanning. The purpose of this 169 * monitor is to call EpgFetcher.onChannelScanFinished() in case when 170 * SetupPassthroughActivity.onActivityResult() is not called properly. b/36008534 171 */ 172 @MainThread 173 private static class ScanTimeoutMonitor { 174 // Set timeout long enough. The message in Sony TV says the scanning takes about 30 minutes. 175 private static final long SCAN_TIMEOUT_MS = TimeUnit.MINUTES.toMillis(30); 176 177 private final Context mContext; 178 private final ChannelDataManager mChannelDataManager; 179 private final Handler mHandler = new Handler(Looper.getMainLooper()); 180 private final Runnable mScanTimeoutRunnable = 181 new Runnable() { 182 @Override 183 public void run() { 184 Log.w( 185 TAG, 186 "No channels has been added for a while." 187 + " The scan might have finished unexpectedly."); 188 onScanTimedOut(); 189 } 190 }; 191 private final Listener mChannelDataManagerListener = 192 new Listener() { 193 @Override 194 public void onLoadFinished() { 195 setupTimer(); 196 } 197 198 @Override 199 public void onChannelListUpdated() { 200 setupTimer(); 201 } 202 203 @Override 204 public void onChannelBrowsableChanged() {} 205 }; 206 private boolean mStarted; 207 ScanTimeoutMonitor(Context context)208 private ScanTimeoutMonitor(Context context) { 209 mContext = context.getApplicationContext(); 210 mChannelDataManager = TvSingletons.getSingletons(context).getChannelDataManager(); 211 } 212 startMonitoring()213 private void startMonitoring() { 214 if (!mStarted) { 215 mStarted = true; 216 mChannelDataManager.addListener(mChannelDataManagerListener); 217 } 218 if (mChannelDataManager.isDbLoadFinished()) { 219 setupTimer(); 220 } 221 } 222 stopMonitoring()223 private void stopMonitoring() { 224 if (mStarted) { 225 mStarted = false; 226 mHandler.removeCallbacks(mScanTimeoutRunnable); 227 mChannelDataManager.removeListener(mChannelDataManagerListener); 228 } 229 } 230 setupTimer()231 private void setupTimer() { 232 mHandler.removeCallbacks(mScanTimeoutRunnable); 233 mHandler.postDelayed(mScanTimeoutRunnable, SCAN_TIMEOUT_MS); 234 } 235 onScanTimedOut()236 private void onScanTimedOut() { 237 stopMonitoring(); 238 TvSingletons.getSingletons(mContext).getEpgFetcher().onChannelScanFinished(); 239 } 240 } 241 } 242