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.onboarding;
18 
19 import android.app.Fragment;
20 import android.content.ActivityNotFoundException;
21 import android.content.ComponentName;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.pm.PackageManager;
25 import android.media.tv.TvInputInfo;
26 import android.os.Bundle;
27 import android.support.annotation.NonNull;
28 import android.util.Log;
29 import android.widget.Toast;
30 
31 import com.android.tv.R;
32 import com.android.tv.SetupPassthroughActivity;
33 import com.android.tv.TvSingletons;
34 import com.android.tv.common.ui.setup.SetupActivity;
35 import com.android.tv.common.ui.setup.SetupMultiPaneFragment;
36 import com.android.tv.common.util.CommonUtils;
37 import com.android.tv.common.util.PermissionUtils;
38 import com.android.tv.data.ChannelDataManager;
39 import com.android.tv.util.OnboardingUtils;
40 import com.android.tv.util.SetupUtils;
41 import com.android.tv.util.TvInputManagerHelper;
42 
43 import dagger.android.AndroidInjection;
44 import dagger.android.ContributesAndroidInjector;
45 
46 import com.android.tv.common.flags.UiFlags;
47 
48 import javax.inject.Inject;
49 
50 public class OnboardingActivity extends SetupActivity {
51     private static final String TAG = "OnboardingActivity";
52     private static final String KEY_INTENT_AFTER_COMPLETION = "key_intent_after_completion";
53 
54     private static final int PERMISSIONS_REQUEST_READ_TV_LISTINGS = 1;
55 
56     private static final int SHOW_RIPPLE_DURATION_MS = 266;
57 
58     private static final int REQUEST_CODE_START_SETUP_ACTIVITY = 1;
59 
60     @Inject ChannelDataManager mChannelDataManager;
61     private TvInputManagerHelper mInputManager;
62     @Inject SetupUtils mSetupUtils;
63     @Inject UiFlags mUiFlags;
64     private final ChannelDataManager.Listener mChannelListener =
65             new ChannelDataManager.Listener() {
66                 @Override
67                 public void onLoadFinished() {
68                     mChannelDataManager.removeListener(this);
69                     mSetupUtils.markNewChannelsBrowsable();
70                 }
71 
72                 @Override
73                 public void onChannelListUpdated() {}
74 
75                 @Override
76                 public void onChannelBrowsableChanged() {}
77             };
78 
79     /**
80      * Returns an intent to start {@link OnboardingActivity}.
81      *
82      * @param context context to create an intent. Should not be {@code null}.
83      * @param intentAfterCompletion intent which will be used to start a new activity when this
84      *     activity finishes. Should not be {@code null}.
85      */
buildIntent( @onNull Context context, @NonNull Intent intentAfterCompletion)86     public static Intent buildIntent(
87             @NonNull Context context, @NonNull Intent intentAfterCompletion) {
88         return new Intent(context, OnboardingActivity.class)
89                 .putExtra(OnboardingActivity.KEY_INTENT_AFTER_COMPLETION, intentAfterCompletion);
90     }
91 
92     @Override
onCreate(Bundle savedInstanceState)93     protected void onCreate(Bundle savedInstanceState) {
94         AndroidInjection.inject(this);
95         super.onCreate(savedInstanceState);
96         TvSingletons singletons = TvSingletons.getSingletons(this);
97         mInputManager = singletons.getTvInputManagerHelper();
98         if (PermissionUtils.hasAccessAllEpg(this) || PermissionUtils.hasReadTvListings(this)) {
99             // Make the channels of the new inputs which have been setup outside TV app
100             // browsable.
101             if (mChannelDataManager.isDbLoadFinished()) {
102                 mSetupUtils.markNewChannelsBrowsable();
103             } else {
104                 mChannelDataManager.addListener(mChannelListener);
105             }
106         } else {
107             requestPermissions(
108                     new String[] {PermissionUtils.PERMISSION_READ_TV_LISTINGS},
109                     PERMISSIONS_REQUEST_READ_TV_LISTINGS);
110         }
111     }
112 
113     @Override
onDestroy()114     protected void onDestroy() {
115         if (mChannelDataManager != null) {
116             mChannelDataManager.removeListener(mChannelListener);
117         }
118         super.onDestroy();
119     }
120 
121     @Override
onCreateInitialFragment()122     protected Fragment onCreateInitialFragment() {
123         if (PermissionUtils.hasAccessAllEpg(this) || PermissionUtils.hasReadTvListings(this)) {
124             return OnboardingUtils.isFirstRunWithCurrentVersion(this)
125                     ? new WelcomeFragment()
126                     : new SetupSourcesFragment();
127         }
128         return null;
129     }
130 
131     @Override
onRequestPermissionsResult( int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults)132     public void onRequestPermissionsResult(
133             int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
134         if (requestCode == PERMISSIONS_REQUEST_READ_TV_LISTINGS) {
135             if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
136                 finish();
137                 Intent intentForNextActivity =
138                         getIntent().getParcelableExtra(KEY_INTENT_AFTER_COMPLETION);
139                 startActivity(buildIntent(this, intentForNextActivity));
140             } else {
141                 Toast.makeText(
142                                 this,
143                                 R.string.msg_read_tv_listing_permission_denied,
144                                 Toast.LENGTH_LONG)
145                         .show();
146                 finish();
147             }
148         }
149     }
150 
finishActivity()151     private void finishActivity() {
152         Intent intentForNextActivity = getIntent().getParcelableExtra(KEY_INTENT_AFTER_COMPLETION);
153         if (intentForNextActivity != null) {
154             startActivity(intentForNextActivity);
155         }
156         finish();
157     }
158 
showMerchantCollection()159     private void showMerchantCollection() {
160         Intent onlineStoreIntent = OnboardingUtils.createOnlineStoreIntent(mUiFlags);
161         if (onlineStoreIntent != null) {
162             executeActionWithDelay(
163                     () -> startActivity(OnboardingUtils.createOnlineStoreIntent(mUiFlags)),
164                     SHOW_RIPPLE_DURATION_MS);
165         } else {
166             Log.w(
167                     TAG,
168                     "Unable to show merchant collection, more channels url is not valid. url is "
169                             + mUiFlags.moreChannelsUrl());
170         }
171     }
172 
173     @Override
executeAction(String category, int actionId, Bundle params)174     protected boolean executeAction(String category, int actionId, Bundle params) {
175         switch (category) {
176             case WelcomeFragment.ACTION_CATEGORY:
177                 switch (actionId) {
178                     case WelcomeFragment.ACTION_NEXT:
179                         OnboardingUtils.setFirstRunWithCurrentVersionCompleted(
180                                 OnboardingActivity.this);
181                         showFragment(new SetupSourcesFragment(), false);
182                         return true;
183                 }
184                 break;
185             case SetupSourcesFragment.ACTION_CATEGORY:
186                 switch (actionId) {
187                     case SetupSourcesFragment.ACTION_ONLINE_STORE:
188                         showMerchantCollection();
189                         return true;
190                     case SetupSourcesFragment.ACTION_SETUP_INPUT:
191                         {
192                             String inputId =
193                                     params.getString(
194                                             SetupSourcesFragment.ACTION_PARAM_KEY_INPUT_ID);
195                             TvInputInfo input = mInputManager.getTvInputInfo(inputId);
196                             Intent intent = CommonUtils.createSetupIntent(input);
197                             if (intent == null) {
198                                 Toast.makeText(
199                                                 this,
200                                                 R.string.msg_no_setup_activity,
201                                                 Toast.LENGTH_SHORT)
202                                         .show();
203                                 return true;
204                             }
205                             // Even though other app can handle the intent, the setup launched by
206                             // Live
207                             // channels should go through TV app SetupPassthroughActivity.
208                             intent.setComponent(
209                                     new ComponentName(this, SetupPassthroughActivity.class));
210                             try {
211                                 // Now we know that the user intends to set up this input. Grant
212                                 // permission for writing EPG data.
213                                 SetupUtils.grantEpgPermission(
214                                         this, input.getServiceInfo().packageName);
215                                 startActivityForResult(intent, REQUEST_CODE_START_SETUP_ACTIVITY);
216                             } catch (ActivityNotFoundException e) {
217                                 Toast.makeText(
218                                                 this,
219                                                 getString(
220                                                         R.string.msg_unable_to_start_setup_activity,
221                                                         input.loadLabel(this)),
222                                                 Toast.LENGTH_SHORT)
223                                         .show();
224                             }
225                             return true;
226                         }
227                     case SetupMultiPaneFragment.ACTION_DONE:
228                         {
229                             ChannelDataManager manager =
230                                     TvSingletons.getSingletons(OnboardingActivity.this)
231                                             .getChannelDataManager();
232                             if (manager.getChannelCount() == 0) {
233                                 finish();
234                             } else {
235                                 finishActivity();
236                             }
237                             return true;
238                         }
239                 }
240                 break;
241         }
242         return false;
243     }
244 
245     /** Exports {@link OnboardingActivity} for Dagger codegen to create the appropriate injector. */
246     @dagger.Module
247     public abstract static class Module {
248         @ContributesAndroidInjector
contributeOnboardingActivityInjector()249         abstract OnboardingActivity contributeOnboardingActivityInjector();
250     }
251 }
252