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.setup;
18 
19 import static com.android.tv.tuner.setup.BaseTunerSetupActivity.PERMISSIONS_REQUEST_ACCESS_COARSE_LOCATION;
20 
21 import android.content.pm.PackageManager;
22 import android.location.Address;
23 import android.os.Bundle;
24 import android.os.Handler;
25 import android.support.annotation.NonNull;
26 import android.util.Log;
27 import androidx.leanback.widget.GuidanceStylist.Guidance;
28 import androidx.leanback.widget.GuidedAction;
29 import com.android.tv.common.ui.setup.SetupActionHelper;
30 import com.android.tv.common.ui.setup.SetupGuidedStepFragment;
31 import com.android.tv.common.ui.setup.SetupMultiPaneFragment;
32 import com.android.tv.common.util.LocationUtils;
33 import com.android.tv.tuner.R;
34 import java.io.IOException;
35 import java.util.ArrayList;
36 import java.util.List;
37 
38 /** A fragment shows the rationale of location permission */
39 public class LocationFragment extends SetupMultiPaneFragment {
40     private static final String TAG = "com.android.tv.tuner.setup.LocationFragment";
41     private static final boolean DEBUG = false;
42 
43     public static final String ACTION_CATEGORY = "com.android.tv.tuner.setup.LocationFragment";
44     public static final String KEY_POSTAL_CODE = "key_postal_code";
45 
46     public static final int ACTION_ALLOW_PERMISSION = 1;
47     public static final int ENTER_ZIP_CODE = 2;
48     public static final int ACTION_GETTING_LOCATION = 3;
49     public static final int GET_LOCATION_TIMEOUT_MS = 3000;
50 
51     @Override
onCreateContentFragment()52     protected SetupGuidedStepFragment onCreateContentFragment() {
53         return new ContentFragment();
54     }
55 
56     @Override
getActionCategory()57     protected String getActionCategory() {
58         return ACTION_CATEGORY;
59     }
60 
61     @Override
needsDoneButton()62     protected boolean needsDoneButton() {
63         return false;
64     }
65 
66     /** The content fragment of {@link LocationFragment}. */
67     public static class ContentFragment extends SetupGuidedStepFragment
68             implements LocationUtils.OnUpdateAddressListener {
69         private final List<GuidedAction> mGettingLocationAction = new ArrayList<>();
70         private final Handler mHandler = new Handler();
71         private final Object mPostalCodeLock = new Object();
72 
73         private String mPostalCode;
74         private boolean mPermissionGranted;
75 
76         private final Runnable mTimeoutRunnable =
77                 () -> {
78                     synchronized (mPostalCodeLock) {
79                         if (DEBUG) {
80                             Log.d(TAG, "get location timeout. mPostalCode=" + mPostalCode);
81                         }
82                         if (mPostalCode == null) {
83                             // timeout. setup activity will get null postal code
84                             LocationUtils.removeOnUpdateAddressListener(this);
85                             passPostalCode();
86                         }
87                     }
88                 };
89 
90         @NonNull
91         @Override
onCreateGuidance(Bundle savedInstanceState)92         public Guidance onCreateGuidance(Bundle savedInstanceState) {
93             String title = getString(R.string.location_guidance_title);
94             String description = getString(R.string.location_guidance_description);
95             return new Guidance(title, description, getString(R.string.ut_setup_breadcrumb), null);
96         }
97 
98         @Override
onCreateActions( @onNull List<GuidedAction> actions, Bundle savedInstanceState)99         public void onCreateActions(
100                 @NonNull List<GuidedAction> actions, Bundle savedInstanceState) {
101             actions.add(
102                     new GuidedAction.Builder(getActivity())
103                             .id(ACTION_ALLOW_PERMISSION)
104                             .title(getString(R.string.location_choices_allow_permission))
105                             .build());
106             actions.add(
107                     new GuidedAction.Builder(getActivity())
108                             .id(ENTER_ZIP_CODE)
109                             .title(getString(R.string.location_choices_enter_zip_code))
110                             .build());
111             actions.add(
112                     new GuidedAction.Builder(getActivity())
113                             .id(ACTION_SKIP)
114                             .title(getString(com.android.tv.common.R.string.action_text_skip))
115                             .build());
116             mGettingLocationAction.add(
117                     new GuidedAction.Builder(getActivity())
118                             .id(ACTION_GETTING_LOCATION)
119                             .title(getString(R.string.location_choices_getting_location))
120                             .focusable(false)
121                             .build());
122         }
123 
124         @Override
onGuidedActionClicked(GuidedAction action)125         public void onGuidedActionClicked(GuidedAction action) {
126             if (DEBUG) {
127                 Log.d(TAG, "onGuidedActionClicked. Action ID = " + action.getId());
128             }
129             if (action.getId() == ACTION_ALLOW_PERMISSION) {
130                 // request permission when users click this action
131                 mPermissionGranted = false;
132                 requestPermissions(
133                         new String[] {android.Manifest.permission.ACCESS_COARSE_LOCATION},
134                         PERMISSIONS_REQUEST_ACCESS_COARSE_LOCATION);
135             } else {
136                 super.onGuidedActionClicked(action);
137             }
138         }
139 
140         @Override
getActionCategory()141         protected String getActionCategory() {
142             return ACTION_CATEGORY;
143         }
144 
145         @Override
onRequestPermissionsResult( int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults)146         public void onRequestPermissionsResult(
147                 int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
148             if (requestCode == PERMISSIONS_REQUEST_ACCESS_COARSE_LOCATION) {
149                 if (grantResults.length > 0
150                         && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
151                     synchronized (mPostalCodeLock) {
152                         mPermissionGranted = true;
153                         if (mPostalCode == null) {
154                             // get postal code immediately if available
155                             try {
156                                 Address address = LocationUtils.getCurrentAddress(getActivity());
157                                 if (address != null) {
158                                     mPostalCode = address.getPostalCode();
159                                 }
160                             } catch (IOException e) {
161                                 // do nothing
162                             }
163                         }
164                         if (DEBUG) {
165                             Log.d(TAG, "permission granted. mPostalCode=" + mPostalCode);
166                         }
167                         if (mPostalCode != null) {
168                             // if postal code is known, pass it the setup activity
169                             LocationUtils.removeOnUpdateAddressListener(this);
170                             passPostalCode();
171                         } else {
172                             // show "getting location" message
173                             setActions(mGettingLocationAction);
174                             // post timeout runnable
175                             mHandler.postDelayed(mTimeoutRunnable, GET_LOCATION_TIMEOUT_MS);
176                         }
177                     }
178                 }
179             }
180         }
181 
182         @Override
onUpdateAddress(Address address)183         public boolean onUpdateAddress(Address address) {
184             synchronized (mPostalCodeLock) {
185                 // it takes time to get location after the permission is granted,
186                 // so this listener is needed
187                 mPostalCode = address.getPostalCode();
188                 if (DEBUG) {
189                     Log.d(TAG, "onUpdateAddress. mPostalCode=" + mPostalCode);
190                 }
191                 if (mPermissionGranted && mPostalCode != null) {
192                     // pass the postal code only if permission is granted
193                     passPostalCode();
194                     return true;
195                 }
196                 return false;
197             }
198         }
199 
200         @Override
onResume()201         public void onResume() {
202             if (DEBUG) {
203                 Log.d(TAG, "onResume");
204             }
205             super.onResume();
206             LocationUtils.addOnUpdateAddressListener(this);
207         }
208 
209         @Override
onPause()210         public void onPause() {
211             if (DEBUG) {
212                 Log.d(TAG, "onPause");
213             }
214             LocationUtils.removeOnUpdateAddressListener(this);
215             mHandler.removeCallbacks(mTimeoutRunnable);
216             super.onPause();
217         }
218 
passPostalCode()219         private void passPostalCode() {
220             synchronized (mPostalCodeLock) {
221                 mHandler.removeCallbacks(mTimeoutRunnable);
222                 Bundle params = new Bundle();
223                 if (mPostalCode != null) {
224                     params.putString(KEY_POSTAL_CODE, mPostalCode);
225                 }
226                 SetupActionHelper.onActionClick(
227                         this, ACTION_CATEGORY, ACTION_ALLOW_PERMISSION, params);
228             }
229         }
230     }
231 }
232