diff options
Diffstat (limited to 'core/java/android')
26 files changed, 570 insertions, 117 deletions
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index ef2e54a..a6658cc 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -138,6 +138,25 @@ public class ActivityManager { } } + /** @hide */ + public boolean getPackageAskScreenCompat(String packageName) { + try { + return ActivityManagerNative.getDefault().getPackageAskScreenCompat(packageName); + } catch (RemoteException e) { + // System dead, we will be dead too soon! + return false; + } + } + + /** @hide */ + public void setPackageAskScreenCompat(String packageName, boolean ask) { + try { + ActivityManagerNative.getDefault().setPackageAskScreenCompat(packageName, ask); + } catch (RemoteException e) { + // System dead, we will be dead too soon! + } + } + /** * Return the approximate per-application memory class of the current * device. This gives you an idea of how hard a memory limit you should diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java index 2a0d798..85f40c9 100644 --- a/core/java/android/app/ActivityManagerNative.java +++ b/core/java/android/app/ActivityManagerNative.java @@ -1483,6 +1483,26 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM return true; } + case GET_PACKAGE_ASK_SCREEN_COMPAT_TRANSACTION: + { + data.enforceInterface(IActivityManager.descriptor); + String pkg = data.readString(); + boolean ask = getPackageAskScreenCompat(pkg); + reply.writeNoException(); + reply.writeInt(ask ? 1 : 0); + return true; + } + + case SET_PACKAGE_ASK_SCREEN_COMPAT_TRANSACTION: + { + data.enforceInterface(IActivityManager.descriptor); + String pkg = data.readString(); + boolean ask = data.readInt() != 0; + setPackageAskScreenCompat(pkg, ask); + reply.writeNoException(); + return true; + } + } return super.onTransact(code, data, reply, flags); @@ -3254,7 +3274,8 @@ class ActivityManagerProxy implements IActivityManager Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); - mRemote.transact(SET_PACKAGE_SCREEN_COMPAT_MODE_TRANSACTION, data, reply, 0); + data.writeString(packageName); + mRemote.transact(GET_PACKAGE_SCREEN_COMPAT_MODE_TRANSACTION, data, reply, 0); reply.readException(); int mode = reply.readInt(); reply.recycle(); @@ -3275,6 +3296,32 @@ class ActivityManagerProxy implements IActivityManager data.recycle(); } + public boolean getPackageAskScreenCompat(String packageName) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeString(packageName); + mRemote.transact(GET_PACKAGE_ASK_SCREEN_COMPAT_TRANSACTION, data, reply, 0); + reply.readException(); + boolean ask = reply.readInt() != 0; + reply.recycle(); + data.recycle(); + return ask; + } + + public void setPackageAskScreenCompat(String packageName, boolean ask) + throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeString(packageName); + data.writeInt(ask ? 1 : 0); + mRemote.transact(SET_PACKAGE_ASK_SCREEN_COMPAT_TRANSACTION, data, reply, 0); + reply.readException(); + reply.recycle(); + data.recycle(); + } + public boolean switchUser(int userid) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 85e59b3..955cef2 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -1980,7 +1980,8 @@ public final class ActivityThread { BackupAgent agent = null; String classname = data.appInfo.backupAgentName; - if (data.backupMode == IApplicationThread.BACKUP_MODE_FULL) { + if (data.backupMode == IApplicationThread.BACKUP_MODE_FULL + || data.backupMode == IApplicationThread.BACKUP_MODE_RESTORE_FULL) { classname = "android.app.backup.FullBackupAgent"; if ((data.appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { // system packages can supply their own full-backup agent @@ -2011,7 +2012,8 @@ public final class ActivityThread { // If this is during restore, fail silently; otherwise go // ahead and let the user see the crash. Slog.e(TAG, "Agent threw during creation: " + e); - if (data.backupMode != IApplicationThread.BACKUP_MODE_RESTORE) { + if (data.backupMode != IApplicationThread.BACKUP_MODE_RESTORE + && data.backupMode != IApplicationThread.BACKUP_MODE_RESTORE_FULL) { throw e; } // falling through with 'binder' still null @@ -3658,12 +3660,16 @@ public final class ActivityThread { Application app = data.info.makeApplication(data.restrictedBackupMode, null); mInitialApplication = app; - List<ProviderInfo> providers = data.providers; - if (providers != null) { - installContentProviders(app, providers); - // For process that contains content providers, we want to - // ensure that the JIT is enabled "at some point". - mH.sendEmptyMessageDelayed(H.ENABLE_JIT, 10*1000); + // don't bring up providers in restricted mode; they may depend on the + // app's custom Application class + if (!data.restrictedBackupMode){ + List<ProviderInfo> providers = data.providers; + if (providers != null) { + installContentProviders(app, providers); + // For process that contains content providers, we want to + // ensure that the JIT is enabled "at some point". + mH.sendEmptyMessageDelayed(H.ENABLE_JIT, 10*1000); + } } try { diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java index 1f53c0e..e2588cf 100644 --- a/core/java/android/app/IActivityManager.java +++ b/core/java/android/app/IActivityManager.java @@ -347,6 +347,9 @@ public interface IActivityManager extends IInterface { public int getPackageScreenCompatMode(String packageName) throws RemoteException; public void setPackageScreenCompatMode(String packageName, int mode) throws RemoteException; + public boolean getPackageAskScreenCompat(String packageName) throws RemoteException; + public void setPackageAskScreenCompat(String packageName, boolean ask) + throws RemoteException; // Multi-user APIs public boolean switchUser(int userid) throws RemoteException; @@ -577,9 +580,11 @@ public interface IActivityManager extends IInterface { int SET_FRONT_ACTIVITY_SCREEN_COMPAT_MODE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+124; int GET_PACKAGE_SCREEN_COMPAT_MODE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+125; int SET_PACKAGE_SCREEN_COMPAT_MODE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+126; - int SWITCH_USER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+127; - int REMOVE_SUB_TASK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+128; - int REMOVE_TASK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+129; - int REGISTER_PROCESS_OBSERVER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+130; - int UNREGISTER_PROCESS_OBSERVER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+131; + int GET_PACKAGE_ASK_SCREEN_COMPAT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+127; + int SET_PACKAGE_ASK_SCREEN_COMPAT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+128; + int SWITCH_USER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+129; + int REMOVE_SUB_TASK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+130; + int REMOVE_TASK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+131; + int REGISTER_PROCESS_OBSERVER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+132; + int UNREGISTER_PROCESS_OBSERVER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+133; } diff --git a/core/java/android/app/IApplicationThread.java b/core/java/android/app/IApplicationThread.java index 8c31559..05a68a8 100644 --- a/core/java/android/app/IApplicationThread.java +++ b/core/java/android/app/IApplicationThread.java @@ -68,6 +68,7 @@ public interface IApplicationThread extends IInterface { static final int BACKUP_MODE_INCREMENTAL = 0; static final int BACKUP_MODE_FULL = 1; static final int BACKUP_MODE_RESTORE = 2; + static final int BACKUP_MODE_RESTORE_FULL = 3; void scheduleCreateBackupAgent(ApplicationInfo app, CompatibilityInfo compatInfo, int backupMode) throws RemoteException; void scheduleDestroyBackupAgent(ApplicationInfo app, CompatibilityInfo compatInfo) diff --git a/core/java/android/app/IBackupAgent.aidl b/core/java/android/app/IBackupAgent.aidl index 52fc623..8af78fa 100644 --- a/core/java/android/app/IBackupAgent.aidl +++ b/core/java/android/app/IBackupAgent.aidl @@ -79,4 +79,23 @@ oneway interface IBackupAgent { */ void doRestore(in ParcelFileDescriptor data, int appVersionCode, in ParcelFileDescriptor newState, int token, IBackupManager callbackBinder); + + /** + * Restore a single "file" to the application. The file was typically obtained from + * a full-backup dataset. The agent reads 'size' bytes of file content + * from the provided file descriptor. + * + * @param data Read-only pipe delivering the file content itself. + * + * @param size Size of the file being restored. + * @param type Type of file system entity, e.g. FullBackup.TYPE_DIRECTORY. + * @param domain Name of the file's semantic domain to which the 'path' argument is a + * relative path. e.g. FullBackup.DATABASE_TREE_TOKEN. + * @param path Relative path of the file within its semantic domain. + * @param mode Access mode of the file system entity, e.g. 0660. + * @param mtime Last modification time of the file system entity. + */ + void doRestoreFile(in ParcelFileDescriptor data, long size, + int type, String domain, String path, long mode, long mtime, + int token, IBackupManager callbackBinder); } diff --git a/core/java/android/app/backup/BackupAgent.java b/core/java/android/app/backup/BackupAgent.java index dc60e24..17f8adb 100644 --- a/core/java/android/app/backup/BackupAgent.java +++ b/core/java/android/app/backup/BackupAgent.java @@ -179,10 +179,18 @@ public abstract class BackupAgent extends ContextWrapper { throws IOException; /** + * @hide + */ + public void onRestoreFile(ParcelFileDescriptor data, long size, + int type, String domain, String path, long mode, long mtime) + throws IOException { + // empty stub implementation + } + + /** * Package-private, used only for dispatching an extra step during full backup */ void onSaveApk(BackupDataOutput data) { - if (DEBUG) Log.v(TAG, "--- base onSaveApk() ---"); } // ----- Core implementation ----- @@ -203,6 +211,7 @@ public abstract class BackupAgent extends ContextWrapper { private class BackupServiceBinder extends IBackupAgent.Stub { private static final String TAG = "BackupServiceBinder"; + @Override public void doBackup(ParcelFileDescriptor oldState, ParcelFileDescriptor data, ParcelFileDescriptor newState, @@ -236,6 +245,7 @@ public abstract class BackupAgent extends ContextWrapper { } } + @Override public void doRestore(ParcelFileDescriptor data, int appVersionCode, ParcelFileDescriptor newState, int token, IBackupManager callbackBinder) throws RemoteException { @@ -261,5 +271,25 @@ public abstract class BackupAgent extends ContextWrapper { } } } + + @Override + public void doRestoreFile(ParcelFileDescriptor data, long size, + int type, String domain, String path, long mode, long mtime, + int token, IBackupManager callbackBinder) throws RemoteException { + long ident = Binder.clearCallingIdentity(); + try { +Log.d(TAG, "doRestoreFile() => onRestoreFile()"); + BackupAgent.this.onRestoreFile(data, size, type, domain, path, mode, mtime); + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + Binder.restoreCallingIdentity(ident); + try { + callbackBinder.opComplete(token); + } catch (RemoteException e) { + // we'll time out anyway, so we're safe + } + } + } } } diff --git a/core/java/android/app/backup/FullBackup.java b/core/java/android/app/backup/FullBackup.java index 9850566..dfb0dd7 100644 --- a/core/java/android/app/backup/FullBackup.java +++ b/core/java/android/app/backup/FullBackup.java @@ -16,6 +16,17 @@ package android.app.backup; +import android.os.ParcelFileDescriptor; +import android.util.Log; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; + +import libcore.io.ErrnoException; +import libcore.io.Libcore; + /** * Global constant definitions et cetera related to the full-backup-to-fd * binary format. @@ -23,18 +34,95 @@ package android.app.backup; * @hide */ public class FullBackup { - public static String APK_TREE_TOKEN = "a"; - public static String OBB_TREE_TOKEN = "obb"; - public static String ROOT_TREE_TOKEN = "r"; - public static String DATA_TREE_TOKEN = "f"; - public static String DATABASE_TREE_TOKEN = "db"; - public static String SHAREDPREFS_TREE_TOKEN = "sp"; - public static String CACHE_TREE_TOKEN = "c"; - - public static String FULL_BACKUP_INTENT_ACTION = "fullback"; - public static String FULL_RESTORE_INTENT_ACTION = "fullrest"; - public static String CONF_TOKEN_INTENT_EXTRA = "conftoken"; + static final String TAG = "FullBackup"; + + public static final String APK_TREE_TOKEN = "a"; + public static final String OBB_TREE_TOKEN = "obb"; + public static final String ROOT_TREE_TOKEN = "r"; + public static final String DATA_TREE_TOKEN = "f"; + public static final String DATABASE_TREE_TOKEN = "db"; + public static final String SHAREDPREFS_TREE_TOKEN = "sp"; + public static final String CACHE_TREE_TOKEN = "c"; + public static final String SHARED_STORAGE_TOKEN = "shared"; + + public static final String APPS_PREFIX = "apps/"; + public static final String SHARED_PREFIX = "shared/"; + + public static final String FULL_BACKUP_INTENT_ACTION = "fullback"; + public static final String FULL_RESTORE_INTENT_ACTION = "fullrest"; + public static final String CONF_TOKEN_INTENT_EXTRA = "conftoken"; + + public static final int TYPE_EOF = 0; + public static final int TYPE_FILE = 1; + public static final int TYPE_DIRECTORY = 2; + public static final int TYPE_SYMLINK = 3; static public native int backupToTar(String packageName, String domain, String linkdomain, String rootpath, String path, BackupDataOutput output); + + static public void restoreToFile(ParcelFileDescriptor data, + long size, int type, long mode, long mtime, File outFile) throws IOException { + if (type == FullBackup.TYPE_DIRECTORY) { + // Canonically a directory has no associated content, so we don't need to read + // anything from the pipe in this case. Just create the directory here and + // drop down to the final metadata adjustment. + if (outFile != null) outFile.mkdirs(); + } else { + FileOutputStream out = null; + + // Pull the data from the pipe, copying it to the output file, until we're done + try { + if (outFile != null) { + File parent = outFile.getParentFile(); + if (!parent.exists()) { + // in practice this will only be for the default semantic directories, + // and using the default mode for those is appropriate. + // TODO: support the edge case of apps that have adjusted the + // permissions on these core directories + parent.mkdirs(); + } + out = new FileOutputStream(outFile); + } + } catch (IOException e) { + Log.e(TAG, "Unable to create/open file " + outFile.getPath(), e); + } + + byte[] buffer = new byte[32 * 1024]; + final long origSize = size; + FileInputStream in = new FileInputStream(data.getFileDescriptor()); + while (size > 0) { + int toRead = (size > buffer.length) ? buffer.length : (int)size; + int got = in.read(buffer, 0, toRead); + if (got <= 0) { + Log.w(TAG, "Incomplete read: expected " + size + " but got " + + (origSize - size)); + break; + } + if (out != null) { + try { + out.write(buffer, 0, got); + } catch (IOException e) { + // Problem writing to the file. Quit copying data and delete + // the file, but of course keep consuming the input stream. + Log.e(TAG, "Unable to write to file " + outFile.getPath(), e); + out.close(); + out = null; + outFile.delete(); + } + } + size -= got; + } + if (out != null) out.close(); + } + + // Now twiddle the state to match the backup, assuming all went well + if (outFile != null) { + try { + Libcore.os.chmod(outFile.getPath(), (int)mode); + } catch (ErrnoException e) { + e.rethrowAsIOException(); + } + outFile.setLastModified(mtime); + } + } } diff --git a/core/java/android/app/backup/FullBackupAgent.java b/core/java/android/app/backup/FullBackupAgent.java index f0a1f2a..4dca593 100644 --- a/core/java/android/app/backup/FullBackupAgent.java +++ b/core/java/android/app/backup/FullBackupAgent.java @@ -28,6 +28,9 @@ import libcore.io.OsConstants; import libcore.io.StructStat; import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; import java.util.HashSet; import java.util.LinkedList; @@ -53,8 +56,12 @@ public class FullBackupAgent extends BackupAgent { private String mCacheDir; private String mLibDir; + private File NULL_FILE; + @Override public void onCreate() { + NULL_FILE = new File("/dev/null"); + mPm = getPackageManager(); try { ApplicationInfo appInfo = mPm.getApplicationInfo(getPackageName(), 0); @@ -177,7 +184,40 @@ public class FullBackupAgent extends BackupAgent { } } + /** + * Dummy -- We're never used for restore of an incremental dataset + */ + @Override + public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState) + throws IOException { + } + + /** + * Restore the described file from the given pipe. + */ @Override - public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState) { + public void onRestoreFile(ParcelFileDescriptor data, long size, + int type, String domain, String relpath, long mode, long mtime) + throws IOException { + String basePath = null; + File outFile = null; + + if (DEBUG) Log.d(TAG, "onRestoreFile() size=" + size + " type=" + type + + " domain=" + domain + " relpath=" + relpath + " mode=" + mode + + " mtime=" + mtime); + + // Parse out the semantic domains into the correct physical location + if (domain.equals(FullBackup.DATA_TREE_TOKEN)) basePath = mFilesDir; + else if (domain.equals(FullBackup.DATABASE_TREE_TOKEN)) basePath = mDatabaseDir; + else if (domain.equals(FullBackup.ROOT_TREE_TOKEN)) basePath = mMainDir; + else if (domain.equals(FullBackup.SHAREDPREFS_TREE_TOKEN)) basePath = mSharedPrefsDir; + + // Not a supported output location? We need to consume the data + // anyway, so send it to /dev/null + outFile = (basePath != null) ? new File(basePath, relpath) : null; + if (DEBUG) Log.i(TAG, "[" + domain + " : " + relpath + "] mapped to " + outFile.getPath()); + + // Now that we've figured out where the data goes, send it on its way + FullBackup.restoreToFile(data, size, type, mode, mtime, outFile); } } diff --git a/core/java/android/app/backup/IBackupManager.aidl b/core/java/android/app/backup/IBackupManager.aidl index 94e31a8..bac874e 100644 --- a/core/java/android/app/backup/IBackupManager.aidl +++ b/core/java/android/app/backup/IBackupManager.aidl @@ -147,6 +147,14 @@ interface IBackupManager { boolean allApps, in String[] packageNames); /** + * Restore device content from the data stream passed through the given socket. The + * data stream must be in the format emitted by fullBackup(). + * + * <p>Callers must hold the android.permission.BACKUP permission to use this method. + */ + void fullRestore(in ParcelFileDescriptor fd); + + /** * Confirm that the requested full backup/restore operation can proceed. The system will * not actually perform the operation described to fullBackup() / fullRestore() unless the * UI calls back into the Backup Manager to confirm, passing the correct token. At diff --git a/core/java/android/content/res/CompatibilityInfo.java b/core/java/android/content/res/CompatibilityInfo.java index 854d410..dca53a8 100644 --- a/core/java/android/content/res/CompatibilityInfo.java +++ b/core/java/android/content/res/CompatibilityInfo.java @@ -113,8 +113,13 @@ public class CompatibilityInfo implements Parcelable { public CompatibilityInfo(ApplicationInfo appInfo, int screenLayout, boolean forceCompat) { int compatFlags = 0; + // We can't rely on the application always setting + // FLAG_RESIZEABLE_FOR_SCREENS so will compute it based on various input. + boolean anyResizeable = false; + if ((appInfo.flags & ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS) != 0) { compatFlags |= LARGE_SCREENS; + anyResizeable = true; if (!forceCompat) { // If we aren't forcing the app into compatibility mode, then // assume if it supports large screens that we should allow it @@ -123,9 +128,13 @@ public class CompatibilityInfo implements Parcelable { } } if ((appInfo.flags & ApplicationInfo.FLAG_SUPPORTS_XLARGE_SCREENS) != 0) { - compatFlags |= XLARGE_SCREENS | EXPANDABLE; + anyResizeable = true; + if (!forceCompat) { + compatFlags |= XLARGE_SCREENS | EXPANDABLE; + } } if ((appInfo.flags & ApplicationInfo.FLAG_RESIZEABLE_FOR_SCREENS) != 0) { + anyResizeable = true; compatFlags |= EXPANDABLE; } @@ -160,7 +169,7 @@ public class CompatibilityInfo implements Parcelable { if ((screenLayout&Configuration.SCREENLAYOUT_COMPAT_NEEDED) != 0) { if ((compatFlags&EXPANDABLE) != 0) { supportsScreen = true; - } else if ((appInfo.flags & ApplicationInfo.FLAG_RESIZEABLE_FOR_SCREENS) == 0) { + } else if (!anyResizeable) { compatFlags |= ALWAYS_COMPAT; } } diff --git a/core/java/android/database/sqlite/SQLiteQueryBuilder.java b/core/java/android/database/sqlite/SQLiteQueryBuilder.java index b6aca2b..9c09e81 100644 --- a/core/java/android/database/sqlite/SQLiteQueryBuilder.java +++ b/core/java/android/database/sqlite/SQLiteQueryBuilder.java @@ -24,8 +24,8 @@ import android.util.Log; import java.util.Iterator; import java.util.Map; -import java.util.Set; import java.util.Map.Entry; +import java.util.Set; import java.util.regex.Pattern; /** @@ -43,7 +43,7 @@ public class SQLiteQueryBuilder private StringBuilder mWhereClause = null; // lazily created private boolean mDistinct; private SQLiteDatabase.CursorFactory mFactory; - private boolean mStrictProjectionMap; + private boolean mStrict; public SQLiteQueryBuilder() { mDistinct = false; @@ -145,10 +145,37 @@ public class SQLiteQueryBuilder } /** + * Need to keep this to not break the build until ContactsProvider2 has been changed to + * use the new API + * TODO: Remove this * @hide */ public void setStrictProjectionMap(boolean flag) { - mStrictProjectionMap = flag; + } + + /** + * When set, the selection is verified against malicious arguments. + * When using this class to create a statement using + * {@link #buildQueryString(boolean, String, String[], String, String, String, String, String)}, + * non-numeric limits will raise an exception. If a projection map is specified, fields + * not in that map will be ignored. + * If this class is used to execute the statement directly using + * {@link #query(SQLiteDatabase, String[], String, String[], String, String, String)} + * or + * {@link #query(SQLiteDatabase, String[], String, String[], String, String, String, String)}, + * additionally also parenthesis escaping selection are caught. + * + * To summarize: To get maximum protection against malicious third party apps (for example + * content provider consumers), make sure to do the following: + * <ul> + * <li>Set this value to true</li> + * <li>Use a projection map</li> + * <li>Use one of the query overloads instead of getting the statement as a sql string</li> + * </ul> + * By default, this value is false. + */ + public void setStrict(boolean flag) { + mStrict = flag; } /** @@ -217,13 +244,6 @@ public class SQLiteQueryBuilder } } - private static void appendClauseEscapeClause(StringBuilder s, String name, String clause) { - if (!TextUtils.isEmpty(clause)) { - s.append(name); - DatabaseUtils.appendEscapedSQLString(s, clause); - } - } - /** * Add the names that are non-null in columns to s, separating * them with commas. @@ -320,6 +340,19 @@ public class SQLiteQueryBuilder return null; } + if (mStrict && selection != null && selection.length() > 0) { + // Validate the user-supplied selection to detect syntactic anomalies + // in the selection string that could indicate a SQL injection attempt. + // The idea is to ensure that the selection clause is a valid SQL expression + // by compiling it twice: once wrapped in parentheses and once as + // originally specified. An attacker cannot create an expression that + // would escape the SQL expression while maintaining balanced parentheses + // in both the wrapped and original forms. + String sqlForValidation = buildQuery(projectionIn, "(" + selection + ")", groupBy, + having, sortOrder, limit); + validateSql(db, sqlForValidation); // will throw if query is invalid + } + String sql = buildQuery( projectionIn, selection, groupBy, having, sortOrder, limit); @@ -329,7 +362,20 @@ public class SQLiteQueryBuilder } return db.rawQueryWithFactory( mFactory, sql, selectionArgs, - SQLiteDatabase.findEditTable(mTables)); + SQLiteDatabase.findEditTable(mTables)); // will throw if query is invalid + } + + /** + * Verifies that a SQL statement is valid by compiling it. + * If the SQL statement is not valid, this method will throw a {@link SQLiteException}. + */ + private void validateSql(SQLiteDatabase db, String sql) { + db.lock(sql); + try { + new SQLiteCompiledSql(db, sql).releaseSqlStatement(); + } finally { + db.unlock(); + } } /** @@ -541,7 +587,7 @@ public class SQLiteQueryBuilder continue; } - if (!mStrictProjectionMap && + if (!mStrict && ( userColumn.contains(" AS ") || userColumn.contains(" as "))) { /* A column alias already exist */ projection[i] = userColumn; diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index 419288b..c72c4b0 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -22,7 +22,6 @@ import android.os.Binder; import android.os.RemoteException; import java.net.InetAddress; -import java.net.UnknownHostException; /** * Class that answers queries about the state of network connectivity. It also @@ -40,8 +39,9 @@ import java.net.UnknownHostException; * state of the available networks</li> * </ol> */ -public class ConnectivityManager -{ +public class ConnectivityManager { + private static final String TAG = "ConnectivityManager"; + /** * A change in network connectivity has occurred. A connection has either * been established or lost. The NetworkInfo for the affected network is @@ -109,7 +109,7 @@ public class ConnectivityManager * The lookup key for an int that provides information about * our connection to the internet at large. 0 indicates no connection, * 100 indicates a great connection. Retrieve it with - * {@link android.content.Intent@getIntExtra(String)}. + * {@link android.content.Intent#getIntExtra(String, int)}. * {@hide} */ public static final String EXTRA_INET_CONDITION = "inetCondition"; @@ -120,13 +120,12 @@ public class ConnectivityManager * <p> * If an application uses the network in the background, it should listen * for this broadcast and stop using the background data if the value is - * false. + * {@code false}. */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_BACKGROUND_DATA_SETTING_CHANGED = "android.net.conn.BACKGROUND_DATA_SETTING_CHANGED"; - /** * Broadcast Action: The network connection may not be good * uses {@code ConnectivityManager.EXTRA_INET_CONDITION} and @@ -255,7 +254,7 @@ public class ConnectivityManager public static final int DEFAULT_NETWORK_PREFERENCE = TYPE_WIFI; - private IConnectivityManager mService; + private final IConnectivityManager mService; static public boolean isNetworkTypeValid(int networkType) { return networkType >= 0 && networkType <= MAX_NETWORK_TYPE; @@ -284,6 +283,15 @@ public class ConnectivityManager } } + /** {@hide} */ + public NetworkInfo getActiveNetworkInfoForUid(int uid) { + try { + return mService.getActiveNetworkInfoForUid(uid); + } catch (RemoteException e) { + return null; + } + } + public NetworkInfo getNetworkInfo(int networkType) { try { return mService.getNetworkInfo(networkType); @@ -300,7 +308,7 @@ public class ConnectivityManager } } - /** @hide */ + /** {@hide} */ public LinkProperties getActiveLinkProperties() { try { return mService.getActiveLinkProperties(); @@ -309,7 +317,7 @@ public class ConnectivityManager } } - /** @hide */ + /** {@hide} */ public LinkProperties getLinkProperties(int networkType) { try { return mService.getLinkProperties(networkType); @@ -479,19 +487,11 @@ public class ConnectivityManager } /** - * Don't allow use of default constructor. - */ - @SuppressWarnings({"UnusedDeclaration"}) - private ConnectivityManager() { - } - - /** * {@hide} */ public ConnectivityManager(IConnectivityManager service) { if (service == null) { - throw new IllegalArgumentException( - "ConnectivityManager() cannot be constructed with null service"); + throw new IllegalArgumentException("missing IConnectivityManager"); } mService = service; } diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl index 8be492c..647a60a 100644 --- a/core/java/android/net/IConnectivityManager.aidl +++ b/core/java/android/net/IConnectivityManager.aidl @@ -33,13 +33,11 @@ interface IConnectivityManager int getNetworkPreference(); NetworkInfo getActiveNetworkInfo(); - + NetworkInfo getActiveNetworkInfoForUid(int uid); NetworkInfo getNetworkInfo(int networkType); - NetworkInfo[] getAllNetworkInfo(); LinkProperties getActiveLinkProperties(); - LinkProperties getLinkProperties(int networkType); boolean setRadios(boolean onOff); diff --git a/core/java/android/net/INetworkPolicyListener.aidl b/core/java/android/net/INetworkPolicyListener.aidl new file mode 100644 index 0000000..9230151 --- /dev/null +++ b/core/java/android/net/INetworkPolicyListener.aidl @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2011 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.net; + +/** {@hide} */ +oneway interface INetworkPolicyListener { + + void onRulesChanged(int uid, int uidRules); + +} diff --git a/core/java/android/net/INetworkPolicyManager.aidl b/core/java/android/net/INetworkPolicyManager.aidl index d9351ee..c1f3530 100644 --- a/core/java/android/net/INetworkPolicyManager.aidl +++ b/core/java/android/net/INetworkPolicyManager.aidl @@ -16,6 +16,8 @@ package android.net; +import android.net.INetworkPolicyListener; + /** * Interface that creates and modifies network policy rules. * @@ -26,6 +28,11 @@ interface INetworkPolicyManager { void setUidPolicy(int uid, int policy); int getUidPolicy(int uid); + boolean isUidForeground(int uid); + + void registerListener(INetworkPolicyListener listener); + void unregisterListener(INetworkPolicyListener listener); + // TODO: build API to surface stats details for settings UI } diff --git a/core/java/android/net/NetworkInfo.java b/core/java/android/net/NetworkInfo.java index 5f5e11c..537750a 100644 --- a/core/java/android/net/NetworkInfo.java +++ b/core/java/android/net/NetworkInfo.java @@ -74,7 +74,9 @@ public class NetworkInfo implements Parcelable { /** IP traffic not available. */ DISCONNECTED, /** Attempt to connect failed. */ - FAILED + FAILED, + /** Access to this network is blocked. */ + BLOCKED } /** @@ -96,6 +98,7 @@ public class NetworkInfo implements Parcelable { stateMap.put(DetailedState.DISCONNECTING, State.DISCONNECTING); stateMap.put(DetailedState.DISCONNECTED, State.DISCONNECTED); stateMap.put(DetailedState.FAILED, State.DISCONNECTED); + stateMap.put(DetailedState.BLOCKED, State.DISCONNECTED); } private int mNetworkType; @@ -138,6 +141,23 @@ public class NetworkInfo implements Parcelable { mIsRoaming = false; } + /** {@hide} */ + public NetworkInfo(NetworkInfo source) { + if (source != null) { + mNetworkType = source.mNetworkType; + mSubtype = source.mSubtype; + mTypeName = source.mTypeName; + mSubtypeName = source.mSubtypeName; + mState = source.mState; + mDetailedState = source.mDetailedState; + mReason = source.mReason; + mExtraInfo = source.mExtraInfo; + mIsFailover = source.mIsFailover; + mIsRoaming = source.mIsRoaming; + mIsAvailable = source.mIsAvailable; + } + } + /** * Reports the type of network (currently mobile or Wi-Fi) to which the * info in this object pertains. diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java index 1913aa7..dd7c1b0 100644 --- a/core/java/android/net/NetworkPolicyManager.java +++ b/core/java/android/net/NetworkPolicyManager.java @@ -19,6 +19,8 @@ package android.net; import android.content.Context; import android.os.RemoteException; +import java.io.PrintWriter; + /** * Manager for creating and modifying network policy rules. * @@ -28,12 +30,13 @@ public class NetworkPolicyManager { /** No specific network policy, use system default. */ public static final int POLICY_NONE = 0x0; - /** Reject network usage when application in background. */ - public static final int POLICY_REJECT_BACKGROUND = 0x1; - /** Reject network usage on paid network connections. */ - public static final int POLICY_REJECT_PAID = 0x2; - /** Application should conserve data. */ - public static final int POLICY_CONSERVE_DATA = 0x4; + /** Reject network usage on paid networks when application in background. */ + public static final int POLICY_REJECT_PAID_BACKGROUND = 0x1; + + /** All network traffic should be allowed. */ + public static final int RULE_ALLOW_ALL = 0x0; + /** Reject traffic on paid networks. */ + public static final int RULE_REJECT_PAID = 0x1; private INetworkPolicyManager mService; @@ -51,9 +54,8 @@ public class NetworkPolicyManager { /** * Set policy flags for specific UID. * - * @param policy {@link #POLICY_NONE} or combination of - * {@link #POLICY_REJECT_BACKGROUND}, {@link #POLICY_REJECT_PAID}, - * or {@link #POLICY_CONSERVE_DATA}. + * @param policy {@link #POLICY_NONE} or combination of flags like + * {@link #POLICY_REJECT_PAID_BACKGROUND}. */ public void setUidPolicy(int uid, int policy) { try { @@ -69,5 +71,23 @@ public class NetworkPolicyManager { return POLICY_NONE; } } + + /** {@hide} */ + public static void dumpPolicy(PrintWriter fout, int policy) { + fout.write("["); + if ((policy & POLICY_REJECT_PAID_BACKGROUND) != 0) { + fout.write("REJECT_PAID_BACKGROUND"); + } + fout.write("]"); + } + + /** {@hide} */ + public static void dumpRules(PrintWriter fout, int rules) { + fout.write("["); + if ((rules & RULE_REJECT_PAID) != 0) { + fout.write("REJECT_PAID"); + } + fout.write("]"); + } } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 017e5e3..c9db697 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -982,10 +982,21 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit */ static final int HORIZONTAL_DIRECTION_MASK = 0xC0000000; + /* + * Array of horizontal direction flags for mapping attribute "horizontalDirection" to correct + * flag value. + * {@hide} + */ private static final int[] HORIZONTAL_DIRECTION_FLAGS = { HORIZONTAL_DIRECTION_LTR, HORIZONTAL_DIRECTION_RTL, HORIZONTAL_DIRECTION_INHERIT, HORIZONTAL_DIRECTION_LOCALE}; /** + * Default horizontalDirection. + * {@hide} + */ + private static final int HORIZONTAL_DIRECTION_DEFAULT = HORIZONTAL_DIRECTION_INHERIT; + + /** * View flag indicating whether {@link #addFocusables(ArrayList, int, int)} * should add all focusable Views regardless if they are focusable in touch mode. */ @@ -2442,7 +2453,7 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit public View(Context context) { mContext = context; mResources = context != null ? context.getResources() : null; - mViewFlags = SOUND_EFFECTS_ENABLED | HAPTIC_FEEDBACK_ENABLED; + mViewFlags = SOUND_EFFECTS_ENABLED | HAPTIC_FEEDBACK_ENABLED | HORIZONTAL_DIRECTION_INHERIT; mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); setOverScrollMode(OVER_SCROLL_IF_CONTENT_SCROLLS); } @@ -2641,12 +2652,18 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit } break; case com.android.internal.R.styleable.View_horizontalDirection: - final int layoutDirection = a.getInt(attr, 0); - if (layoutDirection != 0) { - viewFlagValues |= HORIZONTAL_DIRECTION_FLAGS[layoutDirection]; - viewFlagMasks |= HORIZONTAL_DIRECTION_MASK; - } - break; + // Clear any HORIZONTAL_DIRECTION flag already set + viewFlagValues &= ~HORIZONTAL_DIRECTION_MASK; + // Set the HORIZONTAL_DIRECTION flags depending on the value of the attribute + final int horizontalDirection = a.getInt(attr, -1); + if (horizontalDirection != -1) { + viewFlagValues |= HORIZONTAL_DIRECTION_FLAGS[horizontalDirection]; + } else { + // Set to default (HORIZONTAL_DIRECTION_INHERIT) + viewFlagValues |= HORIZONTAL_DIRECTION_DEFAULT; + } + viewFlagMasks |= HORIZONTAL_DIRECTION_MASK; + break; case com.android.internal.R.styleable.View_drawingCacheQuality: final int cacheQuality = a.getInt(attr, 0); if (cacheQuality != 0) { @@ -8513,10 +8530,14 @@ public class View implements Drawable.Callback2, KeyEvent.Callback, Accessibilit mPrivateFlags &= ~AWAKEN_SCROLL_BARS_ON_ATTACH; } jumpDrawablesToCurrentState(); + resolveHorizontalDirection(); + } - // We are supposing here that the parent directionality will be resolved before its children - // View horizontalDirection public attribute resolution to an internal var. - // Resolving the layout direction. LTR is set initially. + /** + * Resolving the layout direction. LTR is set initially. + * We are supposing here that the parent directionality will be resolved before its children + */ + private void resolveHorizontalDirection() { mPrivateFlags2 &= ~RESOLVED_LAYOUT_RTL; switch (getHorizontalDirection()) { case HORIZONTAL_DIRECTION_INHERIT: diff --git a/core/java/android/webkit/HTML5VideoFullScreen.java b/core/java/android/webkit/HTML5VideoFullScreen.java index 1004b5f..57cda97 100644 --- a/core/java/android/webkit/HTML5VideoFullScreen.java +++ b/core/java/android/webkit/HTML5VideoFullScreen.java @@ -199,6 +199,9 @@ public class HTML5VideoFullScreen extends HTML5VideoView mVideoSurfaceView.getHolder().setFixedSize(mVideoWidth, mVideoHeight); } + public boolean fullScreenExited() { + return (mLayout == null); + } private final WebChromeClient.CustomViewCallback mCallback = new WebChromeClient.CustomViewCallback() { diff --git a/core/java/android/webkit/HTML5VideoInline.java b/core/java/android/webkit/HTML5VideoInline.java index 25921bc..ef1906c 100644 --- a/core/java/android/webkit/HTML5VideoInline.java +++ b/core/java/android/webkit/HTML5VideoInline.java @@ -12,10 +12,15 @@ import android.opengl.GLES20; */ public class HTML5VideoInline extends HTML5VideoView{ - // Due to the fact that SurfaceTexture consume a lot of memory, we make it - // as static. m_textureNames is the texture bound with this SurfaceTexture. + // Due to the fact that the decoder consume a lot of memory, we make the + // surface texture as singleton. But the GL texture (m_textureNames) + // associated with the surface texture can be used for showing the screen + // shot when paused, so they are not singleton. private static SurfaceTexture mSurfaceTexture = null; - private static int[] mTextureNames; + private int[] mTextureNames; + // Every time when the VideoLayer Id change, we need to recreate the + // SurfaceTexture in order to delete the old video's decoder memory. + private static int mVideoLayerUsingSurfaceTexture = -1; // Video control FUNCTIONS: @Override @@ -28,11 +33,12 @@ public class HTML5VideoInline extends HTML5VideoView{ HTML5VideoInline(int videoLayerId, int position, boolean autoStart) { init(videoLayerId, position, autoStart); + mTextureNames = null; } @Override public void decideDisplayMode() { - mPlayer.setTexture(getSurfaceTextureInstance()); + mPlayer.setTexture(getSurfaceTexture(getVideoLayerId())); } // Normally called immediately after setVideoURI. But for full screen, @@ -52,31 +58,38 @@ public class HTML5VideoInline extends HTML5VideoView{ // Inline Video specific FUNCTIONS: @Override - public SurfaceTexture getSurfaceTexture() { + public SurfaceTexture getSurfaceTexture(int videoLayerId) { + // Create the surface texture. + if (videoLayerId != mVideoLayerUsingSurfaceTexture + || mSurfaceTexture == null) { + if (mTextureNames == null) { + mTextureNames = new int[1]; + GLES20.glGenTextures(1, mTextureNames, 0); + } + mSurfaceTexture = new SurfaceTexture(mTextureNames[0]); + } + mVideoLayerUsingSurfaceTexture = videoLayerId; return mSurfaceTexture; } + public boolean surfaceTextureDeleted() { + return (mSurfaceTexture == null); + } + @Override public void deleteSurfaceTexture() { mSurfaceTexture = null; + mVideoLayerUsingSurfaceTexture = -1; return; } - // SurfaceTexture is a singleton here , too - private SurfaceTexture getSurfaceTextureInstance() { - // Create the surface texture. - if (mSurfaceTexture == null) - { - mTextureNames = new int[1]; - GLES20.glGenTextures(1, mTextureNames, 0); - mSurfaceTexture = new SurfaceTexture(mTextureNames[0]); - } - return mSurfaceTexture; - } - @Override public int getTextureName() { - return mTextureNames[0]; + if (mTextureNames != null) { + return mTextureNames[0]; + } else { + return 0; + } } private void setFrameAvailableListener(SurfaceTexture.OnFrameAvailableListener l) { diff --git a/core/java/android/webkit/HTML5VideoView.java b/core/java/android/webkit/HTML5VideoView.java index c05498a..5983a44 100644 --- a/core/java/android/webkit/HTML5VideoView.java +++ b/core/java/android/webkit/HTML5VideoView.java @@ -287,7 +287,7 @@ public class HTML5VideoView implements MediaPlayer.OnPreparedListener { return false; } - public SurfaceTexture getSurfaceTexture() { + public SurfaceTexture getSurfaceTexture(int videoLayerId) { return null; } @@ -315,4 +315,14 @@ public class HTML5VideoView implements MediaPlayer.OnPreparedListener { // Only used in HTML5VideoFullScreen } + public boolean surfaceTextureDeleted() { + // Only meaningful for HTML5VideoInline + return false; + } + + public boolean fullScreenExited() { + // Only meaningful for HTML5VideoFullScreen + return false; + } + } diff --git a/core/java/android/webkit/HTML5VideoViewProxy.java b/core/java/android/webkit/HTML5VideoViewProxy.java index 7d8669b..d0237b5 100644 --- a/core/java/android/webkit/HTML5VideoViewProxy.java +++ b/core/java/android/webkit/HTML5VideoViewProxy.java @@ -106,12 +106,14 @@ class HTML5VideoViewProxy extends Handler public static void setBaseLayer(int layer) { // Don't do this for full screen mode. if (mHTML5VideoView != null - && !mHTML5VideoView.isFullScreenMode()) { + && !mHTML5VideoView.isFullScreenMode() + && !mHTML5VideoView.surfaceTextureDeleted()) { mBaseLayer = layer; - SurfaceTexture surfTexture = mHTML5VideoView.getSurfaceTexture(); - int textureName = mHTML5VideoView.getTextureName(); int currentVideoLayerId = mHTML5VideoView.getVideoLayerId(); + SurfaceTexture surfTexture = mHTML5VideoView.getSurfaceTexture(currentVideoLayerId); + int textureName = mHTML5VideoView.getTextureName(); + if (layer != 0 && surfTexture != null && currentVideoLayerId != -1) { int playerState = mHTML5VideoView.getCurrentState(); if (mHTML5VideoView.getPlayerBuffering()) @@ -171,14 +173,12 @@ class HTML5VideoViewProxy extends Handler boolean backFromFullScreenMode = false; if (mHTML5VideoView != null) { currentVideoLayerId = mHTML5VideoView.getVideoLayerId(); - if (mHTML5VideoView instanceof HTML5VideoFullScreen) { - backFromFullScreenMode = true; - } + backFromFullScreenMode = mHTML5VideoView.fullScreenExited(); } if (backFromFullScreenMode - || currentVideoLayerId != videoLayerId - || mHTML5VideoView.getSurfaceTexture() == null) { + || currentVideoLayerId != videoLayerId + || mHTML5VideoView.surfaceTextureDeleted()) { // Here, we handle the case when switching to a new video, // either inside a WebView or across WebViews // For switching videos within a WebView or across the WebView, diff --git a/core/java/android/webkit/ZoomManager.java b/core/java/android/webkit/ZoomManager.java index e41dd1c..0573d88 100644 --- a/core/java/android/webkit/ZoomManager.java +++ b/core/java/android/webkit/ZoomManager.java @@ -906,7 +906,7 @@ class ZoomManager { // scaleAll(), we need to post a Runnable to ensure requestLayout(). // Additionally, only update the text wrap scale if the width changed. mWebView.post(new PostScale(w != ow && - !mWebView.getSettings().getUseFixedViewport(), mInZoomOverview)); + !mWebView.getSettings().getUseFixedViewport(), mInZoomOverview, w < ow)); } private class PostScale implements Runnable { @@ -915,10 +915,14 @@ class ZoomManager { // it could be changed between the time this callback is initiated and // the time it's actually run. final boolean mInZoomOverviewBeforeSizeChange; + final boolean mInPortraitMode; - public PostScale(boolean updateTextWrap, boolean inZoomOverview) { + public PostScale(boolean updateTextWrap, + boolean inZoomOverview, + boolean inPortraitMode) { mUpdateTextWrap = updateTextWrap; mInZoomOverviewBeforeSizeChange = inZoomOverview; + mInPortraitMode = inPortraitMode; } public void run() { @@ -927,10 +931,10 @@ class ZoomManager { // still want to send the notification over to webkit. // Keep overview mode unchanged when rotating. float newScale = mActualScale; - if (mWebView.getSettings().getUseWideViewPort()) { - final float zoomOverviewScale = getZoomOverviewScale(); - newScale = (mInZoomOverviewBeforeSizeChange) ? - zoomOverviewScale : Math.max(mActualScale, zoomOverviewScale); + if (mWebView.getSettings().getUseWideViewPort() && + mInPortraitMode && + mInZoomOverviewBeforeSizeChange) { + newScale = getZoomOverviewScale(); } setZoomScale(newScale, mUpdateTextWrap, true); // update the zoom buttons as the scale can be changed diff --git a/core/java/android/widget/SearchView.java b/core/java/android/widget/SearchView.java index 9933d68..586ece8 100644 --- a/core/java/android/widget/SearchView.java +++ b/core/java/android/widget/SearchView.java @@ -93,6 +93,7 @@ public class SearchView extends LinearLayout { private boolean mClearingFocus; private int mMaxWidth; private boolean mVoiceButtonEnabled; + private CharSequence mUserQuery; private SearchableInfo mSearchable; private Bundle mAppSearchData; @@ -372,6 +373,7 @@ public class SearchView extends LinearLayout { mQueryTextView.setText(query); if (query != null) { mQueryTextView.setSelection(query.length()); + mUserQuery = query; } // If the query is not empty and submit is requested, submit the query @@ -885,6 +887,7 @@ public class SearchView extends LinearLayout { private void onTextChanged(CharSequence newText) { CharSequence text = mQueryTextView.getText(); + mUserQuery = text; boolean hasText = !TextUtils.isEmpty(text); if (isSubmitButtonEnabled()) { updateSubmitButton(hasText); @@ -1124,7 +1127,7 @@ public class SearchView extends LinearLayout { if (data != null) { intent.setData(data); } - intent.putExtra(SearchManager.USER_QUERY, query); + intent.putExtra(SearchManager.USER_QUERY, mUserQuery); if (query != null) { intent.putExtra(SearchManager.QUERY, query); } diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 18c3b24..5886c64 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -8787,10 +8787,22 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener public boolean onCreateActionMode(ActionMode mode, Menu menu) { TypedArray styledAttributes = mContext.obtainStyledAttributes(R.styleable.Theme); - mode.setTitle(mContext.getString(com.android.internal.R.string.textSelectionCABTitle)); + boolean allowText = getContext().getResources().getBoolean( + com.android.internal.R.bool.allow_action_menu_item_text_with_icon); + + mode.setTitle(allowText ? + mContext.getString(com.android.internal.R.string.textSelectionCABTitle) : null); mode.setSubtitle(null); + int selectAllIconId = 0; // No icon by default + if (!allowText) { + // Provide an icon, text will not be displayed on smaller screens. + selectAllIconId = styledAttributes.getResourceId( + R.styleable.Theme_actionModeSelectAllDrawable, 0); + } + menu.add(0, ID_SELECT_ALL, 0, com.android.internal.R.string.selectAll). + setIcon(selectAllIconId). setAlphabeticShortcut('a'). setShowAsAction( MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT); |
