Added query for Cleartext Storage in Android Database

This commit is contained in:
Tony Torralba
2021-09-03 12:59:03 +02:00
parent 117795c409
commit f0604e2e84
26 changed files with 611 additions and 10 deletions

View File

@@ -132,6 +132,8 @@ private module Frameworks {
private import semmle.code.java.frameworks.Hibernate
private import semmle.code.java.frameworks.jOOQ
private import semmle.code.java.frameworks.spring.SpringHttp
private import semmle.code.java.frameworks.android.ContentProviders
private import semmle.code.java.frameworks.android.Widget
}
private predicate sourceModelCsv(string row) {

View File

@@ -0,0 +1,23 @@
/**
* Provides classes and predicates for working with Content Providers.
*/
import java
import semmle.code.java.dataflow.ExternalFlow
/** The class `android.content.ContentValues`. */
class ContentValues extends Class {
ContentValues() { this.hasQualifiedName("android.content", "ContentValues") }
}
private class SummaryModels extends SummaryModelCsv {
override predicate row(string row) {
row =
[
"android.content;ContentValues;false;put;;;Argument[0];MapKey of Argument[-1];value",
"android.content;ContentValues;false;put;;;Argument[1];MapValue of Argument[-1];value",
"android.content;ContentValues;false;putAll;;;MapKey of Argument[0];MapKey of Argument[-1];value",
"android.content;ContentValues;false;putAll;;;MapValue of Argument[0];MapValue of Argument[-1];value"
]
}
}

View File

@@ -24,6 +24,14 @@ class TypeDatabaseUtils extends Class {
TypeDatabaseUtils() { hasQualifiedName("android.database", "DatabaseUtils") }
}
class TypeSQLiteOpenHelper extends Class {
TypeSQLiteOpenHelper() { this.hasQualifiedName("android.database.sqlite", "SQLiteOpenHelper") }
}
class TypeSQLiteStatement extends Class {
TypeSQLiteStatement() { this.hasQualifiedName("android.database.sqlite", "SQLiteStatement") }
}
private class SQLiteSinkCsv extends SinkModelCsv {
override predicate row(string row) {
row =

View File

@@ -0,0 +1,21 @@
import java
import semmle.code.java.dataflow.ExternalFlow
import semmle.code.java.dataflow.FlowSources
private class AndroidWidgetSourceModels extends SourceModelCsv {
override predicate row(string row) {
row = ["android.widget;EditText;true;getText;;;ReturnValue;android-widget"]
}
}
private class DefaultAndroidWidgetSources extends RemoteFlowSource {
DefaultAndroidWidgetSources() { sourceNode(this, "android-widget") }
override string getSourceType() { result = "Android widget source" }
}
private class AndroidWidgetSummaryModels extends SummaryModelCsv {
override predicate row(string row) {
row = ["android.widget;EditText;true;getText;;;Argument[-1];ReturnValue;taint"]
}
}

View File

@@ -0,0 +1,119 @@
/** Provides classes and predicates to reason about cleartext storage in Android databases. */
import java
import semmle.code.java.dataflow.DataFlow
import semmle.code.java.frameworks.android.ContentProviders
import semmle.code.java.frameworks.android.Intent
import semmle.code.java.frameworks.android.SQLite
import semmle.code.java.security.CleartextStorageQuery
private class LocalDatabaseCleartextStorageSink extends CleartextStorageSink {
LocalDatabaseCleartextStorageSink() { localDatabaseInput(_, this.asExpr()) }
}
private class LocalDatabaseCleartextStorageStep extends CleartextStorageAdditionalTaintStep {
override predicate step(DataFlow::Node n1, DataFlow::Node n2) {
// EditText.getText() return type is parsed as `Object`, so we need to
// add a taint step for `Object.toString` to model `editText.getText().toString()`
exists(MethodAccess ma, Method m |
ma.getMethod() = m and
m.getDeclaringType() instanceof TypeObject and
m.hasName("toString")
|
n1.asExpr() = ma.getQualifier() and n2.asExpr() = ma
)
}
}
/** The creation of an object that can be used to store data in a local database. */
class LocalDatabaseOpenMethodAccess extends Storable, Call {
LocalDatabaseOpenMethodAccess() {
exists(Method m | this.(MethodAccess).getMethod() = m |
m.getDeclaringType().getASupertype*() instanceof TypeSQLiteOpenHelper and
m.hasName("getWritableDatabase")
or
m.getDeclaringType() instanceof TypeSQLiteDatabase and
m.hasName(["create", "open%Database", "compileStatement"])
or
m.getDeclaringType().getASupertype*() instanceof TypeContext and
m.hasName("openOrCreateDatabase")
)
or
this.(ClassInstanceExpr).getConstructedType() instanceof ContentValues
}
override Expr getAnInput() {
exists(LocalDatabaseFlowConfig config, DataFlow::Node database |
localDatabaseInput(database, result) and
config.hasFlow(DataFlow::exprNode(this), database)
)
}
override Expr getAStore() {
exists(LocalDatabaseFlowConfig config, DataFlow::Node database |
localDatabaseStore(database, result) and
config.hasFlow(DataFlow::exprNode(this), database)
)
}
}
/** A method that is both a database input and a database store. */
private class LocalDatabaseInputStoreMethod extends Method {
LocalDatabaseInputStoreMethod() {
this.getDeclaringType() instanceof TypeSQLiteDatabase and
this.getName().matches("exec%SQL")
}
}
private predicate localDatabaseInput(DataFlow::Node database, Argument input) {
exists(Method m | input.getCall().(MethodAccess).getMethod() = m |
m instanceof LocalDatabaseInputStoreMethod and
database.asExpr() = input.getCall().getQualifier()
or
m.getDeclaringType() instanceof TypeSQLiteDatabase and
m.hasName("compileStatement") and
database.asExpr() = input.getCall()
or
m.getDeclaringType() instanceof ContentValues and
m.hasName("put") and
input.getPosition() = 1 and
database.asExpr() = input.getCall().getQualifier()
)
}
private predicate localDatabaseStore(DataFlow::Node database, MethodAccess store) {
exists(Method m | store.getMethod() = m |
m instanceof LocalDatabaseInputStoreMethod and
database.asExpr() = store.getQualifier()
or
m.getDeclaringType() instanceof TypeSQLiteDatabase and
m.getName().matches(["insert%", "replace%", "update%"]) and
database.asExpr() = store.getAnArgument() and
database.getType() instanceof ContentValues
or
m.getDeclaringType() instanceof TypeSQLiteStatement and
m.hasName(["executeInsert", "executeUpdateDelete"]) and
database.asExpr() = store.getQualifier()
)
}
private class LocalDatabaseFlowConfig extends DataFlow::Configuration {
LocalDatabaseFlowConfig() { this = "LocalDatabaseFlowConfig" }
override predicate isSource(DataFlow::Node source) {
source.asExpr() instanceof LocalDatabaseOpenMethodAccess
}
override predicate isSink(DataFlow::Node sink) {
localDatabaseInput(sink, _) or
localDatabaseStore(sink, _)
}
override predicate isAdditionalFlowStep(DataFlow::Node n1, DataFlow::Node n2) {
exists(Field f |
f.getType() instanceof TypeSQLiteDatabase and
f.getAnAssignedValue() = n1.asExpr() and
f = n2.asExpr().(FieldRead).getField()
)
}
}

View File

@@ -14,13 +14,19 @@
import java
private string suspicious() {
result = ["%password%", "%passwd%", "%account%", "%accnt%", "%trusted%"]
result =
[
"%password%", "%passwd%", "pwd", "%account%", "%accnt%", "%trusted%", "%refresh%token%",
"%secret%token"
]
}
private string nonSuspicious() {
result = "%hashed%" or
result = "%encrypted%" or
result = "%crypt%"
result = "%crypt%" or
result = "%create table%" or
result = "%drop table%"
}
/**

View File

@@ -0,0 +1,18 @@
public void sqliteStorageUnsafe(Context ctx, String name, String password) {
// BAD - sensitive information saved in cleartext.
SQLiteDatabase db = ctx.openOrCreateDatabase("test", Context.MODE_PRIVATE, null);
db.execSQL("INSERT INTO users VALUES (?, ?)", new String[] {name, password});
}
public void sqliteStorageSafe(Context ctx, String name, String password) {
// GOOD - sensitive information encrypted with a custom method.
SQLiteDatabase db = ctx.openOrCreateDatabase("test", Context.MODE_PRIVATE, null);
db.execSQL("INSERT INTO users VALUES (?, ?)", new String[] {name, encrypt(password)});
}
public void sqlCipherStorageSafe(String name, String password, String databasePassword) {
// GOOD - sensitive information saved using SQLCipher.
net.sqlcipher.database.SQLiteDatabase db =
net.sqlcipher.database.SQLiteDatabase.openOrCreateDatabase("test", databasePassword, null);
db.execSQL("INSERT INTO users VALUES (?, ?)", new String[] {name, password});
}

View File

@@ -0,0 +1,36 @@
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
<qhelp>
<overview>
<p>
SQLite is a lightweight database engine commonly used in Android devices to store data. By itself, SQLite does not offer any encryption mechanism by default and stores all data in plaintext, which introduces a risk if sensitive data like credentials, authentication tokens or personal identifiable information (PII) are directly stored in a SQLite database. The information could be accessed by any process or user in rooted devices, or can be disclosed through chained vulnerabilities, like unexpected access to the private storage through exposed components.
</p>
</overview>
<recommendation>
<p>
Use <code>SQLCipher</code> or similar libraries to add encryption capabilities to SQLite. Alternatively, encrypt sensitive data using cryptographicaly secure algorithms before storing it in the database.
</p>
</recommendation>
<example>
<p>
In the first example, sensitive user information is stored in cleartext.
</p>
<p>
In the second and third examples, the code encrypts sensitive information before saving it to the database.
</p>
<sample src="CleartextStorageAndroidDatabase.java" />
</example>
<references>
<li>
Android Developers:
<a href="https://developer.android.com/topic/security/data">Work with data more securely</a>
</li>
<li>
SQLCipher:
<a href="https://www.zetetic.net/sqlcipher/sqlcipher-for-android/">Android Application Integration</a>
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,23 @@
/**
* @name Cleartext storage of sensitive information using a local database on Android
* @description Cleartext Storage of Sensitive Information using
* a local database on Android allows access for users with root
* privileges or unexpected exposure from chained vulnerabilities.
* @kind problem
* @problem.severity warning
* @precision medium
* @id java/android/cleartext-storage-database
* @tags security
* external/cwe/cwe-312
*/
import java
import semmle.code.java.security.CleartextStorageAndroidDatabaseQuery
from SensitiveSource data, LocalDatabaseOpenMethodAccess s, Expr input, Expr store
where
input = s.getAnInput() and
store = s.getAStore() and
data.flowsToCached(input)
select store, "SQLite database $@ containing $@ is stored $@. Data was added $@.", s, s.toString(),
data, "sensitive data", store, "here", input, "here"

View File

@@ -0,0 +1,4 @@
---
category: newQuery
---
* A new query "Cleartext storage of sensitive information using a local database on Android" (`java/android/cleartext-storage-database`) has been added. This query finds instances of sensitive data being stored in local databases without encryption, which may expose it to attackers or malicious applications.

View File

@@ -0,0 +1,138 @@
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Base64;
import android.app.Activity;
import android.content.ContentValues;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteStatement;
public class CleartextStorageAndroidDatabaseTest extends Activity {
public void testCleartextStorageAndroiDatabaseSafe1(Context ctx, String name, String password) {
SQLiteDatabase db = ctx.openOrCreateDatabase("test", Context.MODE_PRIVATE, null);
db.execSQL("CREATE TABLE IF NOT EXISTS users(user VARCHAR, password VARCHAR);"); // Safe
}
public void testCleartextStorageAndroiDatabaseSafe2(Context ctx, String name, String password) {
SQLiteDatabase db = ctx.openOrCreateDatabase("test", Context.MODE_PRIVATE, null);
db.execSQL("DROP TABLE passwords;"); // Safe - no sensitive value being stored
}
public void testCleartextStorageAndroiDatabase1(Context ctx, String name, String password) {
SQLiteDatabase db = ctx.openOrCreateDatabase("test", Context.MODE_PRIVATE, null);
String query = "INSERT INTO users VALUES ('" + name + "', '" + password + "');";
db.execSQL(query); // $ hasCleartextStorageAndroidDatabase
}
public void testCleartextStorageAndroiDatabase2(String name, String password) {
SQLiteDatabase db = SQLiteDatabase.create(null);
String query = "INSERT INTO users VALUES (?, ?)";
db.execSQL(query, new String[] {name, password}); // $ hasCleartextStorageAndroidDatabase
}
//@formatter:off
public void testCleartextStorageAndroiDatabase3(String name, String password) {
SQLiteDatabase db = SQLiteDatabase.create(null);
String query = "INSERT INTO users VALUES (?, ?)";
db.execPerConnectionSQL(query, new String[] {name, password}); // $ hasCleartextStorageAndroidDatabase
}
//@formatter:on
public void testCleartextStorageAndroiDatabaseSafe3(String name, String password) {
SQLiteDatabase db = SQLiteDatabase.openDatabase("", null, 0);
ContentValues cv = new ContentValues();
cv.put("username", name);
cv.put("password", password); // Safe - ContentValues aren't added to any database
}
public void testCleartextStorageAndroiDatabase4(String name, String password) {
SQLiteDatabase db = SQLiteDatabase.openDatabase("", null, 0);
ContentValues cv = new ContentValues();
cv.put("username", name);
cv.put("password", password); // $ hasCleartextStorageAndroidDatabase
db.insert("table", null, cv);
}
public void testCleartextStorageAndroiDatabase5(String name, String password) {
SQLiteDatabase db = SQLiteDatabase.openDatabase("", null, 0);
ContentValues cv = new ContentValues();
cv.put("username", name);
cv.put("password", password); // $ hasCleartextStorageAndroidDatabase
db.insertOrThrow("table", null, cv);
}
public void testCleartextStorageAndroiDatabase6(String name, String password) {
SQLiteDatabase db = SQLiteDatabase.openDatabase("", null, 0);
ContentValues cv = new ContentValues();
cv.put("username", name);
cv.put("password", password); // $ hasCleartextStorageAndroidDatabase
db.insertWithOnConflict("table", null, cv, 0);
}
public void testCleartextStorageAndroiDatabase7(String name, String password) {
SQLiteDatabase db = SQLiteDatabase.openDatabase("", null, 0);
ContentValues cv = new ContentValues();
cv.put("username", name);
cv.put("password", password); // $ hasCleartextStorageAndroidDatabase
db.replace("table", null, cv);
}
public void testCleartextStorageAndroiDatabase8(String name, String password) {
SQLiteDatabase db = SQLiteDatabase.openDatabase("", null, 0);
ContentValues cv = new ContentValues();
cv.put("username", name);
cv.put("password", password); // $ hasCleartextStorageAndroidDatabase
db.replaceOrThrow("table", null, cv);
}
public void testCleartextStorageAndroiDatabase9(String name, String password) {
SQLiteDatabase db = SQLiteDatabase.openDatabase("", null, 0);
ContentValues cv = new ContentValues();
cv.put("username", name);
cv.put("password", password); // $ hasCleartextStorageAndroidDatabase
db.update("table", cv, "", new String[] {});
}
public void testCleartextStorageAndroiDatabase10(String name, String password) {
SQLiteDatabase db = SQLiteDatabase.openDatabase("", null, 0);
ContentValues cv = new ContentValues();
cv.put("username", name);
cv.put("password", password); // $ hasCleartextStorageAndroidDatabase
db.updateWithOnConflict("table", cv, "", new String[] {}, 0);
}
public void testCleartextStorageAndroiDatabaseSafe4(SQLiteDatabase db, String name,
String password) {
String query = "INSERT INTO users VALUES ('" + name + "', '" + password + "');";
SQLiteStatement stmt = db.compileStatement(query); // Safe - statement isn't executed
}
public void testCleartextStorageAndroiDatabase11(SQLiteDatabase db, String name,
String password) {
String query = "INSERT INTO users VALUES ('" + name + "', '" + password + "');";
SQLiteStatement stmt = db.compileStatement(query); // $ hasCleartextStorageAndroidDatabase
stmt.executeUpdateDelete();
}
public void testCleartextStorageAndroiDatabase12(SQLiteDatabase db, String name,
String password) {
String query = "INSERT INTO users VALUES ('" + name + "', '" + password + "');";
SQLiteStatement stmt = db.compileStatement(query); // $ hasCleartextStorageAndroidDatabase
stmt.executeInsert();
}
public void testCleartextStorageAndroiDatabaseSafe5(String name, String password)
throws Exception {
SQLiteDatabase db = SQLiteDatabase.create(null);
String query = "INSERT INTO users VALUES (?, ?)";
db.execSQL(query, new String[] {name, encrypt(password)}); // Safe
}
private static String encrypt(String cleartext) throws Exception {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(cleartext.getBytes(StandardCharsets.UTF_8));
String encoded = Base64.getEncoder().encodeToString(hash);
return encoded;
}
}

View File

@@ -0,0 +1,22 @@
import java
import semmle.code.java.security.CleartextStorageAndroidDatabaseQuery
import TestUtilities.InlineExpectationsTest
class CleartextStorageAndroidDatabaseTest extends InlineExpectationsTest {
CleartextStorageAndroidDatabaseTest() { this = "CleartextStorageAndroidDatabaseTest" }
override string getARelevantTag() { result = "hasCleartextStorageAndroidDatabase" }
override predicate hasActualResult(Location location, string element, string tag, string value) {
tag = "hasCleartextStorageAndroidDatabase" and
exists(SensitiveSource data, LocalDatabaseOpenMethodAccess s, Expr input, Expr store |
input = s.getAnInput() and
store = s.getAStore() and
data.flowsToCached(input)
|
input.getLocation() = location and
element = input.toString() and
value = ""
)
}
}

View File

@@ -0,0 +1,21 @@
/*
* Copyright (C) 2015 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 android.annotation;
public @interface IntRange {
long from() default Long.MIN_VALUE;
long to() default Long.MAX_VALUE;
}

View File

@@ -101,6 +101,7 @@ abstract public class Context
public abstract String getSystemServiceName(Class<? extends Object> p0);
public abstract String[] databaseList();
public abstract String[] fileList();
<<<<<<< HEAD
public abstract boolean bindService(Intent p0, ServiceConnection p1, int p2);
public abstract boolean bindServiceAsUser(Intent p0, ServiceConnection p1, int p2, UserHandle p3);
public abstract boolean deleteDatabase(String p0);

View File

@@ -15,6 +15,7 @@ import android.net.Uri;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
<<<<<<< HEAD
import android.util.AttributeSet;
import java.io.Serializable;
import java.util.ArrayList;

View File

@@ -0,0 +1,46 @@
package android.database;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.os.ParcelFileDescriptor;
public class DatabaseUtils {
public static ParcelFileDescriptor blobFileDescriptorForQuery(SQLiteDatabase db, String query,
String[] selectionArgs) {
return null;
}
public static long longForQuery(SQLiteDatabase db, String query, String[] selectionArgs) {
return 0;
}
public static String stringForQuery(SQLiteDatabase db, String query, String[] selectionArgs) {
return null;
}
public static void createDbFromSqlStatements(Context context, String dbName, int dbVersion, String sqlStatements) {
}
public static int queryNumEntries(SQLiteDatabase db, String table, String selection) {
return 0;
}
public static int queryNumEntries(SQLiteDatabase db, String table, String selection, String[] selectionArgs) {
return 0;
}
public static String[] appendSelectionArgs(String[] originalValues, String[] newValues) {
return null;
}
public static String concatenateWhere(String a, String b) {
return null;
}
}

View File

@@ -0,0 +1,29 @@
/*
* Copyright (C) 2006 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 android.database;
public class SQLException extends RuntimeException {
public SQLException() {
}
public SQLException(String error) {
}
public SQLException(String error, Throwable cause) {
}
}

View File

@@ -1,3 +1,4 @@
<<<<<<< HEAD
// Generated automatically from android.database.sqlite.SQLiteDatabase for testing purposes
package android.database.sqlite;

View File

@@ -0,0 +1,30 @@
/*
* Copyright (C) 2006 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 android.database.sqlite;
import android.database.SQLException;
public class SQLiteException extends SQLException {
public SQLiteException() {
}
public SQLiteException(String error) {
}
public SQLiteException(String error, Throwable cause) {
}
}

View File

@@ -0,0 +1,57 @@
package android.database.sqlite;
import java.util.Map;
import java.util.Set;
import android.content.ContentValues;
import android.os.CancellationSignal;
public abstract class SQLiteQueryBuilder {
public abstract void delete(SQLiteDatabase db, String selection, String[] selectionArgs);
public abstract void insert(SQLiteDatabase db, ContentValues values);
public abstract void query(SQLiteDatabase db, String[] projectionIn, String selection, String[] selectionArgs,
String groupBy, String having, String sortOrder);
public abstract void query(SQLiteDatabase db, String[] projectionIn, String selection, String[] selectionArgs,
String groupBy, String having, String sortOrder, String limit);
public abstract void query(SQLiteDatabase db, String[] projectionIn, String selection, String[] selectionArgs,
String groupBy, String having, String sortOrder, String limit, CancellationSignal cancellationSignal);
public abstract void update(SQLiteDatabase db, ContentValues values, String selection, String[] selectionArgs);
public static String buildQueryString(boolean distinct, String tables, String[] columns, String where,
String groupBy, String having, String orderBy, String limit) {
return null;
}
public abstract String buildQuery(String[] projectionIn, String selection, String groupBy, String having, String sortOrder,
String limit);
public abstract String buildQuery(String[] projectionIn, String selection, String[] selectionArgs, String groupBy,
String having, String sortOrder, String limit);
public abstract String buildUnionQuery(String[] subQueries, String sortOrder, String limit);
public abstract String buildUnionSubQuery(String typeDiscriminatorColumn, String[] unionColumns,
Set<String> columnsPresentInTable, int computedColumnsOffset, String typeDiscriminatorValue,
String selection, String[] selectionArgs, String groupBy, String having);
public abstract String buildUnionSubQuery(String typeDiscriminatorColumn, String[] unionColumns,
Set<String> columnsPresentInTable, int computedColumnsOffset, String typeDiscriminatorValue,
String selection, String groupBy, String having);
public static void appendColumns(StringBuilder s, String[] columns) {
}
public abstract void setProjectionMap(Map<String, String> columnMap);
public abstract void setTables(String inTables);
public abstract void appendWhere(CharSequence inWhere);
public abstract void appendWhereStandalone(CharSequence inWhere);
}

View File

@@ -12,6 +12,7 @@ import android.util.SizeF;
import android.util.SparseArray;
import java.io.Serializable;
import java.util.ArrayList;
<<<<<<< HEAD
public class Bundle extends BaseBundle implements Cloneable, Parcelable
{

View File

@@ -16,7 +16,6 @@
package android.webkit;
import java.io.InputStream;
import java.io.StringBufferInputStream;
import java.util.Map;
/**

View File

@@ -15,11 +15,8 @@
*/
package android.webkit;
import java.net.CookieManager;
import android.content.Context;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Manages settings state for a WebView. When a WebView is first created, it

View File

@@ -18,6 +18,7 @@ import android.content.Context;
import android.view.View;
public class WebView extends View {
public WebView(Context context) {
super(context);
}

View File

@@ -15,9 +15,6 @@
*/
package android.webkit;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
public class WebViewClient {
/**
* Give the host application a chance to take over the control when a new url is