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