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.tuner.setup;
18 
19 import android.app.Fragment;
20 import android.app.FragmentManager;
21 import android.app.Notification;
22 import android.app.NotificationManager;
23 import android.app.PendingIntent;
24 import android.content.ComponentName;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.pm.PackageManager;
28 import android.content.res.Resources;
29 import android.graphics.Bitmap;
30 import android.graphics.BitmapFactory;
31 import android.media.tv.TvContract;
32 import android.os.Bundle;
33 import android.support.v4.app.NotificationCompat;
34 import android.util.Log;
35 import android.view.KeyEvent;
36 import android.widget.Toast;
37 
38 import com.android.tv.TvApplication;
39 import com.android.tv.common.TvCommonConstants;
40 import com.android.tv.common.TvCommonUtils;
41 import com.android.tv.common.ui.setup.SetupActivity;
42 import com.android.tv.common.ui.setup.SetupFragment;
43 import com.android.tv.common.ui.setup.SetupMultiPaneFragment;
44 import com.android.tv.tuner.R;
45 import com.android.tv.tuner.TunerHal;
46 import com.android.tv.tuner.TunerPreferences;
47 import com.android.tv.tuner.tvinput.TunerTvInputService;
48 import com.android.tv.tuner.util.TunerInputInfoUtils;
49 
50 /**
51  * An activity that serves tuner setup process.
52  */
53 public class TunerSetupActivity extends SetupActivity {
54     private final String TAG = "TunerSetupActivity";
55     // For the recommendation card
56     private static final String TV_ACTIVITY_CLASS_NAME = "com.android.tv.TvActivity";
57     private static final String NOTIFY_TAG = "TunerSetup";
58     private static final int NOTIFY_ID = 1000;
59     private static final String TAG_DRAWABLE = "drawable";
60     private static final String TAG_ICON = "ic_launcher_s";
61 
62     private static final int CHANNEL_MAP_SCAN_FILE[] = {
63             R.raw.ut_us_atsc_center_frequencies_8vsb,
64             R.raw.ut_us_cable_standard_center_frequencies_qam256,
65             R.raw.ut_us_all,
66             R.raw.ut_kr_atsc_center_frequencies_8vsb,
67             R.raw.ut_kr_cable_standard_center_frequencies_qam256,
68             R.raw.ut_kr_all,
69             R.raw.ut_kr_dev_cj_cable_center_frequencies_qam256};
70 
71     private ScanFragment mLastScanFragment;
72 
73     @Override
onCreate(Bundle savedInstanceState)74     protected void onCreate(Bundle savedInstanceState) {
75         TvApplication.setCurrentRunningProcess(this, false);
76         super.onCreate(savedInstanceState);
77         // TODO: check {@link shouldShowRequestPermissionRationale}.
78         if (checkSelfPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION)
79                 != PackageManager.PERMISSION_GRANTED) {
80             // No need to check the request result.
81             requestPermissions(new String[] {android.Manifest.permission.ACCESS_COARSE_LOCATION},
82                     0);
83         }
84     }
85 
86     @Override
onCreateInitialFragment()87     protected Fragment onCreateInitialFragment() {
88         SetupFragment fragment = new WelcomeFragment();
89         fragment.setShortDistance(SetupFragment.FRAGMENT_EXIT_TRANSITION
90                 | SetupFragment.FRAGMENT_REENTER_TRANSITION);
91         return fragment;
92     }
93 
94     @Override
executeAction(String category, int actionId, Bundle params)95     protected boolean executeAction(String category, int actionId, Bundle params) {
96         switch (category) {
97             case WelcomeFragment.ACTION_CATEGORY:
98                 switch (actionId) {
99                     case SetupMultiPaneFragment.ACTION_DONE:
100                         // If the scan was performed, then the result should be OK.
101                         setResult(mLastScanFragment == null ? RESULT_CANCELED : RESULT_OK);
102                         finish();
103                         break;
104                     default: {
105                         SetupFragment fragment = new ConnectionTypeFragment();
106                         fragment.setShortDistance(SetupFragment.FRAGMENT_ENTER_TRANSITION
107                                 | SetupFragment.FRAGMENT_RETURN_TRANSITION);
108                         showFragment(fragment, true);
109                         break;
110                     }
111                 }
112                 return true;
113             case ConnectionTypeFragment.ACTION_CATEGORY:
114                 TunerHal hal = TunerHal.createInstance(getApplicationContext());
115                 if (hal == null) {
116                     finish();
117                     Toast.makeText(getApplicationContext(),
118                             R.string.ut_channel_scan_tuner_unavailable,Toast.LENGTH_LONG).show();
119                     return true;
120                 }
121                 try {
122                     hal.close();
123                 } catch (Exception e) {
124                     Log.e(TAG, "Tuner hal close failed", e);
125                     return true;
126                 }
127                 mLastScanFragment = new ScanFragment();
128                 Bundle args = new Bundle();
129                 args.putInt(ScanFragment.EXTRA_FOR_CHANNEL_SCAN_FILE,
130                         CHANNEL_MAP_SCAN_FILE[actionId]);
131                 mLastScanFragment.setArguments(args);
132                 showFragment(mLastScanFragment, true);
133                 return true;
134             case ScanFragment.ACTION_CATEGORY:
135                 switch (actionId) {
136                     case ScanFragment.ACTION_CANCEL:
137                         getFragmentManager().popBackStack();
138                         return true;
139                     case ScanFragment.ACTION_FINISH:
140                         SetupFragment fragment = new ScanResultFragment();
141                         fragment.setShortDistance(SetupFragment.FRAGMENT_EXIT_TRANSITION
142                                 | SetupFragment.FRAGMENT_REENTER_TRANSITION);
143                         showFragment(fragment, true);
144                         return true;
145                 }
146                 break;
147             case ScanResultFragment.ACTION_CATEGORY:
148                 switch (actionId) {
149                     case SetupMultiPaneFragment.ACTION_DONE:
150                         setResult(RESULT_OK);
151                         finish();
152                         break;
153                     default:
154                         SetupFragment fragment = new ConnectionTypeFragment();
155                         fragment.setShortDistance(SetupFragment.FRAGMENT_ENTER_TRANSITION
156                                 | SetupFragment.FRAGMENT_RETURN_TRANSITION);
157                         showFragment(fragment, true);
158                         break;
159                 }
160                 return true;
161         }
162         return false;
163     }
164 
165     @Override
onKeyUp(int keyCode, KeyEvent event)166     public boolean onKeyUp(int keyCode, KeyEvent event) {
167         if (keyCode == KeyEvent.KEYCODE_BACK) {
168             FragmentManager manager = getFragmentManager();
169             int count = manager.getBackStackEntryCount();
170             if (count > 0) {
171                 String lastTag = manager.getBackStackEntryAt(count - 1).getName();
172                 if (ScanResultFragment.class.getCanonicalName().equals(lastTag) && count >= 2) {
173                     // Pops fragment including ScanFragment.
174                     manager.popBackStack(manager.getBackStackEntryAt(count - 2).getName(),
175                             FragmentManager.POP_BACK_STACK_INCLUSIVE);
176                     return true;
177                 } else if (ScanFragment.class.getCanonicalName().equals(lastTag)) {
178                     mLastScanFragment.finishScan(true);
179                     return true;
180                 }
181             }
182         }
183         return super.onKeyUp(keyCode, event);
184     }
185 
186     /**
187      * A callback to be invoked when the TvInputService is enabled or disabled.
188      *
189      * @param context a {@link Context} instance
190      * @param enabled {@code true} for the {@link TunerTvInputService} to be enabled;
191      *                otherwise {@code false}
192      */
onTvInputEnabled(Context context, boolean enabled)193     public static void onTvInputEnabled(Context context, boolean enabled) {
194         // Send a recommendation card for tuner setup if there's no channels and the tuner TV input
195         // setup has been not done.
196         boolean channelScanDoneOnPreference = TunerPreferences.isScanDone(context);
197         int channelCountOnPreference = TunerPreferences.getScannedChannelCount(context);
198         if (enabled && !channelScanDoneOnPreference && channelCountOnPreference == 0) {
199             TunerPreferences.setShouldShowSetupActivity(context, true);
200             sendRecommendationCard(context);
201         } else {
202             TunerPreferences.setShouldShowSetupActivity(context, false);
203             cancelRecommendationCard(context);
204         }
205     }
206 
207     /**
208      * Returns a {@link Intent} to launch the tuner TV input service.
209      *
210      * @param context a {@link Context} instance
211      */
createSetupActivity(Context context)212     public static Intent createSetupActivity(Context context) {
213         String inputId = TvContract.buildInputId(new ComponentName(context.getPackageName(),
214                 TunerTvInputService.class.getName()));
215 
216         // Make an intent to launch the setup activity of USB tuner TV input.
217         Intent intent = TvCommonUtils.createSetupIntent(
218                 new Intent(context, TunerSetupActivity.class), inputId);
219         intent.putExtra(TvCommonConstants.EXTRA_INPUT_ID, inputId);
220         Intent tvActivityIntent = new Intent();
221         tvActivityIntent.setComponent(new ComponentName(context, TV_ACTIVITY_CLASS_NAME));
222         intent.putExtra(TvCommonConstants.EXTRA_ACTIVITY_AFTER_COMPLETION, tvActivityIntent);
223         return intent;
224     }
225 
226     /**
227      * Returns a {@link PendingIntent} to launch the tuner TV input service.
228      *
229      * @param context a {@link Context} instance
230      */
createPendingIntentForSetupActivity(Context context)231     private static PendingIntent createPendingIntentForSetupActivity(Context context) {
232         return PendingIntent.getActivity(context, 0, createSetupActivity(context),
233                 PendingIntent.FLAG_UPDATE_CURRENT);
234     }
235 
236     /**
237      * Sends the recommendation card to start the tuner TV input setup activity.
238      *
239      * @param context a {@link Context} instance
240      */
sendRecommendationCard(Context context)241     private static void sendRecommendationCard(Context context) {
242         Resources resources = context.getResources();
243         String focusedTitle = resources.getString(
244                 R.string.ut_setup_recommendation_card_focused_title);
245         String title;
246         if (TunerInputInfoUtils.isBuiltInTuner(context)) {
247             title = resources.getString(R.string.bt_setup_recommendation_card_title);
248         } else {
249             title = resources.getString(R.string.ut_setup_recommendation_card_title);
250         }
251         Bitmap largeIcon = BitmapFactory.decodeResource(resources,
252                 R.drawable.recommendation_antenna);
253 
254         // Build and send the notification.
255         Notification notification = new NotificationCompat.BigPictureStyle(
256                 new NotificationCompat.Builder(context)
257                         .setAutoCancel(false)
258                         .setContentTitle(focusedTitle)
259                         .setContentText(title)
260                         .setContentInfo(title)
261                         .setCategory(Notification.CATEGORY_RECOMMENDATION)
262                         .setLargeIcon(largeIcon)
263                         .setSmallIcon(resources.getIdentifier(
264                                 TAG_ICON, TAG_DRAWABLE, context.getPackageName()))
265                         .setContentIntent(createPendingIntentForSetupActivity(context)))
266                 .build();
267         NotificationManager notificationManager = (NotificationManager) context
268                 .getSystemService(Context.NOTIFICATION_SERVICE);
269         notificationManager.notify(NOTIFY_TAG, NOTIFY_ID, notification);
270     }
271 
272     /**
273      * Cancels the previously shown recommendation card.
274      *
275      * @param context a {@link Context} instance
276      */
cancelRecommendationCard(Context context)277     public static void cancelRecommendationCard(Context context) {
278         NotificationManager notificationManager = (NotificationManager) context
279                 .getSystemService(Context.NOTIFICATION_SERVICE);
280         notificationManager.cancel(NOTIFY_TAG, NOTIFY_ID);
281     }
282 }
283