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