Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(29)

Side by Side Diff: chrome/android/java/src/org/chromium/chrome/browser/pageinfo/WebsiteSettingsPopup.java

Issue 2765733002: Rename Android Java pageinfo folder to page_info (matching other platforms). (Closed)
Patch Set: Rename Android Java pageinfo folder to page_info (matching other platforms). Created 3 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698