| OLD | NEW |
| (Empty) |
| 1 // Copyright 2013 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 package org.chromium.chrome.browser.pageinfo; | |
| 6 | |
| 7 import android.animation.Animator; | |
| 8 import android.animation.AnimatorListenerAdapter; | |
| 9 import android.animation.AnimatorSet; | |
| 10 import android.animation.ObjectAnimator; | |
| 11 import android.app.Activity; | |
| 12 import android.app.Dialog; | |
| 13 import android.content.ActivityNotFoundException; | |
| 14 import android.content.ClipData; | |
| 15 import android.content.ClipboardManager; | |
| 16 import android.content.Context; | |
| 17 import android.content.DialogInterface; | |
| 18 import android.content.Intent; | |
| 19 import android.content.pm.PackageManager; | |
| 20 import android.graphics.Color; | |
| 21 import android.graphics.drawable.ColorDrawable; | |
| 22 import android.net.Uri; | |
| 23 import android.os.Bundle; | |
| 24 import android.provider.Settings; | |
| 25 import android.support.annotation.IntDef; | |
| 26 import android.support.v7.widget.AppCompatTextView; | |
| 27 import android.text.Layout; | |
| 28 import android.text.Spannable; | |
| 29 import android.text.SpannableString; | |
| 30 import android.text.SpannableStringBuilder; | |
| 31 import android.text.TextUtils; | |
| 32 import android.text.style.ForegroundColorSpan; | |
| 33 import android.text.style.StyleSpan; | |
| 34 import android.util.AttributeSet; | |
| 35 import android.view.Gravity; | |
| 36 import android.view.LayoutInflater; | |
| 37 import android.view.View; | |
| 38 import android.view.View.OnClickListener; | |
| 39 import android.view.View.OnLongClickListener; | |
| 40 import android.view.ViewGroup; | |
| 41 import android.view.Window; | |
| 42 import android.widget.Button; | |
| 43 import android.widget.ImageView; | |
| 44 import android.widget.LinearLayout; | |
| 45 import android.widget.ScrollView; | |
| 46 import android.widget.TextView; | |
| 47 | |
| 48 import org.chromium.base.ApiCompatibilityUtils; | |
| 49 import org.chromium.base.annotations.CalledByNative; | |
| 50 import org.chromium.base.metrics.RecordHistogram; | |
| 51 import org.chromium.base.metrics.RecordUserAction; | |
| 52 import org.chromium.chrome.R; | |
| 53 import org.chromium.chrome.browser.ContentSettingsType; | |
| 54 import org.chromium.chrome.browser.UrlConstants; | |
| 55 import org.chromium.chrome.browser.instantapps.InstantAppsHandler; | |
| 56 import org.chromium.chrome.browser.offlinepages.OfflinePageItem; | |
| 57 import org.chromium.chrome.browser.offlinepages.OfflinePageUtils; | |
| 58 import org.chromium.chrome.browser.omnibox.OmniboxUrlEmphasizer; | |
| 59 import org.chromium.chrome.browser.preferences.PrefServiceBridge; | |
| 60 import org.chromium.chrome.browser.preferences.Preferences; | |
| 61 import org.chromium.chrome.browser.preferences.PreferencesLauncher; | |
| 62 import org.chromium.chrome.browser.preferences.website.ContentSetting; | |
| 63 import org.chromium.chrome.browser.preferences.website.ContentSettingsResources; | |
| 64 import org.chromium.chrome.browser.preferences.website.SingleWebsitePreferences; | |
| 65 import org.chromium.chrome.browser.preferences.website.WebsitePreferenceBridge; | |
| 66 import org.chromium.chrome.browser.profiles.Profile; | |
| 67 import org.chromium.chrome.browser.ssl.SecurityStateModel; | |
| 68 import org.chromium.chrome.browser.tab.Tab; | |
| 69 import org.chromium.chrome.browser.util.UrlUtilities; | |
| 70 import org.chromium.components.location.LocationUtils; | |
| 71 import org.chromium.content_public.browser.WebContents; | |
| 72 import org.chromium.content_public.browser.WebContentsObserver; | |
| 73 import org.chromium.ui.base.DeviceFormFactor; | |
| 74 import org.chromium.ui.base.WindowAndroid; | |
| 75 import org.chromium.ui.base.WindowAndroid.PermissionCallback; | |
| 76 import org.chromium.ui.interpolators.BakedBezierInterpolator; | |
| 77 import org.chromium.ui.widget.Toast; | |
| 78 | |
| 79 import java.lang.annotation.Retention; | |
| 80 import java.lang.annotation.RetentionPolicy; | |
| 81 import java.net.URI; | |
| 82 import java.net.URISyntaxException; | |
| 83 import java.text.DateFormat; | |
| 84 import java.util.ArrayList; | |
| 85 import java.util.Date; | |
| 86 import java.util.List; | |
| 87 | |
| 88 /** | |
| 89 * Java side of Android implementation of the website settings UI. | |
| 90 * TODO(sashab): Rename this, and all its resources, to PageInfo* and page_info_
* instead of | |
| 91 * WebsiteSettings* and website_settings_*. Do this on the C++ sid
e as well. | |
| 92 */ | |
| 93 public class WebsiteSettingsPopup implements OnClickListener { | |
| 94 @Retention(RetentionPolicy.SOURCE) | |
| 95 @IntDef({OPENED_FROM_MENU, OPENED_FROM_TOOLBAR}) | |
| 96 private @interface OpenedFromSource {} | |
| 97 | |
| 98 public static final int OPENED_FROM_MENU = 1; | |
| 99 public static final int OPENED_FROM_TOOLBAR = 2; | |
| 100 | |
| 101 /** | |
| 102 * An entry in the settings dropdown for a given permission. There are two o
ptions for each | |
| 103 * permission: Allow and Block. | |
| 104 */ | |
| 105 private static final class PageInfoPermissionEntry { | |
| 106 public final String name; | |
| 107 public final int type; | |
| 108 public final ContentSetting setting; | |
| 109 | |
| 110 PageInfoPermissionEntry(String name, int type, ContentSetting setting) { | |
| 111 this.name = name; | |
| 112 this.type = type; | |
| 113 this.setting = setting; | |
| 114 } | |
| 115 | |
| 116 @Override | |
| 117 public String toString() { | |
| 118 return name; | |
| 119 } | |
| 120 } | |
| 121 | |
| 122 /** | |
| 123 * A TextView which truncates and displays a URL such that the origin is alw
ays visible. | |
| 124 * The URL can be expanded by clicking on the it. | |
| 125 */ | |
| 126 public static class ElidedUrlTextView extends AppCompatTextView { | |
| 127 // The number of lines to display when the URL is truncated. This number | |
| 128 // should still allow the origin to be displayed. NULL before | |
| 129 // setUrlAfterLayout() is called. | |
| 130 private Integer mTruncatedUrlLinesToDisplay; | |
| 131 | |
| 132 // The number of lines to display when the URL is expanded. This should
be enough to display | |
| 133 // at most two lines of the fragment if there is one in the URL. | |
| 134 private Integer mFullLinesToDisplay; | |
| 135 | |
| 136 // If true, the text view will show the truncated text. If false, it | |
| 137 // will show the full, expanded text. | |
| 138 private boolean mIsShowingTruncatedText = true; | |
| 139 | |
| 140 // The profile to use when getting the end index for the origin. | |
| 141 private Profile mProfile; | |
| 142 | |
| 143 // The maximum number of lines currently shown in the view | |
| 144 private int mCurrentMaxLines = Integer.MAX_VALUE; | |
| 145 | |
| 146 /** Constructor for inflating from XML. */ | |
| 147 public ElidedUrlTextView(Context context, AttributeSet attrs) { | |
| 148 super(context, attrs); | |
| 149 } | |
| 150 | |
| 151 @Override | |
| 152 public void setMaxLines(int maxlines) { | |
| 153 super.setMaxLines(maxlines); | |
| 154 mCurrentMaxLines = maxlines; | |
| 155 } | |
| 156 | |
| 157 /** | |
| 158 * Find the number of lines of text which must be shown in order to disp
lay the character at | |
| 159 * a given index. | |
| 160 */ | |
| 161 private int getLineForIndex(int index) { | |
| 162 Layout layout = getLayout(); | |
| 163 int endLine = 0; | |
| 164 while (endLine < layout.getLineCount() && layout.getLineEnd(endLine)
< index) { | |
| 165 endLine++; | |
| 166 } | |
| 167 // Since endLine is an index, add 1 to get the number of lines. | |
| 168 return endLine + 1; | |
| 169 } | |
| 170 | |
| 171 @Override | |
| 172 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { | |
| 173 setMaxLines(Integer.MAX_VALUE); | |
| 174 super.onMeasure(widthMeasureSpec, heightMeasureSpec); | |
| 175 assert mProfile != null : "setProfile() must be called before layout
."; | |
| 176 String urlText = getText().toString(); | |
| 177 | |
| 178 // Lay out the URL in a StaticLayout that is the same size as our fi
nal | |
| 179 // container. | |
| 180 int originEndIndex = OmniboxUrlEmphasizer.getOriginEndIndex(urlText,
mProfile); | |
| 181 | |
| 182 // Find the range of lines containing the origin. | |
| 183 int originEndLine = getLineForIndex(originEndIndex); | |
| 184 | |
| 185 // Display an extra line so we don't accidentally hide the origin wi
th | |
| 186 // ellipses | |
| 187 mTruncatedUrlLinesToDisplay = originEndLine + 1; | |
| 188 | |
| 189 // Find the line where the fragment starts. Since # is a reserved ch
aracter, it is safe | |
| 190 // to just search for the first # to appear in the url. | |
| 191 int fragmentStartIndex = urlText.indexOf('#'); | |
| 192 if (fragmentStartIndex == -1) fragmentStartIndex = urlText.length(); | |
| 193 | |
| 194 int fragmentStartLine = getLineForIndex(fragmentStartIndex); | |
| 195 mFullLinesToDisplay = fragmentStartLine + 1; | |
| 196 | |
| 197 // If there is no origin (according to OmniboxUrlEmphasizer), make s
ure the fragment is | |
| 198 // still hidden correctly. | |
| 199 if (mFullLinesToDisplay < mTruncatedUrlLinesToDisplay) { | |
| 200 mTruncatedUrlLinesToDisplay = mFullLinesToDisplay; | |
| 201 } | |
| 202 | |
| 203 if (updateMaxLines()) super.onMeasure(widthMeasureSpec, heightMeasur
eSpec); | |
| 204 } | |
| 205 | |
| 206 /** | |
| 207 * Sets the profile to use when calculating the end index of the origin. | |
| 208 * Must be called before layout. | |
| 209 * | |
| 210 * @param profile The profile to use when coloring the URL. | |
| 211 */ | |
| 212 public void setProfile(Profile profile) { | |
| 213 mProfile = profile; | |
| 214 } | |
| 215 | |
| 216 /** | |
| 217 * Toggles truncating/expanding the URL text. If the URL text is not | |
| 218 * truncated, has no effect. | |
| 219 */ | |
| 220 public void toggleTruncation() { | |
| 221 mIsShowingTruncatedText = !mIsShowingTruncatedText; | |
| 222 updateMaxLines(); | |
| 223 } | |
| 224 | |
| 225 private boolean updateMaxLines() { | |
| 226 int maxLines = mFullLinesToDisplay; | |
| 227 if (mIsShowingTruncatedText) maxLines = mTruncatedUrlLinesToDisplay; | |
| 228 if (maxLines != mCurrentMaxLines) { | |
| 229 setMaxLines(maxLines); | |
| 230 return true; | |
| 231 } | |
| 232 return false; | |
| 233 } | |
| 234 } | |
| 235 | |
| 236 // Delay enter to allow the triggering button to animate before we cover it. | |
| 237 private static final int ENTER_START_DELAY = 100; | |
| 238 private static final int FADE_DURATION = 200; | |
| 239 private static final int FADE_IN_BASE_DELAY = 150; | |
| 240 private static final int FADE_IN_DELAY_OFFSET = 20; | |
| 241 private static final int CLOSE_CLEANUP_DELAY = 10; | |
| 242 | |
| 243 private static final int MAX_TABLET_DIALOG_WIDTH_DP = 400; | |
| 244 | |
| 245 private final Context mContext; | |
| 246 private final WindowAndroid mWindowAndroid; | |
| 247 private final Tab mTab; | |
| 248 | |
| 249 // A pointer to the C++ object for this UI. | |
| 250 private long mNativeWebsiteSettingsPopup; | |
| 251 | |
| 252 // The outer container, filled with the layout from website_settings.xml. | |
| 253 private final LinearLayout mContainer; | |
| 254 | |
| 255 // UI elements in the dialog. | |
| 256 private final ElidedUrlTextView mUrlTitle; | |
| 257 private final TextView mConnectionSummary; | |
| 258 private final TextView mConnectionMessage; | |
| 259 private final LinearLayout mPermissionsList; | |
| 260 private final Button mInstantAppButton; | |
| 261 private final Button mSiteSettingsButton; | |
| 262 private final Button mOpenOnlineButton; | |
| 263 | |
| 264 // The dialog the container is placed in. | |
| 265 private final Dialog mDialog; | |
| 266 | |
| 267 // Animation which is currently running, if there is one. | |
| 268 private AnimatorSet mCurrentAnimation; | |
| 269 | |
| 270 private boolean mDismissWithoutAnimation; | |
| 271 | |
| 272 // The full URL from the URL bar, which is copied to the user's clipboard wh
en they select 'Copy | |
| 273 // URL'. | |
| 274 private String mFullUrl; | |
| 275 | |
| 276 // A parsed version of mFullUrl. Is null if the URL is invalid/cannot be | |
| 277 // parsed. | |
| 278 private URI mParsedUrl; | |
| 279 | |
| 280 // Whether or not this page is an internal chrome page (e.g. the | |
| 281 // chrome://settings page). | |
| 282 private boolean mIsInternalPage; | |
| 283 | |
| 284 // The security level of the page (a valid ConnectionSecurityLevel). | |
| 285 private int mSecurityLevel; | |
| 286 | |
| 287 // Permissions available to be displayed in mPermissionsList. | |
| 288 private List<PageInfoPermissionEntry> mDisplayedPermissions; | |
| 289 | |
| 290 // Creation date of an offline copy, if web contents contains an offline pag
e. | |
| 291 private String mOfflinePageCreationDate; | |
| 292 | |
| 293 // The name of the content publisher, if any. | |
| 294 private String mContentPublisher; | |
| 295 | |
| 296 // The intent associated with the instant app for this URL (or null if one d
oes not exist). | |
| 297 private Intent mInstantAppIntent; | |
| 298 | |
| 299 /** | |
| 300 * Creates the WebsiteSettingsPopup, but does not display it. Also initializ
es the corresponding | |
| 301 * C++ object and saves a pointer to it. | |
| 302 * @param activity Activity which is used for showing a popu
p. | |
| 303 * @param tab Tab for which the pop up is shown. | |
| 304 * @param offlinePageCreationDate Date when the offline page was created. | |
| 305 * @param publisher The name of the content publisher, if any
. | |
| 306 */ | |
| 307 private WebsiteSettingsPopup(Activity activity, Tab tab, String offlinePageC
reationDate, | |
| 308 String publisher) { | |
| 309 mContext = activity; | |
| 310 mTab = tab; | |
| 311 if (offlinePageCreationDate != null) { | |
| 312 mOfflinePageCreationDate = offlinePageCreationDate; | |
| 313 } | |
| 314 mWindowAndroid = mTab.getWebContents().getTopLevelNativeWindow(); | |
| 315 mContentPublisher = publisher; | |
| 316 | |
| 317 // Find the container and all it's important subviews. | |
| 318 mContainer = (LinearLayout) LayoutInflater.from(mContext).inflate( | |
| 319 R.layout.website_settings, null); | |
| 320 mContainer.setVisibility(View.INVISIBLE); | |
| 321 mContainer.addOnLayoutChangeListener(new View.OnLayoutChangeListener() { | |
| 322 @Override | |
| 323 public void onLayoutChange( | |
| 324 View v, int l, int t, int r, int b, int ol, int ot, int or,
int ob) { | |
| 325 // Trigger the entrance animations once the main container has b
een laid out and has | |
| 326 // a height. | |
| 327 mContainer.removeOnLayoutChangeListener(this); | |
| 328 mContainer.setVisibility(View.VISIBLE); | |
| 329 createAllAnimations(true).start(); | |
| 330 } | |
| 331 }); | |
| 332 | |
| 333 mUrlTitle = (ElidedUrlTextView) mContainer.findViewById(R.id.website_set
tings_url); | |
| 334 mUrlTitle.setProfile(mTab.getProfile()); | |
| 335 mUrlTitle.setOnClickListener(this); | |
| 336 // Long press the url text to copy it to the clipboard. | |
| 337 mUrlTitle.setOnLongClickListener(new OnLongClickListener() { | |
| 338 @Override | |
| 339 public boolean onLongClick(View v) { | |
| 340 ClipboardManager clipboard = (ClipboardManager) mContext | |
| 341 .getSystemService(Context.CLIPBOARD_SERVICE); | |
| 342 ClipData clip = ClipData.newPlainText("url", mFullUrl); | |
| 343 clipboard.setPrimaryClip(clip); | |
| 344 Toast.makeText(mContext, R.string.url_copied, Toast.LENGTH_SHORT
).show(); | |
| 345 return true; | |
| 346 } | |
| 347 }); | |
| 348 | |
| 349 mConnectionSummary = (TextView) mContainer | |
| 350 .findViewById(R.id.website_settings_connection_summary); | |
| 351 mConnectionMessage = (TextView) mContainer | |
| 352 .findViewById(R.id.website_settings_connection_message); | |
| 353 mPermissionsList = (LinearLayout) mContainer | |
| 354 .findViewById(R.id.website_settings_permissions_list); | |
| 355 | |
| 356 mInstantAppButton = | |
| 357 (Button) mContainer.findViewById(R.id.website_settings_instant_a
pp_button); | |
| 358 mInstantAppButton.setOnClickListener(this); | |
| 359 | |
| 360 mSiteSettingsButton = | |
| 361 (Button) mContainer.findViewById(R.id.website_settings_site_sett
ings_button); | |
| 362 mSiteSettingsButton.setOnClickListener(this); | |
| 363 | |
| 364 mOpenOnlineButton = | |
| 365 (Button) mContainer.findViewById(R.id.website_settings_open_onli
ne_button); | |
| 366 mOpenOnlineButton.setOnClickListener(this); | |
| 367 | |
| 368 mDisplayedPermissions = new ArrayList<PageInfoPermissionEntry>(); | |
| 369 | |
| 370 // Hide the permissions list for sites with no permissions. | |
| 371 setVisibilityOfPermissionsList(false); | |
| 372 | |
| 373 // Work out the URL and connection message and status visibility. | |
| 374 mFullUrl = mTab.getWebContents().getVisibleUrl(); | |
| 375 if (isShowingOfflinePage()) { | |
| 376 mFullUrl = OfflinePageUtils.stripSchemeFromOnlineUrl(mFullUrl); | |
| 377 } | |
| 378 | |
| 379 try { | |
| 380 mParsedUrl = new URI(mFullUrl); | |
| 381 mIsInternalPage = UrlUtilities.isInternalScheme(mParsedUrl); | |
| 382 } catch (URISyntaxException e) { | |
| 383 mParsedUrl = null; | |
| 384 mIsInternalPage = false; | |
| 385 } | |
| 386 mSecurityLevel = SecurityStateModel.getSecurityLevelForWebContents(mTab.
getWebContents()); | |
| 387 | |
| 388 SpannableStringBuilder urlBuilder = new SpannableStringBuilder(mFullUrl)
; | |
| 389 OmniboxUrlEmphasizer.emphasizeUrl(urlBuilder, mContext.getResources(), m
Tab.getProfile(), | |
| 390 mSecurityLevel, mIsInternalPage, true, true); | |
| 391 mUrlTitle.setText(urlBuilder); | |
| 392 | |
| 393 if (mParsedUrl == null || mParsedUrl.getScheme() == null | |
| 394 || !(mParsedUrl.getScheme().equals(UrlConstants.HTTP_SCHEME) | |
| 395 || mParsedUrl.getScheme().equals(UrlConstants.HTTPS_S
CHEME))) { | |
| 396 mSiteSettingsButton.setVisibility(View.GONE); | |
| 397 } | |
| 398 | |
| 399 if (isShowingOfflinePage()) { | |
| 400 boolean isConnected = OfflinePageUtils.isConnected(); | |
| 401 RecordHistogram.recordBooleanHistogram( | |
| 402 "OfflinePages.WebsiteSettings.OpenOnlineButtonVisible", isCo
nnected); | |
| 403 if (!isConnected) mOpenOnlineButton.setVisibility(View.GONE); | |
| 404 } else { | |
| 405 mOpenOnlineButton.setVisibility(View.GONE); | |
| 406 } | |
| 407 | |
| 408 mInstantAppIntent = (mIsInternalPage || isShowingOfflinePage()) ? null | |
| 409 : InstantAppsHandler.getInstance().getInstantAppIntentForUrl(mFu
llUrl); | |
| 410 if (mInstantAppIntent == null) mInstantAppButton.setVisibility(View.GONE
); | |
| 411 | |
| 412 // Create the dialog. | |
| 413 mDialog = new Dialog(mContext) { | |
| 414 private void superDismiss() { | |
| 415 super.dismiss(); | |
| 416 } | |
| 417 | |
| 418 @Override | |
| 419 public void dismiss() { | |
| 420 if (DeviceFormFactor.isTablet(mContext) || mDismissWithoutAnimat
ion) { | |
| 421 // Dismiss the dialog without any custom animations on table
t. | |
| 422 super.dismiss(); | |
| 423 } else { | |
| 424 Animator animator = createAllAnimations(false); | |
| 425 animator.addListener(new AnimatorListenerAdapter() { | |
| 426 @Override | |
| 427 public void onAnimationEnd(Animator animation) { | |
| 428 // onAnimationEnd is called during the final frame o
f the animation. | |
| 429 // Delay the cleanup by a tiny amount to give this f
rame a chance to be | |
| 430 // displayed before we destroy the dialog. | |
| 431 mContainer.postDelayed(new Runnable() { | |
| 432 @Override | |
| 433 public void run() { | |
| 434 superDismiss(); | |
| 435 } | |
| 436 }, CLOSE_CLEANUP_DELAY); | |
| 437 } | |
| 438 }); | |
| 439 animator.start(); | |
| 440 } | |
| 441 } | |
| 442 }; | |
| 443 mDialog.requestWindowFeature(Window.FEATURE_NO_TITLE); | |
| 444 mDialog.setCanceledOnTouchOutside(true); | |
| 445 | |
| 446 // On smaller screens, place the dialog at the top of the screen, and re
move its border. | |
| 447 if (!DeviceFormFactor.isTablet(mContext)) { | |
| 448 Window window = mDialog.getWindow(); | |
| 449 window.setGravity(Gravity.TOP); | |
| 450 window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); | |
| 451 } | |
| 452 | |
| 453 // This needs to come after other member initialization. | |
| 454 mNativeWebsiteSettingsPopup = nativeInit(this, mTab.getWebContents()); | |
| 455 final WebContentsObserver webContentsObserver = | |
| 456 new WebContentsObserver(mTab.getWebContents()) { | |
| 457 @Override | |
| 458 public void navigationEntryCommitted() { | |
| 459 // If a navigation is committed (e.g. from in-page redirect), th
e data we're showing | |
| 460 // is stale so dismiss the dialog. | |
| 461 mDialog.dismiss(); | |
| 462 } | |
| 463 | |
| 464 @Override | |
| 465 public void wasHidden() { | |
| 466 // The web contents were hidden (potentially by loading another
URL via an intent), | |
| 467 // so dismiss the dialog). | |
| 468 mDialog.dismiss(); | |
| 469 } | |
| 470 | |
| 471 @Override | |
| 472 public void destroy() { | |
| 473 super.destroy(); | |
| 474 // Force the dialog to close immediately in case the destroy was
from Chrome | |
| 475 // quitting. | |
| 476 mDismissWithoutAnimation = true; | |
| 477 mDialog.dismiss(); | |
| 478 } | |
| 479 }; | |
| 480 mDialog.setOnDismissListener(new DialogInterface.OnDismissListener() { | |
| 481 @Override | |
| 482 public void onDismiss(DialogInterface dialog) { | |
| 483 assert mNativeWebsiteSettingsPopup != 0; | |
| 484 webContentsObserver.destroy(); | |
| 485 nativeDestroy(mNativeWebsiteSettingsPopup); | |
| 486 mNativeWebsiteSettingsPopup = 0; | |
| 487 } | |
| 488 }); | |
| 489 | |
| 490 showDialog(); | |
| 491 } | |
| 492 | |
| 493 /** | |
| 494 * Sets the visibility of the permissions list, which contains padding and b
orders that should | |
| 495 * not be shown if a site has no permissions. | |
| 496 * | |
| 497 * @param isVisible Whether to show or hide the dialog area. | |
| 498 */ | |
| 499 private void setVisibilityOfPermissionsList(boolean isVisible) { | |
| 500 int visibility = isVisible ? View.VISIBLE : View.GONE; | |
| 501 mPermissionsList.setVisibility(visibility); | |
| 502 } | |
| 503 | |
| 504 /** | |
| 505 * Finds the Image resource of the icon to use for the given permission. | |
| 506 * | |
| 507 * @param permission A valid ContentSettingsType that can be displayed in th
e PageInfo dialog to | |
| 508 * retrieve the image for. | |
| 509 * @return The resource ID of the icon to use for that permission. | |
| 510 */ | |
| 511 private int getImageResourceForPermission(int permission) { | |
| 512 int icon = ContentSettingsResources.getIcon(permission); | |
| 513 assert icon != 0 : "Icon requested for invalid permission: " + permissio
n; | |
| 514 return icon; | |
| 515 } | |
| 516 | |
| 517 /** | |
| 518 * Whether to show a 'Details' link to the connection info popup. The link i
s only shown for | |
| 519 * HTTPS connections. | |
| 520 */ | |
| 521 private boolean isConnectionDetailsLinkVisible() { | |
| 522 return mContentPublisher == null && !isShowingOfflinePage() && mParsedUr
l != null | |
| 523 && mParsedUrl.getScheme() != null | |
| 524 && mParsedUrl.getScheme().equals(UrlConstants.HTTPS_SCHE
ME); | |
| 525 } | |
| 526 | |
| 527 private boolean hasAndroidPermission(int contentSettingType) { | |
| 528 String[] androidPermissions = | |
| 529 PrefServiceBridge.getAndroidPermissionsForContentSetting(content
SettingType); | |
| 530 if (androidPermissions == null) return true; | |
| 531 for (int i = 0; i < androidPermissions.length; i++) { | |
| 532 if (!mWindowAndroid.hasPermission(androidPermissions[i])) { | |
| 533 return false; | |
| 534 } | |
| 535 } | |
| 536 return true; | |
| 537 } | |
| 538 | |
| 539 /** | |
| 540 * Adds a new row for the given permission. | |
| 541 * | |
| 542 * @param name The title of the permission to display to the user. | |
| 543 * @param type The ContentSettingsType of the permission. | |
| 544 * @param currentSettingValue The ContentSetting value of the currently sele
cted setting. | |
| 545 */ | |
| 546 @CalledByNative | |
| 547 private void addPermissionSection(String name, int type, int currentSettingV
alue) { | |
| 548 // We have at least one permission, so show the lower permissions area. | |
| 549 setVisibilityOfPermissionsList(true); | |
| 550 mDisplayedPermissions.add(new PageInfoPermissionEntry(name, type, Conten
tSetting | |
| 551 .fromInt(currentSettingValue))); | |
| 552 } | |
| 553 | |
| 554 /** | |
| 555 * Update the permissions view based on the contents of mDisplayedPermission
s. | |
| 556 */ | |
| 557 @CalledByNative | |
| 558 private void updatePermissionDisplay() { | |
| 559 mPermissionsList.removeAllViews(); | |
| 560 for (PageInfoPermissionEntry permission : mDisplayedPermissions) { | |
| 561 addReadOnlyPermissionSection(permission); | |
| 562 } | |
| 563 } | |
| 564 | |
| 565 private void addReadOnlyPermissionSection(PageInfoPermissionEntry permission
) { | |
| 566 View permissionRow = LayoutInflater.from(mContext).inflate( | |
| 567 R.layout.website_settings_permission_row, null); | |
| 568 | |
| 569 ImageView permissionIcon = (ImageView) permissionRow.findViewById( | |
| 570 R.id.website_settings_permission_icon); | |
| 571 permissionIcon.setImageResource(getImageResourceForPermission(permission
.type)); | |
| 572 | |
| 573 if (permission.setting == ContentSetting.ALLOW) { | |
| 574 int warningTextResource = 0; | |
| 575 | |
| 576 // If warningTextResource is non-zero, then the view must be tagged
with either | |
| 577 // permission_intent_override or permission_type. | |
| 578 LocationUtils locationUtils = LocationUtils.getInstance(); | |
| 579 if (permission.type == ContentSettingsType.CONTENT_SETTINGS_TYPE_GEO
LOCATION | |
| 580 && !locationUtils.isSystemLocationSettingEnabled()) { | |
| 581 warningTextResource = R.string.page_info_android_location_blocke
d; | |
| 582 permissionRow.setTag(R.id.permission_intent_override, | |
| 583 locationUtils.getSystemLocationSettingsIntent()); | |
| 584 } else if (!hasAndroidPermission(permission.type)) { | |
| 585 warningTextResource = R.string.page_info_android_permission_bloc
ked; | |
| 586 permissionRow.setTag(R.id.permission_type, | |
| 587 PrefServiceBridge.getAndroidPermissionsForContentSetting
(permission.type)); | |
| 588 } | |
| 589 | |
| 590 if (warningTextResource != 0) { | |
| 591 TextView permissionUnavailable = (TextView) permissionRow.findVi
ewById( | |
| 592 R.id.website_settings_permission_unavailable_message); | |
| 593 permissionUnavailable.setVisibility(View.VISIBLE); | |
| 594 permissionUnavailable.setText(warningTextResource); | |
| 595 | |
| 596 permissionIcon.setImageResource(R.drawable.exclamation_triangle)
; | |
| 597 permissionIcon.setColorFilter(ApiCompatibilityUtils.getColor( | |
| 598 mContext.getResources(), R.color.website_settings_popup_
text_link)); | |
| 599 | |
| 600 permissionRow.setOnClickListener(this); | |
| 601 } | |
| 602 } | |
| 603 | |
| 604 TextView permissionStatus = (TextView) permissionRow.findViewById( | |
| 605 R.id.website_settings_permission_status); | |
| 606 SpannableStringBuilder builder = new SpannableStringBuilder(); | |
| 607 SpannableString nameString = new SpannableString(permission.name); | |
| 608 final StyleSpan boldSpan = new StyleSpan(android.graphics.Typeface.BOLD)
; | |
| 609 nameString.setSpan(boldSpan, 0, nameString.length(), Spannable.SPAN_INCL
USIVE_EXCLUSIVE); | |
| 610 | |
| 611 builder.append(nameString); | |
| 612 builder.append(" – "); // en-dash. | |
| 613 String status_text = ""; | |
| 614 switch (permission.setting) { | |
| 615 case ALLOW: | |
| 616 status_text = mContext.getString(R.string.page_info_permission_a
llowed); | |
| 617 break; | |
| 618 case BLOCK: | |
| 619 status_text = mContext.getString(R.string.page_info_permission_b
locked); | |
| 620 break; | |
| 621 default: | |
| 622 assert false : "Invalid setting " + permission.setting + " for p
ermission " | |
| 623 + permission.type; | |
| 624 } | |
| 625 if (permission.type == ContentSettingsType.CONTENT_SETTINGS_TYPE_GEOLOCA
TION | |
| 626 && WebsitePreferenceBridge.shouldUseDSEGeolocationSetting(mFullU
rl, false)) { | |
| 627 status_text = statusTextForDSEPermission(permission); | |
| 628 } | |
| 629 builder.append(status_text); | |
| 630 permissionStatus.setText(builder); | |
| 631 mPermissionsList.addView(permissionRow); | |
| 632 } | |
| 633 | |
| 634 /** | |
| 635 * Update the permission string for the Default Search Engine. | |
| 636 */ | |
| 637 private String statusTextForDSEPermission(PageInfoPermissionEntry permission
) { | |
| 638 if (permission.setting == ContentSetting.ALLOW) { | |
| 639 return mContext.getString(R.string.page_info_dse_permission_allowed)
; | |
| 640 } | |
| 641 | |
| 642 return mContext.getString(R.string.page_info_dse_permission_blocked); | |
| 643 } | |
| 644 | |
| 645 /** | |
| 646 * Sets the connection security summary and detailed description strings. Th
ese strings may be | |
| 647 * overridden based on the state of the Android UI. | |
| 648 */ | |
| 649 @CalledByNative | |
| 650 private void setSecurityDescription(String summary, String details) { | |
| 651 // Display the appropriate connection message. | |
| 652 SpannableStringBuilder messageBuilder = new SpannableStringBuilder(); | |
| 653 if (mContentPublisher != null) { | |
| 654 messageBuilder.append( | |
| 655 mContext.getString(R.string.page_info_domain_hidden, mConten
tPublisher)); | |
| 656 } else if (isShowingOfflinePage()) { | |
| 657 messageBuilder.append(String.format( | |
| 658 mContext.getString(R.string.page_info_connection_offline), | |
| 659 mOfflinePageCreationDate)); | |
| 660 } else { | |
| 661 if (!TextUtils.equals(summary, details)) { | |
| 662 mConnectionSummary.setVisibility(View.VISIBLE); | |
| 663 mConnectionSummary.setText(summary); | |
| 664 } | |
| 665 messageBuilder.append(details); | |
| 666 } | |
| 667 | |
| 668 if (isConnectionDetailsLinkVisible()) { | |
| 669 messageBuilder.append(" "); | |
| 670 SpannableString detailsText = new SpannableString( | |
| 671 mContext.getString(R.string.page_info_details_link)); | |
| 672 final ForegroundColorSpan blueSpan = new ForegroundColorSpan( | |
| 673 ApiCompatibilityUtils.getColor(mContext.getResources(), | |
| 674 R.color.website_settings_popup_text_link)); | |
| 675 detailsText.setSpan( | |
| 676 blueSpan, 0, detailsText.length(), Spannable.SPAN_INCLUSIVE_
EXCLUSIVE); | |
| 677 messageBuilder.append(detailsText); | |
| 678 } | |
| 679 mConnectionMessage.setText(messageBuilder); | |
| 680 if (isConnectionDetailsLinkVisible()) mConnectionMessage.setOnClickListe
ner(this); | |
| 681 } | |
| 682 | |
| 683 /** | |
| 684 * Displays the WebsiteSettingsPopup. | |
| 685 */ | |
| 686 private void showDialog() { | |
| 687 if (!DeviceFormFactor.isTablet(mContext)) { | |
| 688 // On smaller screens, make the dialog fill the width of the screen. | |
| 689 ScrollView scrollView = new ScrollView(mContext); | |
| 690 scrollView.addView(mContainer); | |
| 691 mDialog.addContentView(scrollView, new LinearLayout.LayoutParams( | |
| 692 LinearLayout.LayoutParams.MATCH_PARENT, | |
| 693 LinearLayout.LayoutParams.MATCH_PARENT)); | |
| 694 | |
| 695 // This must be called after addContentView, or it won't fully fill
to the edge. | |
| 696 Window window = mDialog.getWindow(); | |
| 697 window.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, | |
| 698 ViewGroup.LayoutParams.WRAP_CONTENT); | |
| 699 } else { | |
| 700 // On larger screens, make the dialog centered in the screen and hav
e a maximum width. | |
| 701 ScrollView scrollView = new ScrollView(mContext) { | |
| 702 @Override | |
| 703 protected void onMeasure(int widthMeasureSpec, int heightMeasure
Spec) { | |
| 704 final int maxDialogWidthInPx = (int) (MAX_TABLET_DIALOG_WIDT
H_DP | |
| 705 * mContext.getResources().getDisplayMetrics().densit
y); | |
| 706 if (MeasureSpec.getSize(widthMeasureSpec) > maxDialogWidthIn
Px) { | |
| 707 widthMeasureSpec = MeasureSpec.makeMeasureSpec(maxDialog
WidthInPx, | |
| 708 MeasureSpec.EXACTLY); | |
| 709 } | |
| 710 super.onMeasure(widthMeasureSpec, heightMeasureSpec); | |
| 711 } | |
| 712 }; | |
| 713 | |
| 714 scrollView.addView(mContainer); | |
| 715 mDialog.addContentView(scrollView, new LinearLayout.LayoutParams( | |
| 716 LinearLayout.LayoutParams.WRAP_CONTENT, | |
| 717 LinearLayout.LayoutParams.MATCH_PARENT)); | |
| 718 } | |
| 719 | |
| 720 mDialog.show(); | |
| 721 } | |
| 722 | |
| 723 /** | |
| 724 * Dismiss the popup, and then run a task after the animation has completed
(if there is one). | |
| 725 */ | |
| 726 private void runAfterDismiss(Runnable task) { | |
| 727 mDialog.dismiss(); | |
| 728 if (DeviceFormFactor.isTablet(mContext)) { | |
| 729 task.run(); | |
| 730 } else { | |
| 731 mContainer.postDelayed(task, FADE_DURATION + CLOSE_CLEANUP_DELAY); | |
| 732 } | |
| 733 } | |
| 734 | |
| 735 @Override | |
| 736 public void onClick(View view) { | |
| 737 if (view == mSiteSettingsButton) { | |
| 738 // Delay while the WebsiteSettingsPopup closes. | |
| 739 runAfterDismiss(new Runnable() { | |
| 740 @Override | |
| 741 public void run() { | |
| 742 recordAction(WebsiteSettingsAction.WEBSITE_SETTINGS_SITE_SET
TINGS_OPENED); | |
| 743 Bundle fragmentArguments = | |
| 744 SingleWebsitePreferences.createFragmentArgsForSite(m
FullUrl); | |
| 745 fragmentArguments.putParcelable(SingleWebsitePreferences.EXT
RA_WEB_CONTENTS, | |
| 746 mTab.getWebContents()); | |
| 747 Intent preferencesIntent = PreferencesLauncher.createIntentF
orSettingsPage( | |
| 748 mContext, SingleWebsitePreferences.class.getName()); | |
| 749 preferencesIntent.putExtra( | |
| 750 Preferences.EXTRA_SHOW_FRAGMENT_ARGUMENTS, fragmentA
rguments); | |
| 751 mContext.startActivity(preferencesIntent); | |
| 752 } | |
| 753 }); | |
| 754 } else if (view == mInstantAppButton) { | |
| 755 try { | |
| 756 mContext.startActivity(mInstantAppIntent); | |
| 757 RecordUserAction.record("Android.InstantApps.LaunchedFromWebsite
SettingsPopup"); | |
| 758 } catch (ActivityNotFoundException e) { | |
| 759 mInstantAppButton.setEnabled(false); | |
| 760 } | |
| 761 } else if (view == mUrlTitle) { | |
| 762 // Expand/collapse the displayed URL title. | |
| 763 mUrlTitle.toggleTruncation(); | |
| 764 } else if (view == mConnectionMessage) { | |
| 765 runAfterDismiss(new Runnable() { | |
| 766 @Override | |
| 767 public void run() { | |
| 768 if (!mTab.getWebContents().isDestroyed()) { | |
| 769 recordAction( | |
| 770 WebsiteSettingsAction.WEBSITE_SETTINGS_SECURITY_
DETAILS_OPENED); | |
| 771 ConnectionInfoPopup.show(mContext, mTab.getWebContents()
); | |
| 772 } | |
| 773 } | |
| 774 }); | |
| 775 } else if (view.getId() == R.id.website_settings_permission_row) { | |
| 776 final Object intentOverride = view.getTag(R.id.permission_intent_ove
rride); | |
| 777 | |
| 778 if (intentOverride == null && mWindowAndroid != null) { | |
| 779 // Try and immediately request missing Android permissions where
possible. | |
| 780 final String[] permissionType = (String[]) view.getTag(R.id.perm
ission_type); | |
| 781 for (int i = 0; i < permissionType.length; i++) { | |
| 782 if (!mWindowAndroid.canRequestPermission(permissionType[i]))
continue; | |
| 783 | |
| 784 // If any permissions can be requested, attempt to request t
hem all. | |
| 785 mWindowAndroid.requestPermissions(permissionType, new Permis
sionCallback() { | |
| 786 @Override | |
| 787 public void onRequestPermissionsResult( | |
| 788 String[] permissions, int[] grantResults) { | |
| 789 boolean allGranted = true; | |
| 790 for (int i = 0; i < grantResults.length; i++) { | |
| 791 if (grantResults[i] != PackageManager.PERMISSION
_GRANTED) { | |
| 792 allGranted = false; | |
| 793 break; | |
| 794 } | |
| 795 } | |
| 796 if (allGranted) updatePermissionDisplay(); | |
| 797 } | |
| 798 }); | |
| 799 return; | |
| 800 } | |
| 801 } | |
| 802 | |
| 803 runAfterDismiss(new Runnable() { | |
| 804 @Override | |
| 805 public void run() { | |
| 806 Intent settingsIntent; | |
| 807 if (intentOverride != null) { | |
| 808 settingsIntent = (Intent) intentOverride; | |
| 809 } else { | |
| 810 settingsIntent = new Intent(Settings.ACTION_APPLICATION_
DETAILS_SETTINGS); | |
| 811 settingsIntent.setData(Uri.parse("package:" + mContext.g
etPackageName())); | |
| 812 } | |
| 813 settingsIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); | |
| 814 mContext.startActivity(settingsIntent); | |
| 815 } | |
| 816 }); | |
| 817 } else if (view == mOpenOnlineButton) { | |
| 818 runAfterDismiss(new Runnable() { | |
| 819 @Override | |
| 820 public void run() { | |
| 821 // Attempt to reload to an online version of the viewed offl
ine web page. This | |
| 822 // attempt might fail if the user is offline, in which case
an offline copy will | |
| 823 // be reloaded. | |
| 824 RecordHistogram.recordBooleanHistogram( | |
| 825 "OfflinePages.WebsiteSettings.ConnectedWhenOpenOnlin
eButtonClicked", | |
| 826 OfflinePageUtils.isConnected()); | |
| 827 OfflinePageUtils.reload(mTab); | |
| 828 } | |
| 829 }); | |
| 830 } | |
| 831 } | |
| 832 | |
| 833 /** | |
| 834 * Create a list of all the views which we want to individually fade in. | |
| 835 */ | |
| 836 private List<View> collectAnimatableViews() { | |
| 837 List<View> animatableViews = new ArrayList<View>(); | |
| 838 animatableViews.add(mUrlTitle); | |
| 839 if (mConnectionSummary.getVisibility() == View.VISIBLE) { | |
| 840 animatableViews.add(mConnectionSummary); | |
| 841 } | |
| 842 animatableViews.add(mConnectionMessage); | |
| 843 animatableViews.add(mInstantAppButton); | |
| 844 for (int i = 0; i < mPermissionsList.getChildCount(); i++) { | |
| 845 animatableViews.add(mPermissionsList.getChildAt(i)); | |
| 846 } | |
| 847 animatableViews.add(mSiteSettingsButton); | |
| 848 | |
| 849 return animatableViews; | |
| 850 } | |
| 851 | |
| 852 /** | |
| 853 * Create an animator to fade an individual dialog element. | |
| 854 */ | |
| 855 private Animator createInnerFadeAnimator(final View view, int position, bool
ean isEnter) { | |
| 856 ObjectAnimator alphaAnim; | |
| 857 | |
| 858 if (isEnter) { | |
| 859 view.setAlpha(0f); | |
| 860 alphaAnim = ObjectAnimator.ofFloat(view, View.ALPHA, 1f); | |
| 861 alphaAnim.setStartDelay(FADE_IN_BASE_DELAY + FADE_IN_DELAY_OFFSET *
position); | |
| 862 } else { | |
| 863 alphaAnim = ObjectAnimator.ofFloat(view, View.ALPHA, 0f); | |
| 864 } | |
| 865 | |
| 866 alphaAnim.setDuration(FADE_DURATION); | |
| 867 return alphaAnim; | |
| 868 } | |
| 869 | |
| 870 /** | |
| 871 * Create an animator to slide in the entire dialog from the top of the scre
en. | |
| 872 */ | |
| 873 private Animator createDialogSlideAnimator(boolean isEnter) { | |
| 874 final float animHeight = -1f * mContainer.getHeight(); | |
| 875 ObjectAnimator translateAnim; | |
| 876 if (isEnter) { | |
| 877 mContainer.setTranslationY(animHeight); | |
| 878 translateAnim = ObjectAnimator.ofFloat(mContainer, View.TRANSLATION_
Y, 0f); | |
| 879 translateAnim.setInterpolator(BakedBezierInterpolator.FADE_IN_CURVE)
; | |
| 880 } else { | |
| 881 translateAnim = ObjectAnimator.ofFloat(mContainer, View.TRANSLATION_
Y, animHeight); | |
| 882 translateAnim.setInterpolator(BakedBezierInterpolator.FADE_OUT_CURVE
); | |
| 883 } | |
| 884 translateAnim.setDuration(FADE_DURATION); | |
| 885 return translateAnim; | |
| 886 } | |
| 887 | |
| 888 /** | |
| 889 * Create animations for showing/hiding the popup. | |
| 890 * | |
| 891 * Tablets use the default Dialog fade-in instead of sliding in manually. | |
| 892 */ | |
| 893 private Animator createAllAnimations(boolean isEnter) { | |
| 894 AnimatorSet animation = new AnimatorSet(); | |
| 895 AnimatorSet.Builder builder = null; | |
| 896 Animator startAnim; | |
| 897 | |
| 898 if (DeviceFormFactor.isTablet(mContext)) { | |
| 899 // The start time of the entire AnimatorSet is the start time of the
first animation | |
| 900 // added to the Builder. We use a blank AnimatorSet on tablet as an
easy way to | |
| 901 // co-ordinate this start time. | |
| 902 startAnim = new AnimatorSet(); | |
| 903 } else { | |
| 904 startAnim = createDialogSlideAnimator(isEnter); | |
| 905 } | |
| 906 | |
| 907 if (isEnter) startAnim.setStartDelay(ENTER_START_DELAY); | |
| 908 builder = animation.play(startAnim); | |
| 909 | |
| 910 List<View> animatableViews = collectAnimatableViews(); | |
| 911 for (int i = 0; i < animatableViews.size(); i++) { | |
| 912 View view = animatableViews.get(i); | |
| 913 Animator anim = createInnerFadeAnimator(view, i, isEnter); | |
| 914 builder.with(anim); | |
| 915 } | |
| 916 | |
| 917 animation.addListener(new AnimatorListenerAdapter() { | |
| 918 @Override | |
| 919 public void onAnimationEnd(Animator animation) { | |
| 920 mCurrentAnimation = null; | |
| 921 } | |
| 922 }); | |
| 923 if (mCurrentAnimation != null) mCurrentAnimation.cancel(); | |
| 924 mCurrentAnimation = animation; | |
| 925 return animation; | |
| 926 } | |
| 927 | |
| 928 private void recordAction(int action) { | |
| 929 if (mNativeWebsiteSettingsPopup != 0) { | |
| 930 nativeRecordWebsiteSettingsAction(mNativeWebsiteSettingsPopup, actio
n); | |
| 931 } | |
| 932 } | |
| 933 | |
| 934 /** | |
| 935 * Whether website dialog is displayed for an offline page. | |
| 936 */ | |
| 937 private boolean isShowingOfflinePage() { | |
| 938 return mOfflinePageCreationDate != null; | |
| 939 } | |
| 940 | |
| 941 /** | |
| 942 * Shows a WebsiteSettings dialog for the provided Tab. The popup adds itsel
f to the view | |
| 943 * hierarchy which owns the reference while it's visible. | |
| 944 * | |
| 945 * @param activity Activity which is used for launching a dialog. | |
| 946 * @param tab The tab hosting the web contents for which to show Website inf
ormation. This | |
| 947 * information is retrieved for the visible entry. | |
| 948 * @param contentPublisher The name of the publisher of the content. | |
| 949 * @param source Determines the source that triggered the popup. | |
| 950 */ | |
| 951 public static void show(final Activity activity, final Tab tab, final String
contentPublisher, | |
| 952 @OpenedFromSource int source) { | |
| 953 if (source == OPENED_FROM_MENU) { | |
| 954 RecordUserAction.record("MobileWebsiteSettingsOpenedFromMenu"); | |
| 955 } else if (source == OPENED_FROM_TOOLBAR) { | |
| 956 RecordUserAction.record("MobileWebsiteSettingsOpenedFromToolbar"); | |
| 957 } else { | |
| 958 assert false : "Invalid source passed"; | |
| 959 } | |
| 960 | |
| 961 String offlinePageCreationDate = null; | |
| 962 | |
| 963 OfflinePageItem offlinePage = tab.getOfflinePage(); | |
| 964 if (offlinePage != null) { | |
| 965 // Get formatted creation date of the offline page. | |
| 966 Date creationDate = new Date(offlinePage.getCreationTimeMs()); | |
| 967 DateFormat df = DateFormat.getDateInstance(DateFormat.MEDIUM); | |
| 968 offlinePageCreationDate = df.format(creationDate); | |
| 969 } | |
| 970 | |
| 971 new WebsiteSettingsPopup(activity, tab, offlinePageCreationDate, content
Publisher); | |
| 972 } | |
| 973 | |
| 974 private static native long nativeInit(WebsiteSettingsPopup popup, WebContent
s webContents); | |
| 975 | |
| 976 private native void nativeDestroy(long nativeWebsiteSettingsPopupAndroid); | |
| 977 | |
| 978 private native void nativeRecordWebsiteSettingsAction( | |
| 979 long nativeWebsiteSettingsPopupAndroid, int action); | |
| 980 } | |
| OLD | NEW |