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