diff options
Diffstat (limited to 'src/com')
| -rw-r--r-- | src/com/android/browser/BrowserDownloadListener.java | 58 | ||||
| -rw-r--r-- | src/com/android/browser/BrowserPreferencesPage.java | 15 | ||||
| -rw-r--r-- | src/com/android/browser/BrowserSettings.java | 25 | ||||
| -rw-r--r-- | src/com/android/browser/BrowserWebViewFactory.java | 10 | ||||
| -rw-r--r-- | src/com/android/browser/Controller.java | 7 | ||||
| -rw-r--r-- | src/com/android/browser/KeyChainLookup.java | 52 | ||||
| -rw-r--r-- | src/com/android/browser/PermissionsPrompt.java | 125 | ||||
| -rw-r--r-- | src/com/android/browser/PreloadController.java | 5 | ||||
| -rw-r--r-- | src/com/android/browser/Tab.java | 82 | ||||
| -rw-r--r-- | src/com/android/browser/TabBar.java | 4 | ||||
| -rw-r--r-- | src/com/android/browser/UploadHandler.java | 276 | ||||
| -rw-r--r-- | src/com/android/browser/UrlHandler.java | 5 | ||||
| -rw-r--r-- | src/com/android/browser/WebBackForwardListClient.java | 41 | ||||
| -rw-r--r-- | src/com/android/browser/WebViewController.java | 3 |
14 files changed, 489 insertions, 219 deletions
diff --git a/src/com/android/browser/BrowserDownloadListener.java b/src/com/android/browser/BrowserDownloadListener.java new file mode 100644 index 000000000..08947fad3 --- /dev/null +++ b/src/com/android/browser/BrowserDownloadListener.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.browser; + +import android.webkit.DownloadListener; + +/** + * An abstract download listener that allows passing extra information as + * part of onDownloadStart callback. + */ +public abstract class BrowserDownloadListener implements DownloadListener { + + /** + * Notify the host application that a file should be downloaded + * @param url The full url to the content that should be downloaded + * @param userAgent the user agent to be used for the download. + * @param contentDisposition Content-disposition http header, if + * present. + * @param mimetype The mimetype of the content reported by the server + * @param referer The referer associated with this url + * @param contentLength The file size reported by the server + */ + public abstract void onDownloadStart(String url, String userAgent, + String contentDisposition, String mimetype, String referer, + long contentLength); + + + /** + * Notify the host application that a file should be downloaded + * @param url The full url to the content that should be downloaded + * @param userAgent the user agent to be used for the download. + * @param contentDisposition Content-disposition http header, if + * present. + * @param mimetype The mimetype of the content reported by the server + * @param contentLength The file size reported by the server + */ + @Override + public void onDownloadStart(String url, String userAgent, + String contentDisposition, String mimetype, long contentLength) { + + onDownloadStart(url, userAgent, contentDisposition, mimetype, null, + contentLength); + } +} diff --git a/src/com/android/browser/BrowserPreferencesPage.java b/src/com/android/browser/BrowserPreferencesPage.java index 9b538e0af..6e6da178c 100644 --- a/src/com/android/browser/BrowserPreferencesPage.java +++ b/src/com/android/browser/BrowserPreferencesPage.java @@ -25,10 +25,7 @@ import android.view.MenuItem; import com.android.browser.preferences.BandwidthPreferencesFragment; import com.android.browser.preferences.DebugPreferencesFragment; -import java.util.Arrays; -import java.util.HashSet; import java.util.List; -import java.util.Set; public class BrowserPreferencesPage extends PreferenceActivity { @@ -101,16 +98,4 @@ public class BrowserPreferencesPage extends PreferenceActivity { return intent; } - private static final Set<String> sKnownFragments = new HashSet<String>(Arrays.asList( - "com.android.browser.preferences.GeneralPreferencesFragment", - "com.android.browser.preferences.PrivacySecurityPreferencesFragment", - "com.android.browser.preferences.AccessibilityPreferencesFragment", - "com.android.browser.preferences.AdvancedPreferencesFragment", - "com.android.browser.preferences.BandwidthPreferencesFragment", - "com.android.browser.preferences.LabPreferencesFragment")); - - @Override - protected boolean isValidFragment(String fragmentName) { - return sKnownFragments.contains(fragmentName); - } } diff --git a/src/com/android/browser/BrowserSettings.java b/src/com/android/browser/BrowserSettings.java index ed443941f..586db289e 100644 --- a/src/com/android/browser/BrowserSettings.java +++ b/src/com/android/browser/BrowserSettings.java @@ -311,6 +311,9 @@ public class BrowserSettings implements OnSharedPreferenceChangeListener, mNeedsSharedSync = false; CookieManager.getInstance().setAcceptCookie(acceptCookies()); if (mController != null) { + for (Tab tab : mController.getTabs()) { + tab.setAcceptThirdPartyCookies(acceptCookies()); + } mController.setShouldShowErrorConsole(enableJavascriptConsole()); } } @@ -357,18 +360,17 @@ public class BrowserSettings implements OnSharedPreferenceChangeListener, public LayoutAlgorithm getLayoutAlgorithm() { LayoutAlgorithm layoutAlgorithm = LayoutAlgorithm.NORMAL; + final LayoutAlgorithm autosize = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT ? + LayoutAlgorithm.TEXT_AUTOSIZING : LayoutAlgorithm.NARROW_COLUMNS; + if (autofitPages()) { - layoutAlgorithm = LayoutAlgorithm.NARROW_COLUMNS; + layoutAlgorithm = autosize; } if (isDebugEnabled()) { - if (isSmallScreen()) { - layoutAlgorithm = LayoutAlgorithm.SINGLE_COLUMN; + if (isNormalLayout()) { + layoutAlgorithm = LayoutAlgorithm.NORMAL; } else { - if (isNormalLayout()) { - layoutAlgorithm = LayoutAlgorithm.NORMAL; - } else { - layoutAlgorithm = LayoutAlgorithm.NARROW_COLUMNS; - } + layoutAlgorithm = autosize; } } return layoutAlgorithm; @@ -724,13 +726,6 @@ public class BrowserSettings implements OnSharedPreferenceChangeListener, return mPrefs.getBoolean(PREF_JAVASCRIPT_CONSOLE, true); } - public boolean isSmallScreen() { - if (!isDebugEnabled()) { - return false; - } - return mPrefs.getBoolean(PREF_SMALL_SCREEN, false); - } - public boolean isWideViewport() { if (!isDebugEnabled()) { return true; diff --git a/src/com/android/browser/BrowserWebViewFactory.java b/src/com/android/browser/BrowserWebViewFactory.java index f976b0719..2349c282f 100644 --- a/src/com/android/browser/BrowserWebViewFactory.java +++ b/src/com/android/browser/BrowserWebViewFactory.java @@ -19,6 +19,7 @@ import android.content.Context; import android.content.pm.PackageManager; import android.util.AttributeSet; import android.view.View; +import android.webkit.CookieManager; import android.webkit.WebView; /** @@ -66,8 +67,13 @@ public class BrowserWebViewFactory implements WebViewFactory { final BrowserSettings s = BrowserSettings.getInstance(); s.startManagingSettings(w.getSettings()); - // Remote Web Debugging is always enabled - WebView.setWebContentsDebuggingEnabled(true); + CookieManager cookieManager = CookieManager.getInstance(); + cookieManager.setAcceptThirdPartyCookies(w, cookieManager.acceptCookie()); + + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) { + // Remote Web Debugging is always enabled, where available. + WebView.setWebContentsDebuggingEnabled(true); + } } } diff --git a/src/com/android/browser/Controller.java b/src/com/android/browser/Controller.java index 70f4383d9..3e4ae66c7 100644 --- a/src/com/android/browser/Controller.java +++ b/src/com/android/browser/Controller.java @@ -75,6 +75,7 @@ import android.webkit.MimeTypeMap; import android.webkit.SslErrorHandler; import android.webkit.ValueCallback; import android.webkit.WebChromeClient; +import android.webkit.WebChromeClient.FileChooserParams; import android.webkit.WebIconDatabase; import android.webkit.WebSettings; import android.webkit.WebView; @@ -1922,9 +1923,9 @@ public class Controller // file chooser @Override - public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) { + public void showFileChooser(ValueCallback<Uri[]> callback, FileChooserParams params) { mUploadHandler = new UploadHandler(this); - mUploadHandler.openFileChooser(uploadMsg, acceptType, capture); + mUploadHandler.openFileChooser(callback, params); } // thumbnails @@ -2709,7 +2710,7 @@ public class Controller @Override public void startVoiceRecognizer() { Intent voice = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH); - voice.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, + voice.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM); voice.putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, 1); mActivity.startActivityForResult(voice, VOICE_RESULT); diff --git a/src/com/android/browser/KeyChainLookup.java b/src/com/android/browser/KeyChainLookup.java new file mode 100644 index 000000000..b89afc72a --- /dev/null +++ b/src/com/android/browser/KeyChainLookup.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 201 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.browser; + +import android.content.Context; +import android.os.AsyncTask; +import android.security.KeyChain; +import android.security.KeyChainException; +import android.webkit.ClientCertRequest; +import java.security.PrivateKey; +import java.security.cert.X509Certificate; + +final class KeyChainLookup extends AsyncTask<Void, Void, Void> { + private final Context mContext; + private final ClientCertRequest mHandler; + private final String mAlias; + KeyChainLookup(Context context, ClientCertRequest handler, String alias) { + mContext = context.getApplicationContext(); + mHandler = handler; + mAlias = alias; + } + @Override protected Void doInBackground(Void... params) { + PrivateKey privateKey; + X509Certificate[] certificateChain; + try { + privateKey = KeyChain.getPrivateKey(mContext, mAlias); + certificateChain = KeyChain.getCertificateChain(mContext, mAlias); + } catch (InterruptedException e) { + mHandler.ignore(); + return null; + } catch (KeyChainException e) { + mHandler.ignore(); + return null; + } + mHandler.proceed(privateKey, certificateChain); + return null; + } +} diff --git a/src/com/android/browser/PermissionsPrompt.java b/src/com/android/browser/PermissionsPrompt.java new file mode 100644 index 000000000..29412d9b3 --- /dev/null +++ b/src/com/android/browser/PermissionsPrompt.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.browser; + +import android.content.Context; +import android.net.Uri; +import android.util.AttributeSet; +import android.view.Gravity; +import android.view.View; +import android.webkit.GeolocationPermissions; +import android.webkit.PermissionRequest; +import android.widget.Button; +import android.widget.CheckBox; +import android.widget.RelativeLayout; +import android.widget.TextView; +import android.widget.Toast; + +import java.util.Enumeration; +import java.util.Vector; + +public class PermissionsPrompt extends RelativeLayout { + private TextView mMessage; + private Button mAllowButton; + private Button mDenyButton; + private CheckBox mRemember; + private PermissionRequest mRequest; + + public PermissionsPrompt(Context context) { + this(context, null); + } + + public PermissionsPrompt(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + init(); + } + + private void init() { + mMessage = (TextView) findViewById(R.id.message); + mAllowButton = (Button) findViewById(R.id.allow_button); + mDenyButton = (Button) findViewById(R.id.deny_button); + mRemember = (CheckBox) findViewById(R.id.remember); + + mAllowButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + handleButtonClick(true); + } + }); + mDenyButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + handleButtonClick(false); + } + }); + } + + public void show(PermissionRequest request) { + mRequest = request; + setMessage(); + mRemember.setChecked(true); + setVisibility(View.VISIBLE); + } + + public void setMessage() { + String[] resources = mRequest.getResources(); + Vector<String> strings = new Vector<String>(); + for (String resource : resources) { + if (resource.equals(PermissionRequest.RESOURCE_VIDEO_CAPTURE)) + strings.add(getResources().getString(R.string.resource_video_capture)); + else if (resource.equals(PermissionRequest.RESOURCE_AUDIO_CAPTURE)) + strings.add(getResources().getString(R.string.resource_audio_capture)); + else if (resource.equals(PermissionRequest.RESOURCE_PROTECTED_MEDIA_ID)) + strings.add(getResources().getString(R.string.resource_protected_media_id)); + } + if (strings.isEmpty()) return; + + Enumeration<String> e = strings.elements(); + StringBuilder sb = new StringBuilder(e.nextElement()); + if (e.hasMoreElements()) { + sb.append(", "); + sb.append(e.nextElement()); + } + + mMessage.setText(String.format( + getResources().getString(R.string.permissions_prompt_message), + mRequest.getOrigin(), sb.toString())); + } + + /** + * Hides the prompt. + */ + public void hide() { + setVisibility(View.GONE); + } + + /** + * Handles a click on one the buttons by invoking the callback. + */ + private void handleButtonClick(boolean allow) { + hide(); + if (allow) + mRequest.grant(mRequest.getResources()); + else + mRequest.deny(); + } +} diff --git a/src/com/android/browser/PreloadController.java b/src/com/android/browser/PreloadController.java index acd9709ba..19299517a 100644 --- a/src/com/android/browser/PreloadController.java +++ b/src/com/android/browser/PreloadController.java @@ -29,6 +29,7 @@ import android.webkit.HttpAuthHandler; import android.webkit.SslErrorHandler; import android.webkit.ValueCallback; import android.webkit.WebChromeClient.CustomViewCallback; +import android.webkit.WebChromeClient.FileChooserParams; import android.webkit.WebView; public class PreloadController implements WebViewController { @@ -203,8 +204,8 @@ public class PreloadController implements WebViewController { } @Override - public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) { - if (LOGD_ENABLED) Log.d(LOGTAG, "openFileChooser()"); + public void showFileChooser(ValueCallback<Uri[]> callback, FileChooserParams params) { + if (LOGD_ENABLED) Log.d(LOGTAG, "showFileChooser()"); } @Override diff --git a/src/com/android/browser/Tab.java b/src/com/android/browser/Tab.java index 78bf70e47..a3e4a59b7 100644 --- a/src/com/android/browser/Tab.java +++ b/src/com/android/browser/Tab.java @@ -46,16 +46,19 @@ import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; import android.view.ViewStub; -import android.webkit.BrowserDownloadListener; +import android.webkit.ClientCertRequest; import android.webkit.ConsoleMessage; +import android.webkit.CookieManager; import android.webkit.GeolocationPermissions; +import android.webkit.GeolocationPermissions.Callback; import android.webkit.HttpAuthHandler; +import android.webkit.PermissionRequest; import android.webkit.SslErrorHandler; import android.webkit.URLUtil; import android.webkit.ValueCallback; import android.webkit.WebBackForwardList; -import android.webkit.WebBackForwardListClient; import android.webkit.WebChromeClient; +import android.webkit.WebChromeClient.FileChooserParams; import android.webkit.WebHistoryItem; import android.webkit.WebResourceResponse; import android.webkit.WebStorage; @@ -75,6 +78,7 @@ import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.nio.ByteBuffer; +import java.security.Principal; import java.util.LinkedList; import java.util.Map; import java.util.UUID; @@ -133,6 +137,8 @@ class Tab implements PictureListener { // The Geolocation permissions prompt private GeolocationPermissionsPrompt mGeolocationPermissionsPrompt; + // The permissions prompt + private PermissionsPrompt mPermissionsPrompt; // Main WebView wrapper private View mContainer; // Main WebView @@ -560,6 +566,29 @@ class Tab implements PictureListener { } } + /** + * Displays client certificate request to the user. + */ + @Override + public void onReceivedClientCertRequest(final WebView view, + final ClientCertRequest request) { + if (!mInForeground) { + request.ignore(); + return; + } + KeyChain.choosePrivateKeyAlias( + mWebViewController.getActivity(), new KeyChainAliasCallback() { + @Override public void alias(String alias) { + if (alias == null) { + request.cancel(); + return; + } + new KeyChainLookup(mContext, request, alias).execute(); + } + }, request.getKeyTypes(), request.getPrincipals(), request.getHost(), + request.getPort(), null); + } + /** * Handles an HTTP authentication request. * @@ -886,6 +915,19 @@ class Tab implements PictureListener { } } + @Override + public void onPermissionRequest(PermissionRequest request) { + if (!mInForeground) return; + getPermissionsPrompt().show(request); + } + + @Override + public void onPermissionRequestCanceled(PermissionRequest request) { + if (mInForeground && mPermissionsPrompt != null) { + mPermissionsPrompt.hide(); + } + } + /* Adds a JavaScript error message to the system log and if the JS * console is enabled in the about:debug options, to that console * also. @@ -959,11 +1001,13 @@ class Tab implements PictureListener { } @Override - public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) { + public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> callback, + FileChooserParams params) { if (mInForeground) { - mWebViewController.openFileChooser(uploadMsg, acceptType, capture); + mWebViewController.showFileChooser(callback, params); + return true; } else { - uploadMsg.onReceiveValue(null); + return false; } } @@ -1014,6 +1058,10 @@ class Tab implements PictureListener { mClient.onReceivedSslError(view, handler, error); } @Override + public void onReceivedClientCertRequest(WebView view, ClientCertRequest request) { + mClient.onReceivedClientCertRequest(view, request); + } + @Override public void onReceivedHttpAuthRequest(WebView view, HttpAuthHandler handler, String host, String realm) { mClient.onReceivedHttpAuthRequest(view, handler, host, realm); @@ -1202,6 +1250,10 @@ class Tab implements PictureListener { mGeolocationPermissionsPrompt.hide(); } + if (mPermissionsPrompt != null) { + mPermissionsPrompt.hide(); + } + mWebViewController.onSetWebView(this, w); if (mMainView != null) { @@ -1509,6 +1561,18 @@ class Tab implements PictureListener { } /** + * @return The permissions prompt for this tab. + */ + PermissionsPrompt getPermissionsPrompt() { + if (mPermissionsPrompt == null) { + ViewStub stub = (ViewStub) mContainer + .findViewById(R.id.permissions_prompt); + mPermissionsPrompt = (PermissionsPrompt) stub.inflate(); + } + return mPermissionsPrompt; + } + + /** * @return The application id string */ String getAppId() { @@ -1911,4 +1975,12 @@ class Tab implements PictureListener { setSecurityState(SecurityState.SECURITY_STATE_MIXED); } } + + public void setAcceptThirdPartyCookies(boolean accept) { + CookieManager cookieManager = CookieManager.getInstance(); + if (mMainView != null) + cookieManager.setAcceptThirdPartyCookies(mMainView, accept); + if (mSubView != null) + cookieManager.setAcceptThirdPartyCookies(mSubView, accept); + } } diff --git a/src/com/android/browser/TabBar.java b/src/com/android/browser/TabBar.java index cf82e015a..d6c02f9f5 100644 --- a/src/com/android/browser/TabBar.java +++ b/src/com/android/browser/TabBar.java @@ -398,7 +398,9 @@ public class TabBar extends LinearLayout implements OnClickListener { // TODO: We should change the matrix/shader only when needed final Matrix matrix = mSelected ? mActiveMatrix : mInactiveMatrix; matrix.setTranslate(-left, 0.0f); - (mSelected ? mActiveShader : mInactiveShader).setLocalMatrix(matrix); + final Shader shader = mSelected ? mActiveShader : mInactiveShader; + shader.setLocalMatrix(matrix); + paint.setShader(shader); canvas.drawPath(clipPath, paint); if (isFocused()) { canvas.drawPath(mFocusPath, mFocusPaint); diff --git a/src/com/android/browser/UploadHandler.java b/src/com/android/browser/UploadHandler.java index a9560bb50..87c7897ab 100644 --- a/src/com/android/browser/UploadHandler.java +++ b/src/com/android/browser/UploadHandler.java @@ -18,53 +18,82 @@ package com.android.browser; import android.app.Activity; import android.content.ActivityNotFoundException; +import android.content.ClipData; import android.content.Intent; import android.net.Uri; -import android.os.Environment; import android.provider.MediaStore; +import android.support.v4.content.FileProvider; +import android.webkit.WebChromeClient.FileChooserParams; import android.webkit.ValueCallback; import android.widget.Toast; import java.io.File; -import java.util.Vector; /** - * Handle the file upload callbacks from WebView here + * Handle the file upload. This does not support selecting multiple files yet. */ public class UploadHandler { + private final static String IMAGE_MIME_TYPE = "image/*"; + private final static String VIDEO_MIME_TYPE = "video/*"; + private final static String AUDIO_MIME_TYPE = "audio/*"; + + private final static String FILE_PROVIDER_AUTHORITY = "com.android.browser-classic.file"; /* * The Object used to inform the WebView of the file to upload. */ - private ValueCallback<Uri> mUploadMessage; - private String mCameraFilePath; + private ValueCallback<Uri[]> mUploadMessage; private boolean mHandled; - private boolean mCaughtActivityNotFoundException; - private Controller mController; + private FileChooserParams mParams; + private Uri mCapturedMedia; public UploadHandler(Controller controller) { mController = controller; } - String getFilePath() { - return mCameraFilePath; - } - boolean handled() { return mHandled; } void onResult(int resultCode, Intent intent) { + Uri[] uris; + // As the media capture is always supported, we can't use + // FileChooserParams.parseResult(). + uris = parseResult(resultCode, intent); + mUploadMessage.onReceiveValue(uris); + mHandled = true; + } - if (resultCode == Activity.RESULT_CANCELED && mCaughtActivityNotFoundException) { - // Couldn't resolve an activity, we are going to try again so skip - // this result. - mCaughtActivityNotFoundException = false; + void openFileChooser(ValueCallback<Uri[]> callback, FileChooserParams fileChooserParams) { + + if (mUploadMessage != null) { + // Already a file picker operation in progress. return; } + mUploadMessage = callback; + mParams = fileChooserParams; + Intent[] captureIntents = createCaptureIntent(); + assert(captureIntents != null && captureIntents.length > 0); + Intent intent = null; + // Go to the media capture directly if capture is specified, this is the + // preferred way. + if (fileChooserParams.isCaptureEnabled() && captureIntents.length == 1) { + intent = captureIntents[0]; + } else { + intent = new Intent(Intent.ACTION_CHOOSER); + intent.putExtra(Intent.EXTRA_INITIAL_INTENTS, captureIntents); + intent.putExtra(Intent.EXTRA_INTENT, fileChooserParams.createIntent()); + } + startActivity(intent); + } + + private Uri[] parseResult(int resultCode, Intent intent) { + if (resultCode == Activity.RESULT_CANCELED) { + return null; + } Uri result = intent == null || resultCode != Activity.RESULT_OK ? null : intent.getData(); @@ -74,121 +103,17 @@ public class UploadHandler { // was written to disk in the in the case that we // did not get an intent returned but did get a RESULT_OK. If it was, // we assume that this result has came back from the camera. - if (result == null && intent == null && resultCode == Activity.RESULT_OK) { - File cameraFile = new File(mCameraFilePath); - if (cameraFile.exists()) { - result = Uri.fromFile(cameraFile); - // Broadcast to the media scanner that we have a new photo - // so it will be added into the gallery for the user. - mController.getActivity().sendBroadcast( - new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, result)); - } + if (result == null && intent == null && resultCode == Activity.RESULT_OK + && mCapturedMedia != null) { + result = mCapturedMedia; } - mUploadMessage.onReceiveValue(result); - mHandled = true; - mCaughtActivityNotFoundException = false; - } - - void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) { - - final String imageMimeType = "image/*"; - final String videoMimeType = "video/*"; - final String audioMimeType = "audio/*"; - final String mediaSourceKey = "capture"; - final String mediaSourceValueCamera = "camera"; - final String mediaSourceValueFileSystem = "filesystem"; - final String mediaSourceValueCamcorder = "camcorder"; - final String mediaSourceValueMicrophone = "microphone"; - - // According to the spec, media source can be 'filesystem' or 'camera' or 'camcorder' - // or 'microphone' and the default value should be 'filesystem'. - String mediaSource = mediaSourceValueFileSystem; - - if (mUploadMessage != null) { - // Already a file picker operation in progress. - return; + Uri[] uris = null; + if (result != null) { + uris = new Uri[1]; + uris[0] = result; } - - mUploadMessage = uploadMsg; - - // Parse the accept type. - String params[] = acceptType.split(";"); - String mimeType = params[0]; - - if (capture.length() > 0) { - mediaSource = capture; - } - - if (capture.equals(mediaSourceValueFileSystem)) { - // To maintain backwards compatibility with the previous implementation - // of the media capture API, if the value of the 'capture' attribute is - // "filesystem", we should examine the accept-type for a MIME type that - // may specify a different capture value. - for (String p : params) { - String[] keyValue = p.split("="); - if (keyValue.length == 2) { - // Process key=value parameters. - if (mediaSourceKey.equals(keyValue[0])) { - mediaSource = keyValue[1]; - } - } - } - } - - //Ensure it is not still set from a previous upload. - mCameraFilePath = null; - - if (mimeType.equals(imageMimeType)) { - if (mediaSource.equals(mediaSourceValueCamera)) { - // Specified 'image/*' and requested the camera, so go ahead and launch the - // camera directly. - startActivity(createCameraIntent()); - return; - } else { - // Specified just 'image/*', capture=filesystem, or an invalid capture parameter. - // In all these cases we show a traditional picker filetered on accept type - // so launch an intent for both the Camera and image/* OPENABLE. - Intent chooser = createChooserIntent(createCameraIntent()); - chooser.putExtra(Intent.EXTRA_INTENT, createOpenableIntent(imageMimeType)); - startActivity(chooser); - return; - } - } else if (mimeType.equals(videoMimeType)) { - if (mediaSource.equals(mediaSourceValueCamcorder)) { - // Specified 'video/*' and requested the camcorder, so go ahead and launch the - // camcorder directly. - startActivity(createCamcorderIntent()); - return; - } else { - // Specified just 'video/*', capture=filesystem or an invalid capture parameter. - // In all these cases we show an intent for the traditional file picker, filtered - // on accept type so launch an intent for both camcorder and video/* OPENABLE. - Intent chooser = createChooserIntent(createCamcorderIntent()); - chooser.putExtra(Intent.EXTRA_INTENT, createOpenableIntent(videoMimeType)); - startActivity(chooser); - return; - } - } else if (mimeType.equals(audioMimeType)) { - if (mediaSource.equals(mediaSourceValueMicrophone)) { - // Specified 'audio/*' and requested microphone, so go ahead and launch the sound - // recorder. - startActivity(createSoundRecorderIntent()); - return; - } else { - // Specified just 'audio/*', capture=filesystem of an invalid capture parameter. - // In all these cases so go ahead and launch an intent for both the sound - // recorder and audio/* OPENABLE. - Intent chooser = createChooserIntent(createSoundRecorderIntent()); - chooser.putExtra(Intent.EXTRA_INTENT, createOpenableIntent(audioMimeType)); - startActivity(chooser); - return; - } - } - - // No special handling based on the accept type was necessary, so trigger the default - // file upload chooser. - startActivity(createDefaultOpenableIntent()); + return uris; } private void startActivity(Intent intent) { @@ -196,60 +121,62 @@ public class UploadHandler { mController.getActivity().startActivityForResult(intent, Controller.FILE_SELECTED); } catch (ActivityNotFoundException e) { // No installed app was able to handle the intent that - // we sent, so fallback to the default file upload control. - try { - mCaughtActivityNotFoundException = true; - mController.getActivity().startActivityForResult(createDefaultOpenableIntent(), - Controller.FILE_SELECTED); - } catch (ActivityNotFoundException e2) { - // Nothing can return us a file, so file upload is effectively disabled. - Toast.makeText(mController.getActivity(), R.string.uploads_disabled, - Toast.LENGTH_LONG).show(); - } + // we sent, so file upload is effectively disabled. + Toast.makeText(mController.getActivity(), R.string.uploads_disabled, + Toast.LENGTH_LONG).show(); } } - private Intent createDefaultOpenableIntent() { - // Create and return a chooser with the default OPENABLE - // actions including the camera, camcorder and sound - // recorder where available. - Intent i = new Intent(Intent.ACTION_GET_CONTENT); - i.addCategory(Intent.CATEGORY_OPENABLE); - i.setType("*/*"); - - Intent chooser = createChooserIntent(createCameraIntent(), createCamcorderIntent(), - createSoundRecorderIntent()); - chooser.putExtra(Intent.EXTRA_INTENT, i); - return chooser; - } - - private Intent createChooserIntent(Intent... intents) { - Intent chooser = new Intent(Intent.ACTION_CHOOSER); - chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, intents); - chooser.putExtra(Intent.EXTRA_TITLE, - mController.getActivity().getResources() - .getString(R.string.choose_upload)); - return chooser; + private Intent[] createCaptureIntent() { + String mimeType = "*/*"; + String[] acceptTypes = mParams.getAcceptTypes(); + if ( acceptTypes != null && acceptTypes.length > 0) { + mimeType = acceptTypes[0]; + } + Intent[] intents; + if (mimeType.equals(IMAGE_MIME_TYPE)) { + intents = new Intent[1]; + intents[0] = createCameraIntent(createTempFileContentUri(".jpg")); + } else if (mimeType.equals(VIDEO_MIME_TYPE)) { + intents = new Intent[1]; + intents[0] = createCamcorderIntent(); + } else if (mimeType.equals(AUDIO_MIME_TYPE)) { + intents = new Intent[1]; + intents[0] = createSoundRecorderIntent(); + } else { + intents = new Intent[3]; + intents[0] = createCameraIntent(createTempFileContentUri(".jpg")); + intents[1] = createCamcorderIntent(); + intents[2] = createSoundRecorderIntent(); + } + return intents; } - private Intent createOpenableIntent(String type) { - Intent i = new Intent(Intent.ACTION_GET_CONTENT); - i.addCategory(Intent.CATEGORY_OPENABLE); - i.setType(type); - return i; + private Uri createTempFileContentUri(String suffix) { + try { + File mediaPath = new File(mController.getActivity().getFilesDir(), "captured_media"); + if (!mediaPath.exists() && !mediaPath.mkdir()) { + throw new RuntimeException("Folder cannot be created."); + } + File mediaFile = File.createTempFile( + String.valueOf(System.currentTimeMillis()), suffix, mediaPath); + return FileProvider.getUriForFile(mController.getActivity(), + FILE_PROVIDER_AUTHORITY, mediaFile); + } catch (java.io.IOException e) { + throw new RuntimeException(e); + } } - private Intent createCameraIntent() { - Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); - File externalDataDir = Environment.getExternalStoragePublicDirectory( - Environment.DIRECTORY_DCIM); - File cameraDataDir = new File(externalDataDir.getAbsolutePath() + - File.separator + "browser-photos"); - cameraDataDir.mkdirs(); - mCameraFilePath = cameraDataDir.getAbsolutePath() + File.separator + - System.currentTimeMillis() + ".jpg"; - cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(new File(mCameraFilePath))); - return cameraIntent; + private Intent createCameraIntent(Uri contentUri) { + if (contentUri == null) throw new IllegalArgumentException(); + mCapturedMedia = contentUri; + Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); + intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | + Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + intent.putExtra(MediaStore.EXTRA_OUTPUT, mCapturedMedia); + intent.setClipData(ClipData.newUri(mController.getActivity().getContentResolver(), + FILE_PROVIDER_AUTHORITY, mCapturedMedia)); + return intent; } private Intent createCamcorderIntent() { @@ -259,5 +186,4 @@ public class UploadHandler { private Intent createSoundRecorderIntent() { return new Intent(MediaStore.Audio.Media.RECORD_SOUND_ACTION); } - } diff --git a/src/com/android/browser/UrlHandler.java b/src/com/android/browser/UrlHandler.java index 167d4105c..e21e3e87a 100644 --- a/src/com/android/browser/UrlHandler.java +++ b/src/com/android/browser/UrlHandler.java @@ -160,6 +160,11 @@ public class UrlHandler { // security (only access to BROWSABLE activities). intent.addCategory(Intent.CATEGORY_BROWSABLE); intent.setComponent(null); + Intent selector = intent.getSelector(); + if (selector != null) { + selector.addCategory(Intent.CATEGORY_BROWSABLE); + selector.setComponent(null); + } // Re-use the existing tab if the intent comes back to us if (tab != null) { if (tab.getAppId() == null) { diff --git a/src/com/android/browser/WebBackForwardListClient.java b/src/com/android/browser/WebBackForwardListClient.java new file mode 100644 index 000000000..60d48fce9 --- /dev/null +++ b/src/com/android/browser/WebBackForwardListClient.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.browser; + +import android.webkit.WebHistoryItem; + +/** + * Interface to receive notifications when items are added to the + * {@link android.webkit.WebBackForwardList}. + */ +public abstract class WebBackForwardListClient { + + /** + * Notify the client that <var>item</var> has been added to the + * WebBackForwardList. + * @param item The newly created WebHistoryItem + */ + public void onNewHistoryItem(WebHistoryItem item) { } + + /** + * Notify the client that the <var>item</var> at <var>index</var> is now + * the current history item. + * @param item A WebHistoryItem + * @param index The new history index + */ + public void onIndexChanged(WebHistoryItem item, int index) { } +} diff --git a/src/com/android/browser/WebViewController.java b/src/com/android/browser/WebViewController.java index 498f8e7ca..dfede356a 100644 --- a/src/com/android/browser/WebViewController.java +++ b/src/com/android/browser/WebViewController.java @@ -28,6 +28,7 @@ import android.webkit.HttpAuthHandler; import android.webkit.SslErrorHandler; import android.webkit.ValueCallback; import android.webkit.WebChromeClient; +import android.webkit.WebChromeClient.FileChooserParams; import android.webkit.WebView; import java.util.List; @@ -93,7 +94,7 @@ public interface WebViewController { void onUpdatedSecurityState(Tab tab); - void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture); + void showFileChooser(ValueCallback<Uri[]> callback, FileChooserParams params); void endActionMode(); |
