// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.omnibox.geo;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.text.SpannableString;
import android.text.style.TypefaceSpan;
import android.view.View;
import org.chromium.base.ContextUtils;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.device.DeviceClassManager;
import org.chromium.chrome.browser.preferences.PreferencesLauncher;
import org.chromium.chrome.browser.preferences.SearchEnginePreference;
import org.chromium.chrome.browser.search_engines.TemplateUrlService;
import org.chromium.chrome.browser.snackbar.Snackbar;
import org.chromium.chrome.browser.snackbar.SnackbarManager;
import org.chromium.chrome.browser.snackbar.SnackbarManager.SnackbarController;
import org.chromium.ui.UiUtils;
import org.chromium.ui.text.SpanApplier;
import org.chromium.ui.text.SpanApplier.SpanInfo;
/**
* Controller for the geolocation disclosure snackbar, which notifies the user that google.com uses
* the device location to provide localized search results.
*
* This snackbar appears only once: the first time the user focuses the omnibox when the X-Geo
* header has the potential to be sent along with a search request. For this to happen, several
* conditions have to be met:
* - The current tab is not incognito
* - (Android M+) Chrome has been granted location permission
* - The default search engine is Google
* - Location is not disabled for google.com (or google.fr, etc) in Chrome's site settings
*/
public class GeolocationSnackbarController implements SnackbarController {
private static final String GEOLOCATION_SNACKBAR_SHOWN_PREF = "geolocation_snackbar_shown";
private static final int SNACKBAR_DURATION_MS = 8000;
private static final int ACCESSIBILITY_SNACKBAR_DURATION_MS = 15000;
private static Boolean sGeolocationSnackbarShown;
private GeolocationSnackbarController() {}
/**
* Shows the geolocation snackbar if it hasn't already been shown and the geolocation snackbar
* is currently relevant: i.e. the default search engine is Google, location is enabled
* for Chrome, the tab is not incognito, etc.
*
* @param snackbarManager The SnackbarManager used to show the snackbar.
* @param view Any view that's attached to the view hierarchy.
* @param isIncognito Whether the currently visible tab is incognito.
* @param delayMs The delay in ms before the snackbar should be shown. This is intended to
* give the keyboard time to animate in.
*/
public static void maybeShowSnackbar(final SnackbarManager snackbarManager, View view,
boolean isIncognito, int delayMs) {
final Context context = view.getContext();
if (getGeolocationSnackbarShown(context)) return;
// If in incognito mode, don't show the snackbar now, but maybe show it later.
if (isIncognito) return;
if (neverShowSnackbar(context)) {
setGeolocationSnackbarShown(context);
return;
}
Uri searchUri = Uri.parse(TemplateUrlService.getInstance().getUrlForSearchQuery("foo"));
TypefaceSpan robotoMediumSpan = new TypefaceSpan("sans-serif-medium");
String messageWithoutSpans = context.getResources().getString(
R.string.omnibox_geolocation_disclosure, "<b>" + searchUri.getHost() + "</b>");
SpannableString message = SpanApplier.applySpans(messageWithoutSpans,
new SpanInfo("<b>", "</b>", robotoMediumSpan));
String settings = context.getResources().getString(R.string.preferences);
int durationMs = DeviceClassManager.isAccessibilityModeEnabled(view.getContext())
? ACCESSIBILITY_SNACKBAR_DURATION_MS : SNACKBAR_DURATION_MS;
final GeolocationSnackbarController controller = new GeolocationSnackbarController();
final Snackbar snackbar = Snackbar
.make(message, controller, Snackbar.TYPE_ACTION, Snackbar.UMA_OMNIBOX_GEOLOCATION)
.setAction(settings, view)
.setSingleLine(false)
.setDuration(durationMs);
view.postDelayed(new Runnable() {
@Override
public void run() {
snackbarManager.dismissSnackbars(controller);
snackbarManager.showSnackbar(snackbar);
setGeolocationSnackbarShown(context);
}
}, delayMs);
}
private static boolean neverShowSnackbar(Context context) {
// Don't show the snackbar on pre-M devices because location permission was explicitly
// granted at install time.
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return true;
// Don't show the snackbar if Chrome doesn't have location permission since X-Geo won't be
// sent unless the user explicitly grants this permission.
if (!GeolocationHeader.hasGeolocationPermission(context)) return true;
// Don't show the snackbar if Google isn't the default search engine since X-Geo won't be
// sent unless the user explicitly sets Google as their default search engine and sees that
// "location is allowed" for omnibox searches in the process.
if (!TemplateUrlService.getInstance().isDefaultSearchEngineGoogle()) return true;
// Don't show the snackbar if location is disabled for google.com, since X-Geo won't be sent
// unless the user explicitly reenables location for google.com.
Uri searchUri = Uri.parse(TemplateUrlService.getInstance().getUrlForSearchQuery("foo"));
if (GeolocationHeader.isLocationDisabledForUrl(searchUri, false)) return true;
return false;
}
// SnackbarController implementation:
@Override
public void onDismissNoAction(Object actionData) {}
@Override
public void onAction(Object actionData) {
View view = (View) actionData;
UiUtils.hideKeyboard(view);
Context context = view.getContext();
Intent intent = PreferencesLauncher.createIntentForSettingsPage(
context, SearchEnginePreference.class.getName());
context.startActivity(intent);
}
/**
* Returns whether the geolocation snackbar has been shown before.
*/
static boolean getGeolocationSnackbarShown(Context context) {
if (sGeolocationSnackbarShown == null) {
// Cache the preference value since this method is called often.
sGeolocationSnackbarShown = ContextUtils.getAppSharedPreferences()
.getBoolean(GEOLOCATION_SNACKBAR_SHOWN_PREF, false);
}
return sGeolocationSnackbarShown;
}
private static void setGeolocationSnackbarShown(Context context) {
ContextUtils.getAppSharedPreferences().edit()
.putBoolean(GEOLOCATION_SNACKBAR_SHOWN_PREF, true).apply();
sGeolocationSnackbarShown = Boolean.TRUE;
}
}