1/* 2 * Copyright (C) 2014 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 17package com.android.tv.settings.dialog.old; 18 19import android.annotation.TargetApi; 20import android.app.Activity; 21import android.app.Fragment; 22import android.content.Context; 23import android.content.res.Configuration; 24import android.graphics.Bitmap; 25import android.net.Uri; 26import android.os.Build; 27import android.os.Bundle; 28import android.os.Message; 29import android.provider.Settings; 30import android.provider.Settings.Secure; 31import android.telephony.TelephonyManager; 32import android.text.TextUtils; 33import android.util.Log; 34import android.view.KeyEvent; 35import android.view.LayoutInflater; 36import android.view.View; 37import android.view.ViewGroup; 38import android.webkit.CookieManager; 39import android.webkit.CookieSyncManager; 40import android.webkit.WebChromeClient; 41import android.webkit.WebSettings; 42import android.webkit.WebView; 43import android.webkit.WebViewClient; 44 45import com.android.tv.settings.R; 46 47import java.io.BufferedReader; 48import java.io.FileNotFoundException; 49import java.io.InputStream; 50import java.io.InputStreamReader; 51import java.io.IOException; 52import java.util.Locale; 53 54/** 55 * Fragment that shows a link containing the ToS. 56 */ 57public class TosWebViewFragment extends Fragment { 58 59 private static final String TAG = "TosWebViewFragment"; 60 private static final boolean DEBUG = false; 61 // TODO switch to pointing to proper TLD once there is a reliable way to 62 // map geography to proper TLD, b/11032160 63 private static final String GOOGLE_TOS_URL = "http://www.google.com/intl/%y_%z/policies/terms/"; 64 private static final String GOOGLE_PRIVACY_URL = 65 "http://www.google.com/intl/%y_%z/policies/privacy/"; 66 67 private static final String SETTINGS_SECURE_TOS_URL = "canvas_tos_url"; 68 private static final String SETTINGS_SECURE_PRIVACY_URL = "canvas_privacy_url"; 69 70 private static final int SOURCE_URL = 1; 71 private static final int SOURCE_STRING = 2; 72 private static final int SOURCE_RESOURCE_ID = 3; 73 74 private static final String ARGUMENT_SHOW = "show"; 75 private static final String ARGUMENT_CONTENT_STRING = "content_string"; 76 private static final String ARGUMENT_CONTENT_URL = "content_url"; 77 private static final String ARGUMENT_CONTENT_RESOURCE_ID = "content_resource_id"; 78 79 public static final int SHOW_TERMS_OF_SERVICE = 3; 80 public static final int SHOW_PRIVACY_POLICY = 4; 81 public static final int SHOW_ADDITIONAL_TERMS = 5; 82 83 /** 84 * Create instance and select page to display. The content is selected by the single parameter 85 * and determined by a URL or resource string depending on the page to show. 86 * 87 * @param show One of SHOW_TERMS_OF_SERVICE, SHOW_PRIVACY_POLICY, SHOW_ADDITIONAL_TERMS 88 */ 89 public static TosWebViewFragment newInstance(int show) { 90 TosWebViewFragment f = new TosWebViewFragment(); 91 Bundle args = new Bundle(); 92 args.putInt(ARGUMENT_SHOW, show); 93 f.setArguments(args); 94 return f; 95 } 96 97 /** 98 * Create instance and display page from URL. 99 * 100 * @param uri URL of page to show. 101 */ 102 public static TosWebViewFragment newInstanceUrl(String pageUrl) { 103 TosWebViewFragment f = new TosWebViewFragment(); 104 Bundle args = new Bundle(); 105 args.putString(ARGUMENT_CONTENT_URL, pageUrl); 106 f.setArguments(args); 107 return f; 108 } 109 110 /** 111 * Create instance and display page from a string. 112 * 113 * @param page Text to show. 114 */ 115 public static TosWebViewFragment newInstance(String page) { 116 TosWebViewFragment f = new TosWebViewFragment(); 117 Bundle args = new Bundle(); 118 args.putString(ARGUMENT_CONTENT_STRING, page); 119 f.setArguments(args); 120 return f; 121 } 122 123 /** 124 * Create instance and display page from resource id. 125 * 126 * @param resourceId id or string resourse to show. 127 */ 128 public static TosWebViewFragment newInstance_resourceId(int resourceId) { 129 TosWebViewFragment f = new TosWebViewFragment(); 130 Bundle args = new Bundle(); 131 args.putInt(ARGUMENT_CONTENT_RESOURCE_ID, resourceId); 132 f.setArguments(args); 133 return f; 134 } 135 136 private class MyChromeClient extends WebChromeClient { 137 @Override 138 public boolean onCreateWindow(WebView view, boolean dialog, boolean userGesture, 139 Message resultMsg) { 140 resultMsg.obj = mWebView; 141 resultMsg.sendToTarget(); 142 return true; 143 } 144 145 @Override 146 public void onProgressChanged(WebView view, int newProgress) { 147 } 148 149 @Override 150 public boolean onConsoleMessage(android.webkit.ConsoleMessage consoleMessage) { 151 return true; 152 } 153 } 154 155 private class MyWebViewClient extends WebViewClient { 156 157 /** 158 * Before we load any page, we check with our session object to see if 159 * we should do any special handling at this point 160 */ 161 @Override 162 public boolean shouldOverrideUrlLoading(WebView view, String url) { 163 return false; 164 } 165 166 @Override 167 public void onPageStarted(WebView view, String url, Bitmap favicon) { 168 } 169 170 @Override 171 public void onReceivedError(WebView view, int errorCode, String description, 172 String failingUrl) { 173 Log.w(TAG, String.format( 174 "onReceivedError: errorCode %d, description: %s, url: %s", errorCode, 175 description, failingUrl)); 176 mIsLoading = false; 177 // If this is the first attempted page load, this could be a 178 // connectivity issue 179 // We treat it all as server error, let ShowError decide if network 180 // is still up 181 onWebLoginError(errorCode, description); 182 super.onReceivedError(view, errorCode, description, failingUrl); 183 } 184 185 @Override 186 public void onTooManyRedirects(WebView view, Message cancelMsg, Message continueMsg) { 187 Log.e(TAG, "onTooManyRedirects"); 188 // Users probably don't care about redirects, it's a server error to 189 // them 190 onWebLoginError(0, ""); 191 } 192 193 @Override 194 public void onPageFinished(WebView view, String url) { 195 if (DEBUG) { 196 Log.d(TAG, "Loaded " + url); 197 } 198 view.requestFocus(); 199 } 200 201 private void onWebLoginError(int errorCode, String description) { 202 // TODO: Show error? 203 } 204 } 205 206 private WebView mWebView; 207 private String mSource; 208 private int mSourceType; 209 private boolean mIsLoading; 210 211 @Override 212 public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle icicle) { 213 View content = inflater.inflate(R.layout.navigable_webview, null); 214 215 if (icicle == null) { 216 mWebView = (WebView) content.findViewById(R.id.webview); 217 CookieSyncManager.createInstance(getActivity()); 218 mWebView.setWebViewClient(new MyWebViewClient()); 219 mWebView.setWebChromeClient(new MyChromeClient()); 220 WebSettings s = mWebView.getSettings(); 221 s.setNeedInitialFocus(true); 222 s.setJavaScriptEnabled(true); 223 s.setSupportMultipleWindows(false); 224 s.setSaveFormData(false); 225 s.setSavePassword(false); 226 s.setAllowFileAccess(false); 227 s.setDatabaseEnabled(false); 228 s.setJavaScriptCanOpenWindowsAutomatically(false); 229 s.setLoadsImagesAutomatically(true); 230 s.setLightTouchEnabled(false); 231 s.setNeedInitialFocus(false); 232 s.setUseWideViewPort(true); 233 s.setSupportZoom(false); 234 mWebView.setMapTrackballToArrowKeys(false); 235 236 int show = getArguments().getInt(ARGUMENT_SHOW, -1); 237 switch (show) { 238 case SHOW_TERMS_OF_SERVICE: 239 mSourceType = SOURCE_URL; 240 mSource = Settings.Secure.getString(getActivity().getContentResolver(), 241 SETTINGS_SECURE_TOS_URL); 242 if (mSource == null) { 243 mSource = GOOGLE_TOS_URL; 244 } 245 mSource = substitueArguments(mSource); 246 break; 247 case SHOW_PRIVACY_POLICY: 248 mSourceType = SOURCE_URL; 249 mSource = Settings.Secure.getString(getActivity().getContentResolver(), 250 SETTINGS_SECURE_PRIVACY_URL); 251 if (mSource == null) { 252 mSource = GOOGLE_PRIVACY_URL; 253 } 254 mSource = substitueArguments(mSource); 255 break; 256 case SHOW_ADDITIONAL_TERMS: 257 mSourceType = SOURCE_STRING; 258 mSource = readStringFromResource(getActivity(), 259 R.raw.additional_terms_of_service); 260 break; 261 default: 262 // User is specifying a page to display, either by Url, resource id or string. 263 mSource = getArguments().getString(ARGUMENT_CONTENT_URL); 264 if (mSource != null) { 265 mSourceType = SOURCE_URL; 266 mSource = substitueArguments(mSource); 267 } else { 268 mSourceType = SOURCE_STRING; 269 mSource = getArguments().getString(ARGUMENT_CONTENT_STRING); 270 if (mSource == null) { 271 int resourceId = getArguments().getInt(ARGUMENT_CONTENT_RESOURCE_ID); 272 mSource = getResources().getString(resourceId); 273 } 274 } 275 break; 276 } 277 } 278 return content; 279 } 280 281 /** 282 * Determines the current activity mode and initializes ui appropriately. 283 */ 284 @Override 285 public void onResume() { 286 super.onResume(); 287 mIsLoading = true; 288 switch (mSourceType) { 289 case SOURCE_URL: 290 mWebView.loadUrl(mSource); 291 break; 292 case SOURCE_STRING: 293 mWebView.loadData(mSource,"text/html", null); 294 break; 295 } 296 mWebView.onResume(); 297 } 298 299 /** 300 * Stops page loading if it is in progress. 301 */ 302 @Override 303 public void onPause() { 304 if (mIsLoading) { 305 mWebView.stopLoading(); 306 mIsLoading = false; 307 } 308 mWebView.onPause(); 309 super.onPause(); 310 } 311 312 private String substitueArguments(String url) { 313 // Substitute locale if present in property 314 if (url.contains("%m")) { 315 try { 316 Configuration config = new Configuration(); 317 Settings.System.getConfiguration(getActivity().getContentResolver(), config); 318 if (config.mcc != 0) { 319 url = url.replace("%m", Integer.toString(config.mcc)); 320 } else { 321 // This will happen if the device doesn't have a SIM, in 322 // which case we just use the locale. 323 url = url.replace("%m", "%s"); 324 } 325 } catch (Exception e) { 326 // Intentionally left blank 327 } 328 } 329 if (url.contains("%s")) { 330 Locale locale = Locale.getDefault(); 331 String tmp = locale.getLanguage() + "_" + locale.getCountry().toLowerCase(); 332 url = url.replace("%s", tmp); 333 } 334 335 // %y is used to indicate a language variable to be replaced at runtime. 336 if (url.contains("%y")) { 337 Locale locale = Locale.getDefault(); 338 url = url.replace("%y", locale.getLanguage()); 339 } 340 341 // %z is used to indicate the country. We use the value set by the 342 // SIM. We choose not to use the operator/network country but that 343 // which is set on SIM as they most likely procured the device there 344 if (url.contains("%z")) { 345 try { 346 TelephonyManager telephony = (TelephonyManager) getActivity() 347 .getSystemService(Context.TELEPHONY_SERVICE); 348 349 Configuration config = new Configuration(); 350 Settings.System.getConfiguration(getActivity().getContentResolver(), config); 351 352 if (telephony != null && config.mcc != 0) { 353 String simCountryIso = telephony.getSimCountryIso(); 354 if (TextUtils.isEmpty(simCountryIso)) { 355 simCountryIso = "us"; 356 } 357 url = url.replace("%z", simCountryIso); 358 } else { 359 // This will happen if the device doesn't have a SIM, in 360 // which case we just use the country with the default 361 // locale country 362 Locale locale = Locale.getDefault(); 363 url = url.replace("%z", locale.getCountry().toLowerCase()); 364 } 365 } catch (Exception e) { 366 // Intentionally left blank 367 } 368 } 369 return url; 370 } 371 372 private String readStringFromResource(Context context, int resourceId) { 373 StringBuilder contents = new StringBuilder(); 374 String sep = System.lineSeparator(); 375 try { 376 InputStream is = context.getResources().openRawResource(resourceId); 377 BufferedReader input = new BufferedReader(new InputStreamReader(is), 1024*8); 378 try { 379 String line = null; 380 while ((line = input.readLine()) != null) { 381 contents.append(line); 382 contents.append(sep); 383 } 384 } finally { 385 input.close(); 386 } 387 } catch (FileNotFoundException ex) { 388 Log.e(TAG, "Couldn't find the file " + resourceId + " " + ex); 389 return null; 390 } catch (IOException ex){ 391 Log.e(TAG, "Error reading file " + resourceId + " " + ex); 392 return null; 393 } 394 return contents.toString(); 395 } 396} 397