• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 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.tuner.sample.network.setup;
18 
19 import android.app.FragmentManager;
20 import android.content.ContentResolver;
21 import android.content.ContentValues;
22 import android.content.Intent;
23 import android.content.pm.PackageManager;
24 import android.media.tv.TvInputInfo;
25 import android.os.AsyncTask;
26 import android.os.Bundle;
27 import android.os.Handler;
28 import android.support.annotation.Nullable;
29 import android.text.TextUtils;
30 import android.util.Log;
31 import android.util.Pair;
32 import android.view.KeyEvent;
33 import com.android.tv.common.feature.CommonFeatures;
34 import com.android.tv.common.singletons.HasSingletons;
35 import com.android.tv.common.ui.setup.SetupFragment;
36 import com.android.tv.common.ui.setup.SetupMultiPaneFragment;
37 import com.android.tv.common.util.PostalCodeUtils;
38 import com.android.tv.tuner.sample.network.R;
39 import com.android.tv.tuner.setup.BaseTunerSetupActivity;
40 import com.android.tv.tuner.setup.ConnectionTypeFragment;
41 import com.android.tv.tuner.setup.LineupFragment;
42 import com.android.tv.tuner.setup.LocationFragment;
43 import com.android.tv.tuner.setup.PostalCodeFragment;
44 import com.android.tv.tuner.setup.ScanFragment;
45 import com.android.tv.tuner.setup.ScanResultFragment;
46 import com.android.tv.tuner.setup.WelcomeFragment;
47 import com.android.tv.tuner.singletons.TunerSingletons;
48 import com.google.android.tv.partner.support.EpgContract;
49 import com.google.android.tv.partner.support.EpgInput;
50 import com.google.android.tv.partner.support.EpgInputs;
51 import com.google.android.tv.partner.support.Lineup;
52 import com.google.android.tv.partner.support.Lineups;
53 import com.google.android.tv.partner.support.TunerSetupUtils;
54 import dagger.android.ContributesAndroidInjector;
55 import java.util.ArrayList;
56 import java.util.List;
57 
58 /** An activity that serves Live TV tuner setup process. */
59 public class SampleNetworkTunerSetupActivity extends BaseTunerSetupActivity {
60     private static final String TAG = "SampleNetworkTunerSetupActivity";
61     private static final boolean DEBUG = false;
62 
63     private static final int FETCH_LINEUP_TIMEOUT_MS = 10000; // 10 seconds
64     private static final int FETCH_LINEUP_RETRY_TIMEOUT_MS = 20000; // 20 seconds
65     private static final String OTAD_PREFIX = "OTAD";
66     private static final String STRING_BROADCAST_DIGITAL = "Broadcast Digital";
67 
68     private LineupFragment currentLineupFragment;
69 
70     private List<String> channelNumbers;
71     private List<Lineup> lineups;
72     private Lineup selectedLineup;
73     private List<Pair<Lineup, Integer>> lineupMatchCountPair;
74     private FetchLineupTask fetchLineupTask;
75     private EpgInput epgInput;
76     private String postalCode;
77     private final Handler handler = new Handler();
78     private final Runnable cancelFetchLineupTaskRunnable = this::cancelFetchLineup;
79     private String embeddedInputId;
80 
81     @Override
onCreate(Bundle savedInstanceState)82     protected void onCreate(Bundle savedInstanceState) {
83         super.onCreate(savedInstanceState);
84         if (DEBUG) {
85             Log.d(TAG, "onCreate");
86         }
87         embeddedInputId =
88                 HasSingletons.get(TunerSingletons.class, getApplicationContext())
89                         .getEmbeddedTunerInputId();
90         new QueryEpgInputTask(embeddedInputId).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
91     }
92 
93     @Override
executeGetTunerTypeAndCountAsyncTask()94     protected void executeGetTunerTypeAndCountAsyncTask() {
95         new AsyncTask<Void, Void, Integer>() {
96             @Override
97             protected Integer doInBackground(Void... arg0) {
98                 return mTunerFactory.getTunerTypeAndCount(SampleNetworkTunerSetupActivity.this)
99                         .first;
100             }
101 
102             @Override
103             protected void onPostExecute(Integer result) {
104                 if (!SampleNetworkTunerSetupActivity.this.isDestroyed()) {
105                     mTunerType = result;
106                     if (result == null) {
107                         finish();
108                     } else if (!mActivityStopped) {
109                         showInitialFragment();
110                     } else {
111                         mPendingShowInitialFragment = true;
112                     }
113                 }
114             }
115         }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
116     }
117 
118     @Override
executeAction(String category, int actionId, Bundle params)119     protected boolean executeAction(String category, int actionId, Bundle params) {
120         switch (category) {
121             case WelcomeFragment.ACTION_CATEGORY:
122                 switch (actionId) {
123                     case SetupMultiPaneFragment.ACTION_DONE:
124                         super.executeAction(category, actionId, params);
125                         break;
126                     default:
127                         String postalCode = PostalCodeUtils.getLastPostalCode(this);
128                         boolean needLocation =
129                                 CommonFeatures.ENABLE_CLOUD_EPG_REGION.isEnabled(
130                                                 getApplicationContext())
131                                         && TextUtils.isEmpty(postalCode);
132                         if (needLocation
133                                 && checkSelfPermission(
134                                                 android.Manifest.permission.ACCESS_COARSE_LOCATION)
135                                         != PackageManager.PERMISSION_GRANTED) {
136                             showLocationFragment();
137                         } else if (mNeedToShowPostalCodeFragment || needLocation) {
138                             // We cannot get postal code automatically. Postal code input fragment
139                             // should always be shown even if users have input some valid postal
140                             // code in this activity before.
141                             mNeedToShowPostalCodeFragment = true;
142                             showPostalCodeFragment();
143                         } else {
144                             lineups = null;
145                             selectedLineup = null;
146                             this.postalCode = postalCode;
147                             restartFetchLineupTask();
148                             showConnectionTypeFragment();
149                         }
150                         break;
151                 }
152                 return true;
153             case LocationFragment.ACTION_CATEGORY:
154                 switch (actionId) {
155                     case LocationFragment.ACTION_ALLOW_PERMISSION:
156                         String postalCode =
157                                 params == null
158                                         ? null
159                                         : params.getString(LocationFragment.KEY_POSTAL_CODE);
160                         if (postalCode == null) {
161                             showPostalCodeFragment();
162                         } else {
163                             this.postalCode = postalCode;
164                             restartFetchLineupTask();
165                             showConnectionTypeFragment();
166                         }
167                         break;
168                     default:
169                         cancelFetchLineup();
170                         showConnectionTypeFragment();
171                 }
172                 return true;
173             case PostalCodeFragment.ACTION_CATEGORY:
174                 lineups = null;
175                 selectedLineup = null;
176                 switch (actionId) {
177                     case SetupMultiPaneFragment.ACTION_DONE:
178                         String postalCode = params.getString(PostalCodeFragment.KEY_POSTAL_CODE);
179                         if (postalCode != null) {
180                             this.postalCode = postalCode;
181                             restartFetchLineupTask();
182                         }
183                         // fall through
184                     case SetupMultiPaneFragment.ACTION_SKIP:
185                         showConnectionTypeFragment();
186                         break;
187                     default: // fall out
188                 }
189                 return true;
190             case ConnectionTypeFragment.ACTION_CATEGORY:
191                 channelNumbers = null;
192                 lineupMatchCountPair = null;
193                 return super.executeAction(category, actionId, params);
194             case ScanFragment.ACTION_CATEGORY:
195                 switch (actionId) {
196                     case ScanFragment.ACTION_CANCEL:
197                         getFragmentManager().popBackStack();
198                         return true;
199                     case ScanFragment.ACTION_FINISH:
200                         clearTunerHal();
201                         channelNumbers =
202                                 params.getStringArrayList(ScanFragment.KEY_CHANNEL_NUMBERS);
203                         selectedLineup = null;
204                         if (CommonFeatures.ENABLE_CLOUD_EPG_REGION.isEnabled(
205                                         getApplicationContext())
206                                 && channelNumbers != null
207                                 && !channelNumbers.isEmpty()
208                                 && !TextUtils.isEmpty(this.postalCode)) {
209                             showLineupFragment();
210                         } else {
211                             showScanResultFragment();
212                         }
213                         return true;
214                     default: // fall out
215                 }
216                 break;
217             case LineupFragment.ACTION_CATEGORY:
218                 switch (actionId) {
219                     case LineupFragment.ACTION_SKIP:
220                         selectedLineup = null;
221                         currentLineupFragment = null;
222                         showScanResultFragment();
223                         break;
224                     case LineupFragment.ACTION_ID_RETRY:
225                         currentLineupFragment.onRetry();
226                         restartFetchLineupTask();
227                         handler.postDelayed(
228                                 cancelFetchLineupTaskRunnable, FETCH_LINEUP_RETRY_TIMEOUT_MS);
229                         break;
230                     default:
231                         if (actionId >= 0 && actionId < lineupMatchCountPair.size()) {
232                             if (DEBUG) {
233                                 if (selectedLineup != null) {
234                                     Log.d(
235                                             TAG,
236                                             "Lineup " + selectedLineup.getName() + " is selected.");
237                                 }
238                             }
239                             selectedLineup = lineupMatchCountPair.get(actionId).first;
240                         }
241                         currentLineupFragment = null;
242                         showScanResultFragment();
243                         break;
244                 }
245                 return true;
246             case ScanResultFragment.ACTION_CATEGORY:
247                 switch (actionId) {
248                     case SetupMultiPaneFragment.ACTION_DONE:
249                         new InsertOrModifyEpgInputTask(selectedLineup, embeddedInputId)
250                                 .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
251                         break;
252                     default:
253                         // scan again
254                         if (lineups == null || lineups.isEmpty()) {
255                             lineups = null;
256                             restartFetchLineupTask();
257                         }
258                         super.executeAction(category, actionId, params);
259                         break;
260                 }
261                 return true;
262             default: // fall out
263         }
264         return false;
265     }
266 
267     @Override
onKeyUp(int keyCode, KeyEvent event)268     public boolean onKeyUp(int keyCode, KeyEvent event) {
269         if (keyCode == KeyEvent.KEYCODE_BACK) {
270             FragmentManager manager = getFragmentManager();
271             int count = manager.getBackStackEntryCount();
272             if (count > 0) {
273                 String lastTag = manager.getBackStackEntryAt(count - 1).getName();
274                 if (LineupFragment.class.getCanonicalName().equals(lastTag) && count >= 2) {
275                     // Pops fragment including ScanFragment.
276                     manager.popBackStack(
277                             manager.getBackStackEntryAt(count - 2).getName(),
278                             FragmentManager.POP_BACK_STACK_INCLUSIVE);
279                     return true;
280                 }
281                 if (ScanResultFragment.class.getCanonicalName().equals(lastTag) && count >= 2) {
282                     String secondLastTag = manager.getBackStackEntryAt(count - 2).getName();
283                     if (ScanFragment.class.getCanonicalName().equals(secondLastTag)) {
284                         // Pops fragment including ScanFragment.
285                         manager.popBackStack(
286                                 secondLastTag, FragmentManager.POP_BACK_STACK_INCLUSIVE);
287                         return true;
288                     }
289                     if (LineupFragment.class.getCanonicalName().equals(secondLastTag)) {
290                         currentLineupFragment =
291                                 (LineupFragment) manager.findFragmentByTag(secondLastTag);
292                         if (lineups == null || lineups.isEmpty()) {
293                             lineups = null;
294                             restartFetchLineupTask();
295                         }
296                     }
297                 } else if (ScanFragment.class.getCanonicalName().equals(lastTag)) {
298                     mLastScanFragment.finishScan(true);
299                     return true;
300                 }
301             }
302         }
303         return super.onKeyUp(keyCode, event);
304     }
305 
showLineupFragment()306     private void showLineupFragment() {
307         if (lineupMatchCountPair == null && lineups != null) {
308             lineupMatchCountPair = TunerSetupUtils.lineupChannelMatchCount(lineups, channelNumbers);
309         }
310         currentLineupFragment = new LineupFragment();
311         currentLineupFragment.setArguments(getArgsForLineupFragment());
312         currentLineupFragment.setShortDistance(
313                 SetupFragment.FRAGMENT_ENTER_TRANSITION | SetupFragment.FRAGMENT_RETURN_TRANSITION);
314         handler.removeCallbacksAndMessages(null);
315         showFragment(currentLineupFragment, true);
316         handler.postDelayed(cancelFetchLineupTaskRunnable, FETCH_LINEUP_TIMEOUT_MS);
317     }
318 
getArgsForLineupFragment()319     private Bundle getArgsForLineupFragment() {
320         Bundle args = new Bundle();
321         if (lineupMatchCountPair == null) {
322             return args;
323         }
324         ArrayList<String> lineupNames = new ArrayList<>(lineupMatchCountPair.size());
325         ArrayList<Integer> matchNumbers = new ArrayList<>(lineupMatchCountPair.size());
326         int defaultLineupIndex = 0;
327         for (Pair<Lineup, Integer> pair : lineupMatchCountPair) {
328             Lineup lineup = pair.first;
329             String name;
330             if (!TextUtils.isEmpty(lineup.getName())) {
331                 name = lineup.getName();
332             } else {
333                 name = lineup.getId();
334             }
335             if (name.equals(OTAD_PREFIX + postalCode) || name.equals(STRING_BROADCAST_DIGITAL)) {
336                 // rename OTA / antenna lineups
337                 name = getString(R.string.ut_lineup_name_antenna);
338             }
339             lineupNames.add(name);
340             matchNumbers.add(pair.second);
341             if (epgInput != null && TextUtils.equals(lineup.getId(), epgInput.getLineupId())) {
342                 // The last index is the current one.
343                 defaultLineupIndex = lineupNames.size() - 1;
344             }
345         }
346         args.putStringArrayList(LineupFragment.KEY_LINEUP_NAMES, lineupNames);
347         args.putIntegerArrayList(LineupFragment.KEY_MATCH_NUMBERS, matchNumbers);
348         args.putInt(LineupFragment.KEY_DEFAULT_LINEUP, defaultLineupIndex);
349         return args;
350     }
351 
cancelFetchLineup()352     private void cancelFetchLineup() {
353         if (fetchLineupTask == null) {
354             return;
355         }
356         AsyncTask.Status status = fetchLineupTask.getStatus();
357         if (status == AsyncTask.Status.RUNNING || status == AsyncTask.Status.PENDING) {
358             fetchLineupTask.cancel(true);
359             fetchLineupTask = null;
360             if (currentLineupFragment != null) {
361                 currentLineupFragment.onLineupNotFound();
362             }
363         }
364     }
365 
restartFetchLineupTask()366     private void restartFetchLineupTask() {
367         if (!CommonFeatures.ENABLE_CLOUD_EPG_REGION.isEnabled(getApplicationContext())
368                 || TextUtils.isEmpty(postalCode)
369                 || checkSelfPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION)
370                         != PackageManager.PERMISSION_GRANTED) {
371             return;
372         }
373         if (fetchLineupTask != null) {
374             fetchLineupTask.cancel(true);
375         }
376         handler.removeCallbacksAndMessages(null);
377         fetchLineupTask = new FetchLineupTask(getContentResolver(), postalCode);
378         fetchLineupTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
379     }
380 
381     private class FetchLineupTask extends AsyncTask<Void, Void, List<Lineup>> {
382         private final ContentResolver contentResolver;
383         private final String postalCode;
384 
FetchLineupTask(ContentResolver contentResolver, String postalCode)385         FetchLineupTask(ContentResolver contentResolver, String postalCode) {
386             this.contentResolver = contentResolver;
387             this.postalCode = postalCode;
388         }
389 
390         @Override
doInBackground(Void... args)391         protected List<Lineup> doInBackground(Void... args) {
392             if (contentResolver == null || TextUtils.isEmpty(postalCode)) {
393                 return new ArrayList<>();
394             }
395             return new ArrayList<>(Lineups.query(contentResolver, postalCode));
396         }
397 
398         @Override
onPostExecute(List<Lineup> lineups)399         protected void onPostExecute(List<Lineup> lineups) {
400             if (DEBUG) {
401                 if (lineups != null) {
402                     Log.d(TAG, "FetchLineupTask fetched " + lineups.size() + " lineups");
403                 } else {
404                     Log.d(TAG, "FetchLineupTask returned null");
405                 }
406             }
407             SampleNetworkTunerSetupActivity.this.lineups = lineups;
408             if (currentLineupFragment != null) {
409                 if (lineups == null || lineups.isEmpty()) {
410                     currentLineupFragment.onLineupNotFound();
411                 } else {
412                     lineupMatchCountPair =
413                             TunerSetupUtils.lineupChannelMatchCount(
414                                     SampleNetworkTunerSetupActivity.this.lineups, channelNumbers);
415                     currentLineupFragment.onLineupFound(getArgsForLineupFragment());
416                 }
417             }
418         }
419     }
420 
421     private class InsertOrModifyEpgInputTask extends AsyncTask<Void, Void, Void> {
422         private final Lineup lineup;
423         private final String inputId;
424 
InsertOrModifyEpgInputTask(@ullable Lineup lineup, String inputId)425         InsertOrModifyEpgInputTask(@Nullable Lineup lineup, String inputId) {
426             this.lineup = lineup;
427             this.inputId = inputId;
428         }
429 
430         @Override
doInBackground(Void... args)431         protected Void doInBackground(Void... args) {
432             if (lineup == null
433                     || (SampleNetworkTunerSetupActivity.this.epgInput != null
434                             && TextUtils.equals(
435                                     lineup.getId(),
436                                     SampleNetworkTunerSetupActivity.this.epgInput.getLineupId()))) {
437                 return null;
438             }
439             ContentValues values = new ContentValues();
440             values.put(EpgContract.EpgInputs.COLUMN_INPUT_ID, inputId);
441             values.put(EpgContract.EpgInputs.COLUMN_LINEUP_ID, lineup.getId());
442 
443             ContentResolver contentResolver = getContentResolver();
444             if (SampleNetworkTunerSetupActivity.this.epgInput != null) {
445                 values.put(
446                         EpgContract.EpgInputs.COLUMN_ID,
447                         SampleNetworkTunerSetupActivity.this.epgInput.getId());
448                 EpgInputs.update(contentResolver, EpgInput.createEpgChannel(values));
449                 return null;
450             }
451             EpgInput epgInput = EpgInputs.queryEpgInput(contentResolver, inputId);
452             if (epgInput == null) {
453                 contentResolver.insert(EpgContract.EpgInputs.CONTENT_URI, values);
454             } else {
455                 values.put(EpgContract.EpgInputs.COLUMN_ID, epgInput.getId());
456                 EpgInputs.update(contentResolver, EpgInput.createEpgChannel(values));
457             }
458             return null;
459         }
460 
461         @Override
onPostExecute(Void result)462         protected void onPostExecute(Void result) {
463             Intent data = new Intent();
464             data.putExtra(TvInputInfo.EXTRA_INPUT_ID, inputId);
465             data.putExtra(EpgContract.EXTRA_USE_CLOUD_EPG, true);
466             setResult(RESULT_OK, data);
467             finish();
468         }
469     }
470 
471     /**
472      * Exports {@link SampleNetworkTunerSetupActivity} for Dagger codegen to create the appropriate
473      * injector.
474      */
475     @dagger.Module
476     public abstract static class Module {
477         @ContributesAndroidInjector
478         abstract SampleNetworkTunerSetupActivity
contributeSampleNetworkTunerSetupActivityInjector()479                 contributeSampleNetworkTunerSetupActivityInjector();
480     }
481 
482     private class QueryEpgInputTask extends AsyncTask<Void, Void, EpgInput> {
483         private final String inputId;
484 
QueryEpgInputTask(String inputId)485         QueryEpgInputTask(String inputId) {
486             this.inputId = inputId;
487         }
488 
489         @Override
doInBackground(Void... args)490         protected EpgInput doInBackground(Void... args) {
491             ContentResolver contentResolver = getContentResolver();
492             return EpgInputs.queryEpgInput(contentResolver, inputId);
493         }
494 
495         @Override
onPostExecute(EpgInput result)496         protected void onPostExecute(EpgInput result) {
497             epgInput = result;
498         }
499     }
500 }
501