summaryrefslogtreecommitdiffstats
path: root/src/com
diff options
context:
space:
mode:
Diffstat (limited to 'src/com')
-rw-r--r--src/com/android/browser/BrowserDownloadListener.java58
-rw-r--r--src/com/android/browser/BrowserPreferencesPage.java15
-rw-r--r--src/com/android/browser/BrowserSettings.java25
-rw-r--r--src/com/android/browser/BrowserWebViewFactory.java10
-rw-r--r--src/com/android/browser/Controller.java7
-rw-r--r--src/com/android/browser/KeyChainLookup.java52
-rw-r--r--src/com/android/browser/PermissionsPrompt.java125
-rw-r--r--src/com/android/browser/PreloadController.java5
-rw-r--r--src/com/android/browser/Tab.java82
-rw-r--r--src/com/android/browser/TabBar.java4
-rw-r--r--src/com/android/browser/UploadHandler.java276
-rw-r--r--src/com/android/browser/UrlHandler.java5
-rw-r--r--src/com/android/browser/WebBackForwardListClient.java41
-rw-r--r--src/com/android/browser/WebViewController.java3
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();