diff options
Diffstat (limited to 'core')
595 files changed, 38087 insertions, 12435 deletions
diff --git a/core/java/android/accounts/AccountManagerService.java b/core/java/android/accounts/AccountManagerService.java index 1cd7aa77e271..1d9e0f166c5b 100644 --- a/core/java/android/accounts/AccountManagerService.java +++ b/core/java/android/accounts/AccountManagerService.java @@ -1483,7 +1483,13 @@ public class AccountManagerService } private static String getDatabaseName() { - return DATABASE_NAME; + if(Environment.isEncryptedFilesystemEnabled()) { + // Hard-coded path in case of encrypted file system + return Environment.getSystemSecureDirectory().getPath() + File.separator + DATABASE_NAME; + } else { + // Regular path in case of non-encrypted file system + return DATABASE_NAME; + } } private class DatabaseHelper extends SQLiteOpenHelper { diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index a9623917f757..6e6e86f78950 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -39,7 +39,6 @@ import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; -import android.os.Looper; import android.os.RemoteException; import android.text.Selection; import android.text.SpannableStringBuilder; @@ -68,6 +67,8 @@ import android.view.View.OnCreateContextMenuListener; import android.view.ViewGroup.LayoutParams; import android.view.accessibility.AccessibilityEvent; import android.widget.AdapterView; +import android.widget.FrameLayout; +import android.widget.LinearLayout; import java.util.ArrayList; import java.util.HashMap; @@ -1161,6 +1162,7 @@ public class Activity extends ContextThemeWrapper */ protected void onPause() { mCalled = true; + QueuedWork.waitToFinish(); } /** @@ -1205,19 +1207,37 @@ public class Activity extends ContextThemeWrapper * @see #onPause */ public boolean onCreateThumbnail(Bitmap outBitmap, Canvas canvas) { - final View view = mDecor; - if (view == null) { + if (mDecor == null) { return false; } - final int vw = view.getWidth(); - final int vh = view.getHeight(); - final int dw = outBitmap.getWidth(); - final int dh = outBitmap.getHeight(); + int paddingLeft = 0; + int paddingRight = 0; + int paddingTop = 0; + int paddingBottom = 0; + + // Find System window and use padding so we ignore space reserved for decorations + // like the status bar and such. + final FrameLayout top = (FrameLayout) mDecor; + for (int i = 0; i < top.getChildCount(); i++) { + View child = top.getChildAt(i); + if (child.isFitsSystemWindowsFlagSet()) { + paddingLeft = child.getPaddingLeft(); + paddingRight = child.getPaddingRight(); + paddingTop = child.getPaddingTop(); + paddingBottom = child.getPaddingBottom(); + break; + } + } + + final int visibleWidth = mDecor.getWidth() - paddingLeft - paddingRight; + final int visibleHeight = mDecor.getHeight() - paddingTop - paddingBottom; canvas.save(); - canvas.scale(((float)dw)/vw, ((float)dh)/vh); - view.draw(canvas); + canvas.scale( (float) outBitmap.getWidth() / visibleWidth, + (float) outBitmap.getHeight() / visibleHeight); + canvas.translate(-paddingLeft, -paddingTop); + mDecor.draw(canvas); canvas.restore(); return true; @@ -1600,6 +1620,9 @@ public class Activity extends ContextThemeWrapper } /** + * @deprecated This functionality will be removed in the future; please do + * not use. + * * Control whether this activity is required to be persistent. By default * activities are not persistent; setting this to true will prevent the * system from stopping this activity or its process when running low on @@ -1614,6 +1637,7 @@ public class Activity extends ContextThemeWrapper * persistent, true if so, false for the normal * behavior. */ + @Deprecated public void setPersistent(boolean isPersistent) { if (mParent == null) { try { @@ -3839,7 +3863,14 @@ public class Activity extends ContextThemeWrapper } final void performPause() { + mCalled = false; onPause(); + if (!mCalled && getApplicationInfo().targetSdkVersion + >= android.os.Build.VERSION_CODES.GINGERBREAD) { + throw new SuperNotCalledException( + "Activity " + mComponent.toShortString() + + " did not call through to super.onPause()"); + } } final void performUserLeaving() { diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index c9096cfe6e99..4736404d9b0f 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -285,24 +285,54 @@ public class ActivityManager { * @param maxNum The maximum number of entries to return in the list. The * actual number returned may be smaller, depending on how many tasks the * user has started. - * + * + * @param flags Optional flags + * @param receiver Optional receiver for delayed thumbnails + * * @return Returns a list of RunningTaskInfo records describing each of * the running tasks. * + * Some thumbnails may not be available at the time of this call. The optional + * receiver may be used to receive those thumbnails. + * * @throws SecurityException Throws SecurityException if the caller does * not hold the {@link android.Manifest.permission#GET_TASKS} permission. + * + * @hide */ - public List<RunningTaskInfo> getRunningTasks(int maxNum) + public List<RunningTaskInfo> getRunningTasks(int maxNum, int flags, IThumbnailReceiver receiver) throws SecurityException { try { - return (List<RunningTaskInfo>)ActivityManagerNative.getDefault() - .getTasks(maxNum, 0, null); + return ActivityManagerNative.getDefault().getTasks(maxNum, flags, receiver); } catch (RemoteException e) { // System dead, we will be dead too soon! return null; } } - + + /** + * Return a list of the tasks that are currently running, with + * the most recent being first and older ones after in order. Note that + * "running" does not mean any of the task's code is currently loaded or + * activity -- the task may have been frozen by the system, so that it + * can be restarted in its previous state when next brought to the + * foreground. + * + * @param maxNum The maximum number of entries to return in the list. The + * actual number returned may be smaller, depending on how many tasks the + * user has started. + * + * @return Returns a list of RunningTaskInfo records describing each of + * the running tasks. + * + * @throws SecurityException Throws SecurityException if the caller does + * not hold the {@link android.Manifest.permission#GET_TASKS} permission. + */ + public List<RunningTaskInfo> getRunningTasks(int maxNum) + throws SecurityException { + return getRunningTasks(maxNum, 0, null); + } + /** * Information you can retrieve about a particular Service that is * currently running in the system. @@ -335,7 +365,8 @@ public class ActivityManager { /** * The time when the service was first made active, either by someone - * starting or binding to it. + * starting or binding to it. This + * is in units of {@link android.os.SystemClock#elapsedRealtime()}. */ public long activeSince; @@ -357,7 +388,8 @@ public class ActivityManager { /** * The time when there was last activity in the service (either - * explicit requests to start it or clients binding to it). + * explicit requests to start it or clients binding to it). This + * is in units of {@link android.os.SystemClock#uptimeMillis()}. */ public long lastActivityTime; @@ -717,9 +749,27 @@ public class ActivityManager { */ public int uid; + /** + * All packages that have been loaded into the process. + */ public String pkgList[]; /** + * Constant for {@link #flags}: this is an app that is unable to + * correctly save its state when going to the background, + * so it can not be killed while in the background. + * @hide + */ + public static final int FLAG_CANT_SAVE_STATE = 1<<0; + + /** + * Flags of information. May be any of + * {@link #FLAG_CANT_SAVE_STATE}. + * @hide + */ + public int flags; + + /** * Constant for {@link #importance}: this process is running the * foreground UI. */ @@ -727,11 +777,25 @@ public class ActivityManager { /** * Constant for {@link #importance}: this process is running something - * that is considered to be actively visible to the user. + * that is actively visible to the user, though not in the immediate + * foreground. */ public static final int IMPORTANCE_VISIBLE = 200; /** + * Constant for {@link #importance}: this process is running something + * that is considered to be actively perceptible to the user. An + * example would be an application performing background music playback. + */ + public static final int IMPORTANCE_PERCEPTIBLE = 130; + + /** + * Constant for {@link #importance}: this process is running a + * heavy-weight application and thus should not be killed. + */ + public static final int IMPORTANCE_HEAVY_WEIGHT = 170; + + /** * Constant for {@link #importance}: this process is contains services * that should remain running. */ @@ -832,6 +896,7 @@ public class ActivityManager { dest.writeInt(pid); dest.writeInt(uid); dest.writeStringArray(pkgList); + dest.writeInt(this.flags); dest.writeInt(importance); dest.writeInt(lru); dest.writeInt(importanceReasonCode); @@ -844,6 +909,7 @@ public class ActivityManager { pid = source.readInt(); uid = source.readInt(); pkgList = source.readStringArray(); + flags = source.readInt(); importance = source.readInt(); lru = source.readInt(); importanceReasonCode = source.readInt(); diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java index f69428589e07..a1808377c69c 100644 --- a/core/java/android/app/ActivityManagerNative.java +++ b/core/java/android/app/ActivityManagerNative.java @@ -37,6 +37,7 @@ import android.os.RemoteException; import android.os.IBinder; import android.os.Parcel; import android.os.ServiceManager; +import android.os.StrictMode; import android.text.TextUtils; import android.util.Config; import android.util.Log; @@ -1021,16 +1022,6 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM return true; } - case REPORT_PSS_TRANSACTION: { - data.enforceInterface(IActivityManager.descriptor); - IBinder b = data.readStrongBinder(); - IApplicationThread app = ApplicationThreadNative.asInterface(b); - int pss = data.readInt(); - reportPss(app, pss); - reply.writeNoException(); - return true; - } - case START_RUNNING_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); String pkg = data.readString(); @@ -1062,6 +1053,16 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM return true; } + case HANDLE_APPLICATION_STRICT_MODE_VIOLATION_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + IBinder app = data.readStrongBinder(); + int violationMask = data.readInt(); + StrictMode.ViolationInfo info = new StrictMode.ViolationInfo(data); + handleApplicationStrictModeViolation(app, violationMask, info); + reply.writeNoException(); + return true; + } + case SIGNAL_PERSISTENT_PROCESSES_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); int sig = data.readInt(); @@ -1247,10 +1248,64 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM case IS_USER_A_MONKEY_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); - reply.writeInt(isUserAMonkey() ? 1 : 0); + boolean areThey = isUserAMonkey(); reply.writeNoException(); + reply.writeInt(areThey ? 1 : 0); return true; } + + case FINISH_HEAVY_WEIGHT_APP_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + finishHeavyWeightApp(); + reply.writeNoException(); + return true; + } + + case CRASH_APPLICATION_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + int uid = data.readInt(); + int initialPid = data.readInt(); + String packageName = data.readString(); + String message = data.readString(); + crashApplication(uid, initialPid, packageName, message); + reply.writeNoException(); + return true; + } + + case NEW_URI_PERMISSION_OWNER_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + String name = data.readString(); + IBinder perm = newUriPermissionOwner(name); + reply.writeNoException(); + reply.writeStrongBinder(perm); + return true; + } + + case GRANT_URI_PERMISSION_FROM_OWNER_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + IBinder owner = data.readStrongBinder(); + int fromUid = data.readInt(); + String targetPkg = data.readString(); + Uri uri = Uri.CREATOR.createFromParcel(data); + int mode = data.readInt(); + grantUriPermissionFromOwner(owner, fromUid, targetPkg, uri, mode); + reply.writeNoException(); + return true; + } + + case REVOKE_URI_PERMISSION_FROM_OWNER_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + IBinder owner = data.readStrongBinder(); + Uri uri = null; + if (data.readInt() != 0) { + Uri.CREATOR.createFromParcel(data); + } + int mode = data.readInt(); + revokeUriPermissionFromOwner(owner, uri, mode); + reply.writeNoException(); + return true; + } + } return super.onTransact(code, data, reply, flags); @@ -2476,14 +2531,6 @@ class ActivityManagerProxy implements IActivityManager reply.recycle(); return res; } - public void reportPss(IApplicationThread caller, int pss) throws RemoteException { - Parcel data = Parcel.obtain(); - data.writeInterfaceToken(IActivityManager.descriptor); - data.writeStrongBinder(caller.asBinder()); - data.writeInt(pss); - mRemote.transact(REPORT_PSS_TRANSACTION, data, null, 0); - data.recycle(); - } public void startRunning(String pkg, String cls, String action, String indata) throws RemoteException { Parcel data = Parcel.obtain(); @@ -2516,6 +2563,7 @@ class ActivityManagerProxy implements IActivityManager reply.recycle(); data.recycle(); } + public boolean handleApplicationWtf(IBinder app, String tag, ApplicationErrorReport.CrashInfo crashInfo) throws RemoteException { @@ -2533,6 +2581,22 @@ class ActivityManagerProxy implements IActivityManager return res; } + public void handleApplicationStrictModeViolation(IBinder app, + int violationMask, + StrictMode.ViolationInfo info) throws RemoteException + { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(app); + data.writeInt(violationMask); + info.writeToParcel(data, 0); + mRemote.transact(HANDLE_APPLICATION_STRICT_MODE_VIOLATION_TRANSACTION, data, reply, 0); + reply.readException(); + reply.recycle(); + data.recycle(); + } + public void signalPersistentProcesses(int sig) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); @@ -2758,5 +2822,79 @@ class ActivityManagerProxy implements IActivityManager return res; } + public void finishHeavyWeightApp() throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + mRemote.transact(FINISH_HEAVY_WEIGHT_APP_TRANSACTION, data, reply, 0); + reply.readException(); + data.recycle(); + reply.recycle(); + } + + public void crashApplication(int uid, int initialPid, String packageName, + String message) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeInt(uid); + data.writeInt(initialPid); + data.writeString(packageName); + data.writeString(message); + mRemote.transact(CRASH_APPLICATION_TRANSACTION, data, reply, 0); + reply.readException(); + data.recycle(); + reply.recycle(); + } + + public IBinder newUriPermissionOwner(String name) + throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeString(name); + mRemote.transact(NEW_URI_PERMISSION_OWNER_TRANSACTION, data, reply, 0); + reply.readException(); + IBinder res = reply.readStrongBinder(); + data.recycle(); + reply.recycle(); + return res; + } + + public void grantUriPermissionFromOwner(IBinder owner, int fromUid, String targetPkg, + Uri uri, int mode) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(owner); + data.writeInt(fromUid); + data.writeString(targetPkg); + uri.writeToParcel(data, 0); + data.writeInt(mode); + mRemote.transact(GRANT_URI_PERMISSION_TRANSACTION, data, reply, 0); + reply.readException(); + data.recycle(); + reply.recycle(); + } + + public void revokeUriPermissionFromOwner(IBinder owner, Uri uri, + int mode) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(owner); + if (uri != null) { + data.writeInt(1); + uri.writeToParcel(data, 0); + } else { + data.writeInt(0); + } + data.writeInt(mode); + mRemote.transact(REVOKE_URI_PERMISSION_TRANSACTION, data, reply, 0); + reply.readException(); + data.recycle(); + reply.recycle(); + } + private IBinder mRemote; } diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 773c344bb3f6..f8407c22d7ec 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -25,7 +25,6 @@ import android.content.Context; import android.content.IContentProvider; import android.content.Intent; import android.content.IIntentReceiver; -import android.content.ServiceConnection; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; @@ -42,6 +41,7 @@ import android.database.sqlite.SQLiteDebug; import android.database.sqlite.SQLiteDebug.DbStats; import android.graphics.Bitmap; import android.graphics.Canvas; +import android.os.Build; import android.os.Bundle; import android.os.Debug; import android.os.Handler; @@ -53,6 +53,7 @@ import android.os.ParcelFileDescriptor; import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.StrictMode; import android.os.SystemClock; import android.util.AndroidRuntimeException; import android.util.Config; @@ -72,7 +73,6 @@ import android.view.WindowManagerImpl; import com.android.internal.os.BinderInternal; import com.android.internal.os.RuntimeInit; import com.android.internal.os.SamplingProfilerIntegration; -import com.android.internal.util.ArrayUtils; import org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl; @@ -80,12 +80,9 @@ import java.io.File; import java.io.FileDescriptor; import java.io.FileOutputStream; import java.io.IOException; -import java.io.InputStream; import java.io.PrintWriter; import java.lang.ref.WeakReference; -import java.net.URL; import java.util.ArrayList; -import java.util.Enumeration; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -96,20 +93,14 @@ import java.util.regex.Pattern; import dalvik.system.SamplingProfiler; -final class IntentReceiverLeaked extends AndroidRuntimeException { - public IntentReceiverLeaked(String msg) { - super(msg); - } -} - -final class ServiceConnectionLeaked extends AndroidRuntimeException { - public ServiceConnectionLeaked(String msg) { +final class SuperNotCalledException extends AndroidRuntimeException { + public SuperNotCalledException(String msg) { super(msg); } } -final class SuperNotCalledException extends AndroidRuntimeException { - public SuperNotCalledException(String msg) { +final class RemoteServiceException extends AndroidRuntimeException { + public RemoteServiceException(String msg) { super(msg); } } @@ -123,10 +114,11 @@ final class SuperNotCalledException extends AndroidRuntimeException { * {@hide} */ public final class ActivityThread { - private static final String TAG = "ActivityThread"; + static final String TAG = "ActivityThread"; + private static final android.graphics.Bitmap.Config THUMBNAIL_FORMAT = Bitmap.Config.RGB_565; private static final boolean DEBUG = false; - private static final boolean localLOGV = DEBUG ? Config.LOGD : Config.LOGV; - private static final boolean DEBUG_BROADCAST = false; + static final boolean localLOGV = DEBUG ? Config.LOGD : Config.LOGV; + static final boolean DEBUG_BROADCAST = false; private static final boolean DEBUG_RESULTS = false; private static final boolean DEBUG_BACKUP = false; private static final boolean DEBUG_CONFIGURATION = false; @@ -136,1162 +128,67 @@ public final class ActivityThread { private static final int LOG_ON_PAUSE_CALLED = 30021; private static final int LOG_ON_RESUME_CALLED = 30022; + static ContextImpl mSystemContext = null; - public static final ActivityThread currentActivityThread() { - return (ActivityThread)sThreadLocal.get(); - } - - public static final String currentPackageName() - { - ActivityThread am = currentActivityThread(); - return (am != null && am.mBoundApplication != null) - ? am.mBoundApplication.processName : null; - } - - public static IPackageManager getPackageManager() { - if (sPackageManager != null) { - //Slog.v("PackageManager", "returning cur default = " + sPackageManager); - return sPackageManager; - } - IBinder b = ServiceManager.getService("package"); - //Slog.v("PackageManager", "default service binder = " + b); - sPackageManager = IPackageManager.Stub.asInterface(b); - //Slog.v("PackageManager", "default service = " + sPackageManager); - return sPackageManager; - } - - DisplayMetrics getDisplayMetricsLocked(boolean forceUpdate) { - if (mDisplayMetrics != null && !forceUpdate) { - return mDisplayMetrics; - } - if (mDisplay == null) { - WindowManager wm = WindowManagerImpl.getDefault(); - mDisplay = wm.getDefaultDisplay(); - } - DisplayMetrics metrics = mDisplayMetrics = new DisplayMetrics(); - mDisplay.getMetrics(metrics); - //Slog.i("foo", "New metrics: w=" + metrics.widthPixels + " h=" - // + metrics.heightPixels + " den=" + metrics.density - // + " xdpi=" + metrics.xdpi + " ydpi=" + metrics.ydpi); - return metrics; - } - - /** - * Creates the top level Resources for applications with the given compatibility info. - * - * @param resDir the resource directory. - * @param compInfo the compability info. It will use the default compatibility info when it's - * null. - */ - Resources getTopLevelResources(String resDir, CompatibilityInfo compInfo) { - ResourcesKey key = new ResourcesKey(resDir, compInfo.applicationScale); - Resources r; - synchronized (mPackages) { - // Resources is app scale dependent. - if (false) { - Slog.w(TAG, "getTopLevelResources: " + resDir + " / " - + compInfo.applicationScale); - } - WeakReference<Resources> wr = mActiveResources.get(key); - r = wr != null ? wr.get() : null; - //if (r != null) Slog.i(TAG, "isUpToDate " + resDir + ": " + r.getAssets().isUpToDate()); - if (r != null && r.getAssets().isUpToDate()) { - if (false) { - Slog.w(TAG, "Returning cached resources " + r + " " + resDir - + ": appScale=" + r.getCompatibilityInfo().applicationScale); - } - return r; - } - } - - //if (r != null) { - // Slog.w(TAG, "Throwing away out-of-date resources!!!! " - // + r + " " + resDir); - //} - - AssetManager assets = new AssetManager(); - if (assets.addAssetPath(resDir) == 0) { - return null; - } - - //Slog.i(TAG, "Resource: key=" + key + ", display metrics=" + metrics); - DisplayMetrics metrics = getDisplayMetricsLocked(false); - r = new Resources(assets, metrics, getConfiguration(), compInfo); - if (false) { - Slog.i(TAG, "Created app resources " + resDir + " " + r + ": " - + r.getConfiguration() + " appScale=" - + r.getCompatibilityInfo().applicationScale); - } - - synchronized (mPackages) { - WeakReference<Resources> wr = mActiveResources.get(key); - Resources existing = wr != null ? wr.get() : null; - if (existing != null && existing.getAssets().isUpToDate()) { - // Someone else already created the resources while we were - // unlocked; go ahead and use theirs. - r.getAssets().close(); - return existing; - } - - // XXX need to remove entries when weak references go away - mActiveResources.put(key, new WeakReference<Resources>(r)); - return r; - } - } - - /** - * Creates the top level resources for the given package. - */ - Resources getTopLevelResources(String resDir, PackageInfo pkgInfo) { - return getTopLevelResources(resDir, pkgInfo.mCompatibilityInfo); - } - - final Handler getHandler() { - return mH; - } - - public final static class PackageInfo { - - private final ActivityThread mActivityThread; - private final ApplicationInfo mApplicationInfo; - private final String mPackageName; - private final String mAppDir; - private final String mResDir; - private final String[] mSharedLibraries; - private final String mDataDir; - private final File mDataDirFile; - private final ClassLoader mBaseClassLoader; - private final boolean mSecurityViolation; - private final boolean mIncludeCode; - private Resources mResources; - private ClassLoader mClassLoader; - private Application mApplication; - private CompatibilityInfo mCompatibilityInfo; - - private final HashMap<Context, HashMap<BroadcastReceiver, ReceiverDispatcher>> mReceivers - = new HashMap<Context, HashMap<BroadcastReceiver, ReceiverDispatcher>>(); - private final HashMap<Context, HashMap<BroadcastReceiver, ReceiverDispatcher>> mUnregisteredReceivers - = new HashMap<Context, HashMap<BroadcastReceiver, ReceiverDispatcher>>(); - private final HashMap<Context, HashMap<ServiceConnection, ServiceDispatcher>> mServices - = new HashMap<Context, HashMap<ServiceConnection, ServiceDispatcher>>(); - private final HashMap<Context, HashMap<ServiceConnection, ServiceDispatcher>> mUnboundServices - = new HashMap<Context, HashMap<ServiceConnection, ServiceDispatcher>>(); - - int mClientCount = 0; - - Application getApplication() { - return mApplication; - } - - public PackageInfo(ActivityThread activityThread, ApplicationInfo aInfo, - ActivityThread mainThread, ClassLoader baseLoader, - boolean securityViolation, boolean includeCode) { - mActivityThread = activityThread; - mApplicationInfo = aInfo; - mPackageName = aInfo.packageName; - mAppDir = aInfo.sourceDir; - mResDir = aInfo.uid == Process.myUid() ? aInfo.sourceDir - : aInfo.publicSourceDir; - mSharedLibraries = aInfo.sharedLibraryFiles; - mDataDir = aInfo.dataDir; - mDataDirFile = mDataDir != null ? new File(mDataDir) : null; - mBaseClassLoader = baseLoader; - mSecurityViolation = securityViolation; - mIncludeCode = includeCode; - mCompatibilityInfo = new CompatibilityInfo(aInfo); - - if (mAppDir == null) { - if (mSystemContext == null) { - mSystemContext = - ContextImpl.createSystemContext(mainThread); - mSystemContext.getResources().updateConfiguration( - mainThread.getConfiguration(), - mainThread.getDisplayMetricsLocked(false)); - //Slog.i(TAG, "Created system resources " - // + mSystemContext.getResources() + ": " - // + mSystemContext.getResources().getConfiguration()); - } - mClassLoader = mSystemContext.getClassLoader(); - mResources = mSystemContext.getResources(); - } - } - - public PackageInfo(ActivityThread activityThread, String name, - Context systemContext, ApplicationInfo info) { - mActivityThread = activityThread; - mApplicationInfo = info != null ? info : new ApplicationInfo(); - mApplicationInfo.packageName = name; - mPackageName = name; - mAppDir = null; - mResDir = null; - mSharedLibraries = null; - mDataDir = null; - mDataDirFile = null; - mBaseClassLoader = null; - mSecurityViolation = false; - mIncludeCode = true; - mClassLoader = systemContext.getClassLoader(); - mResources = systemContext.getResources(); - mCompatibilityInfo = new CompatibilityInfo(mApplicationInfo); - } - - public String getPackageName() { - return mPackageName; - } - - public ApplicationInfo getApplicationInfo() { - return mApplicationInfo; - } - - public boolean isSecurityViolation() { - return mSecurityViolation; - } - - /** - * Gets the array of shared libraries that are listed as - * used by the given package. - * - * @param packageName the name of the package (note: not its - * file name) - * @return null-ok; the array of shared libraries, each one - * a fully-qualified path - */ - private static String[] getLibrariesFor(String packageName) { - ApplicationInfo ai = null; - try { - ai = getPackageManager().getApplicationInfo(packageName, - PackageManager.GET_SHARED_LIBRARY_FILES); - } catch (RemoteException e) { - throw new AssertionError(e); - } - - if (ai == null) { - return null; - } - - return ai.sharedLibraryFiles; - } - - /** - * Combines two arrays (of library names) such that they are - * concatenated in order but are devoid of duplicates. The - * result is a single string with the names of the libraries - * separated by colons, or <code>null</code> if both lists - * were <code>null</code> or empty. - * - * @param list1 null-ok; the first list - * @param list2 null-ok; the second list - * @return null-ok; the combination - */ - private static String combineLibs(String[] list1, String[] list2) { - StringBuilder result = new StringBuilder(300); - boolean first = true; - - if (list1 != null) { - for (String s : list1) { - if (first) { - first = false; - } else { - result.append(':'); - } - result.append(s); - } - } - - // Only need to check for duplicates if list1 was non-empty. - boolean dupCheck = !first; - - if (list2 != null) { - for (String s : list2) { - if (dupCheck && ArrayUtils.contains(list1, s)) { - continue; - } - - if (first) { - first = false; - } else { - result.append(':'); - } - result.append(s); - } - } - - return result.toString(); - } - - public ClassLoader getClassLoader() { - synchronized (this) { - if (mClassLoader != null) { - return mClassLoader; - } - - if (mIncludeCode && !mPackageName.equals("android")) { - String zip = mAppDir; - - /* - * The following is a bit of a hack to inject - * instrumentation into the system: If the app - * being started matches one of the instrumentation names, - * then we combine both the "instrumentation" and - * "instrumented" app into the path, along with the - * concatenation of both apps' shared library lists. - */ - - String instrumentationAppDir = - mActivityThread.mInstrumentationAppDir; - String instrumentationAppPackage = - mActivityThread.mInstrumentationAppPackage; - String instrumentedAppDir = - mActivityThread.mInstrumentedAppDir; - String[] instrumentationLibs = null; - - if (mAppDir.equals(instrumentationAppDir) - || mAppDir.equals(instrumentedAppDir)) { - zip = instrumentationAppDir + ":" + instrumentedAppDir; - if (! instrumentedAppDir.equals(instrumentationAppDir)) { - instrumentationLibs = - getLibrariesFor(instrumentationAppPackage); - } - } - - if ((mSharedLibraries != null) || - (instrumentationLibs != null)) { - zip = - combineLibs(mSharedLibraries, instrumentationLibs) - + ':' + zip; - } - - /* - * With all the combination done (if necessary, actually - * create the class loader. - */ - - if (localLOGV) Slog.v(TAG, "Class path: " + zip); - - mClassLoader = - ApplicationLoaders.getDefault().getClassLoader( - zip, mDataDir, mBaseClassLoader); - initializeJavaContextClassLoader(); - } else { - if (mBaseClassLoader == null) { - mClassLoader = ClassLoader.getSystemClassLoader(); - } else { - mClassLoader = mBaseClassLoader; - } - } - return mClassLoader; - } - } - - /** - * Setup value for Thread.getContextClassLoader(). If the - * package will not run in in a VM with other packages, we set - * the Java context ClassLoader to the - * PackageInfo.getClassLoader value. However, if this VM can - * contain multiple packages, we intead set the Java context - * ClassLoader to a proxy that will warn about the use of Java - * context ClassLoaders and then fall through to use the - * system ClassLoader. - * - * <p> Note that this is similar to but not the same as the - * android.content.Context.getClassLoader(). While both - * context class loaders are typically set to the - * PathClassLoader used to load the package archive in the - * single application per VM case, a single Android process - * may contain several Contexts executing on one thread with - * their own logical ClassLoaders while the Java context - * ClassLoader is a thread local. This is why in the case when - * we have multiple packages per VM we do not set the Java - * context ClassLoader to an arbitrary but instead warn the - * user to set their own if we detect that they are using a - * Java library that expects it to be set. - */ - private void initializeJavaContextClassLoader() { - IPackageManager pm = getPackageManager(); - android.content.pm.PackageInfo pi; - try { - pi = pm.getPackageInfo(mPackageName, 0); - } catch (RemoteException e) { - throw new AssertionError(e); - } - /* - * Two possible indications that this package could be - * sharing its virtual machine with other packages: - * - * 1.) the sharedUserId attribute is set in the manifest, - * indicating a request to share a VM with other - * packages with the same sharedUserId. - * - * 2.) the application element of the manifest has an - * attribute specifying a non-default process name, - * indicating the desire to run in another packages VM. - */ - boolean sharedUserIdSet = (pi.sharedUserId != null); - boolean processNameNotDefault = - (pi.applicationInfo != null && - !mPackageName.equals(pi.applicationInfo.processName)); - boolean sharable = (sharedUserIdSet || processNameNotDefault); - ClassLoader contextClassLoader = - (sharable) - ? new WarningContextClassLoader() - : mClassLoader; - Thread.currentThread().setContextClassLoader(contextClassLoader); - } - - private static class WarningContextClassLoader extends ClassLoader { - - private static boolean warned = false; - - private void warn(String methodName) { - if (warned) { - return; - } - warned = true; - Thread.currentThread().setContextClassLoader(getParent()); - Slog.w(TAG, "ClassLoader." + methodName + ": " + - "The class loader returned by " + - "Thread.getContextClassLoader() may fail for processes " + - "that host multiple applications. You should explicitly " + - "specify a context class loader. For example: " + - "Thread.setContextClassLoader(getClass().getClassLoader());"); - } - - @Override public URL getResource(String resName) { - warn("getResource"); - return getParent().getResource(resName); - } - - @Override public Enumeration<URL> getResources(String resName) throws IOException { - warn("getResources"); - return getParent().getResources(resName); - } - - @Override public InputStream getResourceAsStream(String resName) { - warn("getResourceAsStream"); - return getParent().getResourceAsStream(resName); - } - - @Override public Class<?> loadClass(String className) throws ClassNotFoundException { - warn("loadClass"); - return getParent().loadClass(className); - } - - @Override public void setClassAssertionStatus(String cname, boolean enable) { - warn("setClassAssertionStatus"); - getParent().setClassAssertionStatus(cname, enable); - } - - @Override public void setPackageAssertionStatus(String pname, boolean enable) { - warn("setPackageAssertionStatus"); - getParent().setPackageAssertionStatus(pname, enable); - } - - @Override public void setDefaultAssertionStatus(boolean enable) { - warn("setDefaultAssertionStatus"); - getParent().setDefaultAssertionStatus(enable); - } - - @Override public void clearAssertionStatus() { - warn("clearAssertionStatus"); - getParent().clearAssertionStatus(); - } - } - - public String getAppDir() { - return mAppDir; - } - - public String getResDir() { - return mResDir; - } - - public String getDataDir() { - return mDataDir; - } - - public File getDataDirFile() { - return mDataDirFile; - } - - public AssetManager getAssets(ActivityThread mainThread) { - return getResources(mainThread).getAssets(); - } - - public Resources getResources(ActivityThread mainThread) { - if (mResources == null) { - mResources = mainThread.getTopLevelResources(mResDir, this); - } - return mResources; - } - - public Application makeApplication(boolean forceDefaultAppClass, - Instrumentation instrumentation) { - if (mApplication != null) { - return mApplication; - } - - Application app = null; - - String appClass = mApplicationInfo.className; - if (forceDefaultAppClass || (appClass == null)) { - appClass = "android.app.Application"; - } - - try { - java.lang.ClassLoader cl = getClassLoader(); - ContextImpl appContext = new ContextImpl(); - appContext.init(this, null, mActivityThread); - app = mActivityThread.mInstrumentation.newApplication( - cl, appClass, appContext); - appContext.setOuterContext(app); - } catch (Exception e) { - if (!mActivityThread.mInstrumentation.onException(app, e)) { - throw new RuntimeException( - "Unable to instantiate application " + appClass - + ": " + e.toString(), e); - } - } - mActivityThread.mAllApplications.add(app); - mApplication = app; - - if (instrumentation != null) { - try { - instrumentation.callApplicationOnCreate(app); - } catch (Exception e) { - if (!instrumentation.onException(app, e)) { - throw new RuntimeException( - "Unable to create application " + app.getClass().getName() - + ": " + e.toString(), e); - } - } - } - - return app; - } - - public void removeContextRegistrations(Context context, - String who, String what) { - HashMap<BroadcastReceiver, ReceiverDispatcher> rmap = - mReceivers.remove(context); - if (rmap != null) { - Iterator<ReceiverDispatcher> it = rmap.values().iterator(); - while (it.hasNext()) { - ReceiverDispatcher rd = it.next(); - IntentReceiverLeaked leak = new IntentReceiverLeaked( - what + " " + who + " has leaked IntentReceiver " - + rd.getIntentReceiver() + " that was " + - "originally registered here. Are you missing a " + - "call to unregisterReceiver()?"); - leak.setStackTrace(rd.getLocation().getStackTrace()); - Slog.e(TAG, leak.getMessage(), leak); - try { - ActivityManagerNative.getDefault().unregisterReceiver( - rd.getIIntentReceiver()); - } catch (RemoteException e) { - // system crashed, nothing we can do - } - } - } - mUnregisteredReceivers.remove(context); - //Slog.i(TAG, "Receiver registrations: " + mReceivers); - HashMap<ServiceConnection, ServiceDispatcher> smap = - mServices.remove(context); - if (smap != null) { - Iterator<ServiceDispatcher> it = smap.values().iterator(); - while (it.hasNext()) { - ServiceDispatcher sd = it.next(); - ServiceConnectionLeaked leak = new ServiceConnectionLeaked( - what + " " + who + " has leaked ServiceConnection " - + sd.getServiceConnection() + " that was originally bound here"); - leak.setStackTrace(sd.getLocation().getStackTrace()); - Slog.e(TAG, leak.getMessage(), leak); - try { - ActivityManagerNative.getDefault().unbindService( - sd.getIServiceConnection()); - } catch (RemoteException e) { - // system crashed, nothing we can do - } - sd.doForget(); - } - } - mUnboundServices.remove(context); - //Slog.i(TAG, "Service registrations: " + mServices); - } - - public IIntentReceiver getReceiverDispatcher(BroadcastReceiver r, - Context context, Handler handler, - Instrumentation instrumentation, boolean registered) { - synchronized (mReceivers) { - ReceiverDispatcher rd = null; - HashMap<BroadcastReceiver, ReceiverDispatcher> map = null; - if (registered) { - map = mReceivers.get(context); - if (map != null) { - rd = map.get(r); - } - } - if (rd == null) { - rd = new ReceiverDispatcher(r, context, handler, - instrumentation, registered); - if (registered) { - if (map == null) { - map = new HashMap<BroadcastReceiver, ReceiverDispatcher>(); - mReceivers.put(context, map); - } - map.put(r, rd); - } - } else { - rd.validate(context, handler); - } - return rd.getIIntentReceiver(); - } - } - - public IIntentReceiver forgetReceiverDispatcher(Context context, - BroadcastReceiver r) { - synchronized (mReceivers) { - HashMap<BroadcastReceiver, ReceiverDispatcher> map = mReceivers.get(context); - ReceiverDispatcher rd = null; - if (map != null) { - rd = map.get(r); - if (rd != null) { - map.remove(r); - if (map.size() == 0) { - mReceivers.remove(context); - } - if (r.getDebugUnregister()) { - HashMap<BroadcastReceiver, ReceiverDispatcher> holder - = mUnregisteredReceivers.get(context); - if (holder == null) { - holder = new HashMap<BroadcastReceiver, ReceiverDispatcher>(); - mUnregisteredReceivers.put(context, holder); - } - RuntimeException ex = new IllegalArgumentException( - "Originally unregistered here:"); - ex.fillInStackTrace(); - rd.setUnregisterLocation(ex); - holder.put(r, rd); - } - return rd.getIIntentReceiver(); - } - } - HashMap<BroadcastReceiver, ReceiverDispatcher> holder - = mUnregisteredReceivers.get(context); - if (holder != null) { - rd = holder.get(r); - if (rd != null) { - RuntimeException ex = rd.getUnregisterLocation(); - throw new IllegalArgumentException( - "Unregistering Receiver " + r - + " that was already unregistered", ex); - } - } - if (context == null) { - throw new IllegalStateException("Unbinding Receiver " + r - + " from Context that is no longer in use: " + context); - } else { - throw new IllegalArgumentException("Receiver not registered: " + r); - } - - } - } - - static final class ReceiverDispatcher { - - final static class InnerReceiver extends IIntentReceiver.Stub { - final WeakReference<ReceiverDispatcher> mDispatcher; - final ReceiverDispatcher mStrongRef; - - InnerReceiver(ReceiverDispatcher rd, boolean strong) { - mDispatcher = new WeakReference<ReceiverDispatcher>(rd); - mStrongRef = strong ? rd : null; - } - public void performReceive(Intent intent, int resultCode, - String data, Bundle extras, boolean ordered, boolean sticky) { - ReceiverDispatcher rd = mDispatcher.get(); - if (DEBUG_BROADCAST) { - int seq = intent.getIntExtra("seq", -1); - Slog.i(TAG, "Receiving broadcast " + intent.getAction() + " seq=" + seq - + " to " + (rd != null ? rd.mReceiver : null)); - } - if (rd != null) { - rd.performReceive(intent, resultCode, data, extras, - ordered, sticky); - } else { - // The activity manager dispatched a broadcast to a registered - // receiver in this process, but before it could be delivered the - // receiver was unregistered. Acknowledge the broadcast on its - // behalf so that the system's broadcast sequence can continue. - if (DEBUG_BROADCAST) Slog.i(TAG, - "Finishing broadcast to unregistered receiver"); - IActivityManager mgr = ActivityManagerNative.getDefault(); - try { - mgr.finishReceiver(this, resultCode, data, extras, false); - } catch (RemoteException e) { - Slog.w(TAG, "Couldn't finish broadcast to unregistered receiver"); - } - } - } - } - - final IIntentReceiver.Stub mIIntentReceiver; - final BroadcastReceiver mReceiver; - final Context mContext; - final Handler mActivityThread; - final Instrumentation mInstrumentation; - final boolean mRegistered; - final IntentReceiverLeaked mLocation; - RuntimeException mUnregisterLocation; - - final class Args implements Runnable { - private Intent mCurIntent; - private int mCurCode; - private String mCurData; - private Bundle mCurMap; - private boolean mCurOrdered; - private boolean mCurSticky; - - public void run() { - BroadcastReceiver receiver = mReceiver; - if (DEBUG_BROADCAST) { - int seq = mCurIntent.getIntExtra("seq", -1); - Slog.i(TAG, "Dispatching broadcast " + mCurIntent.getAction() - + " seq=" + seq + " to " + mReceiver); - Slog.i(TAG, " mRegistered=" + mRegistered - + " mCurOrdered=" + mCurOrdered); - } - - IActivityManager mgr = ActivityManagerNative.getDefault(); - Intent intent = mCurIntent; - mCurIntent = null; - - if (receiver == null) { - if (mRegistered && mCurOrdered) { - try { - if (DEBUG_BROADCAST) Slog.i(TAG, - "Finishing null broadcast to " + mReceiver); - mgr.finishReceiver(mIIntentReceiver, - mCurCode, mCurData, mCurMap, false); - } catch (RemoteException ex) { - } - } - return; - } - - try { - ClassLoader cl = mReceiver.getClass().getClassLoader(); - intent.setExtrasClassLoader(cl); - if (mCurMap != null) { - mCurMap.setClassLoader(cl); - } - receiver.setOrderedHint(true); - receiver.setResult(mCurCode, mCurData, mCurMap); - receiver.clearAbortBroadcast(); - receiver.setOrderedHint(mCurOrdered); - receiver.setInitialStickyHint(mCurSticky); - receiver.onReceive(mContext, intent); - } catch (Exception e) { - if (mRegistered && mCurOrdered) { - try { - if (DEBUG_BROADCAST) Slog.i(TAG, - "Finishing failed broadcast to " + mReceiver); - mgr.finishReceiver(mIIntentReceiver, - mCurCode, mCurData, mCurMap, false); - } catch (RemoteException ex) { - } - } - if (mInstrumentation == null || - !mInstrumentation.onException(mReceiver, e)) { - throw new RuntimeException( - "Error receiving broadcast " + intent - + " in " + mReceiver, e); - } - } - if (mRegistered && mCurOrdered) { - try { - if (DEBUG_BROADCAST) Slog.i(TAG, - "Finishing broadcast to " + mReceiver); - mgr.finishReceiver(mIIntentReceiver, - receiver.getResultCode(), - receiver.getResultData(), - receiver.getResultExtras(false), - receiver.getAbortBroadcast()); - } catch (RemoteException ex) { - } - } - } - } - - ReceiverDispatcher(BroadcastReceiver receiver, Context context, - Handler activityThread, Instrumentation instrumentation, - boolean registered) { - if (activityThread == null) { - throw new NullPointerException("Handler must not be null"); - } - - mIIntentReceiver = new InnerReceiver(this, !registered); - mReceiver = receiver; - mContext = context; - mActivityThread = activityThread; - mInstrumentation = instrumentation; - mRegistered = registered; - mLocation = new IntentReceiverLeaked(null); - mLocation.fillInStackTrace(); - } - - void validate(Context context, Handler activityThread) { - if (mContext != context) { - throw new IllegalStateException( - "Receiver " + mReceiver + - " registered with differing Context (was " + - mContext + " now " + context + ")"); - } - if (mActivityThread != activityThread) { - throw new IllegalStateException( - "Receiver " + mReceiver + - " registered with differing handler (was " + - mActivityThread + " now " + activityThread + ")"); - } - } - - IntentReceiverLeaked getLocation() { - return mLocation; - } - - BroadcastReceiver getIntentReceiver() { - return mReceiver; - } - - IIntentReceiver getIIntentReceiver() { - return mIIntentReceiver; - } - - void setUnregisterLocation(RuntimeException ex) { - mUnregisterLocation = ex; - } - - RuntimeException getUnregisterLocation() { - return mUnregisterLocation; - } - - public void performReceive(Intent intent, int resultCode, - String data, Bundle extras, boolean ordered, boolean sticky) { - if (DEBUG_BROADCAST) { - int seq = intent.getIntExtra("seq", -1); - Slog.i(TAG, "Enqueueing broadcast " + intent.getAction() + " seq=" + seq - + " to " + mReceiver); - } - Args args = new Args(); - args.mCurIntent = intent; - args.mCurCode = resultCode; - args.mCurData = data; - args.mCurMap = extras; - args.mCurOrdered = ordered; - args.mCurSticky = sticky; - if (!mActivityThread.post(args)) { - if (mRegistered && ordered) { - IActivityManager mgr = ActivityManagerNative.getDefault(); - try { - if (DEBUG_BROADCAST) Slog.i(TAG, - "Finishing sync broadcast to " + mReceiver); - mgr.finishReceiver(mIIntentReceiver, args.mCurCode, - args.mCurData, args.mCurMap, false); - } catch (RemoteException ex) { - } - } - } - } - - } - - public final IServiceConnection getServiceDispatcher(ServiceConnection c, - Context context, Handler handler, int flags) { - synchronized (mServices) { - ServiceDispatcher sd = null; - HashMap<ServiceConnection, ServiceDispatcher> map = mServices.get(context); - if (map != null) { - sd = map.get(c); - } - if (sd == null) { - sd = new ServiceDispatcher(c, context, handler, flags); - if (map == null) { - map = new HashMap<ServiceConnection, ServiceDispatcher>(); - mServices.put(context, map); - } - map.put(c, sd); - } else { - sd.validate(context, handler); - } - return sd.getIServiceConnection(); - } - } - - public final IServiceConnection forgetServiceDispatcher(Context context, - ServiceConnection c) { - synchronized (mServices) { - HashMap<ServiceConnection, ServiceDispatcher> map - = mServices.get(context); - ServiceDispatcher sd = null; - if (map != null) { - sd = map.get(c); - if (sd != null) { - map.remove(c); - sd.doForget(); - if (map.size() == 0) { - mServices.remove(context); - } - if ((sd.getFlags()&Context.BIND_DEBUG_UNBIND) != 0) { - HashMap<ServiceConnection, ServiceDispatcher> holder - = mUnboundServices.get(context); - if (holder == null) { - holder = new HashMap<ServiceConnection, ServiceDispatcher>(); - mUnboundServices.put(context, holder); - } - RuntimeException ex = new IllegalArgumentException( - "Originally unbound here:"); - ex.fillInStackTrace(); - sd.setUnbindLocation(ex); - holder.put(c, sd); - } - return sd.getIServiceConnection(); - } - } - HashMap<ServiceConnection, ServiceDispatcher> holder - = mUnboundServices.get(context); - if (holder != null) { - sd = holder.get(c); - if (sd != null) { - RuntimeException ex = sd.getUnbindLocation(); - throw new IllegalArgumentException( - "Unbinding Service " + c - + " that was already unbound", ex); - } - } - if (context == null) { - throw new IllegalStateException("Unbinding Service " + c - + " from Context that is no longer in use: " + context); - } else { - throw new IllegalArgumentException("Service not registered: " + c); - } - } - } - - static final class ServiceDispatcher { - private final InnerConnection mIServiceConnection; - private final ServiceConnection mConnection; - private final Context mContext; - private final Handler mActivityThread; - private final ServiceConnectionLeaked mLocation; - private final int mFlags; - - private RuntimeException mUnbindLocation; - - private boolean mDied; - - private static class ConnectionInfo { - IBinder binder; - IBinder.DeathRecipient deathMonitor; - } - - private static class InnerConnection extends IServiceConnection.Stub { - final WeakReference<ServiceDispatcher> mDispatcher; - - InnerConnection(ServiceDispatcher sd) { - mDispatcher = new WeakReference<ServiceDispatcher>(sd); - } - - public void connected(ComponentName name, IBinder service) throws RemoteException { - ServiceDispatcher sd = mDispatcher.get(); - if (sd != null) { - sd.connected(name, service); - } - } - } - - private final HashMap<ComponentName, ConnectionInfo> mActiveConnections - = new HashMap<ComponentName, ConnectionInfo>(); - - ServiceDispatcher(ServiceConnection conn, - Context context, Handler activityThread, int flags) { - mIServiceConnection = new InnerConnection(this); - mConnection = conn; - mContext = context; - mActivityThread = activityThread; - mLocation = new ServiceConnectionLeaked(null); - mLocation.fillInStackTrace(); - mFlags = flags; - } - - void validate(Context context, Handler activityThread) { - if (mContext != context) { - throw new RuntimeException( - "ServiceConnection " + mConnection + - " registered with differing Context (was " + - mContext + " now " + context + ")"); - } - if (mActivityThread != activityThread) { - throw new RuntimeException( - "ServiceConnection " + mConnection + - " registered with differing handler (was " + - mActivityThread + " now " + activityThread + ")"); - } - } - - void doForget() { - synchronized(this) { - Iterator<ConnectionInfo> it = mActiveConnections.values().iterator(); - while (it.hasNext()) { - ConnectionInfo ci = it.next(); - ci.binder.unlinkToDeath(ci.deathMonitor, 0); - } - mActiveConnections.clear(); - } - } - - ServiceConnectionLeaked getLocation() { - return mLocation; - } - - ServiceConnection getServiceConnection() { - return mConnection; - } - - IServiceConnection getIServiceConnection() { - return mIServiceConnection; - } - - int getFlags() { - return mFlags; - } - - void setUnbindLocation(RuntimeException ex) { - mUnbindLocation = ex; - } - - RuntimeException getUnbindLocation() { - return mUnbindLocation; - } - - public void connected(ComponentName name, IBinder service) { - if (mActivityThread != null) { - mActivityThread.post(new RunConnection(name, service, 0)); - } else { - doConnected(name, service); - } - } - - public void death(ComponentName name, IBinder service) { - ConnectionInfo old; - - synchronized (this) { - mDied = true; - old = mActiveConnections.remove(name); - if (old == null || old.binder != service) { - // Death for someone different than who we last - // reported... just ignore it. - return; - } - old.binder.unlinkToDeath(old.deathMonitor, 0); - } - - if (mActivityThread != null) { - mActivityThread.post(new RunConnection(name, service, 1)); - } else { - doDeath(name, service); - } - } - - public void doConnected(ComponentName name, IBinder service) { - ConnectionInfo old; - ConnectionInfo info; - - synchronized (this) { - old = mActiveConnections.get(name); - if (old != null && old.binder == service) { - // Huh, already have this one. Oh well! - return; - } - - if (service != null) { - // A new service is being connected... set it all up. - mDied = false; - info = new ConnectionInfo(); - info.binder = service; - info.deathMonitor = new DeathMonitor(name, service); - try { - service.linkToDeath(info.deathMonitor, 0); - mActiveConnections.put(name, info); - } catch (RemoteException e) { - // This service was dead before we got it... just - // don't do anything with it. - mActiveConnections.remove(name); - return; - } - - } else { - // The named service is being disconnected... clean up. - mActiveConnections.remove(name); - } - - if (old != null) { - old.binder.unlinkToDeath(old.deathMonitor, 0); - } - } - - // If there was an old service, it is not disconnected. - if (old != null) { - mConnection.onServiceDisconnected(name); - } - // If there is a new service, it is now connected. - if (service != null) { - mConnection.onServiceConnected(name, service); - } - } - - public void doDeath(ComponentName name, IBinder service) { - mConnection.onServiceDisconnected(name); - } - - private final class RunConnection implements Runnable { - RunConnection(ComponentName name, IBinder service, int command) { - mName = name; - mService = service; - mCommand = command; - } - - public void run() { - if (mCommand == 0) { - doConnected(mName, mService); - } else if (mCommand == 1) { - doDeath(mName, mService); - } - } + static IPackageManager sPackageManager; - final ComponentName mName; - final IBinder mService; - final int mCommand; - } + final ApplicationThread mAppThread = new ApplicationThread(); + final Looper mLooper = Looper.myLooper(); + final H mH = new H(); + final HashMap<IBinder, ActivityClientRecord> mActivities + = new HashMap<IBinder, ActivityClientRecord>(); + // List of new activities (via ActivityRecord.nextIdle) that should + // be reported when next we idle. + ActivityClientRecord mNewActivities = null; + // Number of activities that are currently visible on-screen. + int mNumVisibleActivities = 0; + final HashMap<IBinder, Service> mServices + = new HashMap<IBinder, Service>(); + AppBindData mBoundApplication; + Configuration mConfiguration; + Configuration mResConfiguration; + Application mInitialApplication; + final ArrayList<Application> mAllApplications + = new ArrayList<Application>(); + // set of instantiated backup agents, keyed by package name + final HashMap<String, BackupAgent> mBackupAgents = new HashMap<String, BackupAgent>(); + static final ThreadLocal<ActivityThread> sThreadLocal = new ThreadLocal(); + Instrumentation mInstrumentation; + String mInstrumentationAppDir = null; + String mInstrumentationAppPackage = null; + String mInstrumentedAppDir = null; + boolean mSystemThread = false; + boolean mJitEnabled = false; - private final class DeathMonitor implements IBinder.DeathRecipient - { - DeathMonitor(ComponentName name, IBinder service) { - mName = name; - mService = service; - } + // These can be accessed by multiple threads; mPackages is the lock. + // XXX For now we keep around information about all packages we have + // seen, not removing entries from this map. + final HashMap<String, WeakReference<LoadedApk>> mPackages + = new HashMap<String, WeakReference<LoadedApk>>(); + final HashMap<String, WeakReference<LoadedApk>> mResourcePackages + = new HashMap<String, WeakReference<LoadedApk>>(); + Display mDisplay = null; + DisplayMetrics mDisplayMetrics = null; + final HashMap<ResourcesKey, WeakReference<Resources> > mActiveResources + = new HashMap<ResourcesKey, WeakReference<Resources> >(); + final ArrayList<ActivityClientRecord> mRelaunchingActivities + = new ArrayList<ActivityClientRecord>(); + Configuration mPendingConfiguration = null; - public void binderDied() { - death(mName, mService); - } + // The lock of mProviderMap protects the following variables. + final HashMap<String, ProviderClientRecord> mProviderMap + = new HashMap<String, ProviderClientRecord>(); + final HashMap<IBinder, ProviderRefCount> mProviderRefCountMap + = new HashMap<IBinder, ProviderRefCount>(); + final HashMap<IBinder, ProviderClientRecord> mLocalProviders + = new HashMap<IBinder, ProviderClientRecord>(); - final ComponentName mName; - final IBinder mService; - } - } - } + final GcIdler mGcIdler = new GcIdler(); + boolean mGcIdlerScheduled = false; - private static ContextImpl mSystemContext = null; + static Handler sMainThreadHandler; // set once in main() - private static final class ActivityRecord { + private static final class ActivityClientRecord { IBinder token; int ident; Intent intent; @@ -1307,10 +204,10 @@ public final class ActivityThread { boolean hideForNow; Configuration newConfig; Configuration createdConfig; - ActivityRecord nextIdle; + ActivityClientRecord nextIdle; ActivityInfo activityInfo; - PackageInfo packageInfo; + LoadedApk packageInfo; List<ResultInfo> pendingResults; List<Intent> pendingIntents; @@ -1318,7 +215,7 @@ public final class ActivityThread { boolean startsNotResumed; boolean isForward; - ActivityRecord() { + ActivityClientRecord() { parent = null; embeddedID = null; paused = false; @@ -1337,12 +234,12 @@ public final class ActivityThread { } } - private final class ProviderRecord implements IBinder.DeathRecipient { + private final class ProviderClientRecord implements IBinder.DeathRecipient { final String mName; final IContentProvider mProvider; final ContentProvider mLocalProvider; - ProviderRecord(String name, IContentProvider provider, + ProviderClientRecord(String name, IContentProvider provider, ContentProvider localProvider) { mName = name; mProvider = provider; @@ -1419,7 +316,7 @@ public final class ActivityThread { } private static final class AppBindData { - PackageInfo info; + LoadedApk info; String processName; ApplicationInfo appInfo; List<ProviderInfo> providers; @@ -1509,7 +406,7 @@ public final class ActivityThread { public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident, ActivityInfo info, Bundle state, List<ResultInfo> pendingResults, List<Intent> pendingNewIntents, boolean notResumed, boolean isForward) { - ActivityRecord r = new ActivityRecord(); + ActivityClientRecord r = new ActivityClientRecord(); r.token = token; r.ident = ident; @@ -1529,7 +426,7 @@ public final class ActivityThread { public final void scheduleRelaunchActivity(IBinder token, List<ResultInfo> pendingResults, List<Intent> pendingNewIntents, int configChanges, boolean notResumed, Configuration config) { - ActivityRecord r = new ActivityRecord(); + ActivityClientRecord r = new ActivityClientRecord(); r.token = token; r.pendingResults = pendingResults; @@ -1722,14 +619,6 @@ public final class ActivityThread { queueOrSendMessage(H.ACTIVITY_CONFIGURATION_CHANGED, token); } - public void requestPss() { - try { - ActivityManagerNative.getDefault().reportPss(this, - (int)Process.getPss(Process.myPid())); - } catch (RemoteException e) { - } - } - public void profilerControl(boolean start, String path, ParcelFileDescriptor fd) { ProfilerControlData pcd = new ProfilerControlData(); pcd.path = path; @@ -1756,6 +645,10 @@ public final class ActivityThread { public void dispatchPackageBroadcast(int cmd, String[] packages) { queueOrSendMessage(H.DISPATCH_PACKAGE_BROADCAST, packages, cmd); } + + public void scheduleCrash(String msg) { + queueOrSendMessage(H.SCHEDULE_CRASH, msg); + } @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { @@ -1944,10 +837,6 @@ public final class ActivityThread { } private final class H extends Handler { - private H() { - SamplingProfiler.getInstance().setEventThread(mLooper.getThread()); - } - public static final int LAUNCH_ACTIVITY = 100; public static final int PAUSE_ACTIVITY = 101; public static final int PAUSE_ACTIVITY_FINISHING= 102; @@ -1982,6 +871,7 @@ public final class ActivityThread { public static final int REMOVE_PROVIDER = 131; public static final int ENABLE_JIT = 132; public static final int DISPATCH_PACKAGE_BROADCAST = 133; + public static final int SCHEDULE_CRASH = 134; String codeToString(int code) { if (localLOGV) { switch (code) { @@ -2019,6 +909,7 @@ public final class ActivityThread { case REMOVE_PROVIDER: return "REMOVE_PROVIDER"; case ENABLE_JIT: return "ENABLE_JIT"; case DISPATCH_PACKAGE_BROADCAST: return "DISPATCH_PACKAGE_BROADCAST"; + case SCHEDULE_CRASH: return "SCHEDULE_CRASH"; } } return "(unknown)"; @@ -2026,14 +917,14 @@ public final class ActivityThread { public void handleMessage(Message msg) { switch (msg.what) { case LAUNCH_ACTIVITY: { - ActivityRecord r = (ActivityRecord)msg.obj; + ActivityClientRecord r = (ActivityClientRecord)msg.obj; r.packageInfo = getPackageInfoNoCheck( r.activityInfo.applicationInfo); handleLaunchActivity(r, null); } break; case RELAUNCH_ACTIVITY: { - ActivityRecord r = (ActivityRecord)msg.obj; + ActivityClientRecord r = (ActivityClientRecord)msg.obj; handleRelaunchActivity(r, msg.arg1); } break; case PAUSE_ACTIVITY: @@ -2142,6 +1033,8 @@ public final class ActivityThread { case DISPATCH_PACKAGE_BROADCAST: handleDispatchPackageBroadcast(msg.arg1, (String[])msg.obj); break; + case SCHEDULE_CRASH: + throw new RemoteServiceException((String)msg.obj); } } @@ -2155,11 +1048,11 @@ public final class ActivityThread { private final class Idler implements MessageQueue.IdleHandler { public final boolean queueIdle() { - ActivityRecord a = mNewActivities; + ActivityClientRecord a = mNewActivities; if (a != null) { mNewActivities = null; IActivityManager am = ActivityManagerNative.getDefault(); - ActivityRecord prev; + ActivityClientRecord prev; do { if (localLOGV) Slog.v( TAG, "Reporting idle of " + a + @@ -2215,71 +1108,132 @@ public final class ActivityThread { } } - static IPackageManager sPackageManager; + public static final ActivityThread currentActivityThread() { + return sThreadLocal.get(); + } - final ApplicationThread mAppThread = new ApplicationThread(); - final Looper mLooper = Looper.myLooper(); - final H mH = new H(); - final HashMap<IBinder, ActivityRecord> mActivities - = new HashMap<IBinder, ActivityRecord>(); - // List of new activities (via ActivityRecord.nextIdle) that should - // be reported when next we idle. - ActivityRecord mNewActivities = null; - // Number of activities that are currently visible on-screen. - int mNumVisibleActivities = 0; - final HashMap<IBinder, Service> mServices - = new HashMap<IBinder, Service>(); - AppBindData mBoundApplication; - Configuration mConfiguration; - Configuration mResConfiguration; - Application mInitialApplication; - final ArrayList<Application> mAllApplications - = new ArrayList<Application>(); - // set of instantiated backup agents, keyed by package name - final HashMap<String, BackupAgent> mBackupAgents = new HashMap<String, BackupAgent>(); - static final ThreadLocal sThreadLocal = new ThreadLocal(); - Instrumentation mInstrumentation; - String mInstrumentationAppDir = null; - String mInstrumentationAppPackage = null; - String mInstrumentedAppDir = null; - boolean mSystemThread = false; - boolean mJitEnabled = false; + public static final String currentPackageName() { + ActivityThread am = currentActivityThread(); + return (am != null && am.mBoundApplication != null) + ? am.mBoundApplication.processName : null; + } - // These can be accessed by multiple threads; mPackages is the lock. - // XXX For now we keep around information about all packages we have - // seen, not removing entries from this map. - final HashMap<String, WeakReference<PackageInfo>> mPackages - = new HashMap<String, WeakReference<PackageInfo>>(); - final HashMap<String, WeakReference<PackageInfo>> mResourcePackages - = new HashMap<String, WeakReference<PackageInfo>>(); - Display mDisplay = null; - DisplayMetrics mDisplayMetrics = null; - final HashMap<ResourcesKey, WeakReference<Resources> > mActiveResources - = new HashMap<ResourcesKey, WeakReference<Resources> >(); - final ArrayList<ActivityRecord> mRelaunchingActivities - = new ArrayList<ActivityRecord>(); - Configuration mPendingConfiguration = null; + public static final Application currentApplication() { + ActivityThread am = currentActivityThread(); + return am != null ? am.mInitialApplication : null; + } - // The lock of mProviderMap protects the following variables. - final HashMap<String, ProviderRecord> mProviderMap - = new HashMap<String, ProviderRecord>(); - final HashMap<IBinder, ProviderRefCount> mProviderRefCountMap - = new HashMap<IBinder, ProviderRefCount>(); - final HashMap<IBinder, ProviderRecord> mLocalProviders - = new HashMap<IBinder, ProviderRecord>(); + public static IPackageManager getPackageManager() { + if (sPackageManager != null) { + //Slog.v("PackageManager", "returning cur default = " + sPackageManager); + return sPackageManager; + } + IBinder b = ServiceManager.getService("package"); + //Slog.v("PackageManager", "default service binder = " + b); + sPackageManager = IPackageManager.Stub.asInterface(b); + //Slog.v("PackageManager", "default service = " + sPackageManager); + return sPackageManager; + } - final GcIdler mGcIdler = new GcIdler(); - boolean mGcIdlerScheduled = false; + DisplayMetrics getDisplayMetricsLocked(boolean forceUpdate) { + if (mDisplayMetrics != null && !forceUpdate) { + return mDisplayMetrics; + } + if (mDisplay == null) { + WindowManager wm = WindowManagerImpl.getDefault(); + mDisplay = wm.getDefaultDisplay(); + } + DisplayMetrics metrics = mDisplayMetrics = new DisplayMetrics(); + mDisplay.getMetrics(metrics); + //Slog.i("foo", "New metrics: w=" + metrics.widthPixels + " h=" + // + metrics.heightPixels + " den=" + metrics.density + // + " xdpi=" + metrics.xdpi + " ydpi=" + metrics.ydpi); + return metrics; + } - public final PackageInfo getPackageInfo(String packageName, int flags) { + /** + * Creates the top level Resources for applications with the given compatibility info. + * + * @param resDir the resource directory. + * @param compInfo the compability info. It will use the default compatibility info when it's + * null. + */ + Resources getTopLevelResources(String resDir, CompatibilityInfo compInfo) { + ResourcesKey key = new ResourcesKey(resDir, compInfo.applicationScale); + Resources r; synchronized (mPackages) { - WeakReference<PackageInfo> ref; + // Resources is app scale dependent. + if (false) { + Slog.w(TAG, "getTopLevelResources: " + resDir + " / " + + compInfo.applicationScale); + } + WeakReference<Resources> wr = mActiveResources.get(key); + r = wr != null ? wr.get() : null; + //if (r != null) Slog.i(TAG, "isUpToDate " + resDir + ": " + r.getAssets().isUpToDate()); + if (r != null && r.getAssets().isUpToDate()) { + if (false) { + Slog.w(TAG, "Returning cached resources " + r + " " + resDir + + ": appScale=" + r.getCompatibilityInfo().applicationScale); + } + return r; + } + } + + //if (r != null) { + // Slog.w(TAG, "Throwing away out-of-date resources!!!! " + // + r + " " + resDir); + //} + + AssetManager assets = new AssetManager(); + if (assets.addAssetPath(resDir) == 0) { + return null; + } + + //Slog.i(TAG, "Resource: key=" + key + ", display metrics=" + metrics); + DisplayMetrics metrics = getDisplayMetricsLocked(false); + r = new Resources(assets, metrics, getConfiguration(), compInfo); + if (false) { + Slog.i(TAG, "Created app resources " + resDir + " " + r + ": " + + r.getConfiguration() + " appScale=" + + r.getCompatibilityInfo().applicationScale); + } + + synchronized (mPackages) { + WeakReference<Resources> wr = mActiveResources.get(key); + Resources existing = wr != null ? wr.get() : null; + if (existing != null && existing.getAssets().isUpToDate()) { + // Someone else already created the resources while we were + // unlocked; go ahead and use theirs. + r.getAssets().close(); + return existing; + } + + // XXX need to remove entries when weak references go away + mActiveResources.put(key, new WeakReference<Resources>(r)); + return r; + } + } + + /** + * Creates the top level resources for the given package. + */ + Resources getTopLevelResources(String resDir, LoadedApk pkgInfo) { + return getTopLevelResources(resDir, pkgInfo.mCompatibilityInfo); + } + + final Handler getHandler() { + return mH; + } + + public final LoadedApk getPackageInfo(String packageName, int flags) { + synchronized (mPackages) { + WeakReference<LoadedApk> ref; if ((flags&Context.CONTEXT_INCLUDE_CODE) != 0) { ref = mPackages.get(packageName); } else { ref = mResourcePackages.get(packageName); } - PackageInfo packageInfo = ref != null ? ref.get() : null; + LoadedApk packageInfo = ref != null ? ref.get() : null; //Slog.i(TAG, "getPackageInfo " + packageName + ": " + packageInfo); //if (packageInfo != null) Slog.i(TAG, "isUptoDate " + packageInfo.mResDir // + ": " + packageInfo.mResources.getAssets().isUpToDate()); @@ -2311,7 +1265,7 @@ public final class ActivityThread { return null; } - public final PackageInfo getPackageInfo(ApplicationInfo ai, int flags) { + public final LoadedApk getPackageInfo(ApplicationInfo ai, int flags) { boolean includeCode = (flags&Context.CONTEXT_INCLUDE_CODE) != 0; boolean securityViolation = includeCode && ai.uid != 0 && ai.uid != Process.SYSTEM_UID && (mBoundApplication != null @@ -2333,20 +1287,20 @@ public final class ActivityThread { return getPackageInfo(ai, null, securityViolation, includeCode); } - public final PackageInfo getPackageInfoNoCheck(ApplicationInfo ai) { + public final LoadedApk getPackageInfoNoCheck(ApplicationInfo ai) { return getPackageInfo(ai, null, false, true); } - private final PackageInfo getPackageInfo(ApplicationInfo aInfo, + private final LoadedApk getPackageInfo(ApplicationInfo aInfo, ClassLoader baseLoader, boolean securityViolation, boolean includeCode) { synchronized (mPackages) { - WeakReference<PackageInfo> ref; + WeakReference<LoadedApk> ref; if (includeCode) { ref = mPackages.get(aInfo.packageName); } else { ref = mResourcePackages.get(aInfo.packageName); } - PackageInfo packageInfo = ref != null ? ref.get() : null; + LoadedApk packageInfo = ref != null ? ref.get() : null; if (packageInfo == null || (packageInfo.mResources != null && !packageInfo.mResources.getAssets().isUpToDate())) { if (localLOGV) Slog.v(TAG, (includeCode ? "Loading code package " @@ -2355,15 +1309,15 @@ public final class ActivityThread { ? mBoundApplication.processName : null) + ")"); packageInfo = - new PackageInfo(this, aInfo, this, baseLoader, + new LoadedApk(this, aInfo, this, baseLoader, securityViolation, includeCode && (aInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0); if (includeCode) { mPackages.put(aInfo.packageName, - new WeakReference<PackageInfo>(packageInfo)); + new WeakReference<LoadedApk>(packageInfo)); } else { mResourcePackages.put(aInfo.packageName, - new WeakReference<PackageInfo>(packageInfo)); + new WeakReference<LoadedApk>(packageInfo)); } } return packageInfo; @@ -2412,7 +1366,7 @@ public final class ActivityThread { if (mSystemContext == null) { ContextImpl context = ContextImpl.createSystemContext(this); - PackageInfo info = new PackageInfo(this, "android", context, null); + LoadedApk info = new LoadedApk(this, "android", context, null); context.init(info, null, this); context.getResources().updateConfiguration( getConfiguration(), getDisplayMetricsLocked(false)); @@ -2427,7 +1381,7 @@ public final class ActivityThread { public void installSystemApplicationInfo(ApplicationInfo info) { synchronized (this) { ContextImpl context = getSystemContext(); - context.init(new PackageInfo(this, "android", context, info), null, this); + context.init(new LoadedApk(this, "android", context, info), null, this); } } @@ -2479,7 +1433,7 @@ public final class ActivityThread { public final Activity startActivityNow(Activity parent, String id, Intent intent, ActivityInfo activityInfo, IBinder token, Bundle state, Object lastNonConfigurationInstance) { - ActivityRecord r = new ActivityRecord(); + ActivityClientRecord r = new ActivityClientRecord(); r.token = token; r.ident = 0; r.intent = intent; @@ -2550,7 +1504,7 @@ public final class ActivityThread { queueOrSendMessage(H.CLEAN_UP_CONTEXT, cci); } - private final Activity performLaunchActivity(ActivityRecord r, Intent customIntent) { + private final Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) { // System.out.println("##### [" + System.currentTimeMillis() + "] ActivityThread.performLaunchActivity(" + r + ")"); ActivityInfo aInfo = r.activityInfo; @@ -2669,7 +1623,7 @@ public final class ActivityThread { return activity; } - private final void handleLaunchActivity(ActivityRecord r, Intent customIntent) { + private final void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) { // If we are getting ready to gc after going to the background, well // we are back active so skip it. unscheduleGcIdler(); @@ -2729,7 +1683,7 @@ public final class ActivityThread { } } - private final void deliverNewIntents(ActivityRecord r, + private final void deliverNewIntents(ActivityClientRecord r, List<Intent> intents) { final int N = intents.size(); for (int i=0; i<N; i++) { @@ -2741,7 +1695,7 @@ public final class ActivityThread { public final void performNewIntents(IBinder token, List<Intent> intents) { - ActivityRecord r = mActivities.get(token); + ActivityClientRecord r = mActivities.get(token); if (r != null) { final boolean resumed = !r.paused; if (resumed) { @@ -2765,7 +1719,7 @@ public final class ActivityThread { String component = data.intent.getComponent().getClassName(); - PackageInfo packageInfo = getPackageInfoNoCheck( + LoadedApk packageInfo = getPackageInfoNoCheck( data.info.applicationInfo); IActivityManager mgr = ActivityManagerNative.getDefault(); @@ -2824,6 +1778,8 @@ public final class ActivityThread { } } + QueuedWork.waitToFinish(); + try { if (data.sync) { if (DEBUG_BROADCAST) Slog.i(TAG, @@ -2849,7 +1805,7 @@ public final class ActivityThread { unscheduleGcIdler(); // instantiate the BackupAgent class named in the manifest - PackageInfo packageInfo = getPackageInfoNoCheck(data.appInfo); + LoadedApk packageInfo = getPackageInfoNoCheck(data.appInfo); String packageName = packageInfo.mPackageName; if (mBackupAgents.get(packageName) != null) { Slog.d(TAG, "BackupAgent " + " for " + packageName @@ -2911,7 +1867,7 @@ public final class ActivityThread { private final void handleDestroyBackupAgent(CreateBackupAgentData data) { if (DEBUG_BACKUP) Slog.v(TAG, "handleDestroyBackupAgent: " + data); - PackageInfo packageInfo = getPackageInfoNoCheck(data.appInfo); + LoadedApk packageInfo = getPackageInfoNoCheck(data.appInfo); String packageName = packageInfo.mPackageName; BackupAgent agent = mBackupAgents.get(packageName); if (agent != null) { @@ -2932,7 +1888,7 @@ public final class ActivityThread { // we are back active so skip it. unscheduleGcIdler(); - PackageInfo packageInfo = getPackageInfoNoCheck( + LoadedApk packageInfo = getPackageInfoNoCheck( data.info.applicationInfo); Service service = null; try { @@ -3051,6 +2007,9 @@ public final class ActivityThread { data.args.setExtrasClassLoader(s.getClassLoader()); } int res = s.onStartCommand(data.args, data.flags, data.startId); + + QueuedWork.waitToFinish(); + try { ActivityManagerNative.getDefault().serviceDoneExecuting( data.token, 1, data.startId, res); @@ -3079,6 +2038,9 @@ public final class ActivityThread { final String who = s.getClassName(); ((ContextImpl) context).scheduleFinalCleanup(who, "Service"); } + + QueuedWork.waitToFinish(); + try { ActivityManagerNative.getDefault().serviceDoneExecuting( token, 0, 0, 0); @@ -3096,9 +2058,9 @@ public final class ActivityThread { //Slog.i(TAG, "Running services: " + mServices); } - public final ActivityRecord performResumeActivity(IBinder token, + public final ActivityClientRecord performResumeActivity(IBinder token, boolean clearHide) { - ActivityRecord r = mActivities.get(token); + ActivityClientRecord r = mActivities.get(token); if (localLOGV) Slog.v(TAG, "Performing resume of " + r + " finished=" + r.activity.mFinished); if (r != null && !r.activity.mFinished) { @@ -3140,7 +2102,7 @@ public final class ActivityThread { // we are back active so skip it. unscheduleGcIdler(); - ActivityRecord r = performResumeActivity(token, clearHide); + ActivityClientRecord r = performResumeActivity(token, clearHide); if (r != null) { final Activity a = r.activity; @@ -3239,7 +2201,7 @@ public final class ActivityThread { private int mThumbnailWidth = -1; private int mThumbnailHeight = -1; - private final Bitmap createThumbnailBitmap(ActivityRecord r) { + private final Bitmap createThumbnailBitmap(ActivityClientRecord r) { Bitmap thumbnail = null; try { int w = mThumbnailWidth; @@ -3255,13 +2217,24 @@ public final class ActivityThread { h = mThumbnailHeight; } - // XXX Only set hasAlpha if needed? - thumbnail = Bitmap.createBitmap(w, h, Bitmap.Config.RGB_565); - thumbnail.eraseColor(0); - Canvas cv = new Canvas(thumbnail); - if (!r.activity.onCreateThumbnail(thumbnail, cv)) { - thumbnail = null; + // On platforms where we don't want thumbnails, set dims to (0,0) + if ((w > 0) && (h > 0)) { + View topView = r.activity.getWindow().getDecorView(); + + // Maximize bitmap by capturing in native aspect. + if (topView.getWidth() >= topView.getHeight()) { + thumbnail = Bitmap.createBitmap(w, h, THUMBNAIL_FORMAT); + } else { + thumbnail = Bitmap.createBitmap(h, w, THUMBNAIL_FORMAT); + } + + thumbnail.eraseColor(0); + Canvas cv = new Canvas(thumbnail); + if (!r.activity.onCreateThumbnail(thumbnail, cv)) { + thumbnail = null; + } } + } catch (Exception e) { if (!mInstrumentation.onException(r.activity, e)) { throw new RuntimeException( @@ -3277,7 +2250,7 @@ public final class ActivityThread { private final void handlePauseActivity(IBinder token, boolean finished, boolean userLeaving, int configChanges) { - ActivityRecord r = mActivities.get(token); + ActivityClientRecord r = mActivities.get(token); if (r != null) { //Slog.v(TAG, "userLeaving=" + userLeaving + " handling pause of " + r); if (userLeaving) { @@ -3295,17 +2268,17 @@ public final class ActivityThread { } } - final void performUserLeavingActivity(ActivityRecord r) { + final void performUserLeavingActivity(ActivityClientRecord r) { mInstrumentation.callActivityOnUserLeaving(r.activity); } final Bundle performPauseActivity(IBinder token, boolean finished, boolean saveState) { - ActivityRecord r = mActivities.get(token); + ActivityClientRecord r = mActivities.get(token); return r != null ? performPauseActivity(r, finished, saveState) : null; } - final Bundle performPauseActivity(ActivityRecord r, boolean finished, + final Bundle performPauseActivity(ActivityClientRecord r, boolean finished, boolean saveState) { if (r.paused) { if (r.activity.mFinished) { @@ -3356,7 +2329,7 @@ public final class ActivityThread { } final void performStopActivity(IBinder token) { - ActivityRecord r = mActivities.get(token); + ActivityClientRecord r = mActivities.get(token); performStopActivityInner(r, null, false); } @@ -3372,7 +2345,7 @@ public final class ActivityThread { } } - private final void performStopActivityInner(ActivityRecord r, + private final void performStopActivityInner(ActivityClientRecord r, StopInfo info, boolean keepShown) { if (localLOGV) Slog.v(TAG, "Performing stop of " + r); if (r != null) { @@ -3392,7 +2365,7 @@ public final class ActivityThread { if (info != null) { try { // First create a thumbnail for the activity... - //info.thumbnail = createThumbnailBitmap(r); + info.thumbnail = createThumbnailBitmap(r); info.description = r.activity.onCreateDescription(); } catch (Exception e) { if (!mInstrumentation.onException(r.activity, e)) { @@ -3423,7 +2396,7 @@ public final class ActivityThread { } } - private final void updateVisibility(ActivityRecord r, boolean show) { + private final void updateVisibility(ActivityClientRecord r, boolean show) { View v = r.activity.mDecor; if (v != null) { if (show) { @@ -3451,7 +2424,7 @@ public final class ActivityThread { } private final void handleStopActivity(IBinder token, boolean show, int configChanges) { - ActivityRecord r = mActivities.get(token); + ActivityClientRecord r = mActivities.get(token); r.activity.mConfigChangeFlags |= configChanges; StopInfo info = new StopInfo(); @@ -3472,7 +2445,7 @@ public final class ActivityThread { } final void performRestartActivity(IBinder token) { - ActivityRecord r = mActivities.get(token); + ActivityClientRecord r = mActivities.get(token); if (r.stopped) { r.activity.performRestart(); r.stopped = false; @@ -3480,7 +2453,7 @@ public final class ActivityThread { } private final void handleWindowVisibility(IBinder token, boolean show) { - ActivityRecord r = mActivities.get(token); + ActivityClientRecord r = mActivities.get(token); if (!show && !r.stopped) { performStopActivityInner(r, null, show); } else if (show && r.stopped) { @@ -3498,7 +2471,7 @@ public final class ActivityThread { } } - private final void deliverResults(ActivityRecord r, List<ResultInfo> results) { + private final void deliverResults(ActivityClientRecord r, List<ResultInfo> results) { final int N = results.size(); for (int i=0; i<N; i++) { ResultInfo ri = results.get(i); @@ -3522,7 +2495,7 @@ public final class ActivityThread { } private final void handleSendResult(ResultData res) { - ActivityRecord r = mActivities.get(res.token); + ActivityClientRecord r = mActivities.get(res.token); if (DEBUG_RESULTS) Slog.v(TAG, "Handling send result to " + r); if (r != null) { final boolean resumed = !r.paused; @@ -3561,13 +2534,13 @@ public final class ActivityThread { } } - public final ActivityRecord performDestroyActivity(IBinder token, boolean finishing) { + public final ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing) { return performDestroyActivity(token, finishing, 0, false); } - private final ActivityRecord performDestroyActivity(IBinder token, boolean finishing, + private final ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing, int configChanges, boolean getNonConfigInstance) { - ActivityRecord r = mActivities.get(token); + ActivityClientRecord r = mActivities.get(token); if (localLOGV) Slog.v(TAG, "Performing finish of " + r); if (r != null) { r.activity.mConfigChangeFlags |= configChanges; @@ -3670,7 +2643,7 @@ public final class ActivityThread { private final void handleDestroyActivity(IBinder token, boolean finishing, int configChanges, boolean getNonConfigInstance) { - ActivityRecord r = performDestroyActivity(token, finishing, + ActivityClientRecord r = performDestroyActivity(token, finishing, configChanges, getNonConfigInstance); if (r != null) { WindowManager wm = r.activity.getWindowManager(); @@ -3711,7 +2684,7 @@ public final class ActivityThread { } } - private final void handleRelaunchActivity(ActivityRecord tmp, int configChanges) { + private final void handleRelaunchActivity(ActivityClientRecord tmp, int configChanges) { // If we are getting ready to gc after going to the background, well // we are back active so skip it. unscheduleGcIdler(); @@ -3730,7 +2703,7 @@ public final class ActivityThread { IBinder token = tmp.token; tmp = null; for (int i=0; i<N; i++) { - ActivityRecord r = mRelaunchingActivities.get(i); + ActivityClientRecord r = mRelaunchingActivities.get(i); if (r.token == token) { tmp = r; mRelaunchingActivities.remove(i); @@ -3772,7 +2745,7 @@ public final class ActivityThread { handleConfigurationChanged(changedConfig); } - ActivityRecord r = mActivities.get(tmp.token); + ActivityClientRecord r = mActivities.get(tmp.token); if (DEBUG_CONFIGURATION) Slog.v(TAG, "Handling relaunch of " + r); if (r == null) { return; @@ -3816,7 +2789,7 @@ public final class ActivityThread { } private final void handleRequestThumbnail(IBinder token) { - ActivityRecord r = mActivities.get(token); + ActivityClientRecord r = mActivities.get(token); Bitmap thumbnail = createThumbnailBitmap(r); CharSequence description = null; try { @@ -3843,9 +2816,9 @@ public final class ActivityThread { = new ArrayList<ComponentCallbacks>(); if (mActivities.size() > 0) { - Iterator<ActivityRecord> it = mActivities.values().iterator(); + Iterator<ActivityClientRecord> it = mActivities.values().iterator(); while (it.hasNext()) { - ActivityRecord ar = it.next(); + ActivityClientRecord ar = it.next(); Activity a = ar.activity; if (a != null) { if (!ar.activity.mFinished && (allActivities || @@ -3874,7 +2847,7 @@ public final class ActivityThread { } synchronized (mProviderMap) { if (mLocalProviders.size() > 0) { - Iterator<ProviderRecord> it = mLocalProviders.values().iterator(); + Iterator<ProviderClientRecord> it = mLocalProviders.values().iterator(); while (it.hasNext()) { callbacks.add(it.next().mLocalProvider); } @@ -4020,7 +2993,7 @@ public final class ActivityThread { } final void handleActivityConfigurationChanged(IBinder token) { - ActivityRecord r = mActivities.get(token); + ActivityClientRecord r = mActivities.get(token); if (r == null || r.activity == null) { return; } @@ -4057,7 +3030,7 @@ public final class ActivityThread { for (int i=packages.length-1; i>=0; i--) { //Slog.i(TAG, "Cleaning old package: " + packages[i]); if (!hasPkgInfo) { - WeakReference<PackageInfo> ref; + WeakReference<LoadedApk> ref; ref = mPackages.get(packages[i]); if (ref != null && ref.get() != null) { hasPkgInfo = true; @@ -4132,6 +3105,23 @@ public final class ActivityThread { data.info = getPackageInfoNoCheck(data.appInfo); /** + * For system applications on userdebug/eng builds, log stack + * traces of disk and network access to dropbox for analysis. + * + * Similar logic exists in SystemServer.java. + */ + if ((data.appInfo.flags & + (ApplicationInfo.FLAG_SYSTEM | + ApplicationInfo.FLAG_UPDATED_SYSTEM_APP)) != 0 && + !"user".equals(Build.TYPE)) { + StrictMode.setThreadPolicy( + StrictMode.DISALLOW_DISK_WRITE | + StrictMode.DISALLOW_DISK_READ | + StrictMode.DISALLOW_NETWORK | + StrictMode.PENALTY_DROPBOX); + } + + /** * Switch this process to density compatibility mode if needed. */ if ((data.appInfo.flags&ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES) @@ -4189,7 +3179,8 @@ public final class ActivityThread { instrApp.sourceDir = ii.sourceDir; instrApp.publicSourceDir = ii.publicSourceDir; instrApp.dataDir = ii.dataDir; - PackageInfo pi = getPackageInfo(instrApp, + instrApp.nativeLibraryDir = ii.nativeLibraryDir; + LoadedApk pi = getPackageInfo(instrApp, appContext.getClassLoader(), false, true); ContextImpl instrContext = new ContextImpl(); instrContext.init(pi, null, this); @@ -4300,7 +3291,7 @@ public final class ActivityThread { private final IContentProvider getProvider(Context context, String name) { synchronized(mProviderMap) { - final ProviderRecord pr = mProviderMap.get(name); + final ProviderClientRecord pr = mProviderMap.get(name); if (pr != null) { return pr.mProvider; } @@ -4316,10 +3307,6 @@ public final class ActivityThread { Slog.e(TAG, "Failed to find provider info for " + name); return null; } - if (holder.permissionFailure != null) { - throw new SecurityException("Permission " + holder.permissionFailure - + " required for provider " + name); - } IContentProvider prov = installProvider(context, holder.provider, holder.info, true); @@ -4411,9 +3398,9 @@ public final class ActivityThread { String name = null; // remove the provider from mProviderMap - Iterator<ProviderRecord> iter = mProviderMap.values().iterator(); + Iterator<ProviderClientRecord> iter = mProviderMap.values().iterator(); while (iter.hasNext()) { - ProviderRecord pr = iter.next(); + ProviderClientRecord pr = iter.next(); IBinder myBinder = pr.mProvider.asBinder(); if (myBinder == providerBinder) { //find if its published by this process itself @@ -4438,10 +3425,10 @@ public final class ActivityThread { final void removeDeadProvider(String name, IContentProvider provider) { synchronized(mProviderMap) { - ProviderRecord pr = mProviderMap.get(name); + ProviderClientRecord pr = mProviderMap.get(name); if (pr.mProvider.asBinder() == provider.asBinder()) { Slog.i(TAG, "Removing dead content provider: " + name); - ProviderRecord removed = mProviderMap.remove(name); + ProviderClientRecord removed = mProviderMap.remove(name); if (removed != null) { removed.mProvider.asBinder().unlinkToDeath(removed, 0); } @@ -4450,10 +3437,10 @@ public final class ActivityThread { } final void removeDeadProviderLocked(String name, IContentProvider provider) { - ProviderRecord pr = mProviderMap.get(name); + ProviderClientRecord pr = mProviderMap.get(name); if (pr.mProvider.asBinder() == provider.asBinder()) { Slog.i(TAG, "Removing dead content provider: " + name); - ProviderRecord removed = mProviderMap.remove(name); + ProviderClientRecord removed = mProviderMap.remove(name); if (removed != null) { removed.mProvider.asBinder().unlinkToDeath(removed, 0); } @@ -4521,7 +3508,7 @@ public final class ActivityThread { // Cache the pointer for the remote provider. String names[] = PATTERN_SEMICOLON.split(info.authority); for (int i=0; i<names.length; i++) { - ProviderRecord pr = new ProviderRecord(names[i], provider, + ProviderClientRecord pr = new ProviderClientRecord(names[i], provider, localProvider); try { provider.asBinder().linkToDeath(pr, 0); @@ -4532,7 +3519,7 @@ public final class ActivityThread { } if (localProvider != null) { mLocalProviders.put(provider.asBinder(), - new ProviderRecord(null, provider, localProvider)); + new ProviderClientRecord(null, provider, localProvider)); } } @@ -4620,6 +3607,9 @@ public final class ActivityThread { Process.setArgV0("<pre-initialized>"); Looper.prepareMainLooper(); + if (sMainThreadHandler == null) { + sMainThreadHandler = new Handler(); + } ActivityThread thread = new ActivityThread(); thread.attach(false); diff --git a/core/java/android/app/AppGlobals.java b/core/java/android/app/AppGlobals.java new file mode 100644 index 000000000000..9a391296bbdf --- /dev/null +++ b/core/java/android/app/AppGlobals.java @@ -0,0 +1,49 @@ +/* + * 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 android.app; + +import android.content.pm.IPackageManager; + +/** + * Special private access for certain globals related to a process. + * @hide + */ +public class AppGlobals { + /** + * Return the first Application object made in the process. + * NOTE: Only works on the main thread. + */ + public static Application getInitialApplication() { + return ActivityThread.currentApplication(); + } + + /** + * Return the package name of the first .apk loaded into the process. + * NOTE: Only works on the main thread. + */ + public static String getInitialPackage() { + return ActivityThread.currentPackageName(); + } + + /** + * Return the raw interface to the package manager. + * @return + */ + public static IPackageManager getPackageManager() { + return ActivityThread.getPackageManager(); + } +} diff --git a/core/java/android/app/ApplicationErrorReport.java b/core/java/android/app/ApplicationErrorReport.java index f0cef98751af..8f940d5d12b9 100644 --- a/core/java/android/app/ApplicationErrorReport.java +++ b/core/java/android/app/ApplicationErrorReport.java @@ -24,9 +24,11 @@ import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.os.Parcel; import android.os.Parcelable; +import android.os.SystemClock; import android.os.SystemProperties; import android.provider.Settings; import android.util.Printer; + import java.io.PrintWriter; import java.io.StringWriter; @@ -52,7 +54,6 @@ public class ApplicationErrorReport implements Parcelable { // System property defining default error report receiver static final String DEFAULT_ERROR_RECEIVER_PROPERTY = "ro.error.receiver.default"; - /** * Uninitialized error report. */ @@ -74,8 +75,15 @@ public class ApplicationErrorReport implements Parcelable { public static final int TYPE_BATTERY = 3; /** + * A report from a user to a developer about a running service that the + * user doesn't think should be running. + */ + public static final int TYPE_RUNNING_SERVICE = 5; + + /** * Type of this report. Can be one of {@link #TYPE_NONE}, - * {@link #TYPE_CRASH}, {@link #TYPE_ANR}, or {@link #TYPE_BATTERY}. + * {@link #TYPE_CRASH}, {@link #TYPE_ANR}, {@link #TYPE_BATTERY}, + * or {@link #TYPE_RUNNING_SERVICE}. */ public int type; @@ -123,7 +131,13 @@ public class ApplicationErrorReport implements Parcelable { * of BatteryInfo; otherwise null. */ public BatteryInfo batteryInfo; - + + /** + * If this report is of type {@link #TYPE_RUNNING_SERVICE}, contains an instance + * of RunningServiceInfo; otherwise null. + */ + public RunningServiceInfo runningServiceInfo; + /** * Create an uninitialized instance of {@link ApplicationErrorReport}. */ @@ -218,6 +232,9 @@ public class ApplicationErrorReport implements Parcelable { case TYPE_BATTERY: batteryInfo.writeToParcel(dest, flags); break; + case TYPE_RUNNING_SERVICE: + runningServiceInfo.writeToParcel(dest, flags); + break; } } @@ -234,16 +251,25 @@ public class ApplicationErrorReport implements Parcelable { crashInfo = new CrashInfo(in); anrInfo = null; batteryInfo = null; + runningServiceInfo = null; break; case TYPE_ANR: anrInfo = new AnrInfo(in); crashInfo = null; batteryInfo = null; + runningServiceInfo = null; break; case TYPE_BATTERY: batteryInfo = new BatteryInfo(in); anrInfo = null; crashInfo = null; + runningServiceInfo = null; + break; + case TYPE_RUNNING_SERVICE: + batteryInfo = null; + anrInfo = null; + crashInfo = null; + runningServiceInfo = new RunningServiceInfo(in); break; } } @@ -474,6 +500,51 @@ public class ApplicationErrorReport implements Parcelable { } } + /** + * Describes a running service report. + */ + public static class RunningServiceInfo { + /** + * Duration in milliseconds that the service has been running. + */ + public long durationMillis; + + /** + * Dump of debug information about the service. + */ + public String serviceDetails; + + /** + * Create an uninitialized instance of RunningServiceInfo. + */ + public RunningServiceInfo() { + } + + /** + * Create an instance of RunningServiceInfo initialized from a Parcel. + */ + public RunningServiceInfo(Parcel in) { + durationMillis = in.readLong(); + serviceDetails = in.readString(); + } + + /** + * Save a RunningServiceInfo instance to a parcel. + */ + public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(durationMillis); + dest.writeString(serviceDetails); + } + + /** + * Dump a BatteryInfo instance to a Printer. + */ + public void dump(Printer pw, String prefix) { + pw.println(prefix + "durationMillis: " + durationMillis); + pw.println(prefix + "serviceDetails: " + serviceDetails); + } + } + public static final Parcelable.Creator<ApplicationErrorReport> CREATOR = new Parcelable.Creator<ApplicationErrorReport>() { public ApplicationErrorReport createFromParcel(Parcel source) { diff --git a/core/java/android/app/ApplicationLoaders.java b/core/java/android/app/ApplicationLoaders.java index 2e301c9afd56..9e3cd7eb8b57 100644 --- a/core/java/android/app/ApplicationLoaders.java +++ b/core/java/android/app/ApplicationLoaders.java @@ -19,6 +19,7 @@ package android.app; import dalvik.system.PathClassLoader; import java.util.HashMap; +import java.util.Map; class ApplicationLoaders { @@ -27,8 +28,7 @@ class ApplicationLoaders return gApplicationLoaders; } - public ClassLoader getClassLoader(String zip, String appDataDir, - ClassLoader parent) + public ClassLoader getClassLoader(String zip, String libPath, ClassLoader parent) { /* * This is the parent we use if they pass "null" in. In theory @@ -49,13 +49,13 @@ class ApplicationLoaders * new ClassLoader for the zip archive. */ if (parent == baseParent) { - ClassLoader loader = (ClassLoader)mLoaders.get(zip); + ClassLoader loader = mLoaders.get(zip); if (loader != null) { return loader; } PathClassLoader pathClassloader = - new PathClassLoader(zip, appDataDir + "/lib", parent); + new PathClassLoader(zip, libPath, parent); mLoaders.put(zip, pathClassloader); return pathClassloader; @@ -65,7 +65,7 @@ class ApplicationLoaders } } - private final HashMap mLoaders = new HashMap(); + private final Map<String, ClassLoader> mLoaders = new HashMap<String, ClassLoader>(); private static final ApplicationLoaders gApplicationLoaders = new ApplicationLoaders(); diff --git a/core/java/android/app/ApplicationThreadNative.java b/core/java/android/app/ApplicationThreadNative.java index da26a78f8198..1c20062f3f84 100644 --- a/core/java/android/app/ApplicationThreadNative.java +++ b/core/java/android/app/ApplicationThreadNative.java @@ -341,13 +341,6 @@ public abstract class ApplicationThreadNative extends Binder return true; } - case REQUEST_PSS_TRANSACTION: - { - data.enforceInterface(IApplicationThread.descriptor); - requestPss(); - return true; - } - case PROFILER_CONTROL_TRANSACTION: { data.enforceInterface(IApplicationThread.descriptor); @@ -402,6 +395,14 @@ public abstract class ApplicationThreadNative extends Binder dispatchPackageBroadcast(cmd, packages); return true; } + + case SCHEDULE_CRASH_TRANSACTION: + { + data.enforceInterface(IApplicationThread.descriptor); + String msg = data.readString(); + scheduleCrash(msg); + return true; + } } return super.onTransact(code, data, reply, flags); @@ -771,14 +772,6 @@ class ApplicationThreadProxy implements IApplicationThread { data.recycle(); } - public final void requestPss() throws RemoteException { - Parcel data = Parcel.obtain(); - data.writeInterfaceToken(IApplicationThread.descriptor); - mRemote.transact(REQUEST_PSS_TRANSACTION, data, null, - IBinder.FLAG_ONEWAY); - data.recycle(); - } - public void profilerControl(boolean start, String path, ParcelFileDescriptor fd) throws RemoteException { Parcel data = Parcel.obtain(); @@ -826,5 +819,15 @@ class ApplicationThreadProxy implements IApplicationThread { data.recycle(); } + + public void scheduleCrash(String msg) throws RemoteException { + Parcel data = Parcel.obtain(); + data.writeInterfaceToken(IApplicationThread.descriptor); + data.writeString(msg); + mRemote.transact(SCHEDULE_CRASH_TRANSACTION, data, null, + IBinder.FLAG_ONEWAY); + data.recycle(); + + } } diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index f471f579e482..09ef71024ad4 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -67,6 +67,7 @@ import android.location.LocationManager; import android.media.AudioManager; import android.net.ConnectivityManager; import android.net.IConnectivityManager; +import android.net.DownloadManager; import android.net.ThrottleManager; import android.net.IThrottleManager; import android.net.Uri; @@ -118,9 +119,11 @@ import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; import java.util.WeakHashMap; -import java.util.Map.Entry; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; class ReceiverRestrictedContext extends ContextWrapper { ReceiverRestrictedContext(Context base) { @@ -169,11 +172,11 @@ class ContextImpl extends Context { private static ThrottleManager sThrottleManager; private static WifiManager sWifiManager; private static LocationManager sLocationManager; - private static final HashMap<File, SharedPreferencesImpl> sSharedPrefs = - new HashMap<File, SharedPreferencesImpl>(); + private static final HashMap<String, SharedPreferencesImpl> sSharedPrefs = + new HashMap<String, SharedPreferencesImpl>(); private AudioManager mAudioManager; - /*package*/ ActivityThread.PackageInfo mPackageInfo; + /*package*/ LoadedApk mPackageInfo; private Resources mResources; /*package*/ ActivityThread mMainThread; private Context mOuterContext; @@ -199,6 +202,7 @@ class ContextImpl extends Context { private DropBoxManager mDropBoxManager = null; private DevicePolicyManager mDevicePolicyManager = null; private UiModeManager mUiModeManager = null; + private DownloadManager mDownloadManager = null; private final Object mSync = new Object(); @@ -208,7 +212,7 @@ class ContextImpl extends Context { private File mCacheDir; private File mExternalFilesDir; private File mExternalCacheDir; - + private static long sInstanceCount = 0; private static final String[] EMPTY_FILE_LIST = {}; @@ -321,7 +325,7 @@ class ContextImpl extends Context { } throw new RuntimeException("Not supported in system context"); } - + private static File makeBackupFile(File prefsFile) { return new File(prefsFile.getPath() + ".bak"); } @@ -333,55 +337,54 @@ class ContextImpl extends Context { @Override public SharedPreferences getSharedPreferences(String name, int mode) { SharedPreferencesImpl sp; - File f = getSharedPrefsFile(name); + File prefsFile; + boolean needInitialLoad = false; synchronized (sSharedPrefs) { - sp = sSharedPrefs.get(f); - if (sp != null && !sp.hasFileChanged()) { - //Log.i(TAG, "Returning existing prefs " + name + ": " + sp); + sp = sSharedPrefs.get(name); + if (sp != null && !sp.hasFileChangedUnexpectedly()) { return sp; } - } - - FileInputStream str = null; - File backup = makeBackupFile(f); - if (backup.exists()) { - f.delete(); - backup.renameTo(f); + prefsFile = getSharedPrefsFile(name); + if (sp == null) { + sp = new SharedPreferencesImpl(prefsFile, mode, null); + sSharedPrefs.put(name, sp); + needInitialLoad = true; + } } - // Debugging - if (f.exists() && !f.canRead()) { - Log.w(TAG, "Attempt to read preferences file " + f + " without permission"); - } - - Map map = null; - if (f.exists() && f.canRead()) { - try { - str = new FileInputStream(f); - map = XmlUtils.readMapXml(str); - str.close(); - } catch (org.xmlpull.v1.XmlPullParserException e) { - Log.w(TAG, "getSharedPreferences", e); - } catch (FileNotFoundException e) { - Log.w(TAG, "getSharedPreferences", e); - } catch (IOException e) { - Log.w(TAG, "getSharedPreferences", e); + synchronized (sp) { + if (needInitialLoad && sp.isLoaded()) { + // lost the race to load; another thread handled it + return sp; + } + File backup = makeBackupFile(prefsFile); + if (backup.exists()) { + prefsFile.delete(); + backup.renameTo(prefsFile); } - } - synchronized (sSharedPrefs) { - if (sp != null) { - //Log.i(TAG, "Updating existing prefs " + name + " " + sp + ": " + map); - sp.replace(map); - } else { - sp = sSharedPrefs.get(f); - if (sp == null) { - sp = new SharedPreferencesImpl(f, mode, map); - sSharedPrefs.put(f, sp); + // Debugging + if (prefsFile.exists() && !prefsFile.canRead()) { + Log.w(TAG, "Attempt to read preferences file " + prefsFile + " without permission"); + } + + Map map = null; + if (prefsFile.exists() && prefsFile.canRead()) { + try { + FileInputStream str = new FileInputStream(prefsFile); + map = XmlUtils.readMapXml(str); + str.close(); + } catch (org.xmlpull.v1.XmlPullParserException e) { + Log.w(TAG, "getSharedPreferences", e); + } catch (FileNotFoundException e) { + Log.w(TAG, "getSharedPreferences", e); + } catch (IOException e) { + Log.w(TAG, "getSharedPreferences", e); } } - return sp; + sp.replace(map); } + return sp; } private File getPreferencesDir() { @@ -696,7 +699,7 @@ class ContextImpl extends Context { if (scheduler == null) { scheduler = mMainThread.getHandler(); } - rd = new ActivityThread.PackageInfo.ReceiverDispatcher( + rd = new LoadedApk.ReceiverDispatcher( resultReceiver, getOuterContext(), scheduler, null, false).getIIntentReceiver(); } } @@ -739,7 +742,7 @@ class ContextImpl extends Context { if (scheduler == null) { scheduler = mMainThread.getHandler(); } - rd = new ActivityThread.PackageInfo.ReceiverDispatcher( + rd = new LoadedApk.ReceiverDispatcher( resultReceiver, getOuterContext(), scheduler, null, false).getIIntentReceiver(); } } @@ -795,7 +798,7 @@ class ContextImpl extends Context { if (scheduler == null) { scheduler = mMainThread.getHandler(); } - rd = new ActivityThread.PackageInfo.ReceiverDispatcher( + rd = new LoadedApk.ReceiverDispatcher( receiver, context, scheduler, null, true).getIIntentReceiver(); } } @@ -973,6 +976,8 @@ class ContextImpl extends Context { return getDevicePolicyManager(); } else if (UI_MODE_SERVICE.equals(name)) { return getUiModeManager(); + } else if (DOWNLOAD_SERVICE.equals(name)) { + return getDownloadManager(); } return null; @@ -1157,12 +1162,16 @@ class ContextImpl extends Context { return mAudioManager; } + /* package */ static DropBoxManager createDropBoxManager() { + IBinder b = ServiceManager.getService(DROPBOX_SERVICE); + IDropBoxManagerService service = IDropBoxManagerService.Stub.asInterface(b); + return new DropBoxManager(service); + } + private DropBoxManager getDropBoxManager() { synchronized (mSync) { if (mDropBoxManager == null) { - IBinder b = ServiceManager.getService(DROPBOX_SERVICE); - IDropBoxManagerService service = IDropBoxManagerService.Stub.asInterface(b); - mDropBoxManager = new DropBoxManager(service); + mDropBoxManager = createDropBoxManager(); } } return mDropBoxManager; @@ -1187,6 +1196,15 @@ class ContextImpl extends Context { return mUiModeManager; } + private DownloadManager getDownloadManager() { + synchronized (mSync) { + if (mDownloadManager == null) { + mDownloadManager = new DownloadManager(getContentResolver(), getPackageName()); + } + } + return mDownloadManager; + } + @Override public int checkPermission(String permission, int pid, int uid) { if (permission == null) { @@ -1421,7 +1439,7 @@ class ContextImpl extends Context { return new ContextImpl(mMainThread.getSystemContext()); } - ActivityThread.PackageInfo pi = + LoadedApk pi = mMainThread.getPackageInfo(packageName, flags); if (pi != null) { ContextImpl c = new ContextImpl(); @@ -1488,12 +1506,12 @@ class ContextImpl extends Context { mOuterContext = this; } - final void init(ActivityThread.PackageInfo packageInfo, + final void init(LoadedApk packageInfo, IBinder activityToken, ActivityThread mainThread) { init(packageInfo, activityToken, mainThread, null); } - final void init(ActivityThread.PackageInfo packageInfo, + final void init(LoadedApk packageInfo, IBinder activityToken, ActivityThread mainThread, Resources container) { mPackageInfo = packageInfo; @@ -1826,6 +1844,21 @@ class ContextImpl extends Context { } @Override + public ProviderInfo getProviderInfo(ComponentName className, int flags) + throws NameNotFoundException { + try { + ProviderInfo pi = mPM.getProviderInfo(className, flags); + if (pi != null) { + return pi; + } + } catch (RemoteException e) { + throw new RuntimeException("Package manager has died", e); + } + + throw new NameNotFoundException(className.toString()); + } + + @Override public String[] getSystemSharedLibraryNames() { try { return mPM.getSystemSharedLibraryNames(); @@ -2173,6 +2206,39 @@ class ContextImpl extends Context { throws NameNotFoundException { return getApplicationIcon(getApplicationInfo(packageName, 0)); } + + @Override + public Drawable getActivityLogo(ComponentName activityName) + throws NameNotFoundException { + return getActivityInfo(activityName, 0).loadLogo(this); + } + + @Override + public Drawable getActivityLogo(Intent intent) + throws NameNotFoundException { + if (intent.getComponent() != null) { + return getActivityLogo(intent.getComponent()); + } + + ResolveInfo info = resolveActivity( + intent, PackageManager.MATCH_DEFAULT_ONLY); + if (info != null) { + return info.activityInfo.loadLogo(this); + } + + throw new NameNotFoundException(intent.toUri(0)); + } + + @Override + public Drawable getApplicationLogo(ApplicationInfo info) { + return info.loadLogo(this); + } + + @Override + public Drawable getApplicationLogo(String packageName) + throws NameNotFoundException { + return getApplicationLogo(getApplicationInfo(packageName, 0)); + } @Override public Resources getResourcesForActivity( ComponentName activityName) throws NameNotFoundException { @@ -2620,6 +2686,15 @@ class ContextImpl extends Context { return PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; } + @Override + public void setPackageObbPath(String packageName, String path) { + try { + mPM.setPackageObbPath(packageName, path); + } catch (RemoteException e) { + // Should never happen! + } + } + private final ContextImpl mContext; private final IPackageManager mPM; @@ -2636,13 +2711,20 @@ class ContextImpl extends Context { private static final class SharedPreferencesImpl implements SharedPreferences { + // Lock ordering rules: + // - acquire SharedPreferencesImpl.this before EditorImpl.this + // - acquire mWritingToDiskLock before EditorImpl.this + private final File mFile; private final File mBackupFile; private final int mMode; - private Map mMap; - private final FileStatus mFileStatus = new FileStatus(); - private long mTimestamp; + private Map<String, Object> mMap; // guarded by 'this' + private long mTimestamp; // guarded by 'this' + private int mDiskWritesInFlight = 0; // guarded by 'this' + private boolean mLoaded = false; // guarded by 'this' + + private final Object mWritingToDiskLock = new Object(); private static final Object mContent = new Object(); private WeakHashMap<OnSharedPreferenceChangeListener, Object> mListeners; @@ -2651,30 +2733,50 @@ class ContextImpl extends Context { mFile = file; mBackupFile = makeBackupFile(file); mMode = mode; - mMap = initialContents != null ? initialContents : new HashMap(); - if (FileUtils.getFileStatus(file.getPath(), mFileStatus)) { - mTimestamp = mFileStatus.mtime; + mLoaded = initialContents != null; + mMap = initialContents != null ? initialContents : new HashMap<String, Object>(); + FileStatus stat = new FileStatus(); + if (FileUtils.getFileStatus(file.getPath(), stat)) { + mTimestamp = stat.mtime; } mListeners = new WeakHashMap<OnSharedPreferenceChangeListener, Object>(); } - public boolean hasFileChanged() { + // Has this SharedPreferences ever had values assigned to it? + boolean isLoaded() { synchronized (this) { - if (!FileUtils.getFileStatus(mFile.getPath(), mFileStatus)) { - return true; + return mLoaded; + } + } + + // Has the file changed out from under us? i.e. writes that + // we didn't instigate. + public boolean hasFileChangedUnexpectedly() { + synchronized (this) { + if (mDiskWritesInFlight > 0) { + // If we know we caused it, it's not unexpected. + Log.d(TAG, "disk write in flight, not unexpected."); + return false; } - return mTimestamp != mFileStatus.mtime; + } + FileStatus stat = new FileStatus(); + if (!FileUtils.getFileStatus(mFile.getPath(), stat)) { + return true; + } + synchronized (this) { + return mTimestamp != stat.mtime; } } - + public void replace(Map newContents) { - if (newContents != null) { - synchronized (this) { + synchronized (this) { + mLoaded = true; + if (newContents != null) { mMap = newContents; } } } - + public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) { synchronized(this) { mListeners.put(listener, mContent); @@ -2690,7 +2792,7 @@ class ContextImpl extends Context { public Map<String, ?> getAll() { synchronized(this) { //noinspection unchecked - return new HashMap(mMap); + return new HashMap<String, Object>(mMap); } } @@ -2709,7 +2811,7 @@ class ContextImpl extends Context { } public long getLong(String key, long defValue) { synchronized (this) { - Long v = (Long) mMap.get(key); + Long v = (Long)mMap.get(key); return v != null ? v : defValue; } } @@ -2732,6 +2834,25 @@ class ContextImpl extends Context { } } + public Editor edit() { + return new EditorImpl(); + } + + // Return value from EditorImpl#commitToMemory() + private static class MemoryCommitResult { + public boolean changesMade; // any keys different? + public List<String> keysModified; // may be null + public Set<OnSharedPreferenceChangeListener> listeners; // may be null + public Map<?, ?> mapToWriteToDisk; + public final CountDownLatch writtenToDiskLatch = new CountDownLatch(1); + public volatile boolean writeToDiskResult = false; + + public void setDiskWriteResult(boolean result) { + writeToDiskResult = result; + writtenToDiskLatch.countDown(); + } + } + public final class EditorImpl implements Editor { private final Map<String, Object> mModified = Maps.newHashMap(); private boolean mClear = false; @@ -2781,67 +2902,188 @@ class ContextImpl extends Context { } } - public boolean commit() { - boolean returnValue; + public void apply() { + final MemoryCommitResult mcr = commitToMemory(); + final Runnable awaitCommit = new Runnable() { + public void run() { + try { + mcr.writtenToDiskLatch.await(); + } catch (InterruptedException ignored) { + } + } + }; + + QueuedWork.add(awaitCommit); + + Runnable postWriteRunnable = new Runnable() { + public void run() { + awaitCommit.run(); + QueuedWork.remove(awaitCommit); + } + }; - boolean hasListeners; - List<String> keysModified = null; - Set<OnSharedPreferenceChangeListener> listeners = null; + SharedPreferencesImpl.this.enqueueDiskWrite(mcr, postWriteRunnable); + // Okay to notify the listeners before it's hit disk + // because the listeners should always get the same + // SharedPreferences instance back, which has the + // changes reflected in memory. + notifyListeners(mcr); + } + + // Returns true if any changes were made + private MemoryCommitResult commitToMemory() { + MemoryCommitResult mcr = new MemoryCommitResult(); synchronized (SharedPreferencesImpl.this) { - hasListeners = mListeners.size() > 0; + // We optimistically don't make a deep copy until + // a memory commit comes in when we're already + // writing to disk. + if (mDiskWritesInFlight > 0) { + // We can't modify our mMap as a currently + // in-flight write owns it. Clone it before + // modifying it. + // noinspection unchecked + mMap = new HashMap<String, Object>(mMap); + } + mcr.mapToWriteToDisk = mMap; + mDiskWritesInFlight++; + + boolean hasListeners = mListeners.size() > 0; if (hasListeners) { - keysModified = new ArrayList<String>(); - listeners = - new HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet()); + mcr.keysModified = new ArrayList<String>(); + mcr.listeners = + new HashSet<OnSharedPreferenceChangeListener>(mListeners.keySet()); } synchronized (this) { if (mClear) { - mMap.clear(); + if (!mMap.isEmpty()) { + mcr.changesMade = true; + mMap.clear(); + } mClear = false; } for (Entry<String, Object> e : mModified.entrySet()) { String k = e.getKey(); Object v = e.getValue(); - if (v == this) { + if (v == this) { // magic value for a removal mutation + if (!mMap.containsKey(k)) { + continue; + } mMap.remove(k); } else { + boolean isSame = false; + if (mMap.containsKey(k)) { + Object existingValue = mMap.get(k); + if (existingValue != null && existingValue.equals(v)) { + continue; + } + } mMap.put(k, v); } + mcr.changesMade = true; if (hasListeners) { - keysModified.add(k); + mcr.keysModified.add(k); } } mModified.clear(); } + } + return mcr; + } - returnValue = writeFileLocked(); + public boolean commit() { + MemoryCommitResult mcr = commitToMemory(); + SharedPreferencesImpl.this.enqueueDiskWrite( + mcr, null /* sync write on this thread okay */); + try { + mcr.writtenToDiskLatch.await(); + } catch (InterruptedException e) { + return false; } + notifyListeners(mcr); + return mcr.writeToDiskResult; + } - if (hasListeners) { - for (int i = keysModified.size() - 1; i >= 0; i--) { - final String key = keysModified.get(i); - for (OnSharedPreferenceChangeListener listener : listeners) { + private void notifyListeners(final MemoryCommitResult mcr) { + if (mcr.listeners == null || mcr.keysModified == null || + mcr.keysModified.size() == 0) { + return; + } + if (Looper.myLooper() == Looper.getMainLooper()) { + for (int i = mcr.keysModified.size() - 1; i >= 0; i--) { + final String key = mcr.keysModified.get(i); + for (OnSharedPreferenceChangeListener listener : mcr.listeners) { if (listener != null) { listener.onSharedPreferenceChanged(SharedPreferencesImpl.this, key); } } } + } else { + // Run this function on the main thread. + ActivityThread.sMainThreadHandler.post(new Runnable() { + public void run() { + notifyListeners(mcr); + } + }); } - - return returnValue; } } - public Editor edit() { - return new EditorImpl(); + /** + * Enqueue an already-committed-to-memory result to be written + * to disk. + * + * They will be written to disk one-at-a-time in the order + * that they're enqueued. + * + * @param postWriteRunnable if non-null, we're being called + * from apply() and this is the runnable to run after + * the write proceeds. if null (from a regular commit()), + * then we're allowed to do this disk write on the main + * thread (which in addition to reducing allocations and + * creating a background thread, this has the advantage that + * we catch them in userdebug StrictMode reports to convert + * them where possible to apply() ...) + */ + private void enqueueDiskWrite(final MemoryCommitResult mcr, + final Runnable postWriteRunnable) { + final Runnable writeToDiskRunnable = new Runnable() { + public void run() { + synchronized (mWritingToDiskLock) { + writeToFile(mcr); + } + synchronized (SharedPreferencesImpl.this) { + mDiskWritesInFlight--; + } + if (postWriteRunnable != null) { + postWriteRunnable.run(); + } + } + }; + + final boolean isFromSyncCommit = (postWriteRunnable == null); + + // Typical #commit() path with fewer allocations, doing a write on + // the current thread. + if (isFromSyncCommit) { + boolean wasEmpty = false; + synchronized (SharedPreferencesImpl.this) { + wasEmpty = mDiskWritesInFlight == 1; + } + if (wasEmpty) { + writeToDiskRunnable.run(); + return; + } + } + + QueuedWork.singleThreadExecutor().execute(writeToDiskRunnable); } - - private FileOutputStream createFileOutputStream(File file) { + + private static FileOutputStream createFileOutputStream(File file) { FileOutputStream str = null; try { str = new FileOutputStream(file); @@ -2864,42 +3106,56 @@ class ContextImpl extends Context { return str; } - private boolean writeFileLocked() { + // Note: must hold mWritingToDiskLock + private void writeToFile(MemoryCommitResult mcr) { // Rename the current file so it may be used as a backup during the next read if (mFile.exists()) { + if (!mcr.changesMade) { + // If the file already exists, but no changes were + // made to the underlying map, it's wasteful to + // re-write the file. Return as if we wrote it + // out. + mcr.setDiskWriteResult(true); + return; + } if (!mBackupFile.exists()) { if (!mFile.renameTo(mBackupFile)) { Log.e(TAG, "Couldn't rename file " + mFile + " to backup file " + mBackupFile); - return false; + mcr.setDiskWriteResult(false); + return; } } else { mFile.delete(); } } - + // Attempt to write the file, delete the backup and return true as atomically as // possible. If any exception occurs, delete the new file; next time we will restore // from the backup. try { FileOutputStream str = createFileOutputStream(mFile); if (str == null) { - return false; + mcr.setDiskWriteResult(false); + return; } - XmlUtils.writeMapXml(mMap, str); + XmlUtils.writeMapXml(mcr.mapToWriteToDisk, str); str.close(); setFilePermissionsFromMode(mFile.getPath(), mMode, 0); - if (FileUtils.getFileStatus(mFile.getPath(), mFileStatus)) { - mTimestamp = mFileStatus.mtime; + FileStatus stat = new FileStatus(); + if (FileUtils.getFileStatus(mFile.getPath(), stat)) { + synchronized (this) { + mTimestamp = stat.mtime; + } } - // Writing was successful, delete the backup file if there is one. mBackupFile.delete(); - return true; + mcr.setDiskWriteResult(true); + return; } catch (XmlPullParserException e) { - Log.w(TAG, "writeFileLocked: Got exception:", e); + Log.w(TAG, "writeToFile: Got exception:", e); } catch (IOException e) { - Log.w(TAG, "writeFileLocked: Got exception:", e); + Log.w(TAG, "writeToFile: Got exception:", e); } // Clean up an unsuccessfully written file if (mFile.exists()) { @@ -2907,7 +3163,7 @@ class ContextImpl extends Context { Log.e(TAG, "Couldn't clean up partially-written file " + mFile); } } - return false; + mcr.setDiskWriteResult(false); } } } diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java index 02355991a885..da8c9e566977 100644 --- a/core/java/android/app/Dialog.java +++ b/core/java/android/app/Dialog.java @@ -138,7 +138,7 @@ public class Dialog implements DialogInterface, Window.Callback, public Dialog(Context context, int theme) { mContext = new ContextThemeWrapper( context, theme == 0 ? com.android.internal.R.style.Theme_Dialog : theme); - mWindowManager = (WindowManager)context.getSystemService("window"); + mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE); Window w = PolicyManager.makeNewWindow(mContext); mWindow = w; w.setCallback(this); diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java index 31f0a6359ee1..f9bd46134ccb 100644 --- a/core/java/android/app/IActivityManager.java +++ b/core/java/android/app/IActivityManager.java @@ -19,10 +19,10 @@ package android.app; import android.content.ComponentName; import android.content.ContentProviderNative; import android.content.IContentProvider; +import android.content.IIntentReceiver; +import android.content.IIntentSender; import android.content.Intent; import android.content.IntentFilter; -import android.content.IIntentSender; -import android.content.IIntentReceiver; import android.content.IntentSender; import android.content.pm.ApplicationInfo; import android.content.pm.ConfigurationInfo; @@ -31,14 +31,15 @@ import android.content.pm.ProviderInfo; import android.content.res.Configuration; import android.graphics.Bitmap; import android.net.Uri; +import android.os.Bundle; import android.os.Debug; -import android.os.RemoteException; import android.os.IBinder; import android.os.IInterface; import android.os.Parcel; -import android.os.Parcelable; import android.os.ParcelFileDescriptor; -import android.os.Bundle; +import android.os.Parcelable; +import android.os.RemoteException; +import android.os.StrictMode; import java.util.List; @@ -247,8 +248,6 @@ public interface IActivityManager extends IInterface { public boolean killPids(int[] pids, String reason) throws RemoteException; - public void reportPss(IApplicationThread caller, int pss) throws RemoteException; - // Special low-level communication with activity manager. public void startRunning(String pkg, String cls, String action, String data) throws RemoteException; @@ -256,7 +255,14 @@ public interface IActivityManager extends IInterface { ApplicationErrorReport.CrashInfo crashInfo) throws RemoteException; public boolean handleApplicationWtf(IBinder app, String tag, ApplicationErrorReport.CrashInfo crashInfo) throws RemoteException; - + + // A StrictMode violation to be handled. The violationMask is a + // subset of the original StrictMode policy bitmask, with only the + // bit violated and penalty bits to be executed by the + // ActivityManagerService remaining set. + public void handleApplicationStrictModeViolation(IBinder app, int violationMask, + StrictMode.ViolationInfo crashInfo) throws RemoteException; + /* * This will deliver the specified signal to all the persistent processes. Currently only * SIGUSR1 is delivered. All others are ignored. @@ -303,6 +309,17 @@ public interface IActivityManager extends IInterface { public boolean isUserAMonkey() throws RemoteException; + public void finishHeavyWeightApp() throws RemoteException; + + public void crashApplication(int uid, int initialPid, String packageName, + String message) throws RemoteException; + + public IBinder newUriPermissionOwner(String name) throws RemoteException; + public void grantUriPermissionFromOwner(IBinder owner, int fromUid, String targetPkg, + Uri uri, int mode) throws RemoteException; + public void revokeUriPermissionFromOwner(IBinder owner, Uri uri, + int mode) throws RemoteException; + /* * Private non-Binder interfaces */ @@ -311,28 +328,19 @@ public interface IActivityManager extends IInterface { /** Information you can retrieve about a particular application. */ public static class ContentProviderHolder implements Parcelable { public final ProviderInfo info; - public final String permissionFailure; public IContentProvider provider; public boolean noReleaseNeeded; public ContentProviderHolder(ProviderInfo _info) { info = _info; - permissionFailure = null; } - public ContentProviderHolder(ProviderInfo _info, - String _permissionFailure) { - info = _info; - permissionFailure = _permissionFailure; - } - public int describeContents() { return 0; } public void writeToParcel(Parcel dest, int flags) { info.writeToParcel(dest, 0); - dest.writeString(permissionFailure); if (provider != null) { dest.writeStrongBinder(provider.asBinder()); } else { @@ -354,7 +362,6 @@ public interface IActivityManager extends IInterface { private ContentProviderHolder(Parcel source) { info = ProviderInfo.CREATOR.createFromParcel(source); - permissionFailure = source.readString(); provider = ContentProviderNative.asInterface( source.readStrongBinder()); noReleaseNeeded = source.readInt() != 0; @@ -486,7 +493,7 @@ public interface IActivityManager extends IInterface { int FORCE_STOP_PACKAGE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+78; int KILL_PIDS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+79; int GET_SERVICES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+80; - int REPORT_PSS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+81; + int GET_RUNNING_APP_PROCESSES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+82; int GET_DEVICE_CONFIGURATION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+83; int PEEK_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+84; @@ -513,4 +520,13 @@ public interface IActivityManager extends IInterface { int WILL_ACTIVITY_BE_VISIBLE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+105; int START_ACTIVITY_WITH_CONFIG_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+106; int GET_RUNNING_EXTERNAL_APPLICATIONS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+107; + int FINISH_HEAVY_WEIGHT_APP_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+108; + int HANDLE_APPLICATION_STRICT_MODE_VIOLATION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+109; + int IS_IMMERSIVE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+110; + int SET_IMMERSIVE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+111; + int IS_TOP_ACTIVITY_IMMERSIVE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+112; + int CRASH_APPLICATION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+113; + int NEW_URI_PERMISSION_OWNER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+114; + int GRANT_URI_PERMISSION_FROM_OWNER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+115; + int REVOKE_URI_PERMISSION_FROM_OWNER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+116; } diff --git a/core/java/android/app/IApplicationThread.java b/core/java/android/app/IApplicationThread.java index c917e8126843..c8ef17f1b7a9 100644 --- a/core/java/android/app/IApplicationThread.java +++ b/core/java/android/app/IApplicationThread.java @@ -95,7 +95,6 @@ public interface IApplicationThread extends IInterface { throws RemoteException; void scheduleLowMemory() throws RemoteException; void scheduleActivityConfigurationChanged(IBinder token) throws RemoteException; - void requestPss() throws RemoteException; void profilerControl(boolean start, String path, ParcelFileDescriptor fd) throws RemoteException; void setSchedulingGroup(int group) throws RemoteException; @@ -103,6 +102,7 @@ public interface IApplicationThread extends IInterface { static final int PACKAGE_REMOVED = 0; static final int EXTERNAL_STORAGE_UNAVAILABLE = 1; void dispatchPackageBroadcast(int cmd, String[] packages) throws RemoteException; + void scheduleCrash(String msg) throws RemoteException; String descriptor = "android.app.IApplicationThread"; @@ -131,7 +131,7 @@ public interface IApplicationThread extends IInterface { int SCHEDULE_LOW_MEMORY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+23; int SCHEDULE_ACTIVITY_CONFIGURATION_CHANGED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+24; int SCHEDULE_RELAUNCH_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+25; - int REQUEST_PSS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+26; + int PROFILER_CONTROL_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+27; int SET_SCHEDULING_GROUP_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+28; int SCHEDULE_CREATE_BACKUP_AGENT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+29; @@ -139,4 +139,5 @@ public interface IApplicationThread extends IInterface { int GET_MEMORY_INFO_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+31; int SCHEDULE_SUICIDE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+32; int DISPATCH_PACKAGE_BROADCAST_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+33; + int SCHEDULE_CRASH_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+34; } diff --git a/core/java/android/app/ListActivity.java b/core/java/android/app/ListActivity.java index 0a8020765e63..4bf55184af66 100644 --- a/core/java/android/app/ListActivity.java +++ b/core/java/android/app/ListActivity.java @@ -18,9 +18,7 @@ package android.app; import android.os.Bundle; import android.os.Handler; -import android.view.KeyEvent; import android.view.View; -import android.widget.Adapter; import android.widget.AdapterView; import android.widget.ListAdapter; import android.widget.ListView; @@ -316,7 +314,7 @@ public class ListActivity extends Activity { } private AdapterView.OnItemClickListener mOnClickListener = new AdapterView.OnItemClickListener() { - public void onItemClick(AdapterView parent, View v, int position, long id) + public void onItemClick(AdapterView<?> parent, View v, int position, long id) { onListItemClick((ListView)parent, v, position, id); } diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java new file mode 100644 index 000000000000..0644f96e44e5 --- /dev/null +++ b/core/java/android/app/LoadedApk.java @@ -0,0 +1,1107 @@ +/* + * 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 android.app; + +import com.android.internal.util.ArrayUtils; + +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.IIntentReceiver; +import android.content.Intent; +import android.content.ServiceConnection; +import android.content.pm.ApplicationInfo; +import android.content.pm.IPackageManager; +import android.content.pm.PackageManager; +import android.content.res.AssetManager; +import android.content.res.CompatibilityInfo; +import android.content.res.Resources; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.Process; +import android.os.RemoteException; +import android.util.AndroidRuntimeException; +import android.util.Slog; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.lang.ref.WeakReference; +import java.net.URL; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Iterator; + +final class IntentReceiverLeaked extends AndroidRuntimeException { + public IntentReceiverLeaked(String msg) { + super(msg); + } +} + +final class ServiceConnectionLeaked extends AndroidRuntimeException { + public ServiceConnectionLeaked(String msg) { + super(msg); + } +} + +/** + * Local state maintained about a currently loaded .apk. + * @hide + */ +final class LoadedApk { + + private final ActivityThread mActivityThread; + private final ApplicationInfo mApplicationInfo; + final String mPackageName; + private final String mAppDir; + private final String mResDir; + private final String[] mSharedLibraries; + private final String mDataDir; + private final String mLibDir; + private final File mDataDirFile; + private final ClassLoader mBaseClassLoader; + private final boolean mSecurityViolation; + private final boolean mIncludeCode; + Resources mResources; + private ClassLoader mClassLoader; + private Application mApplication; + CompatibilityInfo mCompatibilityInfo; + + private final HashMap<Context, HashMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher>> mReceivers + = new HashMap<Context, HashMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher>>(); + private final HashMap<Context, HashMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher>> mUnregisteredReceivers + = new HashMap<Context, HashMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher>>(); + private final HashMap<Context, HashMap<ServiceConnection, LoadedApk.ServiceDispatcher>> mServices + = new HashMap<Context, HashMap<ServiceConnection, LoadedApk.ServiceDispatcher>>(); + private final HashMap<Context, HashMap<ServiceConnection, LoadedApk.ServiceDispatcher>> mUnboundServices + = new HashMap<Context, HashMap<ServiceConnection, LoadedApk.ServiceDispatcher>>(); + + int mClientCount = 0; + + Application getApplication() { + return mApplication; + } + + public LoadedApk(ActivityThread activityThread, ApplicationInfo aInfo, + ActivityThread mainThread, ClassLoader baseLoader, + boolean securityViolation, boolean includeCode) { + mActivityThread = activityThread; + mApplicationInfo = aInfo; + mPackageName = aInfo.packageName; + mAppDir = aInfo.sourceDir; + mResDir = aInfo.uid == Process.myUid() ? aInfo.sourceDir + : aInfo.publicSourceDir; + mSharedLibraries = aInfo.sharedLibraryFiles; + mDataDir = aInfo.dataDir; + mDataDirFile = mDataDir != null ? new File(mDataDir) : null; + mLibDir = aInfo.nativeLibraryDir; + mBaseClassLoader = baseLoader; + mSecurityViolation = securityViolation; + mIncludeCode = includeCode; + mCompatibilityInfo = new CompatibilityInfo(aInfo); + + if (mAppDir == null) { + if (ActivityThread.mSystemContext == null) { + ActivityThread.mSystemContext = + ContextImpl.createSystemContext(mainThread); + ActivityThread.mSystemContext.getResources().updateConfiguration( + mainThread.getConfiguration(), + mainThread.getDisplayMetricsLocked(false)); + //Slog.i(TAG, "Created system resources " + // + mSystemContext.getResources() + ": " + // + mSystemContext.getResources().getConfiguration()); + } + mClassLoader = ActivityThread.mSystemContext.getClassLoader(); + mResources = ActivityThread.mSystemContext.getResources(); + } + } + + public LoadedApk(ActivityThread activityThread, String name, + Context systemContext, ApplicationInfo info) { + mActivityThread = activityThread; + mApplicationInfo = info != null ? info : new ApplicationInfo(); + mApplicationInfo.packageName = name; + mPackageName = name; + mAppDir = null; + mResDir = null; + mSharedLibraries = null; + mDataDir = null; + mDataDirFile = null; + mLibDir = null; + mBaseClassLoader = null; + mSecurityViolation = false; + mIncludeCode = true; + mClassLoader = systemContext.getClassLoader(); + mResources = systemContext.getResources(); + mCompatibilityInfo = new CompatibilityInfo(mApplicationInfo); + } + + public String getPackageName() { + return mPackageName; + } + + public ApplicationInfo getApplicationInfo() { + return mApplicationInfo; + } + + public boolean isSecurityViolation() { + return mSecurityViolation; + } + + /** + * Gets the array of shared libraries that are listed as + * used by the given package. + * + * @param packageName the name of the package (note: not its + * file name) + * @return null-ok; the array of shared libraries, each one + * a fully-qualified path + */ + private static String[] getLibrariesFor(String packageName) { + ApplicationInfo ai = null; + try { + ai = ActivityThread.getPackageManager().getApplicationInfo(packageName, + PackageManager.GET_SHARED_LIBRARY_FILES); + } catch (RemoteException e) { + throw new AssertionError(e); + } + + if (ai == null) { + return null; + } + + return ai.sharedLibraryFiles; + } + + /** + * Combines two arrays (of library names) such that they are + * concatenated in order but are devoid of duplicates. The + * result is a single string with the names of the libraries + * separated by colons, or <code>null</code> if both lists + * were <code>null</code> or empty. + * + * @param list1 null-ok; the first list + * @param list2 null-ok; the second list + * @return null-ok; the combination + */ + private static String combineLibs(String[] list1, String[] list2) { + StringBuilder result = new StringBuilder(300); + boolean first = true; + + if (list1 != null) { + for (String s : list1) { + if (first) { + first = false; + } else { + result.append(':'); + } + result.append(s); + } + } + + // Only need to check for duplicates if list1 was non-empty. + boolean dupCheck = !first; + + if (list2 != null) { + for (String s : list2) { + if (dupCheck && ArrayUtils.contains(list1, s)) { + continue; + } + + if (first) { + first = false; + } else { + result.append(':'); + } + result.append(s); + } + } + + return result.toString(); + } + + public ClassLoader getClassLoader() { + synchronized (this) { + if (mClassLoader != null) { + return mClassLoader; + } + + if (mIncludeCode && !mPackageName.equals("android")) { + String zip = mAppDir; + + /* + * The following is a bit of a hack to inject + * instrumentation into the system: If the app + * being started matches one of the instrumentation names, + * then we combine both the "instrumentation" and + * "instrumented" app into the path, along with the + * concatenation of both apps' shared library lists. + */ + + String instrumentationAppDir = + mActivityThread.mInstrumentationAppDir; + String instrumentationAppPackage = + mActivityThread.mInstrumentationAppPackage; + String instrumentedAppDir = + mActivityThread.mInstrumentedAppDir; + String[] instrumentationLibs = null; + + if (mAppDir.equals(instrumentationAppDir) + || mAppDir.equals(instrumentedAppDir)) { + zip = instrumentationAppDir + ":" + instrumentedAppDir; + if (! instrumentedAppDir.equals(instrumentationAppDir)) { + instrumentationLibs = + getLibrariesFor(instrumentationAppPackage); + } + } + + if ((mSharedLibraries != null) || + (instrumentationLibs != null)) { + zip = + combineLibs(mSharedLibraries, instrumentationLibs) + + ':' + zip; + } + + /* + * With all the combination done (if necessary, actually + * create the class loader. + */ + + if (ActivityThread.localLOGV) + Slog.v(ActivityThread.TAG, "Class path: " + zip + ", JNI path: " + mLibDir); + + mClassLoader = + ApplicationLoaders.getDefault().getClassLoader( + zip, mLibDir, mBaseClassLoader); + initializeJavaContextClassLoader(); + } else { + if (mBaseClassLoader == null) { + mClassLoader = ClassLoader.getSystemClassLoader(); + } else { + mClassLoader = mBaseClassLoader; + } + } + return mClassLoader; + } + } + + /** + * Setup value for Thread.getContextClassLoader(). If the + * package will not run in in a VM with other packages, we set + * the Java context ClassLoader to the + * PackageInfo.getClassLoader value. However, if this VM can + * contain multiple packages, we intead set the Java context + * ClassLoader to a proxy that will warn about the use of Java + * context ClassLoaders and then fall through to use the + * system ClassLoader. + * + * <p> Note that this is similar to but not the same as the + * android.content.Context.getClassLoader(). While both + * context class loaders are typically set to the + * PathClassLoader used to load the package archive in the + * single application per VM case, a single Android process + * may contain several Contexts executing on one thread with + * their own logical ClassLoaders while the Java context + * ClassLoader is a thread local. This is why in the case when + * we have multiple packages per VM we do not set the Java + * context ClassLoader to an arbitrary but instead warn the + * user to set their own if we detect that they are using a + * Java library that expects it to be set. + */ + private void initializeJavaContextClassLoader() { + IPackageManager pm = ActivityThread.getPackageManager(); + android.content.pm.PackageInfo pi; + try { + pi = pm.getPackageInfo(mPackageName, 0); + } catch (RemoteException e) { + throw new AssertionError(e); + } + /* + * Two possible indications that this package could be + * sharing its virtual machine with other packages: + * + * 1.) the sharedUserId attribute is set in the manifest, + * indicating a request to share a VM with other + * packages with the same sharedUserId. + * + * 2.) the application element of the manifest has an + * attribute specifying a non-default process name, + * indicating the desire to run in another packages VM. + */ + boolean sharedUserIdSet = (pi.sharedUserId != null); + boolean processNameNotDefault = + (pi.applicationInfo != null && + !mPackageName.equals(pi.applicationInfo.processName)); + boolean sharable = (sharedUserIdSet || processNameNotDefault); + ClassLoader contextClassLoader = + (sharable) + ? new WarningContextClassLoader() + : mClassLoader; + Thread.currentThread().setContextClassLoader(contextClassLoader); + } + + private static class WarningContextClassLoader extends ClassLoader { + + private static boolean warned = false; + + private void warn(String methodName) { + if (warned) { + return; + } + warned = true; + Thread.currentThread().setContextClassLoader(getParent()); + Slog.w(ActivityThread.TAG, "ClassLoader." + methodName + ": " + + "The class loader returned by " + + "Thread.getContextClassLoader() may fail for processes " + + "that host multiple applications. You should explicitly " + + "specify a context class loader. For example: " + + "Thread.setContextClassLoader(getClass().getClassLoader());"); + } + + @Override public URL getResource(String resName) { + warn("getResource"); + return getParent().getResource(resName); + } + + @Override public Enumeration<URL> getResources(String resName) throws IOException { + warn("getResources"); + return getParent().getResources(resName); + } + + @Override public InputStream getResourceAsStream(String resName) { + warn("getResourceAsStream"); + return getParent().getResourceAsStream(resName); + } + + @Override public Class<?> loadClass(String className) throws ClassNotFoundException { + warn("loadClass"); + return getParent().loadClass(className); + } + + @Override public void setClassAssertionStatus(String cname, boolean enable) { + warn("setClassAssertionStatus"); + getParent().setClassAssertionStatus(cname, enable); + } + + @Override public void setPackageAssertionStatus(String pname, boolean enable) { + warn("setPackageAssertionStatus"); + getParent().setPackageAssertionStatus(pname, enable); + } + + @Override public void setDefaultAssertionStatus(boolean enable) { + warn("setDefaultAssertionStatus"); + getParent().setDefaultAssertionStatus(enable); + } + + @Override public void clearAssertionStatus() { + warn("clearAssertionStatus"); + getParent().clearAssertionStatus(); + } + } + + public String getAppDir() { + return mAppDir; + } + + public String getResDir() { + return mResDir; + } + + public String getDataDir() { + return mDataDir; + } + + public File getDataDirFile() { + return mDataDirFile; + } + + public AssetManager getAssets(ActivityThread mainThread) { + return getResources(mainThread).getAssets(); + } + + public Resources getResources(ActivityThread mainThread) { + if (mResources == null) { + mResources = mainThread.getTopLevelResources(mResDir, this); + } + return mResources; + } + + public Application makeApplication(boolean forceDefaultAppClass, + Instrumentation instrumentation) { + if (mApplication != null) { + return mApplication; + } + + Application app = null; + + String appClass = mApplicationInfo.className; + if (forceDefaultAppClass || (appClass == null)) { + appClass = "android.app.Application"; + } + + try { + java.lang.ClassLoader cl = getClassLoader(); + ContextImpl appContext = new ContextImpl(); + appContext.init(this, null, mActivityThread); + app = mActivityThread.mInstrumentation.newApplication( + cl, appClass, appContext); + appContext.setOuterContext(app); + } catch (Exception e) { + if (!mActivityThread.mInstrumentation.onException(app, e)) { + throw new RuntimeException( + "Unable to instantiate application " + appClass + + ": " + e.toString(), e); + } + } + mActivityThread.mAllApplications.add(app); + mApplication = app; + + if (instrumentation != null) { + try { + instrumentation.callApplicationOnCreate(app); + } catch (Exception e) { + if (!instrumentation.onException(app, e)) { + throw new RuntimeException( + "Unable to create application " + app.getClass().getName() + + ": " + e.toString(), e); + } + } + } + + return app; + } + + public void removeContextRegistrations(Context context, + String who, String what) { + HashMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher> rmap = + mReceivers.remove(context); + if (rmap != null) { + Iterator<LoadedApk.ReceiverDispatcher> it = rmap.values().iterator(); + while (it.hasNext()) { + LoadedApk.ReceiverDispatcher rd = it.next(); + IntentReceiverLeaked leak = new IntentReceiverLeaked( + what + " " + who + " has leaked IntentReceiver " + + rd.getIntentReceiver() + " that was " + + "originally registered here. Are you missing a " + + "call to unregisterReceiver()?"); + leak.setStackTrace(rd.getLocation().getStackTrace()); + Slog.e(ActivityThread.TAG, leak.getMessage(), leak); + try { + ActivityManagerNative.getDefault().unregisterReceiver( + rd.getIIntentReceiver()); + } catch (RemoteException e) { + // system crashed, nothing we can do + } + } + } + mUnregisteredReceivers.remove(context); + //Slog.i(TAG, "Receiver registrations: " + mReceivers); + HashMap<ServiceConnection, LoadedApk.ServiceDispatcher> smap = + mServices.remove(context); + if (smap != null) { + Iterator<LoadedApk.ServiceDispatcher> it = smap.values().iterator(); + while (it.hasNext()) { + LoadedApk.ServiceDispatcher sd = it.next(); + ServiceConnectionLeaked leak = new ServiceConnectionLeaked( + what + " " + who + " has leaked ServiceConnection " + + sd.getServiceConnection() + " that was originally bound here"); + leak.setStackTrace(sd.getLocation().getStackTrace()); + Slog.e(ActivityThread.TAG, leak.getMessage(), leak); + try { + ActivityManagerNative.getDefault().unbindService( + sd.getIServiceConnection()); + } catch (RemoteException e) { + // system crashed, nothing we can do + } + sd.doForget(); + } + } + mUnboundServices.remove(context); + //Slog.i(TAG, "Service registrations: " + mServices); + } + + public IIntentReceiver getReceiverDispatcher(BroadcastReceiver r, + Context context, Handler handler, + Instrumentation instrumentation, boolean registered) { + synchronized (mReceivers) { + LoadedApk.ReceiverDispatcher rd = null; + HashMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher> map = null; + if (registered) { + map = mReceivers.get(context); + if (map != null) { + rd = map.get(r); + } + } + if (rd == null) { + rd = new ReceiverDispatcher(r, context, handler, + instrumentation, registered); + if (registered) { + if (map == null) { + map = new HashMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher>(); + mReceivers.put(context, map); + } + map.put(r, rd); + } + } else { + rd.validate(context, handler); + } + return rd.getIIntentReceiver(); + } + } + + public IIntentReceiver forgetReceiverDispatcher(Context context, + BroadcastReceiver r) { + synchronized (mReceivers) { + HashMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher> map = mReceivers.get(context); + LoadedApk.ReceiverDispatcher rd = null; + if (map != null) { + rd = map.get(r); + if (rd != null) { + map.remove(r); + if (map.size() == 0) { + mReceivers.remove(context); + } + if (r.getDebugUnregister()) { + HashMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher> holder + = mUnregisteredReceivers.get(context); + if (holder == null) { + holder = new HashMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher>(); + mUnregisteredReceivers.put(context, holder); + } + RuntimeException ex = new IllegalArgumentException( + "Originally unregistered here:"); + ex.fillInStackTrace(); + rd.setUnregisterLocation(ex); + holder.put(r, rd); + } + return rd.getIIntentReceiver(); + } + } + HashMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher> holder + = mUnregisteredReceivers.get(context); + if (holder != null) { + rd = holder.get(r); + if (rd != null) { + RuntimeException ex = rd.getUnregisterLocation(); + throw new IllegalArgumentException( + "Unregistering Receiver " + r + + " that was already unregistered", ex); + } + } + if (context == null) { + throw new IllegalStateException("Unbinding Receiver " + r + + " from Context that is no longer in use: " + context); + } else { + throw new IllegalArgumentException("Receiver not registered: " + r); + } + + } + } + + static final class ReceiverDispatcher { + + final static class InnerReceiver extends IIntentReceiver.Stub { + final WeakReference<LoadedApk.ReceiverDispatcher> mDispatcher; + final LoadedApk.ReceiverDispatcher mStrongRef; + + InnerReceiver(LoadedApk.ReceiverDispatcher rd, boolean strong) { + mDispatcher = new WeakReference<LoadedApk.ReceiverDispatcher>(rd); + mStrongRef = strong ? rd : null; + } + public void performReceive(Intent intent, int resultCode, + String data, Bundle extras, boolean ordered, boolean sticky) { + LoadedApk.ReceiverDispatcher rd = mDispatcher.get(); + if (ActivityThread.DEBUG_BROADCAST) { + int seq = intent.getIntExtra("seq", -1); + Slog.i(ActivityThread.TAG, "Receiving broadcast " + intent.getAction() + " seq=" + seq + + " to " + (rd != null ? rd.mReceiver : null)); + } + if (rd != null) { + rd.performReceive(intent, resultCode, data, extras, + ordered, sticky); + } else { + // The activity manager dispatched a broadcast to a registered + // receiver in this process, but before it could be delivered the + // receiver was unregistered. Acknowledge the broadcast on its + // behalf so that the system's broadcast sequence can continue. + if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG, + "Finishing broadcast to unregistered receiver"); + IActivityManager mgr = ActivityManagerNative.getDefault(); + try { + mgr.finishReceiver(this, resultCode, data, extras, false); + } catch (RemoteException e) { + Slog.w(ActivityThread.TAG, "Couldn't finish broadcast to unregistered receiver"); + } + } + } + } + + final IIntentReceiver.Stub mIIntentReceiver; + final BroadcastReceiver mReceiver; + final Context mContext; + final Handler mActivityThread; + final Instrumentation mInstrumentation; + final boolean mRegistered; + final IntentReceiverLeaked mLocation; + RuntimeException mUnregisterLocation; + + final class Args implements Runnable { + private Intent mCurIntent; + private int mCurCode; + private String mCurData; + private Bundle mCurMap; + private boolean mCurOrdered; + private boolean mCurSticky; + + public void run() { + BroadcastReceiver receiver = mReceiver; + if (ActivityThread.DEBUG_BROADCAST) { + int seq = mCurIntent.getIntExtra("seq", -1); + Slog.i(ActivityThread.TAG, "Dispatching broadcast " + mCurIntent.getAction() + + " seq=" + seq + " to " + mReceiver); + Slog.i(ActivityThread.TAG, " mRegistered=" + mRegistered + + " mCurOrdered=" + mCurOrdered); + } + + IActivityManager mgr = ActivityManagerNative.getDefault(); + Intent intent = mCurIntent; + mCurIntent = null; + + if (receiver == null) { + if (mRegistered && mCurOrdered) { + try { + if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG, + "Finishing null broadcast to " + mReceiver); + mgr.finishReceiver(mIIntentReceiver, + mCurCode, mCurData, mCurMap, false); + } catch (RemoteException ex) { + } + } + return; + } + + try { + ClassLoader cl = mReceiver.getClass().getClassLoader(); + intent.setExtrasClassLoader(cl); + if (mCurMap != null) { + mCurMap.setClassLoader(cl); + } + receiver.setOrderedHint(true); + receiver.setResult(mCurCode, mCurData, mCurMap); + receiver.clearAbortBroadcast(); + receiver.setOrderedHint(mCurOrdered); + receiver.setInitialStickyHint(mCurSticky); + receiver.onReceive(mContext, intent); + } catch (Exception e) { + if (mRegistered && mCurOrdered) { + try { + if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG, + "Finishing failed broadcast to " + mReceiver); + mgr.finishReceiver(mIIntentReceiver, + mCurCode, mCurData, mCurMap, false); + } catch (RemoteException ex) { + } + } + if (mInstrumentation == null || + !mInstrumentation.onException(mReceiver, e)) { + throw new RuntimeException( + "Error receiving broadcast " + intent + + " in " + mReceiver, e); + } + } + if (mRegistered && mCurOrdered) { + try { + if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG, + "Finishing broadcast to " + mReceiver); + mgr.finishReceiver(mIIntentReceiver, + receiver.getResultCode(), + receiver.getResultData(), + receiver.getResultExtras(false), + receiver.getAbortBroadcast()); + } catch (RemoteException ex) { + } + } + } + } + + ReceiverDispatcher(BroadcastReceiver receiver, Context context, + Handler activityThread, Instrumentation instrumentation, + boolean registered) { + if (activityThread == null) { + throw new NullPointerException("Handler must not be null"); + } + + mIIntentReceiver = new InnerReceiver(this, !registered); + mReceiver = receiver; + mContext = context; + mActivityThread = activityThread; + mInstrumentation = instrumentation; + mRegistered = registered; + mLocation = new IntentReceiverLeaked(null); + mLocation.fillInStackTrace(); + } + + void validate(Context context, Handler activityThread) { + if (mContext != context) { + throw new IllegalStateException( + "Receiver " + mReceiver + + " registered with differing Context (was " + + mContext + " now " + context + ")"); + } + if (mActivityThread != activityThread) { + throw new IllegalStateException( + "Receiver " + mReceiver + + " registered with differing handler (was " + + mActivityThread + " now " + activityThread + ")"); + } + } + + IntentReceiverLeaked getLocation() { + return mLocation; + } + + BroadcastReceiver getIntentReceiver() { + return mReceiver; + } + + IIntentReceiver getIIntentReceiver() { + return mIIntentReceiver; + } + + void setUnregisterLocation(RuntimeException ex) { + mUnregisterLocation = ex; + } + + RuntimeException getUnregisterLocation() { + return mUnregisterLocation; + } + + public void performReceive(Intent intent, int resultCode, + String data, Bundle extras, boolean ordered, boolean sticky) { + if (ActivityThread.DEBUG_BROADCAST) { + int seq = intent.getIntExtra("seq", -1); + Slog.i(ActivityThread.TAG, "Enqueueing broadcast " + intent.getAction() + " seq=" + seq + + " to " + mReceiver); + } + Args args = new Args(); + args.mCurIntent = intent; + args.mCurCode = resultCode; + args.mCurData = data; + args.mCurMap = extras; + args.mCurOrdered = ordered; + args.mCurSticky = sticky; + if (!mActivityThread.post(args)) { + if (mRegistered && ordered) { + IActivityManager mgr = ActivityManagerNative.getDefault(); + try { + if (ActivityThread.DEBUG_BROADCAST) Slog.i(ActivityThread.TAG, + "Finishing sync broadcast to " + mReceiver); + mgr.finishReceiver(mIIntentReceiver, args.mCurCode, + args.mCurData, args.mCurMap, false); + } catch (RemoteException ex) { + } + } + } + } + + } + + public final IServiceConnection getServiceDispatcher(ServiceConnection c, + Context context, Handler handler, int flags) { + synchronized (mServices) { + LoadedApk.ServiceDispatcher sd = null; + HashMap<ServiceConnection, LoadedApk.ServiceDispatcher> map = mServices.get(context); + if (map != null) { + sd = map.get(c); + } + if (sd == null) { + sd = new ServiceDispatcher(c, context, handler, flags); + if (map == null) { + map = new HashMap<ServiceConnection, LoadedApk.ServiceDispatcher>(); + mServices.put(context, map); + } + map.put(c, sd); + } else { + sd.validate(context, handler); + } + return sd.getIServiceConnection(); + } + } + + public final IServiceConnection forgetServiceDispatcher(Context context, + ServiceConnection c) { + synchronized (mServices) { + HashMap<ServiceConnection, LoadedApk.ServiceDispatcher> map + = mServices.get(context); + LoadedApk.ServiceDispatcher sd = null; + if (map != null) { + sd = map.get(c); + if (sd != null) { + map.remove(c); + sd.doForget(); + if (map.size() == 0) { + mServices.remove(context); + } + if ((sd.getFlags()&Context.BIND_DEBUG_UNBIND) != 0) { + HashMap<ServiceConnection, LoadedApk.ServiceDispatcher> holder + = mUnboundServices.get(context); + if (holder == null) { + holder = new HashMap<ServiceConnection, LoadedApk.ServiceDispatcher>(); + mUnboundServices.put(context, holder); + } + RuntimeException ex = new IllegalArgumentException( + "Originally unbound here:"); + ex.fillInStackTrace(); + sd.setUnbindLocation(ex); + holder.put(c, sd); + } + return sd.getIServiceConnection(); + } + } + HashMap<ServiceConnection, LoadedApk.ServiceDispatcher> holder + = mUnboundServices.get(context); + if (holder != null) { + sd = holder.get(c); + if (sd != null) { + RuntimeException ex = sd.getUnbindLocation(); + throw new IllegalArgumentException( + "Unbinding Service " + c + + " that was already unbound", ex); + } + } + if (context == null) { + throw new IllegalStateException("Unbinding Service " + c + + " from Context that is no longer in use: " + context); + } else { + throw new IllegalArgumentException("Service not registered: " + c); + } + } + } + + static final class ServiceDispatcher { + private final ServiceDispatcher.InnerConnection mIServiceConnection; + private final ServiceConnection mConnection; + private final Context mContext; + private final Handler mActivityThread; + private final ServiceConnectionLeaked mLocation; + private final int mFlags; + + private RuntimeException mUnbindLocation; + + private boolean mDied; + + private static class ConnectionInfo { + IBinder binder; + IBinder.DeathRecipient deathMonitor; + } + + private static class InnerConnection extends IServiceConnection.Stub { + final WeakReference<LoadedApk.ServiceDispatcher> mDispatcher; + + InnerConnection(LoadedApk.ServiceDispatcher sd) { + mDispatcher = new WeakReference<LoadedApk.ServiceDispatcher>(sd); + } + + public void connected(ComponentName name, IBinder service) throws RemoteException { + LoadedApk.ServiceDispatcher sd = mDispatcher.get(); + if (sd != null) { + sd.connected(name, service); + } + } + } + + private final HashMap<ComponentName, ServiceDispatcher.ConnectionInfo> mActiveConnections + = new HashMap<ComponentName, ServiceDispatcher.ConnectionInfo>(); + + ServiceDispatcher(ServiceConnection conn, + Context context, Handler activityThread, int flags) { + mIServiceConnection = new InnerConnection(this); + mConnection = conn; + mContext = context; + mActivityThread = activityThread; + mLocation = new ServiceConnectionLeaked(null); + mLocation.fillInStackTrace(); + mFlags = flags; + } + + void validate(Context context, Handler activityThread) { + if (mContext != context) { + throw new RuntimeException( + "ServiceConnection " + mConnection + + " registered with differing Context (was " + + mContext + " now " + context + ")"); + } + if (mActivityThread != activityThread) { + throw new RuntimeException( + "ServiceConnection " + mConnection + + " registered with differing handler (was " + + mActivityThread + " now " + activityThread + ")"); + } + } + + void doForget() { + synchronized(this) { + Iterator<ServiceDispatcher.ConnectionInfo> it = mActiveConnections.values().iterator(); + while (it.hasNext()) { + ServiceDispatcher.ConnectionInfo ci = it.next(); + ci.binder.unlinkToDeath(ci.deathMonitor, 0); + } + mActiveConnections.clear(); + } + } + + ServiceConnectionLeaked getLocation() { + return mLocation; + } + + ServiceConnection getServiceConnection() { + return mConnection; + } + + IServiceConnection getIServiceConnection() { + return mIServiceConnection; + } + + int getFlags() { + return mFlags; + } + + void setUnbindLocation(RuntimeException ex) { + mUnbindLocation = ex; + } + + RuntimeException getUnbindLocation() { + return mUnbindLocation; + } + + public void connected(ComponentName name, IBinder service) { + if (mActivityThread != null) { + mActivityThread.post(new RunConnection(name, service, 0)); + } else { + doConnected(name, service); + } + } + + public void death(ComponentName name, IBinder service) { + ServiceDispatcher.ConnectionInfo old; + + synchronized (this) { + mDied = true; + old = mActiveConnections.remove(name); + if (old == null || old.binder != service) { + // Death for someone different than who we last + // reported... just ignore it. + return; + } + old.binder.unlinkToDeath(old.deathMonitor, 0); + } + + if (mActivityThread != null) { + mActivityThread.post(new RunConnection(name, service, 1)); + } else { + doDeath(name, service); + } + } + + public void doConnected(ComponentName name, IBinder service) { + ServiceDispatcher.ConnectionInfo old; + ServiceDispatcher.ConnectionInfo info; + + synchronized (this) { + old = mActiveConnections.get(name); + if (old != null && old.binder == service) { + // Huh, already have this one. Oh well! + return; + } + + if (service != null) { + // A new service is being connected... set it all up. + mDied = false; + info = new ConnectionInfo(); + info.binder = service; + info.deathMonitor = new DeathMonitor(name, service); + try { + service.linkToDeath(info.deathMonitor, 0); + mActiveConnections.put(name, info); + } catch (RemoteException e) { + // This service was dead before we got it... just + // don't do anything with it. + mActiveConnections.remove(name); + return; + } + + } else { + // The named service is being disconnected... clean up. + mActiveConnections.remove(name); + } + + if (old != null) { + old.binder.unlinkToDeath(old.deathMonitor, 0); + } + } + + // If there was an old service, it is not disconnected. + if (old != null) { + mConnection.onServiceDisconnected(name); + } + // If there is a new service, it is now connected. + if (service != null) { + mConnection.onServiceConnected(name, service); + } + } + + public void doDeath(ComponentName name, IBinder service) { + mConnection.onServiceDisconnected(name); + } + + private final class RunConnection implements Runnable { + RunConnection(ComponentName name, IBinder service, int command) { + mName = name; + mService = service; + mCommand = command; + } + + public void run() { + if (mCommand == 0) { + doConnected(mName, mService); + } else if (mCommand == 1) { + doDeath(mName, mService); + } + } + + final ComponentName mName; + final IBinder mService; + final int mCommand; + } + + private final class DeathMonitor implements IBinder.DeathRecipient + { + DeathMonitor(ComponentName name, IBinder service) { + mName = name; + mService = service; + } + + public void binderDied() { + death(mName, mService); + } + + final ComponentName mName; + final IBinder mService; + } + } +} diff --git a/core/java/android/app/NativeActivity.java b/core/java/android/app/NativeActivity.java new file mode 100644 index 000000000000..d7a041243acb --- /dev/null +++ b/core/java/android/app/NativeActivity.java @@ -0,0 +1,369 @@ +package android.app; + +import com.android.internal.view.IInputMethodCallback; +import com.android.internal.view.IInputMethodSession; + +import android.content.Context; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.res.AssetManager; +import android.content.res.Configuration; +import android.graphics.PixelFormat; +import android.os.Build; +import android.os.Bundle; +import android.os.Environment; +import android.os.Looper; +import android.os.MessageQueue; +import android.util.AttributeSet; +import android.view.InputChannel; +import android.view.InputQueue; +import android.view.KeyEvent; +import android.view.Surface; +import android.view.SurfaceHolder; +import android.view.View; +import android.view.WindowManager; +import android.view.ViewTreeObserver.OnGlobalLayoutListener; +import android.view.inputmethod.InputMethodManager; + +import java.io.File; +import java.lang.ref.WeakReference; + +/** + * Convenience for implementing an activity that will be implemented + * purely in native code. That is, a game (or game-like thing). There + * is no need to derive from this class; you can simply declare it in your + * manifest, and use the NDK APIs from there. + * + * <p>A typical manifest would look like: + * + * {@sample development/ndk/platforms/android-9/samples/native-activity/AndroidManifest.xml + * manifest} + * + * <p>A very simple example of native code that is run by NativeActivity + * follows. This reads input events from the user and uses OpenGLES to + * draw into the native activity's window. + * + * {@sample development/ndk/platforms/android-9/samples/native-activity/jni/main.c all} + */ +public class NativeActivity extends Activity implements SurfaceHolder.Callback2, + InputQueue.Callback, OnGlobalLayoutListener { + public static final String META_DATA_LIB_NAME = "android.app.lib_name"; + + public static final String KEY_NATIVE_SAVED_STATE = "android:native_state"; + + private NativeContentView mNativeContentView; + private InputMethodManager mIMM; + private InputMethodCallback mInputMethodCallback; + + private int mNativeHandle; + + private InputQueue mCurInputQueue; + private SurfaceHolder mCurSurfaceHolder; + + final int[] mLocation = new int[2]; + int mLastContentX; + int mLastContentY; + int mLastContentWidth; + int mLastContentHeight; + + private boolean mDispatchingUnhandledKey; + + private boolean mDestroyed; + + private native int loadNativeCode(String path, MessageQueue queue, + String internalDataPath, String externalDataPath, int sdkVersion, + AssetManager assetMgr, byte[] savedState); + private native void unloadNativeCode(int handle); + + private native void onStartNative(int handle); + private native void onResumeNative(int handle); + private native byte[] onSaveInstanceStateNative(int handle); + private native void onPauseNative(int handle); + private native void onStopNative(int handle); + private native void onConfigurationChangedNative(int handle); + private native void onLowMemoryNative(int handle); + private native void onWindowFocusChangedNative(int handle, boolean focused); + private native void onSurfaceCreatedNative(int handle, Surface surface); + private native void onSurfaceChangedNative(int handle, Surface surface, + int format, int width, int height); + private native void onSurfaceRedrawNeededNative(int handle, Surface surface); + private native void onSurfaceDestroyedNative(int handle); + private native void onInputChannelCreatedNative(int handle, InputChannel channel); + private native void onInputChannelDestroyedNative(int handle, InputChannel channel); + private native void onContentRectChangedNative(int handle, int x, int y, int w, int h); + private native void dispatchKeyEventNative(int handle, KeyEvent event); + private native void finishPreDispatchKeyEventNative(int handle, int seq, boolean handled); + + static class NativeContentView extends View { + NativeActivity mActivity; + + public NativeContentView(Context context) { + super(context); + } + + public NativeContentView(Context context, AttributeSet attrs) { + super(context, attrs); + } + } + + static class InputMethodCallback extends IInputMethodCallback.Stub { + WeakReference<NativeActivity> mNa; + + InputMethodCallback(NativeActivity na) { + mNa = new WeakReference<NativeActivity>(na); + } + + @Override + public void finishedEvent(int seq, boolean handled) { + NativeActivity na = mNa.get(); + if (na != null) { + na.finishPreDispatchKeyEventNative(na.mNativeHandle, seq, handled); + } + } + + @Override + public void sessionCreated(IInputMethodSession session) { + // Stub -- not for use in the client. + } + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + String libname = "main"; + ActivityInfo ai; + + mIMM = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE); + mInputMethodCallback = new InputMethodCallback(this); + + getWindow().takeSurface(this); + getWindow().takeInputQueue(this); + getWindow().setFormat(PixelFormat.RGB_565); + getWindow().setSoftInputMode( + WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED + | WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); + + mNativeContentView = new NativeContentView(this); + mNativeContentView.mActivity = this; + setContentView(mNativeContentView); + mNativeContentView.requestFocus(); + mNativeContentView.getViewTreeObserver().addOnGlobalLayoutListener(this); + + try { + ai = getPackageManager().getActivityInfo( + getIntent().getComponent(), PackageManager.GET_META_DATA); + if (ai.metaData != null) { + String ln = ai.metaData.getString(META_DATA_LIB_NAME); + if (ln != null) libname = ln; + } + } catch (PackageManager.NameNotFoundException e) { + throw new RuntimeException("Error getting activity info", e); + } + + String path = null; + + if ((ai.applicationInfo.flags&ApplicationInfo.FLAG_HAS_CODE) == 0) { + // If the application does not have (Java) code, then no ClassLoader + // has been set up for it. We will need to do our own search for + // the native code. + File libraryFile = new File(ai.applicationInfo.nativeLibraryDir, + System.mapLibraryName(libname)); + if (libraryFile.exists()) { + path = libraryFile.getPath(); + } + } + + if (path == null) { + throw new IllegalArgumentException("Unable to find native library: " + libname); + } + + byte[] nativeSavedState = savedInstanceState != null + ? savedInstanceState.getByteArray(KEY_NATIVE_SAVED_STATE) : null; + + mNativeHandle = loadNativeCode(path, Looper.myQueue(), + getFilesDir().toString(), + Environment.getExternalStorageAppFilesDirectory(ai.packageName).toString(), + Build.VERSION.SDK_INT, getAssets(), nativeSavedState); + + if (mNativeHandle == 0) { + throw new IllegalArgumentException("Unable to load native library: " + path); + } + super.onCreate(savedInstanceState); + } + + @Override + protected void onDestroy() { + mDestroyed = true; + if (mCurSurfaceHolder != null) { + onSurfaceDestroyedNative(mNativeHandle); + mCurSurfaceHolder = null; + } + if (mCurInputQueue != null) { + onInputChannelDestroyedNative(mNativeHandle, mCurInputQueue.getInputChannel()); + mCurInputQueue = null; + } + unloadNativeCode(mNativeHandle); + super.onDestroy(); + } + + @Override + protected void onPause() { + super.onPause(); + onPauseNative(mNativeHandle); + } + + @Override + protected void onResume() { + super.onResume(); + onResumeNative(mNativeHandle); + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + byte[] state = onSaveInstanceStateNative(mNativeHandle); + if (state != null) { + outState.putByteArray(KEY_NATIVE_SAVED_STATE, state); + } + } + + @Override + protected void onStart() { + super.onStart(); + onStartNative(mNativeHandle); + } + + @Override + protected void onStop() { + super.onStop(); + onStopNative(mNativeHandle); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + if (!mDestroyed) { + onConfigurationChangedNative(mNativeHandle); + } + } + + @Override + public void onLowMemory() { + super.onLowMemory(); + if (!mDestroyed) { + onLowMemoryNative(mNativeHandle); + } + } + + @Override + public void onWindowFocusChanged(boolean hasFocus) { + super.onWindowFocusChanged(hasFocus); + if (!mDestroyed) { + onWindowFocusChangedNative(mNativeHandle, hasFocus); + } + } + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + if (mDispatchingUnhandledKey) { + return super.dispatchKeyEvent(event); + } else { + // Key events from the IME do not go through the input channel; + // we need to intercept them here to hand to the application. + dispatchKeyEventNative(mNativeHandle, event); + return true; + } + } + + public void surfaceCreated(SurfaceHolder holder) { + if (!mDestroyed) { + mCurSurfaceHolder = holder; + onSurfaceCreatedNative(mNativeHandle, holder.getSurface()); + } + } + + public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { + if (!mDestroyed) { + mCurSurfaceHolder = holder; + onSurfaceChangedNative(mNativeHandle, holder.getSurface(), format, width, height); + } + } + + public void surfaceRedrawNeeded(SurfaceHolder holder) { + if (!mDestroyed) { + mCurSurfaceHolder = holder; + onSurfaceRedrawNeededNative(mNativeHandle, holder.getSurface()); + } + } + + public void surfaceDestroyed(SurfaceHolder holder) { + mCurSurfaceHolder = null; + if (!mDestroyed) { + onSurfaceDestroyedNative(mNativeHandle); + } + } + + public void onInputQueueCreated(InputQueue queue) { + if (!mDestroyed) { + mCurInputQueue = queue; + onInputChannelCreatedNative(mNativeHandle, queue.getInputChannel()); + } + } + + public void onInputQueueDestroyed(InputQueue queue) { + mCurInputQueue = null; + if (!mDestroyed) { + onInputChannelDestroyedNative(mNativeHandle, queue.getInputChannel()); + } + } + + public void onGlobalLayout() { + mNativeContentView.getLocationInWindow(mLocation); + int w = mNativeContentView.getWidth(); + int h = mNativeContentView.getHeight(); + if (mLocation[0] != mLastContentX || mLocation[1] != mLastContentY + || w != mLastContentWidth || h != mLastContentHeight) { + mLastContentX = mLocation[0]; + mLastContentY = mLocation[1]; + mLastContentWidth = w; + mLastContentHeight = h; + if (!mDestroyed) { + onContentRectChangedNative(mNativeHandle, mLastContentX, + mLastContentY, mLastContentWidth, mLastContentHeight); + } + } + } + + void dispatchUnhandledKeyEvent(KeyEvent event) { + try { + mDispatchingUnhandledKey = true; + View decor = getWindow().getDecorView(); + if (decor != null) { + decor.dispatchKeyEvent(event); + } + } finally { + mDispatchingUnhandledKey = false; + } + } + + void preDispatchKeyEvent(KeyEvent event, int seq) { + mIMM.dispatchKeyEvent(this, seq, event, + mInputMethodCallback); + } + + void setWindowFlags(int flags, int mask) { + getWindow().setFlags(flags, mask); + } + + void setWindowFormat(int format) { + getWindow().setFormat(format); + } + + void showIme(int mode) { + mIMM.showSoftInput(mNativeContentView, mode); + } + + void hideIme(int mode) { + mIMM.hideSoftInputFromWindow(mNativeContentView.getWindowToken(), mode); + } +} diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 4d72f73cd377..856943d5f380 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -83,10 +83,10 @@ public class Notification implements Parcelable public int icon; /** - * The number of events that this notification represents. For example, if this is the - * new mail notification, this would be the number of unread messages. This number is - * be superimposed over the icon in the status bar. If the number is 0 or negative, it - * is not shown in the status bar. + * The number of events that this notification represents. For example, in a new mail + * notification, this could be the number of unread messages. This number is superimposed over + * the icon in the status bar. If the number is 0 or negative, it is not shown in the status + * bar. */ public int number; @@ -109,13 +109,24 @@ public class Notification implements Parcelable public PendingIntent deleteIntent; /** + * An intent to launch instead of posting the notification to the status bar. + * Only for use with extremely high-priority notifications demanding the user's + * <strong>immediate</strong> attention, such as an incoming phone call or + * alarm clock that the user has explicitly set to a particular time. + * If this facility is used for something else, please give the user an option + * to turn it off and use a normal notification, as this can be extremely + * disruptive. + */ + public PendingIntent fullScreenIntent; + + /** * Text to scroll across the screen when this item is added to * the status bar. */ public CharSequence tickerText; /** - * The view that shows when this notification is shown in the expanded status bar. + * The view that will represent this notification in the expanded status bar. */ public RemoteViews contentView; @@ -339,6 +350,49 @@ public class Notification implements Parcelable ledOnMS = parcel.readInt(); ledOffMS = parcel.readInt(); iconLevel = parcel.readInt(); + + if (parcel.readInt() != 0) { + fullScreenIntent = PendingIntent.CREATOR.createFromParcel(parcel); + } + } + + public Notification clone() { + Notification that = new Notification(); + + that.when = this.when; + that.icon = this.icon; + that.number = this.number; + + // PendingIntents are global, so there's no reason (or way) to clone them. + that.contentIntent = this.contentIntent; + that.deleteIntent = this.deleteIntent; + that.fullScreenIntent = this.fullScreenIntent; + + if (this.tickerText != null) { + that.tickerText = this.tickerText.toString(); + } + if (this.contentView != null) { + that.contentView = this.contentView.clone(); + } + that.iconLevel = that.iconLevel; + that.sound = this.sound; // android.net.Uri is immutable + that.audioStreamType = this.audioStreamType; + + final long[] vibrate = this.vibrate; + if (vibrate != null) { + final int N = vibrate.length; + final long[] vib = that.vibrate = new long[N]; + System.arraycopy(vibrate, 0, vib, 0, N); + } + + that.ledARGB = this.ledARGB; + that.ledOnMS = this.ledOnMS; + that.ledOffMS = this.ledOffMS; + that.defaults = this.defaults; + + that.flags = this.flags; + + return that; } public int describeContents() { @@ -395,6 +449,13 @@ public class Notification implements Parcelable parcel.writeInt(ledOnMS); parcel.writeInt(ledOffMS); parcel.writeInt(iconLevel); + + if (fullScreenIntent != null) { + parcel.writeInt(1); + fullScreenIntent.writeToParcel(parcel, 0); + } else { + parcel.writeInt(0); + } } /** @@ -480,6 +541,8 @@ public class Notification implements Parcelable } sb.append(",defaults=0x"); sb.append(Integer.toHexString(this.defaults)); + sb.append(",flags=0x"); + sb.append(Integer.toHexString(this.flags)); sb.append(")"); return sb.toString(); } diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index 6fe12fcf0bda..1fae516b060f 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -17,6 +17,7 @@ package android.app; import android.content.Context; +import android.os.Binder; import android.os.RemoteException; import android.os.Handler; import android.os.IBinder; diff --git a/core/java/android/app/QueuedWork.java b/core/java/android/app/QueuedWork.java new file mode 100644 index 000000000000..af6bb1ba4dca --- /dev/null +++ b/core/java/android/app/QueuedWork.java @@ -0,0 +1,91 @@ +/* + * 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 android.app; + +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * Internal utility class to keep track of process-global work that's + * outstanding and hasn't been finished yet. + * + * This was created for writing SharedPreference edits out + * asynchronously so we'd have a mechanism to wait for the writes in + * Activity.onPause and similar places, but we may use this mechanism + * for other things in the future. + * + * @hide + */ +public class QueuedWork { + + // The set of Runnables that will finish or wait on any async + // activities started by the application. + private static final ConcurrentLinkedQueue<Runnable> sPendingWorkFinishers = + new ConcurrentLinkedQueue<Runnable>(); + + private static ExecutorService sSingleThreadExecutor = null; // lazy, guarded by class + + /** + * Returns a single-thread Executor shared by the entire process, + * creating it if necessary. + */ + public static ExecutorService singleThreadExecutor() { + synchronized (QueuedWork.class) { + if (sSingleThreadExecutor == null) { + // TODO: can we give this single thread a thread name? + sSingleThreadExecutor = Executors.newSingleThreadExecutor(); + } + return sSingleThreadExecutor; + } + } + + /** + * Add a runnable to finish (or wait for) a deferred operation + * started in this context earlier. Typically finished by e.g. + * an Activity#onPause. Used by SharedPreferences$Editor#startCommit(). + * + * Note that this doesn't actually start it running. This is just + * a scratch set for callers doing async work to keep updated with + * what's in-flight. In the common case, caller code + * (e.g. SharedPreferences) will pretty quickly call remove() + * after an add(). The only time these Runnables are run is from + * waitToFinish(), below. + */ + public static void add(Runnable finisher) { + sPendingWorkFinishers.add(finisher); + } + + public static void remove(Runnable finisher) { + sPendingWorkFinishers.remove(finisher); + } + + /** + * Finishes or waits for async operations to complete. + * (e.g. SharedPreferences$Editor#startCommit writes) + * + * Is called from the Activity base class's onPause(), after + * BroadcastReceiver's onReceive, after Service command handling, + * etc. (so async work is never lost) + */ + public static void waitToFinish() { + Runnable toFinish; + while ((toFinish = sPendingWorkFinishers.poll()) != null) { + toFinish.run(); + } + } +} diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java index 697a987d5f20..00063af78836 100644 --- a/core/java/android/app/Service.java +++ b/core/java/android/app/Service.java @@ -400,7 +400,15 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac * * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/ForegroundService.java * start_compatibility} - * + * + * <p class="caution">Note that the system calls this on your + * service's main thread. A service's main thread is the same + * thread where UI operations take place for Activities running in the + * same process. You should always avoid stalling the main + * thread's event loop. When doing long-running operations, + * network calls, or heavy disk I/O, you should kick off a new + * thread, or use {@link android.os.AsyncTask}.</p> + * * @param intent The Intent supplied to {@link android.content.Context#startService}, * as given. This may be null if the service is being restarted after * its process has gone away, and it had previously returned anything diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java index 72ec6168cfa2..de544fb5485f 100644 --- a/core/java/android/app/StatusBarManager.java +++ b/core/java/android/app/StatusBarManager.java @@ -23,6 +23,8 @@ import android.os.RemoteException; import android.os.IBinder; import android.os.ServiceManager; +import com.android.internal.statusbar.IStatusBarService; + /** * Allows an app to control the status bar. * @@ -58,12 +60,12 @@ public class StatusBarManager { public static final int DISABLE_NONE = 0x00000000; private Context mContext; - private IStatusBar mService; + private IStatusBarService mService; private IBinder mToken = new Binder(); StatusBarManager(Context context) { mContext = context; - mService = IStatusBar.Stub.asInterface( + mService = IStatusBarService.Stub.asInterface( ServiceManager.getService(Context.STATUS_BAR_SERVICE)); } @@ -85,7 +87,7 @@ public class StatusBarManager { */ public void expand() { try { - mService.activate(); + mService.expand(); } catch (RemoteException ex) { // system process is dead anyway. throw new RuntimeException(ex); @@ -97,46 +99,34 @@ public class StatusBarManager { */ public void collapse() { try { - mService.deactivate(); - } catch (RemoteException ex) { - // system process is dead anyway. - throw new RuntimeException(ex); - } - } - - /** - * Toggle the status bar. - */ - public void toggle() { - try { - mService.toggle(); + mService.collapse(); } catch (RemoteException ex) { // system process is dead anyway. throw new RuntimeException(ex); } } - public IBinder addIcon(String slot, int iconId, int iconLevel) { + public void setIcon(String slot, int iconId, int iconLevel) { try { - return mService.addIcon(slot, mContext.getPackageName(), iconId, iconLevel); + mService.setIcon(slot, mContext.getPackageName(), iconId, iconLevel); } catch (RemoteException ex) { // system process is dead anyway. throw new RuntimeException(ex); } } - public void updateIcon(IBinder key, String slot, int iconId, int iconLevel) { + public void removeIcon(String slot) { try { - mService.updateIcon(key, slot, mContext.getPackageName(), iconId, iconLevel); + mService.removeIcon(slot); } catch (RemoteException ex) { // system process is dead anyway. throw new RuntimeException(ex); } } - public void removeIcon(IBinder key) { + public void setIconVisibility(String slot, boolean visible) { try { - mService.removeIcon(key); + mService.setIconVisibility(slot, visible); } catch (RemoteException ex) { // system process is dead anyway. throw new RuntimeException(ex); diff --git a/core/java/android/app/backup/SharedPreferencesBackupHelper.java b/core/java/android/app/backup/SharedPreferencesBackupHelper.java index 23b170360daa..213bd31698de 100644 --- a/core/java/android/app/backup/SharedPreferencesBackupHelper.java +++ b/core/java/android/app/backup/SharedPreferencesBackupHelper.java @@ -16,6 +16,7 @@ package android.app.backup; +import android.app.QueuedWork; import android.content.Context; import android.content.SharedPreferences; import android.os.ParcelFileDescriptor; @@ -94,7 +95,11 @@ public class SharedPreferencesBackupHelper extends FileBackupHelperBase implemen public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput data, ParcelFileDescriptor newState) { Context context = mContext; - + + // If a SharedPreference has an outstanding write in flight, + // wait for it to finish flushing to disk. + QueuedWork.waitToFinish(); + // make filenames for the prefGroups String[] prefGroups = mPrefGroups; final int N = prefGroups.length; @@ -123,4 +128,3 @@ public class SharedPreferencesBackupHelper extends FileBackupHelperBase implemen } } } - diff --git a/core/java/android/appwidget/AppWidgetHost.java b/core/java/android/appwidget/AppWidgetHost.java index 88adabd042b3..b2fc13ffba6c 100644 --- a/core/java/android/appwidget/AppWidgetHost.java +++ b/core/java/android/appwidget/AppWidgetHost.java @@ -237,7 +237,7 @@ public class AppWidgetHost { v = mViews.get(appWidgetId); } if (v != null) { - v.updateAppWidget(null, AppWidgetHostView.UPDATE_FLAGS_RESET); + v.resetAppWidget(appWidget); } } @@ -247,7 +247,7 @@ public class AppWidgetHost { v = mViews.get(appWidgetId); } if (v != null) { - v.updateAppWidget(views, 0); + v.updateAppWidget(views); } } } diff --git a/core/java/android/appwidget/AppWidgetHostView.java b/core/java/android/appwidget/AppWidgetHostView.java index 5375193d823f..b33b0970d825 100644 --- a/core/java/android/appwidget/AppWidgetHostView.java +++ b/core/java/android/appwidget/AppWidgetHostView.java @@ -46,8 +46,6 @@ public class AppWidgetHostView extends FrameLayout { static final boolean LOGD = false; static final boolean CROSSFADE = false; - static final int UPDATE_FLAGS_RESET = 0x00000001; - static final int VIEW_MODE_NOINIT = 0; static final int VIEW_MODE_CONTENT = 1; static final int VIEW_MODE_ERROR = 2; @@ -102,7 +100,7 @@ public class AppWidgetHostView extends FrameLayout { mAppWidgetId = appWidgetId; mInfo = info; } - + public int getAppWidgetId() { return mAppWidgetId; } @@ -148,21 +146,22 @@ public class AppWidgetHostView extends FrameLayout { } /** + * Update the AppWidgetProviderInfo for this view, and reset it to the + * initial layout. + */ + void resetAppWidget(AppWidgetProviderInfo info) { + mInfo = info; + mViewMode = VIEW_MODE_NOINIT; + updateAppWidget(null); + } + + /** * Process a set of {@link RemoteViews} coming in as an update from the * AppWidget provider. Will animate into these new views as needed */ public void updateAppWidget(RemoteViews remoteViews) { - updateAppWidget(remoteViews, 0); - } + if (LOGD) Log.d(TAG, "updateAppWidget called mOld=" + mOld); - void updateAppWidget(RemoteViews remoteViews, int flags) { - if (LOGD) Log.d(TAG, "updateAppWidget called mOld=" + mOld + " flags=0x" - + Integer.toHexString(flags)); - - if ((flags & UPDATE_FLAGS_RESET) != 0) { - mViewMode = VIEW_MODE_NOINIT; - } - boolean recycled = false; View content = null; Exception exception = null; diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java index 8eda844380e0..16a8c571f41c 100644 --- a/core/java/android/bluetooth/BluetoothAdapter.java +++ b/core/java/android/bluetooth/BluetoothAdapter.java @@ -26,8 +26,10 @@ import android.os.ParcelUuid; import android.os.RemoteException; import android.os.ServiceManager; import android.util.Log; +import android.util.Pair; import java.io.IOException; +import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.LinkedList; @@ -468,12 +470,17 @@ public final class BluetoothAdapter { * <p>Valid Bluetooth names are a maximum of 248 UTF-8 characters, however * many remote devices can only display the first 40 characters, and some * may be limited to just 20. + * <p>If Bluetooth state is not {@link #STATE_ON}, this API + * will return false. After turning on Bluetooth, + * wait for {@link #ACTION_STATE_CHANGED} with {@link #STATE_ON} + * to get the updated value. * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} * * @param name a valid Bluetooth name * @return true if the name was set, false otherwise */ public boolean setName(String name) { + if (getState() != STATE_ON) return false; try { return mService.setName(name); } catch (RemoteException e) {Log.e(TAG, "", e);} @@ -488,11 +495,16 @@ public final class BluetoothAdapter { * {@link #SCAN_MODE_NONE}, * {@link #SCAN_MODE_CONNECTABLE}, * {@link #SCAN_MODE_CONNECTABLE_DISCOVERABLE}. + * <p>If Bluetooth state is not {@link #STATE_ON}, this API + * will return {@link #SCAN_MODE_NONE}. After turning on Bluetooth, + * wait for {@link #ACTION_STATE_CHANGED} with {@link #STATE_ON} + * to get the updated value. * <p>Requires {@link android.Manifest.permission#BLUETOOTH} * * @return scan mode */ public int getScanMode() { + if (getState() != STATE_ON) return SCAN_MODE_NONE; try { return mService.getScanMode(); } catch (RemoteException e) {Log.e(TAG, "", e);} @@ -511,6 +523,10 @@ public final class BluetoothAdapter { * {@link #SCAN_MODE_NONE}, * {@link #SCAN_MODE_CONNECTABLE}, * {@link #SCAN_MODE_CONNECTABLE_DISCOVERABLE}. + * <p>If Bluetooth state is not {@link #STATE_ON}, this API + * will return false. After turning on Bluetooth, + * wait for {@link #ACTION_STATE_CHANGED} with {@link #STATE_ON} + * to get the updated value. * <p>Requires {@link android.Manifest.permission#WRITE_SECURE_SETTINGS} * <p>Applications cannot set the scan mode. They should use * <code>startActivityForResult( @@ -524,6 +540,7 @@ public final class BluetoothAdapter { * @hide */ public boolean setScanMode(int mode, int duration) { + if (getState() != STATE_ON) return false; try { return mService.setScanMode(mode, duration); } catch (RemoteException e) {Log.e(TAG, "", e);} @@ -532,11 +549,13 @@ public final class BluetoothAdapter { /** @hide */ public boolean setScanMode(int mode) { + if (getState() != STATE_ON) return false; return setScanMode(mode, 120); } /** @hide */ public int getDiscoverableTimeout() { + if (getState() != STATE_ON) return -1; try { return mService.getDiscoverableTimeout(); } catch (RemoteException e) {Log.e(TAG, "", e);} @@ -545,6 +564,7 @@ public final class BluetoothAdapter { /** @hide */ public void setDiscoverableTimeout(int timeout) { + if (getState() != STATE_ON) return; try { mService.setDiscoverableTimeout(timeout); } catch (RemoteException e) {Log.e(TAG, "", e);} @@ -572,11 +592,16 @@ public final class BluetoothAdapter { * <p>Device discovery will only find remote devices that are currently * <i>discoverable</i> (inquiry scan enabled). Many Bluetooth devices are * not discoverable by default, and need to be entered into a special mode. + * <p>If Bluetooth state is not {@link #STATE_ON}, this API + * will return false. After turning on Bluetooth, + * wait for {@link #ACTION_STATE_CHANGED} with {@link #STATE_ON} + * to get the updated value. * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}. * * @return true on success, false on error */ public boolean startDiscovery() { + if (getState() != STATE_ON) return false; try { return mService.startDiscovery(); } catch (RemoteException e) {Log.e(TAG, "", e);} @@ -593,10 +618,15 @@ public final class BluetoothAdapter { * the Activity, but is run as a system service, so an application should * always call cancel discovery even if it did not directly request a * discovery, just to be sure. + * <p>If Bluetooth state is not {@link #STATE_ON}, this API + * will return false. After turning on Bluetooth, + * wait for {@link #ACTION_STATE_CHANGED} with {@link #STATE_ON} + * to get the updated value. * * @return true on success, false on error */ public boolean cancelDiscovery() { + if (getState() != STATE_ON) return false; try { mService.cancelDiscovery(); } catch (RemoteException e) {Log.e(TAG, "", e);} @@ -614,11 +644,16 @@ public final class BluetoothAdapter { * <p>Applications can also register for {@link #ACTION_DISCOVERY_STARTED} * or {@link #ACTION_DISCOVERY_FINISHED} to be notified when discovery * starts or completes. + * <p>If Bluetooth state is not {@link #STATE_ON}, this API + * will return false. After turning on Bluetooth, + * wait for {@link #ACTION_STATE_CHANGED} with {@link #STATE_ON} + * to get the updated value. * <p>Requires {@link android.Manifest.permission#BLUETOOTH}. * * @return true if discovering */ public boolean isDiscovering() { + if (getState() != STATE_ON) return false; try { return mService.isDiscovering(); } catch (RemoteException e) {Log.e(TAG, "", e);} @@ -628,11 +663,18 @@ public final class BluetoothAdapter { /** * Return the set of {@link BluetoothDevice} objects that are bonded * (paired) to the local adapter. + * <p>If Bluetooth state is not {@link #STATE_ON}, this API + * will return an empty set. After turning on Bluetooth, + * wait for {@link #ACTION_STATE_CHANGED} with {@link #STATE_ON} + * to get the updated value. * <p>Requires {@link android.Manifest.permission#BLUETOOTH}. * * @return unmodifiable set of {@link BluetoothDevice}, or null on error */ public Set<BluetoothDevice> getBondedDevices() { + if (getState() != STATE_ON) { + return toDeviceSet(new String[0]); + } try { return toDeviceSet(mService.listBonds()); } catch (RemoteException e) {Log.e(TAG, "", e);} @@ -823,6 +865,37 @@ public final class BluetoothAdapter { return socket; } + /** + * Read the local Out of Band Pairing Data + * <p>Requires {@link android.Manifest.permission#BLUETOOTH} + * + * @return Pair<byte[], byte[]> of Hash and Randomizer + * + * @hide + */ + public Pair<byte[], byte[]> readOutOfBandData() { + if (getState() != STATE_ON) return null; + try { + byte[] hash = new byte[16]; + byte[] randomizer = new byte[16]; + + byte[] ret = mService.readOutOfBandData(); + + if (ret == null || ret.length != 32) return null; + + hash = Arrays.copyOfRange(ret, 0, 16); + randomizer = Arrays.copyOfRange(ret, 16, 32); + + if (DBG) { + Log.d(TAG, "readOutOfBandData:" + Arrays.toString(hash) + + ":" + Arrays.toString(randomizer)); + } + return new Pair<byte[], byte[]>(hash, randomizer); + + } catch (RemoteException e) {Log.e(TAG, "", e);} + return null; + } + private Set<BluetoothDevice> toDeviceSet(String[] addresses) { Set<BluetoothDevice> devices = new HashSet<BluetoothDevice>(addresses.length); for (int i = 0; i < addresses.length; i++) { diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java index e77e76f79b2c..e577ec4f737c 100644 --- a/core/java/android/bluetooth/BluetoothDevice.java +++ b/core/java/android/bluetooth/BluetoothDevice.java @@ -325,7 +325,9 @@ public final class BluetoothDevice implements Parcelable { /** The user will be prompted to enter the passkey displayed on remote device * @hide */ public static final int PAIRING_VARIANT_DISPLAY_PASSKEY = 4; - + /** The user will be prompted to accept or deny the OOB pairing request + * @hide */ + public static final int PAIRING_VARIANT_OOB_CONSENT = 5; /** * Used as an extra field in {@link #ACTION_UUID} intents, * Contains the {@link android.os.ParcelUuid}s of the remote device which @@ -464,6 +466,52 @@ public final class BluetoothDevice implements Parcelable { } /** + * Start the bonding (pairing) process with the remote device using the + * Out Of Band mechanism. + * + * <p>This is an asynchronous call, it will return immediately. Register + * for {@link #ACTION_BOND_STATE_CHANGED} intents to be notified when + * the bonding process completes, and its result. + * + * <p>Android system services will handle the necessary user interactions + * to confirm and complete the bonding process. + * + * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}. + * + * @param hash - Simple Secure pairing hash + * @param randomizer - The random key obtained using OOB + * @return false on immediate error, true if bonding will begin + * + * @hide + */ + public boolean createBondOutOfBand(byte[] hash, byte[] randomizer) { + try { + return sService.createBondOutOfBand(mAddress, hash, randomizer); + } catch (RemoteException e) {Log.e(TAG, "", e);} + return false; + } + + /** + * Set the Out Of Band data for a remote device to be used later + * in the pairing mechanism. Users can obtain this data through other + * trusted channels + * + * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}. + * + * @param hash Simple Secure pairing hash + * @param randomizer The random key obtained using OOB + * @return false on error; true otherwise + * + * @hide + */ + public boolean setDeviceOutOfBandData(byte[] hash, byte[] randomizer) { + try { + return sService.setDeviceOutOfBandData(mAddress, hash, randomizer); + } catch (RemoteException e) {Log.e(TAG, "", e);} + return false; + } + + /** * Cancel an in-progress bonding request started with {@link #createBond}. * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}. * @@ -617,6 +665,14 @@ public final class BluetoothDevice implements Parcelable { } /** @hide */ + public boolean setRemoteOutOfBandData() { + try { + return sService.setRemoteOutOfBandData(mAddress); + } catch (RemoteException e) {Log.e(TAG, "", e);} + return false; + } + + /** @hide */ public boolean cancelPairingUserInput() { try { return sService.cancelPairingUserInput(mAddress); diff --git a/core/java/android/bluetooth/BluetoothDeviceProfileState.java b/core/java/android/bluetooth/BluetoothDeviceProfileState.java new file mode 100644 index 000000000000..8e655e213d62 --- /dev/null +++ b/core/java/android/bluetooth/BluetoothDeviceProfileState.java @@ -0,0 +1,665 @@ +/* + * 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 android.bluetooth; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Message; +import android.server.BluetoothA2dpService; +import android.server.BluetoothService; +import android.util.Log; + +import com.android.internal.util.HierarchicalState; +import com.android.internal.util.HierarchicalStateMachine; + +/** + * This class is the Profile connection state machine associated with a remote + * device. When the device bonds an instance of this class is created. + * This tracks incoming and outgoing connections of all the profiles. Incoming + * connections are preferred over outgoing connections and HFP preferred over + * A2DP. When the device is unbonded, the instance is removed. + * + * States: + * {@link BondedDevice}: This state represents a bonded device. When in this + * state none of the profiles are in transition states. + * + * {@link OutgoingHandsfree}: Handsfree profile connection is in a transition + * state because of a outgoing Connect or Disconnect. + * + * {@link IncomingHandsfree}: Handsfree profile connection is in a transition + * state because of a incoming Connect or Disconnect. + * + * {@link IncomingA2dp}: A2dp profile connection is in a transition + * state because of a incoming Connect or Disconnect. + * + * {@link OutgoingA2dp}: A2dp profile connection is in a transition + * state because of a outgoing Connect or Disconnect. + * + * Todo(): Write tests for this class, when the Android Mock support is completed. + * @hide + */ +public final class BluetoothDeviceProfileState extends HierarchicalStateMachine { + private static final String TAG = "BluetoothDeviceProfileState"; + private static final boolean DBG = true; //STOPSHIP - Change to false + + public static final int CONNECT_HFP_OUTGOING = 1; + public static final int CONNECT_HFP_INCOMING = 2; + public static final int CONNECT_A2DP_OUTGOING = 3; + public static final int CONNECT_A2DP_INCOMING = 4; + + public static final int DISCONNECT_HFP_OUTGOING = 5; + private static final int DISCONNECT_HFP_INCOMING = 6; + public static final int DISCONNECT_A2DP_OUTGOING = 7; + public static final int DISCONNECT_A2DP_INCOMING = 8; + + public static final int UNPAIR = 9; + public static final int AUTO_CONNECT_PROFILES = 10; + public static final int TRANSITION_TO_STABLE = 11; + + private static final int AUTO_CONNECT_DELAY = 6000; // 6 secs + + private BondedDevice mBondedDevice = new BondedDevice(); + private OutgoingHandsfree mOutgoingHandsfree = new OutgoingHandsfree(); + private IncomingHandsfree mIncomingHandsfree = new IncomingHandsfree(); + private IncomingA2dp mIncomingA2dp = new IncomingA2dp(); + private OutgoingA2dp mOutgoingA2dp = new OutgoingA2dp(); + + private Context mContext; + private BluetoothService mService; + private BluetoothA2dpService mA2dpService; + private BluetoothHeadset mHeadsetService; + private boolean mHeadsetServiceConnected; + + private BluetoothDevice mDevice; + private int mHeadsetState; + private int mA2dpState; + + private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + if (!device.equals(mDevice)) return; + + if (action.equals(BluetoothHeadset.ACTION_STATE_CHANGED)) { + int newState = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, 0); + int oldState = intent.getIntExtra(BluetoothHeadset.EXTRA_PREVIOUS_STATE, 0); + int initiator = intent.getIntExtra( + BluetoothHeadset.EXTRA_DISCONNECT_INITIATOR, + BluetoothHeadset.LOCAL_DISCONNECT); + mHeadsetState = newState; + if (newState == BluetoothHeadset.STATE_DISCONNECTED && + initiator == BluetoothHeadset.REMOTE_DISCONNECT) { + sendMessage(DISCONNECT_HFP_INCOMING); + } + if (newState == BluetoothHeadset.STATE_CONNECTED || + newState == BluetoothHeadset.STATE_DISCONNECTED) { + sendMessage(TRANSITION_TO_STABLE); + } + } else if (action.equals(BluetoothA2dp.ACTION_SINK_STATE_CHANGED)) { + int newState = intent.getIntExtra(BluetoothA2dp.EXTRA_SINK_STATE, 0); + int oldState = intent.getIntExtra(BluetoothA2dp.EXTRA_PREVIOUS_SINK_STATE, 0); + mA2dpState = newState; + if ((oldState == BluetoothA2dp.STATE_CONNECTED || + oldState == BluetoothA2dp.STATE_PLAYING) && + newState == BluetoothA2dp.STATE_DISCONNECTED) { + sendMessage(DISCONNECT_A2DP_INCOMING); + } + if (newState == BluetoothA2dp.STATE_CONNECTED || + newState == BluetoothA2dp.STATE_DISCONNECTED) { + sendMessage(TRANSITION_TO_STABLE); + } + } else if (action.equals(BluetoothDevice.ACTION_ACL_CONNECTED)) { + if (!getCurrentState().equals(mBondedDevice)) { + Log.e(TAG, "State is: " + getCurrentState()); + return; + } + Message msg = new Message(); + msg.what = AUTO_CONNECT_PROFILES; + sendMessageDelayed(msg, AUTO_CONNECT_DELAY); + } + } + }; + + private boolean isPhoneDocked(BluetoothDevice autoConnectDevice) { + // This works only because these broadcast intents are "sticky" + Intent i = mContext.registerReceiver(null, new IntentFilter(Intent.ACTION_DOCK_EVENT)); + if (i != null) { + int state = i.getIntExtra(Intent.EXTRA_DOCK_STATE, Intent.EXTRA_DOCK_STATE_UNDOCKED); + if (state != Intent.EXTRA_DOCK_STATE_UNDOCKED) { + BluetoothDevice device = i.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); + if (device != null && autoConnectDevice.equals(device)) { + return true; + } + } + } + return false; + } + + public BluetoothDeviceProfileState(Context context, String address, + BluetoothService service, BluetoothA2dpService a2dpService) { + super(address); + mContext = context; + mDevice = new BluetoothDevice(address); + mService = service; + mA2dpService = a2dpService; + + addState(mBondedDevice); + addState(mOutgoingHandsfree); + addState(mIncomingHandsfree); + addState(mIncomingA2dp); + addState(mOutgoingA2dp); + setInitialState(mBondedDevice); + + IntentFilter filter = new IntentFilter(); + // Fine-grained state broadcasts + filter.addAction(BluetoothA2dp.ACTION_SINK_STATE_CHANGED); + filter.addAction(BluetoothHeadset.ACTION_STATE_CHANGED); + filter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED); + + mContext.registerReceiver(mBroadcastReceiver, filter); + + HeadsetServiceListener l = new HeadsetServiceListener(); + } + + private class HeadsetServiceListener implements BluetoothHeadset.ServiceListener { + public HeadsetServiceListener() { + mHeadsetService = new BluetoothHeadset(mContext, this); + } + public void onServiceConnected() { + synchronized(BluetoothDeviceProfileState.this) { + mHeadsetServiceConnected = true; + } + } + public void onServiceDisconnected() { + synchronized(BluetoothDeviceProfileState.this) { + mHeadsetServiceConnected = false; + } + } + } + + private class BondedDevice extends HierarchicalState { + @Override + protected void enter() { + log("Entering ACL Connected state with: " + getCurrentMessage().what); + Message m = new Message(); + m.copyFrom(getCurrentMessage()); + sendMessageAtFrontOfQueue(m); + } + @Override + protected boolean processMessage(Message message) { + log("ACL Connected State -> Processing Message: " + message.what); + switch(message.what) { + case CONNECT_HFP_OUTGOING: + case DISCONNECT_HFP_OUTGOING: + transitionTo(mOutgoingHandsfree); + break; + case CONNECT_HFP_INCOMING: + transitionTo(mIncomingHandsfree); + break; + case DISCONNECT_HFP_INCOMING: + transitionTo(mIncomingHandsfree); + break; + case CONNECT_A2DP_OUTGOING: + case DISCONNECT_A2DP_OUTGOING: + transitionTo(mOutgoingA2dp); + break; + case CONNECT_A2DP_INCOMING: + case DISCONNECT_A2DP_INCOMING: + transitionTo(mIncomingA2dp); + break; + case UNPAIR: + if (mHeadsetState != BluetoothHeadset.STATE_DISCONNECTED) { + sendMessage(DISCONNECT_HFP_OUTGOING); + deferMessage(message); + break; + } else if (mA2dpState != BluetoothA2dp.STATE_DISCONNECTED) { + sendMessage(DISCONNECT_A2DP_OUTGOING); + deferMessage(message); + break; + } + processCommand(UNPAIR); + break; + case AUTO_CONNECT_PROFILES: + if (isPhoneDocked(mDevice)) { + // Don't auto connect to docks. + break; + } else if (!mHeadsetServiceConnected) { + deferMessage(message); + } else { + if (mHeadsetService.getPriority(mDevice) == + BluetoothHeadset.PRIORITY_AUTO_CONNECT && + !mHeadsetService.isConnected(mDevice)) { + mHeadsetService.connectHeadset(mDevice); + } + if (mA2dpService != null && + mA2dpService.getSinkPriority(mDevice) == + BluetoothA2dp.PRIORITY_AUTO_CONNECT && + mA2dpService.getConnectedSinks().length == 0) { + mA2dpService.connectSink(mDevice); + } + } + break; + case TRANSITION_TO_STABLE: + // ignore. + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + } + + private class OutgoingHandsfree extends HierarchicalState { + private boolean mStatus = false; + private int mCommand; + + @Override + protected void enter() { + log("Entering OutgoingHandsfree state with: " + getCurrentMessage().what); + mCommand = getCurrentMessage().what; + if (mCommand != CONNECT_HFP_OUTGOING && + mCommand != DISCONNECT_HFP_OUTGOING) { + Log.e(TAG, "Error: OutgoingHandsfree state with command:" + mCommand); + } + mStatus = processCommand(mCommand); + if (!mStatus) sendMessage(TRANSITION_TO_STABLE); + } + + @Override + protected boolean processMessage(Message message) { + log("OutgoingHandsfree State -> Processing Message: " + message.what); + Message deferMsg = new Message(); + int command = message.what; + switch(command) { + case CONNECT_HFP_OUTGOING: + if (command != mCommand) { + // Disconnect followed by a connect - defer + deferMessage(message); + } + break; + case CONNECT_HFP_INCOMING: + if (mCommand == CONNECT_HFP_OUTGOING) { + // Cancel outgoing connect, accept incoming + cancelCommand(CONNECT_HFP_OUTGOING); + transitionTo(mIncomingHandsfree); + } else { + // We have done the disconnect but we are not + // sure which state we are in at this point. + deferMessage(message); + } + break; + case CONNECT_A2DP_INCOMING: + // accept incoming A2DP, retry HFP_OUTGOING + transitionTo(mIncomingA2dp); + + if (mStatus) { + deferMsg.what = mCommand; + deferMessage(deferMsg); + } + break; + case CONNECT_A2DP_OUTGOING: + deferMessage(message); + break; + case DISCONNECT_HFP_OUTGOING: + if (mCommand == CONNECT_HFP_OUTGOING) { + // Cancel outgoing connect + cancelCommand(CONNECT_HFP_OUTGOING); + processCommand(DISCONNECT_HFP_OUTGOING); + } + // else ignore + break; + case DISCONNECT_HFP_INCOMING: + // When this happens the socket would be closed and the headset + // state moved to DISCONNECTED, cancel the outgoing thread. + // if it still is in CONNECTING state + cancelCommand(CONNECT_HFP_OUTGOING); + break; + case DISCONNECT_A2DP_OUTGOING: + deferMessage(message); + break; + case DISCONNECT_A2DP_INCOMING: + // Bluez will handle the disconnect. If because of this the outgoing + // handsfree connection has failed, then retry. + if (mStatus) { + deferMsg.what = mCommand; + deferMessage(deferMsg); + } + break; + case UNPAIR: + case AUTO_CONNECT_PROFILES: + deferMessage(message); + break; + case TRANSITION_TO_STABLE: + transitionTo(mBondedDevice); + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + } + + private class IncomingHandsfree extends HierarchicalState { + private boolean mStatus = false; + private int mCommand; + + @Override + protected void enter() { + log("Entering IncomingHandsfree state with: " + getCurrentMessage().what); + mCommand = getCurrentMessage().what; + if (mCommand != CONNECT_HFP_INCOMING && + mCommand != DISCONNECT_HFP_INCOMING) { + Log.e(TAG, "Error: IncomingHandsfree state with command:" + mCommand); + } + mStatus = processCommand(mCommand); + if (!mStatus) sendMessage(TRANSITION_TO_STABLE); + } + + @Override + protected boolean processMessage(Message message) { + log("IncomingHandsfree State -> Processing Message: " + message.what); + switch(message.what) { + case CONNECT_HFP_OUTGOING: + deferMessage(message); + break; + case CONNECT_HFP_INCOMING: + // Ignore + Log.e(TAG, "Error: Incoming connection with a pending incoming connection"); + break; + case CONNECT_A2DP_INCOMING: + // Serialize the commands. + deferMessage(message); + break; + case CONNECT_A2DP_OUTGOING: + deferMessage(message); + break; + case DISCONNECT_HFP_OUTGOING: + // We don't know at what state we are in the incoming HFP connection state. + // We can be changing from DISCONNECTED to CONNECTING, or + // from CONNECTING to CONNECTED, so serializing this command is + // the safest option. + deferMessage(message); + break; + case DISCONNECT_HFP_INCOMING: + // Nothing to do here, we will already be DISCONNECTED + // by this point. + break; + case DISCONNECT_A2DP_OUTGOING: + deferMessage(message); + break; + case DISCONNECT_A2DP_INCOMING: + // Bluez handles incoming A2DP disconnect. + // If this causes incoming HFP to fail, it is more of a headset problem + // since both connections are incoming ones. + break; + case UNPAIR: + case AUTO_CONNECT_PROFILES: + deferMessage(message); + break; + case TRANSITION_TO_STABLE: + transitionTo(mBondedDevice); + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + } + + private class OutgoingA2dp extends HierarchicalState { + private boolean mStatus = false; + private int mCommand; + + @Override + protected void enter() { + log("Entering OutgoingA2dp state with: " + getCurrentMessage().what); + mCommand = getCurrentMessage().what; + if (mCommand != CONNECT_A2DP_OUTGOING && + mCommand != DISCONNECT_A2DP_OUTGOING) { + Log.e(TAG, "Error: OutgoingA2DP state with command:" + mCommand); + } + mStatus = processCommand(mCommand); + if (!mStatus) sendMessage(TRANSITION_TO_STABLE); + } + + @Override + protected boolean processMessage(Message message) { + log("OutgoingA2dp State->Processing Message: " + message.what); + Message deferMsg = new Message(); + switch(message.what) { + case CONNECT_HFP_OUTGOING: + processCommand(CONNECT_HFP_OUTGOING); + + // Don't cancel A2DP outgoing as there is no guarantee it + // will get canceled. + // It might already be connected but we might not have got the + // A2DP_SINK_STATE_CHANGE. Hence, no point disconnecting here. + // The worst case, the connection will fail, retry. + // The same applies to Disconnecting an A2DP connection. + if (mStatus) { + deferMsg.what = mCommand; + deferMessage(deferMsg); + } + break; + case CONNECT_HFP_INCOMING: + processCommand(CONNECT_HFP_INCOMING); + + // Don't cancel A2DP outgoing as there is no guarantee + // it will get canceled. + // The worst case, the connection will fail, retry. + if (mStatus) { + deferMsg.what = mCommand; + deferMessage(deferMsg); + } + break; + case CONNECT_A2DP_INCOMING: + // Bluez will take care of conflicts between incoming and outgoing + // connections. + transitionTo(mIncomingA2dp); + break; + case CONNECT_A2DP_OUTGOING: + // Ignore + break; + case DISCONNECT_HFP_OUTGOING: + deferMessage(message); + break; + case DISCONNECT_HFP_INCOMING: + // At this point, we are already disconnected + // with HFP. Sometimes A2DP connection can + // fail due to the disconnection of HFP. So add a retry + // for the A2DP. + if (mStatus) { + deferMsg.what = mCommand; + deferMessage(deferMsg); + } + break; + case DISCONNECT_A2DP_OUTGOING: + processCommand(DISCONNECT_A2DP_OUTGOING); + break; + case DISCONNECT_A2DP_INCOMING: + // Ignore, will be handled by Bluez + break; + case UNPAIR: + case AUTO_CONNECT_PROFILES: + deferMessage(message); + break; + case TRANSITION_TO_STABLE: + transitionTo(mBondedDevice); + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + } + + private class IncomingA2dp extends HierarchicalState { + private boolean mStatus = false; + private int mCommand; + + @Override + protected void enter() { + log("Entering IncomingA2dp state with: " + getCurrentMessage().what); + mCommand = getCurrentMessage().what; + if (mCommand != CONNECT_A2DP_INCOMING && + mCommand != DISCONNECT_A2DP_INCOMING) { + Log.e(TAG, "Error: IncomingA2DP state with command:" + mCommand); + } + mStatus = processCommand(mCommand); + if (!mStatus) sendMessage(TRANSITION_TO_STABLE); + } + + @Override + protected boolean processMessage(Message message) { + log("IncomingA2dp State->Processing Message: " + message.what); + Message deferMsg = new Message(); + switch(message.what) { + case CONNECT_HFP_OUTGOING: + deferMessage(message); + break; + case CONNECT_HFP_INCOMING: + // Shouldn't happen, but serialize the commands. + deferMessage(message); + break; + case CONNECT_A2DP_INCOMING: + // ignore + break; + case CONNECT_A2DP_OUTGOING: + // Defer message and retry + deferMessage(message); + break; + case DISCONNECT_HFP_OUTGOING: + deferMessage(message); + break; + case DISCONNECT_HFP_INCOMING: + // Shouldn't happen but if does, we can handle it. + // Depends if the headset can handle it. + // Incoming A2DP will be handled by Bluez, Disconnect HFP + // the socket would have already been closed. + // ignore + break; + case DISCONNECT_A2DP_OUTGOING: + deferMessage(message); + break; + case DISCONNECT_A2DP_INCOMING: + // Ignore, will be handled by Bluez + break; + case UNPAIR: + case AUTO_CONNECT_PROFILES: + deferMessage(message); + break; + case TRANSITION_TO_STABLE: + transitionTo(mBondedDevice); + break; + default: + return NOT_HANDLED; + } + return HANDLED; + } + } + + + + synchronized void cancelCommand(int command) { + if (command == CONNECT_HFP_OUTGOING ) { + // Cancel the outgoing thread. + if (mHeadsetServiceConnected) { + mHeadsetService.cancelConnectThread(); + } + // HeadsetService is down. Phone process most likely crashed. + // The thread would have got killed. + } + } + + synchronized void deferHeadsetMessage(int command) { + Message msg = new Message(); + msg.what = command; + deferMessage(msg); + } + + synchronized boolean processCommand(int command) { + log("Processing command:" + command); + switch(command) { + case CONNECT_HFP_OUTGOING: + if (mHeadsetService != null) { + return mHeadsetService.connectHeadsetInternal(mDevice); + } + break; + case CONNECT_HFP_INCOMING: + if (!mHeadsetServiceConnected) { + deferHeadsetMessage(command); + } else if (mHeadsetState == BluetoothHeadset.STATE_CONNECTING) { + return mHeadsetService.acceptIncomingConnect(mDevice); + } else if (mHeadsetState == BluetoothHeadset.STATE_DISCONNECTED) { + return mHeadsetService.createIncomingConnect(mDevice); + } + break; + case CONNECT_A2DP_OUTGOING: + if (mA2dpService != null) { + return mA2dpService.connectSinkInternal(mDevice); + } + break; + case CONNECT_A2DP_INCOMING: + // ignore, Bluez takes care + return true; + case DISCONNECT_HFP_OUTGOING: + if (!mHeadsetServiceConnected) { + deferHeadsetMessage(command); + } else { + if (mHeadsetService.getPriority(mDevice) == + BluetoothHeadset.PRIORITY_AUTO_CONNECT) { + mHeadsetService.setPriority(mDevice, BluetoothHeadset.PRIORITY_ON); + } + return mHeadsetService.disconnectHeadsetInternal(mDevice); + } + break; + case DISCONNECT_HFP_INCOMING: + // ignore + return true; + case DISCONNECT_A2DP_INCOMING: + // ignore + return true; + case DISCONNECT_A2DP_OUTGOING: + if (mA2dpService != null) { + if (mA2dpService.getSinkPriority(mDevice) == + BluetoothA2dp.PRIORITY_AUTO_CONNECT) { + mA2dpService.setSinkPriority(mDevice, BluetoothHeadset.PRIORITY_ON); + } + return mA2dpService.disconnectSinkInternal(mDevice); + } + break; + case UNPAIR: + return mService.removeBondInternal(mDevice.getAddress()); + default: + Log.e(TAG, "Error: Unknown Command"); + } + return false; + } + + /*package*/ BluetoothDevice getDevice() { + return mDevice; + } + + private void log(String message) { + if (DBG) { + Log.i(TAG, "Device:" + mDevice + " Message:" + message); + } + } +} diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java index 95e61b6f6ace..4a91a8c90721 100644 --- a/core/java/android/bluetooth/BluetoothHeadset.java +++ b/core/java/android/bluetooth/BluetoothHeadset.java @@ -189,11 +189,11 @@ public final class BluetoothHeadset { * @return One of the STATE_ return codes, or STATE_ERROR if this proxy * object is currently not connected to the Headset service. */ - public int getState() { + public int getState(BluetoothDevice device) { if (DBG) log("getState()"); if (mService != null) { try { - return mService.getState(); + return mService.getState(device); } catch (RemoteException e) {Log.e(TAG, e.toString());} } else { Log.w(TAG, "Proxy not attached to service"); @@ -271,11 +271,11 @@ public final class BluetoothHeadset { * be made asynchornous. Returns false if this proxy object is * not currently connected to the Headset service. */ - public boolean disconnectHeadset() { + public boolean disconnectHeadset(BluetoothDevice device) { if (DBG) log("disconnectHeadset()"); if (mService != null) { try { - mService.disconnectHeadset(); + mService.disconnectHeadset(device); return true; } catch (RemoteException e) {Log.e(TAG, e.toString());} } else { @@ -395,7 +395,6 @@ public final class BluetoothHeadset { } return -1; } - /** * Indicates if current platform supports voice dialing over bluetooth SCO. * @return true if voice dialing over bluetooth is supported, false otherwise. @@ -406,6 +405,92 @@ public final class BluetoothHeadset { com.android.internal.R.bool.config_bluetooth_sco_off_call); } + /** + * Cancel the outgoing connection. + * @hide + */ + public boolean cancelConnectThread() { + if (DBG) log("cancelConnectThread"); + if (mService != null) { + try { + return mService.cancelConnectThread(); + } catch (RemoteException e) {Log.e(TAG, e.toString());} + } else { + Log.w(TAG, "Proxy not attached to service"); + if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); + } + return false; + } + + /** + * Accept the incoming connection. + * @hide + */ + public boolean acceptIncomingConnect(BluetoothDevice device) { + if (DBG) log("acceptIncomingConnect"); + if (mService != null) { + try { + return mService.acceptIncomingConnect(device); + } catch (RemoteException e) {Log.e(TAG, e.toString());} + } else { + Log.w(TAG, "Proxy not attached to service"); + if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); + } + return false; + } + + /** + * Create the connect thread the incoming connection. + * @hide + */ + public boolean createIncomingConnect(BluetoothDevice device) { + if (DBG) log("createIncomingConnect"); + if (mService != null) { + try { + return mService.createIncomingConnect(device); + } catch (RemoteException e) {Log.e(TAG, e.toString());} + } else { + Log.w(TAG, "Proxy not attached to service"); + if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); + } + return false; + } + + /** + * Connect to a Bluetooth Headset. + * Note: This is an internal function and shouldn't be exposed + * @hide + */ + public boolean connectHeadsetInternal(BluetoothDevice device) { + if (DBG) log("connectHeadsetInternal"); + if (mService != null) { + try { + return mService.connectHeadsetInternal(device); + } catch (RemoteException e) {Log.e(TAG, e.toString());} + } else { + Log.w(TAG, "Proxy not attached to service"); + if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); + } + return false; + } + + /** + * Disconnect a Bluetooth Headset. + * Note: This is an internal function and shouldn't be exposed + * @hide + */ + public boolean disconnectHeadsetInternal(BluetoothDevice device) { + if (DBG) log("disconnectHeadsetInternal"); + if (mService != null) { + try { + return mService.disconnectHeadsetInternal(device); + } catch (RemoteException e) {Log.e(TAG, e.toString());} + } else { + Log.w(TAG, "Proxy not attached to service"); + if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); + } + return false; + } private ServiceConnection mConnection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) { if (DBG) Log.d(TAG, "Proxy object connected"); diff --git a/core/java/android/bluetooth/BluetoothProfileState.java b/core/java/android/bluetooth/BluetoothProfileState.java new file mode 100644 index 000000000000..946dcaa01c86 --- /dev/null +++ b/core/java/android/bluetooth/BluetoothProfileState.java @@ -0,0 +1,144 @@ +/* + * 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 android.bluetooth; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Message; +import android.util.Log; + +import com.android.internal.util.HierarchicalState; +import com.android.internal.util.HierarchicalStateMachine; + +/** + * This state machine is used to serialize the connections + * to a particular profile. Currently, we only allow one device + * to be connected to a particular profile. + * States: + * {@link StableState} : No pending commands. Send the + * command to the appropriate remote device specific state machine. + * + * {@link PendingCommandState} : A profile connection / disconnection + * command is being executed. This will result in a profile state + * change. Defer all commands. + * @hide + */ + +public class BluetoothProfileState extends HierarchicalStateMachine { + private static final boolean DBG = true; // STOPSHIP - change to false. + private static final String TAG = "BluetoothProfileState"; + + public static int HFP = 0; + public static int A2DP = 1; + + private static int TRANSITION_TO_STABLE = 100; + + private int mProfile; + private BluetoothDevice mPendingDevice; + private PendingCommandState mPendingCommandState = new PendingCommandState(); + private StableState mStableState = new StableState(); + + private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + + if (action.equals(BluetoothHeadset.ACTION_STATE_CHANGED)) { + int newState = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, 0); + if (mProfile == HFP && (newState == BluetoothHeadset.STATE_CONNECTED || + newState == BluetoothHeadset.STATE_DISCONNECTED)) { + sendMessage(TRANSITION_TO_STABLE); + } + } else if (action.equals(BluetoothA2dp.ACTION_SINK_STATE_CHANGED)) { + int newState = intent.getIntExtra(BluetoothA2dp.EXTRA_SINK_STATE, 0); + if (mProfile == A2DP && (newState == BluetoothA2dp.STATE_CONNECTED || + newState == BluetoothA2dp.STATE_DISCONNECTED)) { + sendMessage(TRANSITION_TO_STABLE); + } + } + } + }; + + public BluetoothProfileState(Context context, int profile) { + super("BluetoothProfileState:" + profile); + mProfile = profile; + addState(mStableState); + addState(mPendingCommandState); + setInitialState(mStableState); + + IntentFilter filter = new IntentFilter(); + filter.addAction(BluetoothA2dp.ACTION_SINK_STATE_CHANGED); + filter.addAction(BluetoothHeadset.ACTION_STATE_CHANGED); + context.registerReceiver(mBroadcastReceiver, filter); + } + + private class StableState extends HierarchicalState { + @Override + protected void enter() { + log("Entering Stable State"); + mPendingDevice = null; + } + + @Override + protected boolean processMessage(Message msg) { + if (msg.what != TRANSITION_TO_STABLE) { + transitionTo(mPendingCommandState); + } + return true; + } + } + + private class PendingCommandState extends HierarchicalState { + @Override + protected void enter() { + log("Entering PendingCommandState State"); + dispatchMessage(getCurrentMessage()); + } + + @Override + protected boolean processMessage(Message msg) { + if (msg.what == TRANSITION_TO_STABLE) { + transitionTo(mStableState); + } else { + dispatchMessage(msg); + } + return true; + } + + private void dispatchMessage(Message msg) { + BluetoothDeviceProfileState deviceProfileMgr = + (BluetoothDeviceProfileState)msg.obj; + int cmd = msg.arg1; + if (mPendingDevice == null || mPendingDevice.equals(deviceProfileMgr.getDevice())) { + mPendingDevice = deviceProfileMgr.getDevice(); + deviceProfileMgr.sendMessage(cmd); + } else { + Message deferMsg = new Message(); + deferMsg.arg1 = cmd; + deferMsg.obj = deviceProfileMgr; + deferMessage(deferMsg); + } + } + } + + private void log(String message) { + if (DBG) { + Log.i(TAG, "Message:" + message); + } + } +} diff --git a/core/java/android/bluetooth/IBluetooth.aidl b/core/java/android/bluetooth/IBluetooth.aidl index 08687795d932..6dd8dd64d844 100644 --- a/core/java/android/bluetooth/IBluetooth.aidl +++ b/core/java/android/bluetooth/IBluetooth.aidl @@ -44,12 +44,15 @@ interface IBluetooth boolean startDiscovery(); boolean cancelDiscovery(); boolean isDiscovering(); + byte[] readOutOfBandData(); boolean createBond(in String address); + boolean createBondOutOfBand(in String address, in byte[] hash, in byte[] randomizer); boolean cancelBondProcess(in String address); boolean removeBond(in String address); String[] listBonds(); int getBondState(in String address); + boolean setDeviceOutOfBandData(in String address, in byte[] hash, in byte[] randomizer); String getRemoteName(in String address); int getRemoteClass(in String address); @@ -60,6 +63,7 @@ interface IBluetooth boolean setPin(in String address, in byte[] pin); boolean setPasskey(in String address, int passkey); boolean setPairingConfirmation(in String address, boolean confirm); + boolean setRemoteOutOfBandData(in String addres); boolean cancelPairingUserInput(in String address); boolean setTrust(in String address, in boolean value); @@ -68,4 +72,8 @@ interface IBluetooth int addRfcommServiceRecord(in String serviceName, in ParcelUuid uuid, int channel, IBinder b); void removeServiceRecord(int handle); + + boolean connectHeadset(String address); + boolean disconnectHeadset(String address); + boolean notifyIncomingConnection(String address); } diff --git a/core/java/android/bluetooth/IBluetoothA2dp.aidl b/core/java/android/bluetooth/IBluetoothA2dp.aidl index 168fe3b252da..40f10583b637 100644 --- a/core/java/android/bluetooth/IBluetoothA2dp.aidl +++ b/core/java/android/bluetooth/IBluetoothA2dp.aidl @@ -33,4 +33,7 @@ interface IBluetoothA2dp { int getSinkState(in BluetoothDevice device); boolean setSinkPriority(in BluetoothDevice device, int priority); int getSinkPriority(in BluetoothDevice device); + + boolean connectSinkInternal(in BluetoothDevice device); + boolean disconnectSinkInternal(in BluetoothDevice device); } diff --git a/core/java/android/bluetooth/IBluetoothHeadset.aidl b/core/java/android/bluetooth/IBluetoothHeadset.aidl index 6cccd506e269..d96f0ca0de8d 100644 --- a/core/java/android/bluetooth/IBluetoothHeadset.aidl +++ b/core/java/android/bluetooth/IBluetoothHeadset.aidl @@ -24,14 +24,20 @@ import android.bluetooth.BluetoothDevice; * {@hide} */ interface IBluetoothHeadset { - int getState(); + int getState(in BluetoothDevice device); BluetoothDevice getCurrentHeadset(); boolean connectHeadset(in BluetoothDevice device); - void disconnectHeadset(); + void disconnectHeadset(in BluetoothDevice device); boolean isConnected(in BluetoothDevice device); boolean startVoiceRecognition(); boolean stopVoiceRecognition(); boolean setPriority(in BluetoothDevice device, int priority); int getPriority(in BluetoothDevice device); int getBatteryUsageHint(); + + boolean createIncomingConnect(in BluetoothDevice device); + boolean acceptIncomingConnect(in BluetoothDevice device); + boolean cancelConnectThread(); + boolean connectHeadsetInternal(in BluetoothDevice device); + boolean disconnectHeadsetInternal(in BluetoothDevice device); } diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java index 9b9f796a1607..dc4e9c4e6525 100644 --- a/core/java/android/content/ContentProvider.java +++ b/core/java/android/content/ContentProvider.java @@ -86,6 +86,7 @@ public abstract class ContentProvider implements ComponentCallbacks { private String mReadPermission; private String mWritePermission; private PathPermission[] mPathPermissions; + private boolean mExported; private Transport mTransport = new Transport(); @@ -257,9 +258,9 @@ public abstract class ContentProvider implements ComponentCallbacks { final Context context = getContext(); final String rperm = getReadPermission(); final int pid = Binder.getCallingPid(); - if (rperm == null + if (mExported && (rperm == null || context.checkPermission(rperm, pid, uid) - == PackageManager.PERMISSION_GRANTED) { + == PackageManager.PERMISSION_GRANTED)) { return; } @@ -303,9 +304,9 @@ public abstract class ContentProvider implements ComponentCallbacks { final Context context = getContext(); final String wperm = getWritePermission(); final int pid = Binder.getCallingPid(); - if (wperm == null + if (mExported && (wperm == null || context.checkPermission(wperm, pid, uid) - == PackageManager.PERMISSION_GRANTED) { + == PackageManager.PERMISSION_GRANTED)) { return true; } @@ -786,6 +787,7 @@ public abstract class ContentProvider implements ComponentCallbacks { setReadPermission(info.readPermission); setWritePermission(info.writePermission); setPathPermissions(info.pathPermissions); + mExported = info.exported; } ContentProvider.this.onCreate(); } diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java index b4718ab6ef2b..69f7611a4d59 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -18,6 +18,7 @@ package android.content; import android.accounts.Account; import android.app.ActivityThread; +import android.app.AppGlobals; import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.AssetFileDescriptor; import android.content.res.Resources; @@ -1306,7 +1307,7 @@ public abstract class ContentResolver { // ActivityThread.currentPackageName() only returns non-null if the // current thread is an application main thread. This parameter tells // us whether an event loop is blocked, and if so, which app it is. - String blockingPackage = ActivityThread.currentPackageName(); + String blockingPackage = AppGlobals.getInitialPackage(); EventLog.writeEvent( EventLogTags.CONTENT_QUERY_SAMPLE, @@ -1329,7 +1330,7 @@ public abstract class ContentResolver { } } } - String blockingPackage = ActivityThread.currentPackageName(); + String blockingPackage = AppGlobals.getInitialPackage(); EventLog.writeEvent( EventLogTags.CONTENT_UPDATE_SAMPLE, uri.toString(), diff --git a/core/java/android/content/ContentService.java b/core/java/android/content/ContentService.java index 377e38373cb6..fc2dfc07b0e1 100644 --- a/core/java/android/content/ContentService.java +++ b/core/java/android/content/ContentService.java @@ -537,6 +537,9 @@ public final class ContentService extends IContentService.Stub { // Look to see if the proper child already exists String segment = getUriSegment(uri, index); + if (segment == null) { + throw new IllegalArgumentException("Invalid Uri (" + uri + ") used for observer"); + } int N = mChildren.size(); for (int i = 0; i < N; i++) { ObserverNode node = mChildren.get(i); diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 30822d4ade1e..0100550e810d 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -1206,6 +1206,8 @@ public abstract class Context { * for management of input methods. * <dt> {@link #UI_MODE_SERVICE} ("uimode") * <dd> An {@link android.app.UiModeManager} for controlling UI modes. + * <dt> {@link #DOWNLOAD_SERVICE} ("download") + * <dd> A {@link android.net.DownloadManager} for requesting HTTP downloads * </dl> * * <p>Note: System services obtained via this API may be closely associated with @@ -1253,6 +1255,8 @@ public abstract class Context { * @see android.view.inputmethod.InputMethodManager * @see #UI_MODE_SERVICE * @see android.app.UiModeManager + * @see #DOWNLOAD_SERVICE + * @see android.net.DownloadManager */ public abstract Object getSystemService(String name); @@ -1372,9 +1376,8 @@ public abstract class Context { public static final String SENSOR_SERVICE = "sensor"; /** - * @hide * Use with {@link #getSystemService} to retrieve a {@link - * android.os.storage.StorageManager} for accesssing system storage + * android.os.storage.StorageManager} for accessing system storage * functions. * * @see #getSystemService @@ -1535,6 +1538,23 @@ public abstract class Context { public static final String UI_MODE_SERVICE = "uimode"; /** + * Use with {@link #getSystemService} to retrieve a + * {@link android.net.DownloadManager} for requesting HTTP downloads. + * + * @see #getSystemService + */ + public static final String DOWNLOAD_SERVICE = "download"; + + /** + * Use with {@link #getSystemService} to retrieve a + * {@link android.net.sip.SipManager} for accessing the SIP related service. + * + * @see #getSystemService + */ + /** @hide */ + public static final String SIP_SERVICE = "sip"; + + /** * Determine whether the given permission is allowed for a particular * process and user ID running in the system. * diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 2acc4a05792a..7154aeee08d4 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -1567,6 +1567,30 @@ public class Intent implements Parcelable, Cloneable { @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_DEVICE_STORAGE_OK = "android.intent.action.DEVICE_STORAGE_OK"; /** + * Broadcast Action: A sticky broadcast that indicates a memory full + * condition on the device. This is intended for activities that want + * to be able to fill the data partition completely, leaving only + * enough free space to prevent system-wide SQLite failures. + * + * <p class="note">This is a protected intent that can only be sent + * by the system. + * + * {@hide} + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_DEVICE_STORAGE_FULL = "android.intent.action.DEVICE_STORAGE_FULL"; + /** + * Broadcast Action: Indicates memory full condition on the device + * no longer exists. + * + * <p class="note">This is a protected intent that can only be sent + * by the system. + * + * {@hide} + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_DEVICE_STORAGE_NOT_FULL = "android.intent.action.DEVICE_STORAGE_NOT_FULL"; + /** * Broadcast Action: Indicates low memory condition notification acknowledged by user * and package management should be started. * This is triggered by the user from the ACTION_DEVICE_STORAGE_LOW diff --git a/core/java/android/content/IntentSender.java b/core/java/android/content/IntentSender.java index e182021f8ef1..007a71515214 100644 --- a/core/java/android/content/IntentSender.java +++ b/core/java/android/content/IntentSender.java @@ -16,6 +16,7 @@ package android.content; +import android.app.ActivityManagerNative; import android.content.Context; import android.content.Intent; import android.content.IIntentSender; @@ -170,6 +171,25 @@ public class IntentSender implements Parcelable { } /** + * Return the package name of the application that created this + * IntentSender, that is the identity under which you will actually be + * sending the Intent. The returned string is supplied by the system, so + * that an application can not spoof its package. + * + * @return The package name of the PendingIntent, or null if there is + * none associated with it. + */ + public String getTargetPackage() { + try { + return ActivityManagerNative.getDefault() + .getPackageForIntentSender(mTarget); + } catch (RemoteException e) { + // Should never happen. + return null; + } + } + + /** * Comparison operator on two IntentSender objects, such that true * is returned then they both represent the same operation from the * same package. diff --git a/core/java/android/content/SharedPreferences.java b/core/java/android/content/SharedPreferences.java index a15e29e754e0..1484204fb5bd 100644 --- a/core/java/android/content/SharedPreferences.java +++ b/core/java/android/content/SharedPreferences.java @@ -40,7 +40,9 @@ public interface SharedPreferences { /** * Called when a shared preference is changed, added, or removed. This * may be called even if a preference is set to its existing value. - * + * + * <p>This callback will be run on your main thread. + * * @param sharedPreferences The {@link SharedPreferences} that received * the change. * @param key The key of the preference that was changed, added, or @@ -52,13 +54,13 @@ public interface SharedPreferences { /** * Interface used for modifying values in a {@link SharedPreferences} * object. All changes you make in an editor are batched, and not copied - * back to the original {@link SharedPreferences} or persistent storage - * until you call {@link #commit}. + * back to the original {@link SharedPreferences} until you call {@link #commit} + * or {@link #apply} */ public interface Editor { /** * Set a String value in the preferences editor, to be written back once - * {@link #commit} is called. + * {@link #commit} or {@link #apply} are called. * * @param key The name of the preference to modify. * @param value The new value for the preference. @@ -70,7 +72,7 @@ public interface SharedPreferences { /** * Set an int value in the preferences editor, to be written back once - * {@link #commit} is called. + * {@link #commit} or {@link #apply} are called. * * @param key The name of the preference to modify. * @param value The new value for the preference. @@ -82,7 +84,7 @@ public interface SharedPreferences { /** * Set a long value in the preferences editor, to be written back once - * {@link #commit} is called. + * {@link #commit} or {@link #apply} are called. * * @param key The name of the preference to modify. * @param value The new value for the preference. @@ -94,7 +96,7 @@ public interface SharedPreferences { /** * Set a float value in the preferences editor, to be written back once - * {@link #commit} is called. + * {@link #commit} or {@link #apply} are called. * * @param key The name of the preference to modify. * @param value The new value for the preference. @@ -106,7 +108,7 @@ public interface SharedPreferences { /** * Set a boolean value in the preferences editor, to be written back - * once {@link #commit} is called. + * once {@link #commit} or {@link #apply} are called. * * @param key The name of the preference to modify. * @param value The new value for the preference. @@ -151,14 +153,44 @@ public interface SharedPreferences { * {@link SharedPreferences} object it is editing. This atomically * performs the requested modifications, replacing whatever is currently * in the SharedPreferences. - * + * * <p>Note that when two editors are modifying preferences at the same * time, the last one to call commit wins. - * + * + * <p>If you don't care about the return value and you're + * using this from your application's main thread, consider + * using {@link #apply} instead. + * * @return Returns true if the new values were successfully written * to persistent storage. */ boolean commit(); + + /** + * Commit your preferences changes back from this Editor to the + * {@link SharedPreferences} object it is editing. This atomically + * performs the requested modifications, replacing whatever is currently + * in the SharedPreferences. + * + * <p>Note that when two editors are modifying preferences at the same + * time, the last one to call apply wins. + * + * <p>Unlike {@link #commit}, which writes its preferences out + * to persistent storage synchronously, {@link #apply} + * commits its changes to the in-memory + * {@link SharedPreferences} immediately but starts an + * asynchronous commit to disk and you won't be notified of + * any failures. If another editor on this + * {@link SharedPreferences} does a regular {@link #commit} + * while a {@link #apply} is still outstanding, the + * {@link #commit} will block until all async commits are + * completed as well as the commit itself. + * + * <p>If you call this from an {@link android.app.Activity}, + * the base class will wait for any async commits to finish in + * its {@link android.app.Activity#onPause}.</p> + */ + void apply(); } /** diff --git a/core/java/android/content/SyncManager.java b/core/java/android/content/SyncManager.java index d0b67cca417a..26b6ad70b351 100644 --- a/core/java/android/content/SyncManager.java +++ b/core/java/android/content/SyncManager.java @@ -45,6 +45,7 @@ import android.os.Process; import android.os.RemoteException; import android.os.SystemClock; import android.os.SystemProperties; +import android.os.WorkSource; import android.provider.Settings; import android.text.format.DateUtils; import android.text.format.Time; @@ -126,8 +127,8 @@ public class SyncManager implements OnAccountsUpdateListener { private static final int INITIALIZATION_UNBIND_DELAY_MS = 5000; - private static final String SYNC_WAKE_LOCK = "SyncManagerSyncWakeLock"; - private static final String HANDLE_SYNC_ALARM_WAKE_LOCK = "SyncManagerHandleSyncAlarmWakeLock"; + private static final String SYNC_WAKE_LOCK = "*sync*"; + private static final String HANDLE_SYNC_ALARM_WAKE_LOCK = "SyncManagerHandleSyncAlarm"; private Context mContext; @@ -1700,10 +1701,12 @@ public class SyncManager implements OnAccountsUpdateListener { mActiveSyncContext.close(); mActiveSyncContext = null; mSyncStorageEngine.setActiveSync(mActiveSyncContext); + mSyncWakeLock.setWorkSource(null); runStateIdle(); return; } + mSyncWakeLock.setWorkSource(new WorkSource(syncAdapterInfo.uid)); mSyncWakeLock.acquire(); // no need to schedule an alarm, as that will be done by our caller. diff --git a/core/java/android/content/SyncStorageEngine.java b/core/java/android/content/SyncStorageEngine.java index 98a499303e26..6413cec32459 100644 --- a/core/java/android/content/SyncStorageEngine.java +++ b/core/java/android/content/SyncStorageEngine.java @@ -317,7 +317,9 @@ public class SyncStorageEngine extends Handler { if (sSyncStorageEngine != null) { return; } - File dataDir = Environment.getDataDirectory(); + // This call will return the correct directory whether Encrypted File Systems is + // enabled or not. + File dataDir = Environment.getSecureDataDirectory(); sSyncStorageEngine = new SyncStorageEngine(context, dataDir); } diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java index 238b840ddc12..395c392b12b2 100644 --- a/core/java/android/content/pm/ActivityInfo.java +++ b/core/java/android/content/pm/ActivityInfo.java @@ -151,12 +151,14 @@ public class ActivityInfo extends ComponentInfo public static final int FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS = 0x0100; /** * Options that have been set in the activity declaration in the - * manifest: {@link #FLAG_MULTIPROCESS}, + * manifest. + * These include: + * {@link #FLAG_MULTIPROCESS}, * {@link #FLAG_FINISH_ON_TASK_LAUNCH}, {@link #FLAG_CLEAR_TASK_ON_LAUNCH}, * {@link #FLAG_ALWAYS_RETAIN_TASK_STATE}, * {@link #FLAG_STATE_NOT_NEEDED}, {@link #FLAG_EXCLUDE_FROM_RECENTS}, * {@link #FLAG_ALLOW_TASK_REPARENTING}, {@link #FLAG_NO_HISTORY}, - * {@link #FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS}. + * {@link #FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS}, */ public int flags; diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index 1577f9e3e28d..0db954db35f0 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -174,7 +174,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { * Value for {@link #flags}: true when the application's window can be * increased in size for larger screens. Corresponds to * {@link android.R.styleable#AndroidManifestSupportsScreens_largeScreens - * android:smallScreens}. + * android:largeScreens}. */ public static final int FLAG_SUPPORTS_LARGE_SCREENS = 1<<11; @@ -252,6 +252,9 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { public static final int FLAG_RESTORE_ANY_VERSION = 1<<17; /** + * Value for {@link #flags}: Set to true if the application has been + * installed using the forward lock option. + * * Value for {@link #flags}: Set to true if the application is * currently installed on external/removable/unprotected storage. Such * applications may not be available if their storage is not currently @@ -262,20 +265,44 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { public static final int FLAG_EXTERNAL_STORAGE = 1<<18; /** + * Value for {@link #flags}: true when the application's window can be + * increased in size for extra large screens. Corresponds to + * {@link android.R.styleable#AndroidManifestSupportsScreens_xlargeScreens + * android:xlargeScreens}. + * @hide + */ + public static final int FLAG_SUPPORTS_XLARGE_SCREENS = 1<<19; + + /** + * Value for {@link #flags}: this is true if the application has set + * its android:neverEncrypt to true, false otherwise. It is used to specify + * that this package specifically "opts-out" of a secured file system solution, + * and will always store its data in-the-clear. + * + * {@hide} + */ + public static final int FLAG_NEVER_ENCRYPT = 1<<30; + + /** * Value for {@link #flags}: Set to true if the application has been * installed using the forward lock option. * * {@hide} */ - public static final int FLAG_FORWARD_LOCK = 1<<20; + public static final int FLAG_FORWARD_LOCK = 1<<29; /** - * Value for {@link #flags}: Set to true if the application is - * native-debuggable, i.e. embeds a gdbserver binary in its .apk + * Value for {@link #flags}: set to <code>true</code> if the application + * has reported that it is heavy-weight, and thus can not participate in + * the normal application lifecycle. + * + * <p>Comes from the + * {@link android.R.styleable#AndroidManifestApplication_heavyWeight android:heavyWeight} + * attribute of the <application> tag. * * {@hide} */ - public static final int FLAG_NATIVE_DEBUGGABLE = 1<<21; + public static final int CANT_SAVE_STATE = 1<<27; /** * Flags associated with the application. Any combination of @@ -285,7 +312,8 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { * {@link #FLAG_ALLOW_CLEAR_USER_DATA}, {@link #FLAG_UPDATED_SYSTEM_APP}, * {@link #FLAG_TEST_ONLY}, {@link #FLAG_SUPPORTS_SMALL_SCREENS}, * {@link #FLAG_SUPPORTS_NORMAL_SCREENS}, - * {@link #FLAG_SUPPORTS_LARGE_SCREENS}, {@link #FLAG_RESIZEABLE_FOR_SCREENS}, + * {@link #FLAG_SUPPORTS_LARGE_SCREENS}, + * {@link #FLAG_RESIZEABLE_FOR_SCREENS}, * {@link #FLAG_SUPPORTS_SCREEN_DENSITIES}, {@link #FLAG_VM_SAFE_MODE} */ public int flags = 0; @@ -324,7 +352,12 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { * data. */ public String dataDir; - + + /** + * Full path to the directory where native JNI libraries are stored. + */ + public String nativeLibraryDir; + /** * The kernel user-ID that has been assigned to this application; * currently this is not a unique ID (multiple applications can have @@ -356,15 +389,17 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { if (permission != null) { pw.println(prefix + "permission=" + permission); } - pw.println(prefix + "uid=" + uid + " taskAffinity=" + taskAffinity); - if (theme != 0) { - pw.println(prefix + "theme=0x" + Integer.toHexString(theme)); - } - pw.println(prefix + "flags=0x" + Integer.toHexString(flags) - + " processName=" + processName); + pw.println(prefix + "processName=" + processName); + pw.println(prefix + "taskAffinity=" + taskAffinity); + pw.println(prefix + "uid=" + uid + " flags=0x" + Integer.toHexString(flags) + + " theme=0x" + Integer.toHexString(theme)); pw.println(prefix + "sourceDir=" + sourceDir); - pw.println(prefix + "publicSourceDir=" + publicSourceDir); - pw.println(prefix + "resourceDirs=" + resourceDirs); + if (!sourceDir.equals(publicSourceDir)) { + pw.println(prefix + "publicSourceDir=" + publicSourceDir); + } + if (resourceDirs != null) { + pw.println(prefix + "resourceDirs=" + resourceDirs); + } pw.println(prefix + "dataDir=" + dataDir); if (sharedLibraryFiles != null) { pw.println(prefix + "sharedLibraryFiles=" + sharedLibraryFiles); @@ -415,6 +450,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { flags = orig.flags; sourceDir = orig.sourceDir; publicSourceDir = orig.publicSourceDir; + nativeLibraryDir = orig.nativeLibraryDir; resourceDirs = orig.resourceDirs; sharedLibraryFiles = orig.sharedLibraryFiles; dataDir = orig.dataDir; @@ -446,6 +482,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { dest.writeInt(flags); dest.writeString(sourceDir); dest.writeString(publicSourceDir); + dest.writeString(nativeLibraryDir); dest.writeStringArray(resourceDirs); dest.writeStringArray(sharedLibraryFiles); dest.writeString(dataDir); @@ -477,6 +514,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { flags = source.readInt(); sourceDir = source.readString(); publicSourceDir = source.readString(); + nativeLibraryDir = source.readString(); resourceDirs = source.readStringArray(); sharedLibraryFiles = source.readStringArray(); dataDir = source.readString(); @@ -517,7 +555,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { public void disableCompatibilityMode() { flags |= (FLAG_SUPPORTS_LARGE_SCREENS | FLAG_SUPPORTS_NORMAL_SCREENS | FLAG_SUPPORTS_SMALL_SCREENS | FLAG_RESIZEABLE_FOR_SCREENS | - FLAG_SUPPORTS_SCREEN_DENSITIES); + FLAG_SUPPORTS_SCREEN_DENSITIES | FLAG_SUPPORTS_XLARGE_SCREENS); } /** diff --git a/core/java/android/content/pm/ComponentInfo.java b/core/java/android/content/pm/ComponentInfo.java index cafe3725ec0a..f16c4efdfaf4 100644 --- a/core/java/android/content/pm/ComponentInfo.java +++ b/core/java/android/content/pm/ComponentInfo.java @@ -157,6 +157,14 @@ public class ComponentInfo extends PackageItemInfo { /** * @hide */ + @Override + protected Drawable loadDefaultLogo(PackageManager pm) { + return applicationInfo.loadLogo(pm); + } + + /** + * @hide + */ @Override protected ApplicationInfo getApplicationInfo() { return applicationInfo; } diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index 9939478b79b5..4cff3bb21538 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -68,6 +68,8 @@ interface IPackageManager { ServiceInfo getServiceInfo(in ComponentName className, int flags); + ProviderInfo getProviderInfo(in ComponentName className, int flags); + int checkPermission(String permName, String pkgName); int checkUidPermission(String permName, int uid); @@ -319,4 +321,6 @@ interface IPackageManager { boolean setInstallLocation(int loc); int getInstallLocation(); + + void setPackageObbPath(String packageName, String path); } diff --git a/core/java/android/content/pm/InstrumentationInfo.java b/core/java/android/content/pm/InstrumentationInfo.java index 3e868a7a7600..ea47e8e6e0d5 100644 --- a/core/java/android/content/pm/InstrumentationInfo.java +++ b/core/java/android/content/pm/InstrumentationInfo.java @@ -50,7 +50,14 @@ public class InstrumentationInfo extends PackageItemInfo implements Parcelable { * data. */ public String dataDir; - + + /** + * Full path to the directory where the native JNI libraries are stored. + * + * {@hide} + */ + public String nativeLibraryDir; + /** * Specifies whether or not this instrumentation will handle profiling. */ @@ -68,6 +75,7 @@ public class InstrumentationInfo extends PackageItemInfo implements Parcelable { sourceDir = orig.sourceDir; publicSourceDir = orig.publicSourceDir; dataDir = orig.dataDir; + nativeLibraryDir = orig.nativeLibraryDir; handleProfiling = orig.handleProfiling; functionalTest = orig.functionalTest; } @@ -88,6 +96,7 @@ public class InstrumentationInfo extends PackageItemInfo implements Parcelable { dest.writeString(sourceDir); dest.writeString(publicSourceDir); dest.writeString(dataDir); + dest.writeString(nativeLibraryDir); dest.writeInt((handleProfiling == false) ? 0 : 1); dest.writeInt((functionalTest == false) ? 0 : 1); } @@ -108,6 +117,7 @@ public class InstrumentationInfo extends PackageItemInfo implements Parcelable { sourceDir = source.readString(); publicSourceDir = source.readString(); dataDir = source.readString(); + nativeLibraryDir = source.readString(); handleProfiling = source.readInt() != 0; functionalTest = source.readInt() != 0; } diff --git a/core/java/android/content/pm/PackageItemInfo.java b/core/java/android/content/pm/PackageItemInfo.java index 14c068006c11..d73aaf6a2e98 100644 --- a/core/java/android/content/pm/PackageItemInfo.java +++ b/core/java/android/content/pm/PackageItemInfo.java @@ -67,6 +67,14 @@ public class PackageItemInfo { public int icon; /** + * A drawable resource identifier (in the package's resources) of this + * component's logo. Logos may be larger/wider than icons and are + * displayed by certain UI elements in place of a name or name/icon + * combination. From the "logo" attribute or, if not set, 0. + */ + public int logo; + + /** * Additional meta-data associated with this component. This field * will only be filled in if you set the * {@link PackageManager#GET_META_DATA} flag when requesting the info. @@ -84,6 +92,7 @@ public class PackageItemInfo { nonLocalizedLabel = orig.nonLocalizedLabel; if (nonLocalizedLabel != null) nonLocalizedLabel = nonLocalizedLabel.toString().trim(); icon = orig.icon; + logo = orig.logo; metaData = orig.metaData; } @@ -152,6 +161,42 @@ public class PackageItemInfo { } /** + * Retrieve the current graphical logo associated with this item. This + * will call back on the given PackageManager to load the logo from + * the application. + * + * @param pm A PackageManager from which the logo can be loaded; usually + * the PackageManager from which you originally retrieved this item. + * + * @return Returns a Drawable containing the item's logo. If the item + * does not have a logo, this method will return null. + */ + public Drawable loadLogo(PackageManager pm) { + if (logo != 0) { + Drawable d = pm.getDrawable(packageName, logo, getApplicationInfo()); + if (d != null) { + return d; + } + } + return loadDefaultLogo(pm); + } + + /** + * Retrieve the default graphical logo associated with this item. + * + * @param pm A PackageManager from which the logo can be loaded; usually + * the PackageManager from which you originally retrieved this item. + * + * @return Returns a Drawable containing the item's default logo + * or null if no default logo is available. + * + * @hide + */ + protected Drawable loadDefaultLogo(PackageManager pm) { + return null; + } + + /** * Load an XML resource attached to the meta-data of this item. This will * retrieved the name meta-data entry, and if defined call back on the * given PackageManager to load its XML file from the application. @@ -196,6 +241,7 @@ public class PackageItemInfo { dest.writeInt(labelRes); TextUtils.writeToParcel(nonLocalizedLabel, dest, parcelableFlags); dest.writeInt(icon); + dest.writeInt(logo); dest.writeBundle(metaData); } @@ -206,6 +252,7 @@ public class PackageItemInfo { nonLocalizedLabel = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source); icon = source.readInt(); + logo = source.readInt(); metaData = source.readBundle(); } diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 68b44e705fce..ef720139c4f6 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -610,6 +610,16 @@ public abstract class PackageManager { public static final int MOVE_FAILED_INTERNAL_ERROR = -6; /** + * Error code that is passed to the {@link IPackageMoveObserver} by + * {@link #movePackage(android.net.Uri, IPackageMoveObserver)} if the + * specified package already has an operation pending in the + * {@link PackageHandler} queue. + * + * @hide + */ + public static final int MOVE_FAILED_OPERATION_PENDING = -7; + + /** * Flag parameter for {@link #movePackage} to indicate that * the package should be moved to internal storage if its * been installed on external media. @@ -656,6 +666,13 @@ public abstract class PackageManager { /** * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device has a front facing camera. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_CAMERA_FRONT = "android.hardware.camera.front"; + + /** + * Feature for {@link #getSystemAvailableFeatures} and * {@link #hasSystemFeature}: The device supports one or more methods of * reporting current location. */ @@ -688,6 +705,14 @@ public abstract class PackageManager { /** * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device can communicate using Near-Field + * Communications (NFC). + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_NFC = "android.hardware.nfc"; + + /** + * Feature for {@link #getSystemAvailableFeatures} and * {@link #hasSystemFeature}: The device includes a magnetometer (compass). */ @SdkConstant(SdkConstantType.FEATURE) @@ -735,7 +760,21 @@ public abstract class PackageManager { */ @SdkConstant(SdkConstantType.FEATURE) public static final String FEATURE_TELEPHONY_GSM = "android.hardware.telephony.gsm"; - + + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The SIP API is enabled on the device. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_SIP = "android.software.sip"; + + /** + * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device supports SIP-based VOIP. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_SIP_VOIP = "android.software.sip.voip"; + /** * Feature for {@link #getSystemAvailableFeatures} and * {@link #hasSystemFeature}: The device's display has a touch screen. @@ -762,6 +801,15 @@ public abstract class PackageManager { /** * Feature for {@link #getSystemAvailableFeatures} and + * {@link #hasSystemFeature}: The device's touch screen is capable of + * tracking a full hand of fingers fully independently -- that is, 5 or + * more simultaneous independent pointers. + */ + @SdkConstant(SdkConstantType.FEATURE) + public static final String FEATURE_TOUCHSCREEN_MULTITOUCH_JAZZHAND = "android.hardware.touchscreen.multitouch.jazzhand"; + + /** + * Feature for {@link #getSystemAvailableFeatures} and * {@link #hasSystemFeature}: The device supports live wallpapers. */ @SdkConstant(SdkConstantType.FEATURE) @@ -979,9 +1027,9 @@ public abstract class PackageManager { * <p>Throws {@link NameNotFoundException} if an activity with the given * class name can not be found on the system. * - * @param className The full name (i.e. - * com.google.apps.contacts.ContactsList) of an Activity - * class. + * @param component The full component name (i.e. + * com.google.apps.contacts/com.google.apps.contacts.ContactsList) of an Activity + * class. * @param flags Additional option flags. Use any combination of * {@link #GET_META_DATA}, {@link #GET_SHARED_LIBRARY_FILES}, * to modify the data (in ApplicationInfo) returned. @@ -992,7 +1040,7 @@ public abstract class PackageManager { * @see #GET_META_DATA * @see #GET_SHARED_LIBRARY_FILES */ - public abstract ActivityInfo getActivityInfo(ComponentName className, + public abstract ActivityInfo getActivityInfo(ComponentName component, int flags) throws NameNotFoundException; /** @@ -1002,9 +1050,9 @@ public abstract class PackageManager { * <p>Throws {@link NameNotFoundException} if a receiver with the given * class name can not be found on the system. * - * @param className The full name (i.e. - * com.google.apps.contacts.CalendarAlarm) of a Receiver - * class. + * @param component The full component name (i.e. + * com.google.apps.calendar/com.google.apps.calendar.CalendarAlarm) of a Receiver + * class. * @param flags Additional option flags. Use any combination of * {@link #GET_META_DATA}, {@link #GET_SHARED_LIBRARY_FILES}, * to modify the data returned. @@ -1015,7 +1063,7 @@ public abstract class PackageManager { * @see #GET_META_DATA * @see #GET_SHARED_LIBRARY_FILES */ - public abstract ActivityInfo getReceiverInfo(ComponentName className, + public abstract ActivityInfo getReceiverInfo(ComponentName component, int flags) throws NameNotFoundException; /** @@ -1025,9 +1073,9 @@ public abstract class PackageManager { * <p>Throws {@link NameNotFoundException} if a service with the given * class name can not be found on the system. * - * @param className The full name (i.e. - * com.google.apps.media.BackgroundPlayback) of a Service - * class. + * @param component The full component name (i.e. + * com.google.apps.media/com.google.apps.media.BackgroundPlayback) of a Service + * class. * @param flags Additional option flags. Use any combination of * {@link #GET_META_DATA}, {@link #GET_SHARED_LIBRARY_FILES}, * to modify the data returned. @@ -1037,7 +1085,29 @@ public abstract class PackageManager { * @see #GET_META_DATA * @see #GET_SHARED_LIBRARY_FILES */ - public abstract ServiceInfo getServiceInfo(ComponentName className, + public abstract ServiceInfo getServiceInfo(ComponentName component, + int flags) throws NameNotFoundException; + + /** + * Retrieve all of the information we know about a particular content + * provider class. + * + * <p>Throws {@link NameNotFoundException} if a provider with the given + * class name can not be found on the system. + * + * @param component The full component name (i.e. + * com.google.providers.media/com.google.providers.media.MediaProvider) of a + * ContentProvider class. + * @param flags Additional option flags. Use any combination of + * {@link #GET_META_DATA}, {@link #GET_SHARED_LIBRARY_FILES}, + * to modify the data returned. + * + * @return ProviderInfo containing information about the service. + * + * @see #GET_META_DATA + * @see #GET_SHARED_LIBRARY_FILES + */ + public abstract ProviderInfo getProviderInfo(ComponentName component, int flags) throws NameNotFoundException; /** @@ -1597,6 +1667,79 @@ public abstract class PackageManager { throws NameNotFoundException; /** + * Retrieve the logo associated with an activity. Given the full name of + * an activity, retrieves the information about it and calls + * {@link ComponentInfo#loadLogo ComponentInfo.loadLogo()} to return its logo. + * If the activity can not be found, NameNotFoundException is thrown. + * + * @param activityName Name of the activity whose logo is to be retrieved. + * + * @return Returns the image of the logo or null if the activity has no + * logo specified. + * + * @throws NameNotFoundException Thrown if the resources for the given + * activity could not be loaded. + * + * @see #getActivityLogo(Intent) + */ + public abstract Drawable getActivityLogo(ComponentName activityName) + throws NameNotFoundException; + + /** + * Retrieve the logo associated with an Intent. If intent.getClassName() is + * set, this simply returns the result of + * getActivityLogo(intent.getClassName()). Otherwise it resolves the intent's + * component and returns the logo associated with the resolved component. + * If intent.getClassName() can not be found or the Intent can not be resolved + * to a component, NameNotFoundException is thrown. + * + * @param intent The intent for which you would like to retrieve a logo. + * + * @return Returns the image of the logo, or null if the activity has no + * logo specified. + * + * @throws NameNotFoundException Thrown if the resources for application + * matching the given intent could not be loaded. + * + * @see #getActivityLogo(ComponentName) + */ + public abstract Drawable getActivityLogo(Intent intent) + throws NameNotFoundException; + + /** + * Retrieve the logo associated with an application. If it has not specified + * a logo, this method returns null. + * + * @param info Information about application being queried. + * + * @return Returns the image of the logo, or null if no logo is specified + * by the application. + * + * @see #getApplicationLogo(String) + */ + public abstract Drawable getApplicationLogo(ApplicationInfo info); + + /** + * Retrieve the logo associated with an application. Given the name of the + * application's package, retrieves the information about it and calls + * getApplicationLogo() to return its logo. If the application can not be + * found, NameNotFoundException is thrown. + * + * @param packageName Name of the package whose application logo is to be + * retrieved. + * + * @return Returns the image of the logo, or null if no application logo + * has been specified. + * + * @throws NameNotFoundException Thrown if the resources for the given + * application could not be loaded. + * + * @see #getApplicationLogo(ApplicationInfo) + */ + public abstract Drawable getApplicationLogo(String packageName) + throws NameNotFoundException; + + /** * Retrieve text from a package. This is a low-level API used by * the various package manager info structures (such as * {@link ComponentInfo} to implement retrieval of their associated @@ -2113,4 +2256,17 @@ public abstract class PackageManager { */ public abstract void movePackage( String packageName, IPackageMoveObserver observer, int flags); + + /** + * Sets the Opaque Binary Blob (OBB) file location. + * <p> + * NOTE: The existence or format of this file is not currently checked, but + * it may be in the future. + * + * @param packageName Name of the package with which to associate the .obb + * file + * @param path Path on the filesystem to the .obb file + * @hide + */ + public abstract void setPackageObbPath(String packageName, String path); } diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 2a20a2d0e363..e20cb5ee4134 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -105,17 +105,19 @@ public class PackageParser { final int nameRes; final int labelRes; final int iconRes; + final int logoRes; String tag; TypedArray sa; ParsePackageItemArgs(Package _owner, String[] _outError, - int _nameRes, int _labelRes, int _iconRes) { + int _nameRes, int _labelRes, int _iconRes, int _logoRes) { owner = _owner; outError = _outError; nameRes = _nameRes; labelRes = _labelRes; iconRes = _iconRes; + logoRes = _logoRes; } } @@ -127,10 +129,10 @@ public class PackageParser { int flags; ParseComponentArgs(Package _owner, String[] _outError, - int _nameRes, int _labelRes, int _iconRes, + int _nameRes, int _labelRes, int _iconRes, int _logoRes, String[] _sepProcesses, int _processRes, int _descriptionRes, int _enabledRes) { - super(_owner, _outError, _nameRes, _labelRes, _iconRes); + super(_owner, _outError, _nameRes, _labelRes, _iconRes, _logoRes); sepProcesses = _sepProcesses; processRes = _processRes; descriptionRes = _descriptionRes; @@ -585,7 +587,7 @@ public class PackageParser { * location from the apk location at the given file path. * @param packageFilePath file location of the apk * @param flags Special parse flags - * @return PackageLite object with package information. + * @return PackageLite object with package information or null on failure. */ public static PackageLite parsePackageLite(String packageFilePath, int flags) { XmlResourceParser parser = null; @@ -789,6 +791,7 @@ public class PackageParser { int supportsSmallScreens = 1; int supportsNormalScreens = 1; int supportsLargeScreens = 1; + int supportsXLargeScreens = 1; int resizeable = 1; int anyDensity = 1; @@ -996,9 +999,12 @@ public class PackageParser { supportsLargeScreens = sa.getInteger( com.android.internal.R.styleable.AndroidManifestSupportsScreens_largeScreens, supportsLargeScreens); + supportsXLargeScreens = sa.getInteger( + com.android.internal.R.styleable.AndroidManifestSupportsScreens_xlargeScreens, + supportsXLargeScreens); resizeable = sa.getInteger( com.android.internal.R.styleable.AndroidManifestSupportsScreens_resizeable, - supportsLargeScreens); + resizeable); anyDensity = sa.getInteger( com.android.internal.R.styleable.AndroidManifestSupportsScreens_anyDensity, anyDensity); @@ -1132,6 +1138,11 @@ public class PackageParser { >= android.os.Build.VERSION_CODES.DONUT)) { pkg.applicationInfo.flags |= ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS; } + if (supportsXLargeScreens < 0 || (supportsXLargeScreens > 0 + && pkg.applicationInfo.targetSdkVersion + >= android.os.Build.VERSION_CODES.GINGERBREAD)) { + pkg.applicationInfo.flags |= ApplicationInfo.FLAG_SUPPORTS_XLARGE_SCREENS; + } if (resizeable < 0 || (resizeable > 0 && pkg.applicationInfo.targetSdkVersion >= android.os.Build.VERSION_CODES.DONUT)) { @@ -1241,7 +1252,8 @@ public class PackageParser { "<permission-group>", sa, com.android.internal.R.styleable.AndroidManifestPermissionGroup_name, com.android.internal.R.styleable.AndroidManifestPermissionGroup_label, - com.android.internal.R.styleable.AndroidManifestPermissionGroup_icon)) { + com.android.internal.R.styleable.AndroidManifestPermissionGroup_icon, + com.android.internal.R.styleable.AndroidManifestPermissionGroup_logo)) { sa.recycle(); mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; return null; @@ -1276,7 +1288,8 @@ public class PackageParser { "<permission>", sa, com.android.internal.R.styleable.AndroidManifestPermission_name, com.android.internal.R.styleable.AndroidManifestPermission_label, - com.android.internal.R.styleable.AndroidManifestPermission_icon)) { + com.android.internal.R.styleable.AndroidManifestPermission_icon, + com.android.internal.R.styleable.AndroidManifestPermission_logo)) { sa.recycle(); mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; return null; @@ -1329,7 +1342,8 @@ public class PackageParser { "<permission-tree>", sa, com.android.internal.R.styleable.AndroidManifestPermissionTree_name, com.android.internal.R.styleable.AndroidManifestPermissionTree_label, - com.android.internal.R.styleable.AndroidManifestPermissionTree_icon)) { + com.android.internal.R.styleable.AndroidManifestPermissionTree_icon, + com.android.internal.R.styleable.AndroidManifestPermissionTree_logo)) { sa.recycle(); mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; return null; @@ -1373,7 +1387,8 @@ public class PackageParser { mParseInstrumentationArgs = new ParsePackageItemArgs(owner, outError, com.android.internal.R.styleable.AndroidManifestInstrumentation_name, com.android.internal.R.styleable.AndroidManifestInstrumentation_label, - com.android.internal.R.styleable.AndroidManifestInstrumentation_icon); + com.android.internal.R.styleable.AndroidManifestInstrumentation_icon, + com.android.internal.R.styleable.AndroidManifestInstrumentation_logo); mParseInstrumentationArgs.tag = "<instrumentation>"; } @@ -1485,6 +1500,8 @@ public class PackageParser { ai.icon = sa.getResourceId( com.android.internal.R.styleable.AndroidManifestApplication_icon, 0); + ai.logo = sa.getResourceId( + com.android.internal.R.styleable.AndroidManifestApplication_logo, 0); ai.theme = sa.getResourceId( com.android.internal.R.styleable.AndroidManifestApplication_theme, 0); ai.descriptionRes = sa.getResourceId( @@ -1542,6 +1559,12 @@ public class PackageParser { ai.flags |= ApplicationInfo.FLAG_TEST_ONLY; } + if (sa.getBoolean( + com.android.internal.R.styleable.AndroidManifestApplication_neverEncrypt, + false)) { + ai.flags |= ApplicationInfo.FLAG_NEVER_ENCRYPT; + } + String str; str = sa.getNonConfigurationString( com.android.internal.R.styleable.AndroidManifestApplication_permission, 0); @@ -1577,6 +1600,20 @@ public class PackageParser { ai.enabled = sa.getBoolean( com.android.internal.R.styleable.AndroidManifestApplication_enabled, true); + + if (false) { + if (sa.getBoolean( + com.android.internal.R.styleable.AndroidManifestApplication_cantSaveState, + false)) { + ai.flags |= ApplicationInfo.CANT_SAVE_STATE; + + // A heavy-weight application can not be in a custom process. + // We can do direct compare because we intern all strings. + if (ai.processName != null && ai.processName != ai.packageName) { + outError[0] = "cantSaveState applications can not use custom processes"; + } + } + } } sa.recycle(); @@ -1705,7 +1742,7 @@ public class PackageParser { private boolean parsePackageItemInfo(Package owner, PackageItemInfo outInfo, String[] outError, String tag, TypedArray sa, - int nameRes, int labelRes, int iconRes) { + int nameRes, int labelRes, int iconRes, int logoRes) { String name = sa.getNonConfigurationString(nameRes, 0); if (name == null) { outError[0] = tag + " does not specify android:name"; @@ -1723,6 +1760,11 @@ public class PackageParser { outInfo.icon = iconVal; outInfo.nonLocalizedLabel = null; } + + int logoVal = sa.getResourceId(logoRes, 0); + if (logoVal != 0) { + outInfo.logo = logoVal; + } TypedValue v = sa.peekValue(labelRes); if (v != null && (outInfo.labelRes=v.resourceId) == 0) { @@ -1745,6 +1787,7 @@ public class PackageParser { com.android.internal.R.styleable.AndroidManifestActivity_name, com.android.internal.R.styleable.AndroidManifestActivity_label, com.android.internal.R.styleable.AndroidManifestActivity_icon, + com.android.internal.R.styleable.AndroidManifestActivity_logo, mSeparateProcesses, com.android.internal.R.styleable.AndroidManifestActivity_process, com.android.internal.R.styleable.AndroidManifestActivity_description, @@ -1860,6 +1903,14 @@ public class PackageParser { sa.recycle(); + if (receiver && (owner.applicationInfo.flags&ApplicationInfo.CANT_SAVE_STATE) != 0) { + // A heavy-weight application can not have receives in its main process + // We can do direct compare because we intern all strings. + if (a.info.processName == owner.packageName) { + outError[0] = "Heavy-weight applications can not have receivers in main process"; + } + } + if (outError[0] != null) { return null; } @@ -1947,6 +1998,7 @@ public class PackageParser { com.android.internal.R.styleable.AndroidManifestActivityAlias_name, com.android.internal.R.styleable.AndroidManifestActivityAlias_label, com.android.internal.R.styleable.AndroidManifestActivityAlias_icon, + com.android.internal.R.styleable.AndroidManifestActivityAlias_logo, mSeparateProcesses, 0, com.android.internal.R.styleable.AndroidManifestActivityAlias_description, @@ -1980,6 +2032,7 @@ public class PackageParser { info.configChanges = target.info.configChanges; info.flags = target.info.flags; info.icon = target.info.icon; + info.logo = target.info.logo; info.labelRes = target.info.labelRes; info.nonLocalizedLabel = target.info.nonLocalizedLabel; info.launchMode = target.info.launchMode; @@ -2074,6 +2127,7 @@ public class PackageParser { com.android.internal.R.styleable.AndroidManifestProvider_name, com.android.internal.R.styleable.AndroidManifestProvider_label, com.android.internal.R.styleable.AndroidManifestProvider_icon, + com.android.internal.R.styleable.AndroidManifestProvider_logo, mSeparateProcesses, com.android.internal.R.styleable.AndroidManifestProvider_process, com.android.internal.R.styleable.AndroidManifestProvider_description, @@ -2139,6 +2193,15 @@ public class PackageParser { sa.recycle(); + if ((owner.applicationInfo.flags&ApplicationInfo.CANT_SAVE_STATE) != 0) { + // A heavy-weight application can not have providers in its main process + // We can do direct compare because we intern all strings. + if (p.info.processName == owner.packageName) { + outError[0] = "Heavy-weight applications can not have providers in main process"; + return null; + } + } + if (cpname == null) { outError[0] = "<provider> does not incude authorities attribute"; return null; @@ -2337,6 +2400,7 @@ public class PackageParser { com.android.internal.R.styleable.AndroidManifestService_name, com.android.internal.R.styleable.AndroidManifestService_label, com.android.internal.R.styleable.AndroidManifestService_icon, + com.android.internal.R.styleable.AndroidManifestService_logo, mSeparateProcesses, com.android.internal.R.styleable.AndroidManifestService_process, com.android.internal.R.styleable.AndroidManifestService_description, @@ -2370,6 +2434,15 @@ public class PackageParser { sa.recycle(); + if ((owner.applicationInfo.flags&ApplicationInfo.CANT_SAVE_STATE) != 0) { + // A heavy-weight application can not have services in its main process + // We can do direct compare because we intern all strings. + if (s.info.processName == owner.packageName) { + outError[0] = "Heavy-weight applications can not have services in main process"; + return null; + } + } + int outerDepth = parser.getDepth(); int type; while ((type=parser.next()) != XmlPullParser.END_DOCUMENT @@ -2540,6 +2613,9 @@ public class PackageParser { outInfo.icon = sa.getResourceId( com.android.internal.R.styleable.AndroidManifestIntentFilter_icon, 0); + + outInfo.logo = sa.getResourceId( + com.android.internal.R.styleable.AndroidManifestIntentFilter_logo, 0); sa.recycle(); @@ -2706,9 +2782,15 @@ public class PackageParser { // For use by package manager to keep track of where it has done dexopt. public boolean mDidDexOpt; + // User set enabled state. + public int mSetEnabled = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; + // Additional data supplied by callers. public Object mExtras; - + + // Whether an operation is currently pending on this package + public boolean mOperationPending; + /* * Applications hardware preferences */ @@ -2801,6 +2883,11 @@ public class PackageParser { outInfo.icon = iconVal; outInfo.nonLocalizedLabel = null; } + + int logoVal = args.sa.getResourceId(args.logoRes, 0); + if (logoVal != 0) { + outInfo.logo = logoVal; + } TypedValue v = args.sa.peekValue(args.labelRes); if (v != null && (outInfo.labelRes=v.resourceId) == 0) { @@ -2927,6 +3014,12 @@ public class PackageParser { } private static boolean copyNeeded(int flags, Package p, Bundle metaData) { + if (p.mSetEnabled != PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) { + boolean enabled = p.mSetEnabled == PackageManager.COMPONENT_ENABLED_STATE_ENABLED; + if (p.applicationInfo.enabled != enabled) { + return true; + } + } if ((flags & PackageManager.GET_META_DATA) != 0 && (metaData != null || p.mAppMetaData != null)) { return true; @@ -2960,6 +3053,7 @@ public class PackageParser { if (!sCompatibilityModeEnabled) { ai.disableCompatibilityMode(); } + ai.enabled = p.mSetEnabled == PackageManager.COMPONENT_ENABLED_STATE_ENABLED; return ai; } @@ -3135,6 +3229,7 @@ public class PackageParser { public int labelRes; public CharSequence nonLocalizedLabel; public int icon; + public int logo; } public final static class ActivityIntentInfo extends IntentInfo { diff --git a/core/java/android/content/pm/Signature.java b/core/java/android/content/pm/Signature.java index 1bb38577fbca..d4e5cc13b1c1 100644 --- a/core/java/android/content/pm/Signature.java +++ b/core/java/android/content/pm/Signature.java @@ -20,6 +20,7 @@ import android.content.ComponentName; import android.os.Parcel; import android.os.Parcelable; +import java.lang.ref.SoftReference; import java.util.Arrays; /** @@ -30,7 +31,7 @@ public class Signature implements Parcelable { private final byte[] mSignature; private int mHashCode; private boolean mHaveHashCode; - private String mString; + private SoftReference<String> mStringRef; /** * Create Signature from an existing raw byte array. @@ -96,10 +97,13 @@ public class Signature implements Parcelable { * cached so future calls will return the same String. */ public String toCharsString() { - if (mString != null) return mString; - String str = new String(toChars()); - mString = str; - return mString; + String str = mStringRef == null ? null : mStringRef.get(); + if (str != null) { + return str; + } + str = new String(toChars()); + mStringRef = new SoftReference<String>(str); + return str; } /** diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java index 1070f08776a8..73d9458fbec3 100644 --- a/core/java/android/content/res/AssetManager.java +++ b/core/java/android/content/res/AssetManager.java @@ -70,6 +70,7 @@ public final class AssetManager { // For communication with native code. private int mObject; + private int mNObject; // used by the NDK private StringBlock mStringBlocks[] = null; diff --git a/core/java/android/content/res/CompatibilityInfo.java b/core/java/android/content/res/CompatibilityInfo.java index 11c67cc18110..d0ba590159f4 100644 --- a/core/java/android/content/res/CompatibilityInfo.java +++ b/core/java/android/content/res/CompatibilityInfo.java @@ -99,7 +99,22 @@ public class CompatibilityInfo { */ private static final int CONFIGURED_LARGE_SCREENS = 16; - private static final int SCALING_EXPANDABLE_MASK = SCALING_REQUIRED | EXPANDABLE | LARGE_SCREENS; + /** + * A flag mask to indicates that the application supports xlarge screens. + * The flag is set to true if + * 1) Application declares it supports xlarge screens in manifest file using <supports-screens> or + * 2) The screen size is not xlarge + * {@see compatibilityFlag} + */ + private static final int XLARGE_SCREENS = 32; + + /** + * A flag mask to tell if the application supports xlarge screens. This differs + * from XLARGE_SCREENS in that the application that does not support xlarge + * screens will be marked as supporting them if the current screen is not + * xlarge. + */ + private static final int CONFIGURED_XLARGE_SCREENS = 64; /** * The effective screen density we have selected for this application. @@ -127,6 +142,9 @@ public class CompatibilityInfo { if ((appInfo.flags & ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS) != 0) { mCompatibilityFlags |= LARGE_SCREENS | CONFIGURED_LARGE_SCREENS; } + if ((appInfo.flags & ApplicationInfo.FLAG_SUPPORTS_XLARGE_SCREENS) != 0) { + mCompatibilityFlags |= XLARGE_SCREENS | CONFIGURED_XLARGE_SCREENS; + } if ((appInfo.flags & ApplicationInfo.FLAG_RESIZEABLE_FOR_SCREENS) != 0) { mCompatibilityFlags |= EXPANDABLE | CONFIGURED_EXPANDABLE; } @@ -157,6 +175,7 @@ public class CompatibilityInfo { this(ApplicationInfo.FLAG_SUPPORTS_SMALL_SCREENS | ApplicationInfo.FLAG_SUPPORTS_NORMAL_SCREENS | ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS + | ApplicationInfo.FLAG_SUPPORTS_XLARGE_SCREENS | ApplicationInfo.FLAG_RESIZEABLE_FOR_SCREENS, EXPANDABLE | CONFIGURED_EXPANDABLE, DisplayMetrics.DENSITY_DEVICE, @@ -196,6 +215,17 @@ public class CompatibilityInfo { } /** + * Sets large screen bit in the compatibility flag. + */ + public void setXLargeScreens(boolean expandable) { + if (expandable) { + mCompatibilityFlags |= CompatibilityInfo.XLARGE_SCREENS; + } else { + mCompatibilityFlags &= ~CompatibilityInfo.XLARGE_SCREENS; + } + } + + /** * @return true if the application is configured to be expandable. */ public boolean isConfiguredExpandable() { @@ -210,6 +240,13 @@ public class CompatibilityInfo { } /** + * @return true if the application is configured to be expandable. + */ + public boolean isConfiguredXLargeScreens() { + return (mCompatibilityFlags & CompatibilityInfo.CONFIGURED_XLARGE_SCREENS) != 0; + } + + /** * @return true if the scaling is required */ public boolean isScalingRequired() { diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java index 1a0c867edf19..5a3dd415b9fc 100644 --- a/core/java/android/content/res/Configuration.java +++ b/core/java/android/content/res/Configuration.java @@ -62,6 +62,8 @@ public final class Configuration implements Parcelable, Comparable<Configuration public static final int SCREENLAYOUT_SIZE_SMALL = 0x01; public static final int SCREENLAYOUT_SIZE_NORMAL = 0x02; public static final int SCREENLAYOUT_SIZE_LARGE = 0x03; + /** @hide */ + public static final int SCREENLAYOUT_SIZE_XLARGE = 0x04; public static final int SCREENLAYOUT_LONG_MASK = 0x30; public static final int SCREENLAYOUT_LONG_UNDEFINED = 0x00; @@ -82,7 +84,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration * <p>The {@link #SCREENLAYOUT_SIZE_MASK} bits define the overall size * of the screen. They may be one of * {@link #SCREENLAYOUT_SIZE_SMALL}, {@link #SCREENLAYOUT_SIZE_NORMAL}, - * or {@link #SCREENLAYOUT_SIZE_LARGE}. + * {@link #SCREENLAYOUT_SIZE_LARGE}. * * <p>The {@link #SCREENLAYOUT_LONG_MASK} defines whether the screen * is wider/taller than normal. They may be one of diff --git a/core/java/android/os/storage/IMountShutdownObserver.aidl b/core/java/android/content/res/ObbInfo.aidl index 0aa8a4592ab6..636ad6ae415e 100644..100755 --- a/core/java/android/os/storage/IMountShutdownObserver.aidl +++ b/core/java/android/content/res/ObbInfo.aidl @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009 The Android Open Source Project + * 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. @@ -14,20 +14,6 @@ * limitations under the License. */ -package android.os.storage; +package android.content.res; -/** - * Callback class for receiving events related - * to shutdown. - * - * @hide - For internal consumption only. - */ -interface IMountShutdownObserver { - /** - * This method is called when the shutdown - * of MountService completed. - * @param statusCode indicates success or failure - * of the shutdown. - */ - void onShutDownComplete(int statusCode); -} +parcelable ObbInfo; diff --git a/core/java/android/content/res/ObbInfo.java b/core/java/android/content/res/ObbInfo.java new file mode 100644 index 000000000000..838c5ff5a764 --- /dev/null +++ b/core/java/android/content/res/ObbInfo.java @@ -0,0 +1,89 @@ +/* + * 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 android.content.res; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Basic information about a Opaque Binary Blob (OBB) that reflects + * the info from the footer on the OBB file. + * @hide + */ +public class ObbInfo implements Parcelable { + /** Flag noting that this OBB is an overlay patch for a base OBB. */ + public static final int OBB_OVERLAY = 1 << 0; + + /** + * The name of the package to which the OBB file belongs. + */ + public String packageName; + + /** + * The version of the package to which the OBB file belongs. + */ + public int version; + + /** + * The flags relating to the OBB. + */ + public int flags; + + public ObbInfo() { + } + + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("ObbInfo{"); + sb.append(Integer.toHexString(System.identityHashCode(this))); + sb.append(" packageName="); + sb.append(packageName); + sb.append(",version="); + sb.append(version); + sb.append(",flags="); + sb.append(flags); + sb.append('}'); + return sb.toString(); + } + + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel dest, int parcelableFlags) { + dest.writeString(packageName); + dest.writeInt(version); + dest.writeInt(flags); + } + + public static final Parcelable.Creator<ObbInfo> CREATOR + = new Parcelable.Creator<ObbInfo>() { + public ObbInfo createFromParcel(Parcel source) { + return new ObbInfo(source); + } + + public ObbInfo[] newArray(int size) { + return new ObbInfo[size]; + } + }; + + private ObbInfo(Parcel source) { + packageName = source.readString(); + version = source.readInt(); + flags = source.readInt(); + } +} diff --git a/core/java/android/content/res/ObbScanner.java b/core/java/android/content/res/ObbScanner.java new file mode 100644 index 000000000000..eb383c360308 --- /dev/null +++ b/core/java/android/content/res/ObbScanner.java @@ -0,0 +1,40 @@ +/* + * 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 android.content.res; + +/** + * Class to scan Opaque Binary Blob (OBB) files. + * @hide + */ +public class ObbScanner { + // Don't allow others to instantiate this class + private ObbScanner() {} + + public static ObbInfo getObbInfo(String filePath) { + if (filePath == null) { + return null; + } + + ObbInfo obbInfo = new ObbInfo(); + if (!getObbInfo_native(filePath, obbInfo)) { + throw new IllegalArgumentException("Could not read OBB file: " + filePath); + } + return obbInfo; + } + + private native static boolean getObbInfo_native(String filePath, ObbInfo obbInfo); +} diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java index 0608cc02bf3d..9bb3b75124f3 100644 --- a/core/java/android/content/res/Resources.java +++ b/core/java/android/content/res/Resources.java @@ -1288,7 +1288,7 @@ public class Resources { height = mMetrics.widthPixels; } int keyboardHidden = mConfiguration.keyboardHidden; - if (keyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO + if (keyboardHidden == Configuration.KEYBOARDHIDDEN_NO && mConfiguration.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_YES) { keyboardHidden = Configuration.KEYBOARDHIDDEN_SOFT; diff --git a/core/java/android/content/res/TypedArray.java b/core/java/android/content/res/TypedArray.java index b0c149d18934..37fdeb652742 100644 --- a/core/java/android/content/res/TypedArray.java +++ b/core/java/android/content/res/TypedArray.java @@ -708,9 +708,7 @@ public class TypedArray { outValue.resourceId = data[index+AssetManager.STYLE_RESOURCE_ID]; outValue.changingConfigurations = data[index+AssetManager.STYLE_CHANGING_CONFIGURATIONS]; outValue.density = data[index+AssetManager.STYLE_DENSITY]; - if (type == TypedValue.TYPE_STRING) { - outValue.string = loadStringValueAt(index); - } + outValue.string = (type == TypedValue.TYPE_STRING) ? loadStringValueAt(index) : null; return true; } diff --git a/core/java/android/database/Cursor.java b/core/java/android/database/Cursor.java index 79178f421453..6539156ee41d 100644 --- a/core/java/android/database/Cursor.java +++ b/core/java/android/database/Cursor.java @@ -25,6 +25,9 @@ import java.util.Map; /** * This interface provides random read-write access to the result set returned * by a database query. + * + * Cursor implementations are not required to be synchronized so code using a Cursor from multiple + * threads should perform its own synchronization when using the Cursor. */ public interface Cursor { /** diff --git a/core/java/android/database/DatabaseUtils.java b/core/java/android/database/DatabaseUtils.java index 9bfbb74eeb27..66406cac439a 100644 --- a/core/java/android/database/DatabaseUtils.java +++ b/core/java/android/database/DatabaseUtils.java @@ -108,7 +108,7 @@ public class DatabaseUtils { * @see Parcel#readException */ public static final void readExceptionFromParcel(Parcel reply) { - int code = reply.readInt(); + int code = reply.readExceptionCode(); if (code == 0) return; String msg = reply.readString(); DatabaseUtils.readExceptionFromParcel(reply, msg, code); @@ -116,7 +116,7 @@ public class DatabaseUtils { public static void readExceptionWithFileNotFoundExceptionFromParcel( Parcel reply) throws FileNotFoundException { - int code = reply.readInt(); + int code = reply.readExceptionCode(); if (code == 0) return; String msg = reply.readString(); if (code == 1) { @@ -128,7 +128,7 @@ public class DatabaseUtils { public static void readExceptionWithOperationApplicationExceptionFromParcel( Parcel reply) throws OperationApplicationException { - int code = reply.readInt(); + int code = reply.readExceptionCode(); if (code == 0) return; String msg = reply.readString(); if (code == 10) { diff --git a/core/java/android/database/sqlite/SQLiteCursor.java b/core/java/android/database/sqlite/SQLiteCursor.java index 6e5b3e168516..c7e58faf26b2 100644 --- a/core/java/android/database/sqlite/SQLiteCursor.java +++ b/core/java/android/database/sqlite/SQLiteCursor.java @@ -36,6 +36,9 @@ import java.util.concurrent.locks.ReentrantLock; /** * A Cursor implementation that exposes results from a query on a * {@link SQLiteDatabase}. + * + * SQLiteCursor is not internally synchronized so code using a SQLiteCursor from multiple + * threads should perform its own synchronization when using the SQLiteCursor. */ public class SQLiteCursor extends AbstractWindowedCursor { static final String TAG = "Cursor"; diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java index fb5507dc8f83..cdc9bbb6a510 100644 --- a/core/java/android/database/sqlite/SQLiteDatabase.java +++ b/core/java/android/database/sqlite/SQLiteDatabase.java @@ -19,6 +19,7 @@ package android.database.sqlite; import com.google.android.collect.Maps; import android.app.ActivityThread; +import android.app.AppGlobals; import android.content.ContentValues; import android.database.Cursor; import android.database.DatabaseUtils; @@ -33,6 +34,8 @@ import android.util.EventLog; import android.util.Log; import android.util.Pair; +import dalvik.system.BlockGuard; + import java.io.File; import java.lang.ref.WeakReference; import java.text.SimpleDateFormat; @@ -1134,7 +1137,8 @@ public class SQLiteDatabase extends SQLiteClosable { * * @param sql The raw SQL statement, may contain ? for unknown values to be * bound later. - * @return a pre-compiled statement object. + * @return A pre-compiled {@link SQLiteStatement} object. Note that + * {@link SQLiteStatement}s are not synchronized, see the documentation for more details. */ public SQLiteStatement compileStatement(String sql) throws SQLException { lock(); @@ -1175,7 +1179,8 @@ public class SQLiteDatabase extends SQLiteClosable { * default sort order, which may be unordered. * @param limit Limits the number of rows returned by the query, * formatted as LIMIT clause. Passing null denotes no LIMIT clause. - * @return A Cursor object, which is positioned before the first entry + * @return A {@link Cursor} object, which is positioned before the first entry. Note that + * {@link Cursor}s are not synchronized, see the documentation for more details. * @see Cursor */ public Cursor query(boolean distinct, String table, String[] columns, @@ -1213,7 +1218,8 @@ public class SQLiteDatabase extends SQLiteClosable { * default sort order, which may be unordered. * @param limit Limits the number of rows returned by the query, * formatted as LIMIT clause. Passing null denotes no LIMIT clause. - * @return A Cursor object, which is positioned before the first entry + * @return A {@link Cursor} object, which is positioned before the first entry. Note that + * {@link Cursor}s are not synchronized, see the documentation for more details. * @see Cursor */ public Cursor queryWithFactory(CursorFactory cursorFactory, @@ -1254,7 +1260,8 @@ public class SQLiteDatabase extends SQLiteClosable { * @param orderBy How to order the rows, formatted as an SQL ORDER BY clause * (excluding the ORDER BY itself). Passing null will use the * default sort order, which may be unordered. - * @return A {@link Cursor} object, which is positioned before the first entry + * @return A {@link Cursor} object, which is positioned before the first entry. Note that + * {@link Cursor}s are not synchronized, see the documentation for more details. * @see Cursor */ public Cursor query(String table, String[] columns, String selection, @@ -1291,7 +1298,8 @@ public class SQLiteDatabase extends SQLiteClosable { * default sort order, which may be unordered. * @param limit Limits the number of rows returned by the query, * formatted as LIMIT clause. Passing null denotes no LIMIT clause. - * @return A {@link Cursor} object, which is positioned before the first entry + * @return A {@link Cursor} object, which is positioned before the first entry. Note that + * {@link Cursor}s are not synchronized, see the documentation for more details. * @see Cursor */ public Cursor query(String table, String[] columns, String selection, @@ -1309,7 +1317,8 @@ public class SQLiteDatabase extends SQLiteClosable { * @param selectionArgs You may include ?s in where clause in the query, * which will be replaced by the values from selectionArgs. The * values will be bound as Strings. - * @return A {@link Cursor} object, which is positioned before the first entry + * @return A {@link Cursor} object, which is positioned before the first entry. Note that + * {@link Cursor}s are not synchronized, see the documentation for more details. */ public Cursor rawQuery(String sql, String[] selectionArgs) { return rawQueryWithFactory(null, sql, selectionArgs, null); @@ -1324,7 +1333,8 @@ public class SQLiteDatabase extends SQLiteClosable { * which will be replaced by the values from selectionArgs. The * values will be bound as Strings. * @param editTable the name of the first table, which is editable - * @return A {@link Cursor} object, which is positioned before the first entry + * @return A {@link Cursor} object, which is positioned before the first entry. Note that + * {@link Cursor}s are not synchronized, see the documentation for more details. */ public Cursor rawQueryWithFactory( CursorFactory cursorFactory, String sql, String[] selectionArgs, @@ -1332,6 +1342,7 @@ public class SQLiteDatabase extends SQLiteClosable { if (!isOpen()) { throw new IllegalStateException("database not open"); } + BlockGuard.getThreadPolicy().onReadFromDisk(); long timeStart = 0; if (Config.LOGV || mSlowQueryThreshold != -1) { @@ -1379,7 +1390,8 @@ public class SQLiteDatabase extends SQLiteClosable { * values will be bound as Strings. * @param initialRead set the initial count of items to read from the cursor * @param maxRead set the count of items to read on each iteration after the first - * @return A {@link Cursor} object, which is positioned before the first entry + * @return A {@link Cursor} object, which is positioned before the first entry. Note that + * {@link Cursor}s are not synchronized, see the documentation for more details. * * This work is incomplete and not fully tested or reviewed, so currently * hidden. @@ -1489,6 +1501,7 @@ public class SQLiteDatabase extends SQLiteClosable { */ public long insertWithOnConflict(String table, String nullColumnHack, ContentValues initialValues, int conflictAlgorithm) { + BlockGuard.getThreadPolicy().onWriteToDisk(); if (!isOpen()) { throw new IllegalStateException("database not open"); } @@ -1580,6 +1593,7 @@ public class SQLiteDatabase extends SQLiteClosable { * whereClause. */ public int delete(String table, String whereClause, String[] whereArgs) { + BlockGuard.getThreadPolicy().onWriteToDisk(); lock(); if (!isOpen()) { throw new IllegalStateException("database not open"); @@ -1635,6 +1649,7 @@ public class SQLiteDatabase extends SQLiteClosable { */ public int updateWithOnConflict(String table, ContentValues values, String whereClause, String[] whereArgs, int conflictAlgorithm) { + BlockGuard.getThreadPolicy().onWriteToDisk(); if (values == null || values.size() == 0) { throw new IllegalArgumentException("Empty values"); } @@ -1717,6 +1732,7 @@ public class SQLiteDatabase extends SQLiteClosable { * @throws SQLException If the SQL string is invalid for some reason */ public void execSQL(String sql) throws SQLException { + BlockGuard.getThreadPolicy().onWriteToDisk(); long timeStart = SystemClock.uptimeMillis(); lock(); if (!isOpen()) { @@ -1752,6 +1768,7 @@ public class SQLiteDatabase extends SQLiteClosable { * @throws SQLException If the SQL string is invalid for some reason */ public void execSQL(String sql, Object[] bindArgs) throws SQLException { + BlockGuard.getThreadPolicy().onWriteToDisk(); if (bindArgs == null) { throw new IllegalArgumentException("Empty bindArgs"); } @@ -1905,7 +1922,7 @@ public class SQLiteDatabase extends SQLiteClosable { // main thread, or when we are invoked via Binder (e.g. ContentProvider). // Hopefully the full path to the database will be informative enough. - String blockingPackage = ActivityThread.currentPackageName(); + String blockingPackage = AppGlobals.getInitialPackage(); if (blockingPackage == null) blockingPackage = ""; EventLog.writeEvent( diff --git a/core/java/android/database/sqlite/SQLiteOpenHelper.java b/core/java/android/database/sqlite/SQLiteOpenHelper.java index 47002b6638a6..b4615eb6b261 100644 --- a/core/java/android/database/sqlite/SQLiteOpenHelper.java +++ b/core/java/android/database/sqlite/SQLiteOpenHelper.java @@ -34,6 +34,12 @@ import android.util.Log; * * <p>For an example, see the NotePadProvider class in the NotePad sample application, * in the <em>samples/</em> directory of the SDK.</p> + * + * <p class="note"><strong>Note:</strong> this class assumes + * monotonically increasing version numbers for upgrades. Also, there + * is no concept of a database downgrade; installing a new version of + * your app which uses a lower version number than a + * previously-installed version will result in undefined behavior.</p> */ public abstract class SQLiteOpenHelper { private static final String TAG = SQLiteOpenHelper.class.getSimpleName(); @@ -119,6 +125,10 @@ public abstract class SQLiteOpenHelper { if (version == 0) { onCreate(db); } else { + if (version > mNewVersion) { + Log.wtf(TAG, "Can't downgrade read-only database from version " + + version + " to " + mNewVersion + ": " + db.getPath()); + } onUpgrade(db, version, mNewVersion); } db.setVersion(mNewVersion); diff --git a/core/java/android/database/sqlite/SQLiteProgram.java b/core/java/android/database/sqlite/SQLiteProgram.java index 89a5f0d1e983..4d96f12c7946 100644 --- a/core/java/android/database/sqlite/SQLiteProgram.java +++ b/core/java/android/database/sqlite/SQLiteProgram.java @@ -20,6 +20,9 @@ import android.util.Log; /** * A base class for compiled SQLite programs. + * + * SQLiteProgram is not internally synchronized so code using a SQLiteProgram from multiple + * threads should perform its own synchronization when using the SQLiteProgram. */ public abstract class SQLiteProgram extends SQLiteClosable { diff --git a/core/java/android/database/sqlite/SQLiteQuery.java b/core/java/android/database/sqlite/SQLiteQuery.java index 43d2fac8b7e6..905b66b78cd6 100644 --- a/core/java/android/database/sqlite/SQLiteQuery.java +++ b/core/java/android/database/sqlite/SQLiteQuery.java @@ -23,6 +23,9 @@ import android.util.Log; /** * A SQLite program that represents a query that reads the resulting rows into a CursorWindow. * This class is used by SQLiteCursor and isn't useful itself. + * + * SQLiteQuery is not internally synchronized so code using a SQLiteQuery from multiple + * threads should perform its own synchronization when using the SQLiteQuery. */ public class SQLiteQuery extends SQLiteProgram { private static final String TAG = "Cursor"; diff --git a/core/java/android/database/sqlite/SQLiteStatement.java b/core/java/android/database/sqlite/SQLiteStatement.java index 98da41449476..9e425c34c05a 100644 --- a/core/java/android/database/sqlite/SQLiteStatement.java +++ b/core/java/android/database/sqlite/SQLiteStatement.java @@ -18,11 +18,16 @@ package android.database.sqlite; import android.os.SystemClock; +import dalvik.system.BlockGuard; + /** * A pre-compiled statement against a {@link SQLiteDatabase} that can be reused. * The statement cannot return multiple rows, but 1x1 result sets are allowed. * Don't use SQLiteStatement constructor directly, please use * {@link SQLiteDatabase#compileStatement(String)} + * + * SQLiteStatement is not internally synchronized so code using a SQLiteStatement from multiple + * threads should perform its own synchronization when using the SQLiteStatement. */ public class SQLiteStatement extends SQLiteProgram { @@ -44,6 +49,7 @@ public class SQLiteStatement extends SQLiteProgram * some reason */ public void execute() { + BlockGuard.getThreadPolicy().onWriteToDisk(); if (!mDatabase.isOpen()) { throw new IllegalStateException("database " + mDatabase.getPath() + " already closed"); } @@ -70,6 +76,7 @@ public class SQLiteStatement extends SQLiteProgram * some reason */ public long executeInsert() { + BlockGuard.getThreadPolicy().onWriteToDisk(); if (!mDatabase.isOpen()) { throw new IllegalStateException("database " + mDatabase.getPath() + " already closed"); } @@ -96,6 +103,7 @@ public class SQLiteStatement extends SQLiteProgram * @throws android.database.sqlite.SQLiteDoneException if the query returns zero rows */ public long simpleQueryForLong() { + BlockGuard.getThreadPolicy().onReadFromDisk(); if (!mDatabase.isOpen()) { throw new IllegalStateException("database " + mDatabase.getPath() + " already closed"); } @@ -122,6 +130,7 @@ public class SQLiteStatement extends SQLiteProgram * @throws android.database.sqlite.SQLiteDoneException if the query returns zero rows */ public String simpleQueryForString() { + BlockGuard.getThreadPolicy().onReadFromDisk(); if (!mDatabase.isOpen()) { throw new IllegalStateException("database " + mDatabase.getPath() + " already closed"); } diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java index 59c386d05699..3cc89e5233e1 100644 --- a/core/java/android/hardware/Camera.java +++ b/core/java/android/hardware/Camera.java @@ -50,7 +50,7 @@ import android.os.Message; * <p>To take pictures with this class, use the following steps:</p> * * <ol> - * <li>Obtain an instance of Camera from {@link #open()}. + * <li>Obtain an instance of Camera from {@link #open(int)}. * * <li>Get existing (default) settings with {@link #getParameters()}. * @@ -102,7 +102,7 @@ import android.os.Message; * <p>This class is not thread-safe, and is meant for use from one event thread. * Most long-running operations (preview, focus, photo capture, etc) happen * asynchronously and invoke callbacks as necessary. Callbacks will be invoked - * on the event thread {@link #open()} was called from. This class's methods + * on the event thread {@link #open(int)} was called from. This class's methods * must never be called from multiple threads at once.</p> * * <p class="caution"><strong>Caution:</strong> Different Android-powered devices @@ -114,7 +114,7 @@ import android.os.Message; public class Camera { private static final String TAG = "Camera"; - // These match the enums in frameworks/base/include/ui/Camera.h + // These match the enums in frameworks/base/include/camera/Camera.h private static final int CAMERA_MSG_ERROR = 0x001; private static final int CAMERA_MSG_SHUTTER = 0x002; private static final int CAMERA_MSG_FOCUS = 0x004; @@ -140,12 +140,52 @@ public class Camera { private boolean mWithBuffer; /** - * Creates a new Camera object. + * Returns the number of physical cameras available on this device. + */ + public native static int getNumberOfCameras(); + + /** + * Returns the information about a particular camera. + * If {@link #getNumberOfCameras()} returns N, the valid id is 0 to N-1. + */ + public native static void getCameraInfo(int cameraId, CameraInfo cameraInfo); + + /** + * Information about a camera + */ + public static class CameraInfo { + public static final int CAMERA_FACING_BACK = 0; + public static final int CAMERA_FACING_FRONT = 1; + + /** + * The direction that the camera faces to. It should be + * CAMERA_FACING_BACK or CAMERA_FACING_FRONT. + */ + public int mFacing; + + /** + * The orientation of the camera image. The value is the angle that the + * camera image needs to be rotated clockwise so it shows correctly on + * the display in its natural orientation. It should be 0, 90, 180, or 270. + * + * For example, suppose a device has a naturally tall screen, but the camera + * sensor is mounted in landscape. If the top side of the camera sensor is + * aligned with the right edge of the display in natural orientation, the + * value should be 90. + * + * @see #setDisplayOrientation(int) + */ + public int mOrientation; + }; + + /** + * Creates a new Camera object to access a particular hardware camera. * * <p>You must call {@link #release()} when you are done using the camera, * otherwise it will remain locked and be unavailable to other applications. * - * <p>Your application should only have one Camera object active at a time. + * <p>Your application should only have one Camera object active at a time + * for a particular hardware camera. * * <p>Callbacks from other methods are delivered to the event loop of the * thread which called open(). If this thread has no event loop, then @@ -157,15 +197,31 @@ public class Camera { * worker thread (possibly using {@link android.os.AsyncTask}) to avoid * blocking the main application UI thread. * + * @param cameraId the hardware camera to access, between 0 and + * {@link #getNumberOfCameras()}-1. Use {@link #CAMERA_ID_DEFAULT} + * to access the default camera. * @return a new Camera object, connected, locked and ready for use. * @throws RuntimeException if connection to the camera service fails (for * example, if the camera is in use by another process). */ + public static Camera open(int cameraId) { + return new Camera(cameraId); + } + + /** + * The id for the default camera. + */ + public static int CAMERA_ID_DEFAULT = 0; + + /** + * Equivalent to Camera.open(Camera.CAMERA_ID_DEFAULT). + * Creates a new Camera object to access the default camera. + */ public static Camera open() { - return new Camera(); + return new Camera(CAMERA_ID_DEFAULT); } - Camera() { + Camera(int cameraId) { mShutterCallback = null; mRawImageCallback = null; mJpegCallback = null; @@ -182,14 +238,14 @@ public class Camera { mEventHandler = null; } - native_setup(new WeakReference<Camera>(this)); + native_setup(new WeakReference<Camera>(this), cameraId); } protected void finalize() { native_release(); } - private native final void native_setup(Object camera_this); + private native final void native_setup(Object camera_this, int cameraId); private native final void native_release(); @@ -296,7 +352,7 @@ public class Camera { { /** * Called as preview frames are displayed. This callback is invoked - * on the event thread {@link #open()} was called from. + * on the event thread {@link #open(int)} was called from. * * @param data the contents of the preview frame in the format defined * by {@link android.graphics.ImageFormat}, which can be queried @@ -713,6 +769,28 @@ public class Camera { * {@link PreviewCallback#onPreviewFrame}. This method is not allowed to * be called during preview. * + * If you want to make the camera image show in the same orientation as + * the display, you can use the following code.<p> + * <pre> + * public static void setCameraDisplayOrientation(Activity activity, + * int cameraId, android.hardware.Camera camera) { + * android.hardware.Camera.CameraInfo info = + * new android.hardware.Camera.CameraInfo(); + * android.hardware.Camera.getCameraInfo(cameraId, info); + * int rotation = activity.getWindowManager().getDefaultDisplay() + * .getRotation(); + * int degrees = 0; + * switch (rotation) { + * case Surface.ROTATION_0: degrees = 0; break; + * case Surface.ROTATION_90: degrees = 90; break; + * case Surface.ROTATION_180: degrees = 180; break; + * case Surface.ROTATION_270: degrees = 270; break; + * } + * + * int result = (info.mOrientation - degrees + 360) % 360; + * camera.setDisplayOrientation(result); + * } + * </pre> * @param degrees the angle that the picture will be rotated clockwise. * Valid values are 0, 90, 180, and 270. The starting * position is 0 (landscape). @@ -882,6 +960,7 @@ public class Camera { private static final String KEY_PREVIEW_SIZE = "preview-size"; private static final String KEY_PREVIEW_FORMAT = "preview-format"; private static final String KEY_PREVIEW_FRAME_RATE = "preview-frame-rate"; + private static final String KEY_PREVIEW_FPS_RANGE = "preview-fps-range"; private static final String KEY_PICTURE_SIZE = "picture-size"; private static final String KEY_PICTURE_FORMAT = "picture-format"; private static final String KEY_JPEG_THUMBNAIL_SIZE = "jpeg-thumbnail-size"; @@ -913,6 +992,8 @@ public class Camera { private static final String KEY_ZOOM_RATIOS = "zoom-ratios"; private static final String KEY_ZOOM_SUPPORTED = "zoom-supported"; private static final String KEY_SMOOTH_ZOOM_SUPPORTED = "smooth-zoom-supported"; + private static final String KEY_FOCUS_DISTANCES = "focus-distances"; + // Parameter key suffix for supported values. private static final String SUPPORTED_VALUES_SUFFIX = "-values"; @@ -974,21 +1055,81 @@ public class Camera { */ public static final String FLASH_MODE_TORCH = "torch"; - // Values for scene mode settings. + /** + * Scene mode is off. + */ public static final String SCENE_MODE_AUTO = "auto"; + + /** + * Take photos of fast moving objects. Same as {@link + * #SCENE_MODE_SPORTS}. + */ public static final String SCENE_MODE_ACTION = "action"; + + /** + * Take people pictures. + */ public static final String SCENE_MODE_PORTRAIT = "portrait"; + + /** + * Take pictures on distant objects. + */ public static final String SCENE_MODE_LANDSCAPE = "landscape"; + + /** + * Take photos at night. + */ public static final String SCENE_MODE_NIGHT = "night"; + + /** + * Take people pictures at night. + */ public static final String SCENE_MODE_NIGHT_PORTRAIT = "night-portrait"; + + /** + * Take photos in a theater. Flash light is off. + */ public static final String SCENE_MODE_THEATRE = "theatre"; + + /** + * Take pictures on the beach. + */ public static final String SCENE_MODE_BEACH = "beach"; + + /** + * Take pictures on the snow. + */ public static final String SCENE_MODE_SNOW = "snow"; + + /** + * Take sunset photos. + */ public static final String SCENE_MODE_SUNSET = "sunset"; + + /** + * Avoid blurry pictures (for example, due to hand shake). + */ public static final String SCENE_MODE_STEADYPHOTO = "steadyphoto"; + + /** + * For shooting firework displays. + */ public static final String SCENE_MODE_FIREWORKS = "fireworks"; + + /** + * Take photos of fast moving objects. Same as {@link + * #SCENE_MODE_ACTION}. + */ public static final String SCENE_MODE_SPORTS = "sports"; + + /** + * Take indoor low-light shot. + */ public static final String SCENE_MODE_PARTY = "party"; + + /** + * Capture the naturally warm color of scenes lit by candles. + */ public static final String SCENE_MODE_CANDLELIGHT = "candlelight"; /** @@ -997,9 +1138,9 @@ public class Camera { */ public static final String SCENE_MODE_BARCODE = "barcode"; - // Values for focus mode settings. /** - * Auto-focus mode. + * Auto-focus mode. Applications should call {@link + * #autoFocus(AutoFocusCallback)} to start the focus in this mode. */ public static final String FOCUS_MODE_AUTO = "auto"; @@ -1008,6 +1149,12 @@ public class Camera { * {@link #autoFocus(AutoFocusCallback)} in this mode. */ public static final String FOCUS_MODE_INFINITY = "infinity"; + + /** + * Macro (close-up) focus mode. Applications should call + * {@link #autoFocus(AutoFocusCallback)} to start the focus in this + * mode. + */ public static final String FOCUS_MODE_MACRO = "macro"; /** @@ -1025,6 +1172,49 @@ public class Camera { */ public static final String FOCUS_MODE_EDOF = "edof"; + /** + * Continuous auto focus mode. The camera continuously tries to focus. + * This is ideal for shooting video or shooting photo of moving object. + * Auto focus starts when the parameter is set. Applications should not + * call {@link #autoFocus(AutoFocusCallback)} in this mode. To stop + * continuous focus, applications should change the focus mode to other + * modes. + */ + public static final String FOCUS_MODE_CONTINUOUS = "continuous"; + + // Indices for focus distance array. + /** + * The array index of near focus distance for use with + * {@link #getFocusDistances(float[])}. + */ + public static final int FOCUS_DISTANCE_NEAR_INDEX = 0; + + /** + * The array index of optimal focus distance for use with + * {@link #getFocusDistances(float[])}. + */ + public static final int FOCUS_DISTANCE_OPTIMAL_INDEX = 1; + + /** + * The array index of far focus distance for use with + * {@link #getFocusDistances(float[])}. + */ + public static final int FOCUS_DISTANCE_FAR_INDEX = 2; + + /** + * The array index of minimum preview fps for use with {@link + * #getPreviewFpsRange(int[])} or {@link + * #getSupportedPreviewFpsRange()}. + */ + public static final int PREVIEW_FPS_MIN_INDEX = 0; + + /** + * The array index of maximum preview fps for use with {@link + * #getPreviewFpsRange(int[])} or {@link + * #getSupportedPreviewFpsRange()}. + */ + public static final int PREVIEW_FPS_MAX_INDEX = 1; + // Formats for setPreviewFormat and setPictureFormat. private static final String PIXEL_FORMAT_YUV422SP = "yuv422sp"; private static final String PIXEL_FORMAT_YUV420SP = "yuv420sp"; @@ -1260,7 +1450,9 @@ public class Camera { * target frame rate. The actual frame rate depends on the driver. * * @param fps the frame rate (frames per second) + * @deprecated replaced by {@link #setPreviewFpsRange(int,int)} */ + @Deprecated public void setPreviewFrameRate(int fps) { set(KEY_PREVIEW_FRAME_RATE, fps); } @@ -1271,7 +1463,9 @@ public class Camera { * depends on the driver. * * @return the frame rate setting (frames per second) + * @deprecated replaced by {@link #getPreviewFpsRange(int[])} */ + @Deprecated public int getPreviewFrameRate() { return getInt(KEY_PREVIEW_FRAME_RATE); } @@ -1281,13 +1475,70 @@ public class Camera { * * @return a list of supported preview frame rates. null if preview * frame rate setting is not supported. + * @deprecated replaced by {@link #getSupportedPreviewFpsRange()} */ + @Deprecated public List<Integer> getSupportedPreviewFrameRates() { String str = get(KEY_PREVIEW_FRAME_RATE + SUPPORTED_VALUES_SUFFIX); return splitInt(str); } /** + * Sets the maximum and maximum preview fps. This controls the rate of + * preview frames received in {@link PreviewCallback}. The minimum and + * maximum preview fps must be one of the elements from {@link + * #getSupportedPreviewFpsRange}. + * + * @param min the minimum preview fps (scaled by 1000). + * @param max the maximum preview fps (scaled by 1000). + * @throws RuntimeException if fps range is invalid. + * @see #setPreviewCallbackWithBuffer(Camera.PreviewCallback) + * @see #getSupportedPreviewFpsRange() + */ + public void setPreviewFpsRange(int min, int max) { + set(KEY_PREVIEW_FPS_RANGE, "" + min + "," + max); + } + + /** + * Returns the current minimum and maximum preview fps. The values are + * one of the elements returned by {@link #getSupportedPreviewFpsRange}. + * + * @return range the minimum and maximum preview fps (scaled by 1000). + * @see #PREVIEW_FPS_MIN_INDEX + * @see #PREVIEW_FPS_MAX_INDEX + * @see #getSupportedPreviewFpsRange() + */ + public void getPreviewFpsRange(int[] range) { + if (range == null || range.length != 2) { + throw new IllegalArgumentException( + "range must be an array with two elements."); + } + splitInt(get(KEY_PREVIEW_FPS_RANGE), range); + } + + /** + * Gets the supported preview fps (frame-per-second) ranges. Each range + * contains a minimum fps and maximum fps. If minimum fps equals to + * maximum fps, the camera outputs frames in fixed frame rate. If not, + * the camera outputs frames in auto frame rate. The actual frame rate + * fluctuates between the minimum and the maximum. The values are + * multiplied by 1000 and represented in integers. For example, if frame + * rate is 26.623 frames per second, the value is 26623. + * + * @return a list of supported preview fps ranges. This method returns a + * list with at least one element. Every element is an int array + * of two values - minimum fps and maximum fps. The list is + * sorted from small to large (first by maximum fps and then + * minimum fps). + * @see #PREVIEW_FPS_MIN_INDEX + * @see #PREVIEW_FPS_MAX_INDEX + */ + public List<int[]> getSupportedPreviewFpsRange() { + String str = get(KEY_PREVIEW_FPS_RANGE + SUPPORTED_VALUES_SUFFIX); + return splitRange(str); + } + + /** * Sets the image format for preview pictures. * <p>If this is never called, the default format will be * {@link android.graphics.ImageFormat#NV21}, which @@ -1754,15 +2005,16 @@ public class Camera { /** * Gets the current focus mode setting. * - * @return current focus mode. If the camera does not support - * auto-focus, this should return {@link #FOCUS_MODE_FIXED}. If - * the focus mode is not FOCUS_MODE_FIXED or {@link - * #FOCUS_MODE_INFINITY}, applications should call {@link - * #autoFocus(AutoFocusCallback)} to start the focus. + * @return current focus mode. This method will always return a non-null + * value. Applications should call {@link + * #autoFocus(AutoFocusCallback)} to start the focus if focus + * mode is FOCUS_MODE_AUTO or FOCUS_MODE_MACRO. * @see #FOCUS_MODE_AUTO * @see #FOCUS_MODE_INFINITY * @see #FOCUS_MODE_MACRO * @see #FOCUS_MODE_FIXED + * @see #FOCUS_MODE_EDOF + * @see #FOCUS_MODE_CONTINUOUS */ public String getFocusMode() { return get(KEY_FOCUS_MODE); @@ -1955,6 +2207,43 @@ public class Camera { return TRUE.equals(str); } + /** + * Gets the distances from the camera to where an object appears to be + * in focus. The object is sharpest at the optimal focus distance. The + * depth of field is the far focus distance minus near focus distance. + * + * Focus distances may change after calling {@link + * #autoFocus(AutoFocusCallback)}, {@link #cancelAutoFocus}, or {@link + * #startPreview()}. Applications can call {@link #getParameters()} + * and this method anytime to get the latest focus distances. If the + * focus mode is FOCUS_MODE_CONTINUOUS, focus distances may change from + * time to time. + * + * This method is intended to estimate the distance between the camera + * and the subject. After autofocus, the subject distance may be within + * near and far focus distance. However, the precision depends on the + * camera hardware, autofocus algorithm, the focus area, and the scene. + * The error can be large and it should be only used as a reference. + * + * Far focus distance >= optimal focus distance >= near focus distance. + * If the focus distance is infinity, the value will be + * Float.POSITIVE_INFINITY. + * + * @param output focus distances in meters. output must be a float + * array with three elements. Near focus distance, optimal focus + * distance, and far focus distance will be filled in the array. + * @see #FOCUS_DISTANCE_NEAR_INDEX + * @see #FOCUS_DISTANCE_OPTIMAL_INDEX + * @see #FOCUS_DISTANCE_FAR_INDEX + */ + public void getFocusDistances(float[] output) { + if (output == null || output.length != 3) { + throw new IllegalArgumentException( + "output must be an float array with three elements."); + } + splitFloat(get(KEY_FOCUS_DISTANCES), output); + } + // Splits a comma delimited string to an ArrayList of String. // Return null if the passing string is null or the size is 0. private ArrayList<String> split(String str) { @@ -1984,6 +2273,29 @@ public class Camera { return substrings; } + private void splitInt(String str, int[] output) { + if (str == null) return; + + StringTokenizer tokenizer = new StringTokenizer(str, ","); + int index = 0; + while (tokenizer.hasMoreElements()) { + String token = tokenizer.nextToken(); + output[index++] = Integer.parseInt(token); + } + } + + // Splits a comma delimited string to an ArrayList of Float. + private void splitFloat(String str, float[] output) { + if (str == null) return; + + StringTokenizer tokenizer = new StringTokenizer(str, ","); + int index = 0; + while (tokenizer.hasMoreElements()) { + String token = tokenizer.nextToken(); + output[index++] = Float.parseFloat(token); + } + } + // Returns the value of a float parameter. private float getFloat(String key, float defaultValue) { try { @@ -2032,5 +2344,30 @@ public class Camera { Log.e(TAG, "Invalid size parameter string=" + str); return null; } + + // Splits a comma delimited string to an ArrayList of int array. + // Example string: "(10000,26623),(10000,30000)". Return null if the + // passing string is null or the size is 0. + private ArrayList<int[]> splitRange(String str) { + if (str == null || str.charAt(0) != '(' + || str.charAt(str.length() - 1) != ')') { + Log.e(TAG, "Invalid range list string=" + str); + return null; + } + + ArrayList<int[]> rangeList = new ArrayList<int[]>(); + int endIndex, fromIndex = 1; + do { + int[] range = new int[2]; + endIndex = str.indexOf("),(", fromIndex); + if (endIndex == -1) endIndex = str.length() - 1; + splitInt(str.substring(fromIndex, endIndex), range); + rangeList.add(range); + fromIndex = endIndex + 3; + } while (endIndex != str.length() - 1); + + if (rangeList.size() == 0) return null; + return rangeList; + } }; } diff --git a/core/java/android/hardware/Sensor.java b/core/java/android/hardware/Sensor.java index 317e5473819d..f2b907bfd234 100644 --- a/core/java/android/hardware/Sensor.java +++ b/core/java/android/hardware/Sensor.java @@ -17,65 +17,92 @@ package android.hardware; -/** - * Class representing a sensor. Use {@link SensorManager#getSensorList} - * to get the list of available Sensors. +/** + * Class representing a sensor. Use {@link SensorManager#getSensorList} to get + * the list of available Sensors. + * + * @see SensorManager + * @see SensorEventListener + * @see SensorEvent + * */ public class Sensor { - /** - * A constant describing an accelerometer sensor type. - * See {@link android.hardware.SensorEvent SensorEvent} - * for more details. + /** + * A constant describing an accelerometer sensor type. See + * {@link android.hardware.SensorEvent#values SensorEvent.values} for more + * details. */ - public static final int TYPE_ACCELEROMETER = 1; + public static final int TYPE_ACCELEROMETER = 1; - /** - * A constant describing a magnetic field sensor type. - * See {@link android.hardware.SensorEvent SensorEvent} - * for more details. + /** + * A constant describing a magnetic field sensor type. See + * {@link android.hardware.SensorEvent#values SensorEvent.values} for more + * details. */ public static final int TYPE_MAGNETIC_FIELD = 2; - - /** - * A constant describing an orientation sensor type. - * See {@link android.hardware.SensorEvent SensorEvent} - * for more details. + + /** + * A constant describing an orientation sensor type. See + * {@link android.hardware.SensorEvent#values SensorEvent.values} for more + * details. + * * @deprecated use {@link android.hardware.SensorManager#getOrientation - * SensorManager.getOrientation()} instead. + * SensorManager.getOrientation()} instead. */ @Deprecated - public static final int TYPE_ORIENTATION = 3; + public static final int TYPE_ORIENTATION = 3; /** A constant describing a gyroscope sensor type */ - public static final int TYPE_GYROSCOPE = 4; + public static final int TYPE_GYROSCOPE = 4; + /** - * A constant describing an light sensor type. - * See {@link android.hardware.SensorEvent SensorEvent} - * for more details. + * A constant describing an light sensor type. See + * {@link android.hardware.SensorEvent#values SensorEvent.values} for more + * details. */ - public static final int TYPE_LIGHT = 5; + public static final int TYPE_LIGHT = 5; /** A constant describing a pressure sensor type */ - public static final int TYPE_PRESSURE = 6; + public static final int TYPE_PRESSURE = 6; /** A constant describing a temperature sensor type */ - public static final int TYPE_TEMPERATURE = 7; + public static final int TYPE_TEMPERATURE = 7; /** - * A constant describing an proximity sensor type. + * A constant describing an proximity sensor type. See + * {@link android.hardware.SensorEvent#values SensorEvent.values} for more + * details. + */ + public static final int TYPE_PROXIMITY = 8; + + /** + * A constant describing a gravity sensor type. * See {@link android.hardware.SensorEvent SensorEvent} * for more details. */ - public static final int TYPE_PROXIMITY = 8; + public static final int TYPE_GRAVITY = 9; + + /** + * A constant describing a linear acceleration sensor type. + * See {@link android.hardware.SensorEvent SensorEvent} + * for more details. + */ + public static final int TYPE_LINEAR_ACCELERATION = 10; + + /** + * A constant describing a rotation vector sensor type. + * See {@link android.hardware.SensorEvent SensorEvent} + * for more details. + */ + public static final int TYPE_ROTATION_VECTOR = 11; - /** * A constant describing all sensor types. */ - public static final int TYPE_ALL = -1; + public static final int TYPE_ALL = -1; - /* Some of these fields are set only by the native bindings in + /* Some of these fields are set only by the native bindings in * SensorManager. */ private String mName; @@ -86,9 +113,10 @@ public class Sensor { private float mMaxRange; private float mResolution; private float mPower; + private int mMinDelay; private int mLegacyType; - - + + Sensor() { } @@ -105,51 +133,60 @@ public class Sensor { public String getVendor() { return mVendor; } - + /** * @return generic type of this sensor. */ public int getType() { return mType; } - + /** * @return version of the sensor's module. */ public int getVersion() { return mVersion; } - + /** * @return maximum range of the sensor in the sensor's unit. */ public float getMaximumRange() { return mMaxRange; } - + /** * @return resolution of the sensor in the sensor's unit. */ public float getResolution() { return mResolution; } - + /** * @return the power in mA used by this sensor while in use */ public float getPower() { return mPower; } - + + /** + * @return the minimum delay allowed between two events in microsecond + * or zero if this sensor only returns a value when the data it's measuring + * changes. + */ + public int getMinDelay() { + return mMinDelay; + } + int getHandle() { return mHandle; } - + void setRange(float max, float res) { mMaxRange = max; mResolution = res; } - + void setLegacyType(int legacyType) { mLegacyType = legacyType; } diff --git a/core/java/android/hardware/SensorEvent.java b/core/java/android/hardware/SensorEvent.java index 9a9f0bfcb198..2c5c9090eb14 100644 --- a/core/java/android/hardware/SensorEvent.java +++ b/core/java/android/hardware/SensorEvent.java @@ -17,143 +17,243 @@ package android.hardware; /** - * This class represents a sensor event and holds informations such as the - * sensor type (eg: accelerometer, orientation, etc...), the time-stamp, - * accuracy and of course the sensor's {@link SensorEvent#values data}. + * <p> + * This class represents a {@link android.hardware.Sensor Sensor} event and + * holds informations such as the sensor's type, the time-stamp, accuracy and of + * course the sensor's {@link SensorEvent#values data}. + * </p> * - * <p><u>Definition of the coordinate system used by the SensorEvent API.</u><p> - * - * <pre> - * The coordinate space is defined relative to the screen of the phone - * in its default orientation. The axes are not swapped when the device's - * screen orientation changes. - * - * The OpenGL ES coordinate system is used. The origin is in the - * lower-left corner with respect to the screen, with the X axis horizontal - * and pointing right, the Y axis vertical and pointing up and the Z axis - * pointing outside the front face of the screen. In this system, coordinates - * behind the screen have negative Z values. - * + * <p> + * <u>Definition of the coordinate system used by the SensorEvent API.</u> + * </p> + * + * <p> + * The coordinate-system is defined relative to the screen of the phone in its + * default orientation. The axes are not swapped when the device's screen + * orientation changes. + * </p> + * + * <p> + * The X axis is horizontal and points to the right, the Y axis is vertical and + * points up and the Z axis points towards the outside of the front face of the + * screen. In this system, coordinates behind the screen have negative Z values. + * </p> + * + * <p> + * <center><img src="../../../images/axis_device.png" + * alt="Sensors coordinate-system diagram." border="0" /></center> + * </p> + * + * <p> * <b>Note:</b> This coordinate system is different from the one used in the - * Android 2D APIs where the origin is in the top-left corner. + * Android 2D APIs where the origin is in the top-left corner. + * </p> * - * x<0 x>0 - * ^ - * | - * +-----------+--> y>0 - * | | - * | | - * | | - * | | / z<0 - * | | / - * | | / - * O-----------+/ - * |[] [ ] []/ - * +----------/+ y<0 - * / - * / - * |/ z>0 (toward the sky) + * @see SensorManager + * @see SensorEvent + * @see Sensor * - * O: Origin (x=0,y=0,z=0) - * </pre> */ public class SensorEvent { /** - * The length and contents of the values array vary depending on which - * sensor type is being monitored (see also {@link SensorEvent} for a - * definition of the coordinate system used): + * <p> + * The length and contents of the {@link #values values} array depends on + * which {@link android.hardware.Sensor sensor} type is being monitored (see + * also {@link SensorEvent} for a definition of the coordinate system used). + * </p> + * + * <h4>{@link android.hardware.Sensor#TYPE_ACCELEROMETER + * Sensor.TYPE_ACCELEROMETER}:</h4> All values are in SI units (m/s^2) * - * <p>{@link android.hardware.Sensor#TYPE_ORIENTATION Sensor.TYPE_ORIENTATION}:<p> - * All values are angles in degrees. + * <ul> + * <p> + * values[0]: Acceleration minus Gx on the x-axis + * </p> + * <p> + * values[1]: Acceleration minus Gy on the y-axis + * </p> + * <p> + * values[2]: Acceleration minus Gz on the z-axis + * </p> + * </ul> * - * <p>values[0]: Azimuth, angle between the magnetic north direction and - * the Y axis, around the Z axis (0 to 359). - * 0=North, 90=East, 180=South, 270=West - * - * <p>values[1]: Pitch, rotation around X axis (-180 to 180), - * with positive values when the z-axis moves <b>toward</b> the y-axis. - * - * <p>values[2]: Roll, rotation around Y axis (-90 to 90), with - * positive values when the x-axis moves <b>toward</b> the z-axis. + * <p> + * A sensor of this type measures the acceleration applied to the device + * (<b>Ad</b>). Conceptually, it does so by measuring forces applied to the + * sensor itself (<b>Fs</b>) using the relation: + * </p> * - * <p><b>Important note:</b> For historical reasons the roll angle is - * positive in the clockwise direction (mathematically speaking, it - * should be positive in the counter-clockwise direction). - * - * <p><b>Note:</b> This definition is different from <b>yaw, pitch and - * roll</b> used in aviation where the X axis is along the long side of - * the plane (tail to nose). - * - * <p><b>Note:</b> This sensor type exists for legacy reasons, please use - * {@link android.hardware.SensorManager#getRotationMatrix - * getRotationMatrix()} in conjunction with - * {@link android.hardware.SensorManager#remapCoordinateSystem - * remapCoordinateSystem()} and - * {@link android.hardware.SensorManager#getOrientation getOrientation()} - * to compute these values instead. - * - * <p>{@link android.hardware.Sensor#TYPE_ACCELEROMETER Sensor.TYPE_ACCELEROMETER}:<p> - * All values are in SI units (m/s^2) and measure the acceleration applied - * to the phone minus the force of gravity. - * - * <p>values[0]: Acceleration minus Gx on the x-axis - * <p>values[1]: Acceleration minus Gy on the y-axis - * <p>values[2]: Acceleration minus Gz on the z-axis - * - * <p><u>Examples</u>: - * <li>When the device lies flat on a table and is pushed on its left - * side toward the right, the x acceleration value is positive.</li> - * - * <li>When the device lies flat on a table, the acceleration value is - * +9.81, which correspond to the acceleration of the device (0 m/s^2) - * minus the force of gravity (-9.81 m/s^2).</li> - * - * <li>When the device lies flat on a table and is pushed toward the sky - * with an acceleration of A m/s^2, the acceleration value is equal to - * A+9.81 which correspond to the acceleration of the - * device (+A m/s^2) minus the force of gravity (-9.81 m/s^2).</li> - * - * - * <p>{@link android.hardware.Sensor#TYPE_MAGNETIC_FIELD Sensor.TYPE_MAGNETIC_FIELD}:<p> - * All values are in micro-Tesla (uT) and measure the ambient magnetic - * field in the X, Y and Z axis. - * - * <p>{@link android.hardware.Sensor#TYPE_LIGHT Sensor.TYPE_LIGHT}:<p> + * <b><center>Ad = - ∑Fs / mass</center></b> + * + * <p> + * In particular, the force of gravity is always influencing the measured + * acceleration: + * </p> + * + * <b><center>Ad = -g - ∑F / mass</center></b> + * + * <p> + * For this reason, when the device is sitting on a table (and obviously not + * accelerating), the accelerometer reads a magnitude of <b>g</b> = 9.81 + * m/s^2 + * </p> + * + * <p> + * Similarly, when the device is in free-fall and therefore dangerously + * accelerating towards to ground at 9.81 m/s^2, its accelerometer reads a + * magnitude of 0 m/s^2. + * </p> + * + * <p> + * It should be apparent that in order to measure the real acceleration of + * the device, the contribution of the force of gravity must be eliminated. + * This can be achieved by applying a <i>high-pass</i> filter. Conversely, a + * <i>low-pass</i> filter can be used to isolate the force of gravity. + * </p> + * <p> + * <u>Examples</u>: + * <ul> + * <li>When the device lies flat on a table and is pushed on its left side + * toward the right, the x acceleration value is positive.</li> + * + * <li>When the device lies flat on a table, the acceleration value is + * +9.81, which correspond to the acceleration of the device (0 m/s^2) minus + * the force of gravity (-9.81 m/s^2).</li> + * + * <li>When the device lies flat on a table and is pushed toward the sky + * with an acceleration of A m/s^2, the acceleration value is equal to + * A+9.81 which correspond to the acceleration of the device (+A m/s^2) + * minus the force of gravity (-9.81 m/s^2).</li> + * </ul> + * + * + * <h4>{@link android.hardware.Sensor#TYPE_MAGNETIC_FIELD + * Sensor.TYPE_MAGNETIC_FIELD}:</h4> + * All values are in micro-Tesla (uT) and measure the ambient magnetic field + * in the X, Y and Z axis. + * + * <h4>{@link android.hardware.Sensor#TYPE_GYROSCOPE Sensor.TYPE_GYROSCOPE}:</h4> + * All values are in radians/second and measure the rate of rotation + * around the X, Y and Z axis. The coordinate system is the same as is + * used for the acceleration sensor. Rotation is positive in the counter-clockwise + * direction. That is, an observer looking from some positive location on the x, y. + * or z axis at a device positioned on the origin would report positive rotation + * if the device appeared to be rotating counter clockwise. Note that this is the + * standard mathematical definition of positive rotation and does not agree with the + * definition of roll given earlier. * - * <p>values[0]: Ambient light level in SI lux units + * <h4>{@link android.hardware.Sensor#TYPE_LIGHT Sensor.TYPE_LIGHT}:</h4> + * + * <ul> + * <p> + * values[0]: Ambient light level in SI lux units + * </ul> + * + * <h4>{@link android.hardware.Sensor#TYPE_PROXIMITY Sensor.TYPE_PROXIMITY}: + * </h4> + * + * <ul> + * <p> + * values[0]: Proximity sensor distance measured in centimeters + * </ul> + * + * <p> + * <b>Note:</b> Some proximity sensors only support a binary <i>near</i> or + * <i>far</i> measurement. In this case, the sensor should report its + * {@link android.hardware.Sensor#getMaximumRange() maximum range} value in + * the <i>far</i> state and a lesser value in the <i>near</i> state. + * </p> + * + * <h4>{@link android.hardware.Sensor#TYPE_GRAVITY Sensor.TYPE_GRAVITY}:</h4> + * A three dimensional vector indicating the direction and magnitude of gravity. Units + * are m/s^2. The coordinate system is the same as is used by the acceleration sensor. * - * <p>{@link android.hardware.Sensor#TYPE_PROXIMITY Sensor.TYPE_PROXIMITY}:<p> + * <h4>{@link android.hardware.Sensor#TYPE_LINEAR_ACCELERATION Sensor.TYPE_LINEAR_ACCELERATION}:</h4> + * A three dimensional vector indicating acceleration along each device axis, not including + * gravity. All values have units of m/s^2. The coordinate system is the same as is used by the + * acceleration sensor. * - * <p>values[0]: Proximity sensor distance measured in centimeters + * <h4>{@link android.hardware.Sensor#TYPE_ROTATION_VECTOR Sensor.TYPE_ROTATION_VECTOR}:</h4> + * The rotation vector represents the orientation of the device as a combination of an angle + * and an axis, in which the device has rotated through an angle theta around an axis + * <x, y, z>. The three elements of the rotation vector are + * <x*sin(theta/2), y*sin(theta/2), z*sin(theta/2)>, such that the magnitude of the rotation + * vector is equal to sin(theta/2), and the direction of the rotation vector is equal to the + * direction of the axis of rotation. The three elements of the rotation vector are equal to + * the last three components of a unit quaternion + * <cos(theta/2), x*sin(theta/2), y*sin(theta/2), z*sin(theta/2)>. Elements of the rotation + * vector are unitless. The x,y, and z axis are defined in the same way as the acceleration + * sensor. * - * <p> Note that some proximity sensors only support a binary "close" or "far" measurement. - * In this case, the sensor should report its maxRange value in the "far" state and a value - * less than maxRange in the "near" state. + * <h4>{@link android.hardware.Sensor#TYPE_ORIENTATION + * Sensor.TYPE_ORIENTATION}:</h4> All values are angles in degrees. + * + * <ul> + * <p> + * values[0]: Azimuth, angle between the magnetic north direction and the + * y-axis, around the z-axis (0 to 359). 0=North, 90=East, 180=South, + * 270=West + * </p> + * + * <p> + * values[1]: Pitch, rotation around x-axis (-180 to 180), with positive + * values when the z-axis moves <b>toward</b> the y-axis. + * </p> + * + * <p> + * values[2]: Roll, rotation around y-axis (-90 to 90), with positive values + * when the x-axis moves <b>toward</b> the z-axis. + * </p> + * </ul> + * + * <p> + * <b>Note:</b> This definition is different from <b>yaw, pitch and roll</b> + * used in aviation where the X axis is along the long side of the plane + * (tail to nose). + * </p> + * + * <p> + * <b>Note:</b> This sensor type exists for legacy reasons, please use + * {@link android.hardware.SensorManager#getRotationMatrix + * getRotationMatrix()} in conjunction with + * {@link android.hardware.SensorManager#remapCoordinateSystem + * remapCoordinateSystem()} and + * {@link android.hardware.SensorManager#getOrientation getOrientation()} to + * compute these values instead. + * </p> + * + * <p> + * <b>Important note:</b> For historical reasons the roll angle is positive + * in the clockwise direction (mathematically speaking, it should be + * positive in the counter-clockwise direction). + * </p> + * + * @see SensorEvent + * @see GeomagneticField */ + public final float[] values; /** - * The sensor that generated this event. - * See {@link android.hardware.SensorManager SensorManager} - * for details. + * The sensor that generated this event. See + * {@link android.hardware.SensorManager SensorManager} for details. */ - public Sensor sensor; - + public Sensor sensor; + /** - * The accuracy of this event. - * See {@link android.hardware.SensorManager SensorManager} - * for details. + * The accuracy of this event. See {@link android.hardware.SensorManager + * SensorManager} for details. */ public int accuracy; - - + + /** * The time in nanosecond at which the event happened */ public long timestamp; - + SensorEvent(int size) { values = new float[size]; } diff --git a/core/java/android/hardware/SensorManager.java b/core/java/android/hardware/SensorManager.java index 98172e61ebf2..006872436e47 100644 --- a/core/java/android/hardware/SensorManager.java +++ b/core/java/android/hardware/SensorManager.java @@ -16,12 +16,7 @@ package android.hardware; -import android.content.Context; -import android.os.Binder; -import android.os.Bundle; import android.os.Looper; -import android.os.Parcelable; -import android.os.ParcelFileDescriptor; import android.os.Process; import android.os.RemoteException; import android.os.Handler; @@ -33,17 +28,58 @@ import android.view.IRotationWatcher; import android.view.IWindowManager; import android.view.Surface; -import java.io.FileDescriptor; -import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; /** - * Class that lets you access the device's sensors. Get an instance of this - * class by calling {@link android.content.Context#getSystemService(java.lang.String) - * Context.getSystemService()} with an argument of {@link android.content.Context#SENSOR_SERVICE}. + * <p> + * SensorManager lets you access the device's {@link android.hardware.Sensor + * sensors}. Get an instance of this class by calling + * {@link android.content.Context#getSystemService(java.lang.String) + * Context.getSystemService()} with the argument + * {@link android.content.Context#SENSOR_SERVICE}. + * </p> + * <p> + * Always make sure to disable sensors you don't need, especially when your + * activity is paused. Failing to do so can drain the battery in just a few + * hours. Note that the system will <i>not</i> disable sensors automatically when + * the screen turns off. + * </p> + * + * <pre class="prettyprint"> + * public class SensorActivity extends Activity, implements SensorEventListener { + * private final SensorManager mSensorManager; + * private final Sensor mAccelerometer; + * + * public SensorActivity() { + * mSensorManager = (SensorManager)getSystemService(SENSOR_SERVICE); + * mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); + * } + * + * protected void onResume() { + * super.onResume(); + * mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_NORMAL); + * } + * + * protected void onPause() { + * super.onPause(); + * mSensorManager.unregisterListener(this); + * } + * + * public void onAccuracyChanged(Sensor sensor, int accuracy) { + * } + * + * public void onSensorChanged(SensorEvent event) { + * } + * } + * </pre> + * + * @see SensorEventListener + * @see SensorEvent + * @see Sensor + * */ public class SensorManager { @@ -53,172 +89,224 @@ public class SensorManager /* NOTE: sensor IDs must be a power of 2 */ /** - * A constant describing an orientation sensor. - * See {@link android.hardware.SensorListener SensorListener} for more details. + * A constant describing an orientation sensor. See + * {@link android.hardware.SensorListener SensorListener} for more details. + * * @deprecated use {@link android.hardware.Sensor Sensor} instead. */ @Deprecated public static final int SENSOR_ORIENTATION = 1 << 0; /** - * A constant describing an accelerometer. - * See {@link android.hardware.SensorListener SensorListener} for more details. + * A constant describing an accelerometer. See + * {@link android.hardware.SensorListener SensorListener} for more details. + * * @deprecated use {@link android.hardware.Sensor Sensor} instead. */ @Deprecated public static final int SENSOR_ACCELEROMETER = 1 << 1; /** - * A constant describing a temperature sensor - * See {@link android.hardware.SensorListener SensorListener} for more details. + * A constant describing a temperature sensor See + * {@link android.hardware.SensorListener SensorListener} for more details. + * * @deprecated use {@link android.hardware.Sensor Sensor} instead. */ @Deprecated public static final int SENSOR_TEMPERATURE = 1 << 2; /** - * A constant describing a magnetic sensor - * See {@link android.hardware.SensorListener SensorListener} for more details. + * A constant describing a magnetic sensor See + * {@link android.hardware.SensorListener SensorListener} for more details. + * * @deprecated use {@link android.hardware.Sensor Sensor} instead. */ @Deprecated public static final int SENSOR_MAGNETIC_FIELD = 1 << 3; /** - * A constant describing an ambient light sensor - * See {@link android.hardware.SensorListener SensorListener} for more details. + * A constant describing an ambient light sensor See + * {@link android.hardware.SensorListener SensorListener} for more details. + * * @deprecated use {@link android.hardware.Sensor Sensor} instead. */ @Deprecated public static final int SENSOR_LIGHT = 1 << 4; /** - * A constant describing a proximity sensor - * See {@link android.hardware.SensorListener SensorListener} for more details. + * A constant describing a proximity sensor See + * {@link android.hardware.SensorListener SensorListener} for more details. + * * @deprecated use {@link android.hardware.Sensor Sensor} instead. */ @Deprecated public static final int SENSOR_PROXIMITY = 1 << 5; /** - * A constant describing a Tricorder - * See {@link android.hardware.SensorListener SensorListener} for more details. + * A constant describing a Tricorder See + * {@link android.hardware.SensorListener SensorListener} for more details. + * * @deprecated use {@link android.hardware.Sensor Sensor} instead. */ @Deprecated public static final int SENSOR_TRICORDER = 1 << 6; /** - * A constant describing an orientation sensor. - * See {@link android.hardware.SensorListener SensorListener} for more details. + * A constant describing an orientation sensor. See + * {@link android.hardware.SensorListener SensorListener} for more details. + * * @deprecated use {@link android.hardware.Sensor Sensor} instead. */ @Deprecated public static final int SENSOR_ORIENTATION_RAW = 1 << 7; - /** A constant that includes all sensors + /** + * A constant that includes all sensors + * * @deprecated use {@link android.hardware.Sensor Sensor} instead. */ @Deprecated public static final int SENSOR_ALL = 0x7F; - /** Smallest sensor ID + /** + * Smallest sensor ID + * * @deprecated use {@link android.hardware.Sensor Sensor} instead. */ @Deprecated public static final int SENSOR_MIN = SENSOR_ORIENTATION; - /** Largest sensor ID + /** + * Largest sensor ID + * * @deprecated use {@link android.hardware.Sensor Sensor} instead. */ @Deprecated public static final int SENSOR_MAX = ((SENSOR_ALL + 1)>>1); - /** Index of the X value in the array returned by + /** + * Index of the X value in the array returned by * {@link android.hardware.SensorListener#onSensorChanged} + * * @deprecated use {@link android.hardware.Sensor Sensor} instead. */ @Deprecated public static final int DATA_X = 0; - /** Index of the Y value in the array returned by + + /** + * Index of the Y value in the array returned by * {@link android.hardware.SensorListener#onSensorChanged} + * * @deprecated use {@link android.hardware.Sensor Sensor} instead. */ @Deprecated public static final int DATA_Y = 1; - /** Index of the Z value in the array returned by + + /** + * Index of the Z value in the array returned by * {@link android.hardware.SensorListener#onSensorChanged} + * * @deprecated use {@link android.hardware.Sensor Sensor} instead. */ @Deprecated public static final int DATA_Z = 2; - /** Offset to the untransformed values in the array returned by + /** + * Offset to the untransformed values in the array returned by * {@link android.hardware.SensorListener#onSensorChanged} + * * @deprecated use {@link android.hardware.Sensor Sensor} instead. */ @Deprecated public static final int RAW_DATA_INDEX = 3; - /** Index of the untransformed X value in the array returned by + /** + * Index of the untransformed X value in the array returned by * {@link android.hardware.SensorListener#onSensorChanged} + * * @deprecated use {@link android.hardware.Sensor Sensor} instead. */ @Deprecated public static final int RAW_DATA_X = 3; - /** Index of the untransformed Y value in the array returned by + + /** + * Index of the untransformed Y value in the array returned by * {@link android.hardware.SensorListener#onSensorChanged} + * * @deprecated use {@link android.hardware.Sensor Sensor} instead. */ @Deprecated public static final int RAW_DATA_Y = 4; - /** Index of the untransformed Z value in the array returned by + + /** + * Index of the untransformed Z value in the array returned by * {@link android.hardware.SensorListener#onSensorChanged} + * * @deprecated use {@link android.hardware.Sensor Sensor} instead. */ @Deprecated public static final int RAW_DATA_Z = 5; - /** Standard gravity (g) on Earth. This value is equivalent to 1G */ public static final float STANDARD_GRAVITY = 9.80665f; - /** values returned by the accelerometer in various locations in the universe. - * all values are in SI units (m/s^2) */ + /** Sun's gravity in SI units (m/s^2) */ public static final float GRAVITY_SUN = 275.0f; + /** Mercury's gravity in SI units (m/s^2) */ public static final float GRAVITY_MERCURY = 3.70f; + /** Venus' gravity in SI units (m/s^2) */ public static final float GRAVITY_VENUS = 8.87f; + /** Earth's gravity in SI units (m/s^2) */ public static final float GRAVITY_EARTH = 9.80665f; + /** The Moon's gravity in SI units (m/s^2) */ public static final float GRAVITY_MOON = 1.6f; + /** Mars' gravity in SI units (m/s^2) */ public static final float GRAVITY_MARS = 3.71f; + /** Jupiter's gravity in SI units (m/s^2) */ public static final float GRAVITY_JUPITER = 23.12f; + /** Saturn's gravity in SI units (m/s^2) */ public static final float GRAVITY_SATURN = 8.96f; + /** Uranus' gravity in SI units (m/s^2) */ public static final float GRAVITY_URANUS = 8.69f; + /** Neptune's gravity in SI units (m/s^2) */ public static final float GRAVITY_NEPTUNE = 11.0f; + /** Pluto's gravity in SI units (m/s^2) */ public static final float GRAVITY_PLUTO = 0.6f; + /** Gravity (estimate) on the first Death Star in Empire units (m/s^2) */ public static final float GRAVITY_DEATH_STAR_I = 0.000000353036145f; + /** Gravity on the island */ public static final float GRAVITY_THE_ISLAND = 4.815162342f; /** Maximum magnetic field on Earth's surface */ public static final float MAGNETIC_FIELD_EARTH_MAX = 60.0f; - /** Minimum magnetic field on Earth's surface */ public static final float MAGNETIC_FIELD_EARTH_MIN = 30.0f; - /** Various luminance values during the day (lux) */ + /** Standard atmosphere, or average sea-level pressure in hPa (millibar) */ + public static final float PRESSURE_STANDARD_ATMOSPHERE = 1013.25f; + + + /** Maximum luminance of sunlight in lux */ public static final float LIGHT_SUNLIGHT_MAX = 120000.0f; + /** luminance of sunlight in lux */ public static final float LIGHT_SUNLIGHT = 110000.0f; + /** luminance in shade in lux */ public static final float LIGHT_SHADE = 20000.0f; + /** luminance under an overcast sky in lux */ public static final float LIGHT_OVERCAST = 10000.0f; + /** luminance at sunrise in lux */ public static final float LIGHT_SUNRISE = 400.0f; + /** luminance under a cloudy sky in lux */ public static final float LIGHT_CLOUDY = 100.0f; - /** Various luminance values during the night (lux) */ + /** luminance at night with full moon in lux */ public static final float LIGHT_FULLMOON = 0.25f; + /** luminance at night with no moon in lux*/ public static final float LIGHT_NO_MOON = 0.001f; + /** get sensor data as fast as possible */ public static final int SENSOR_DELAY_FASTEST = 0; /** rate suitable for games */ @@ -229,16 +317,22 @@ public class SensorManager public static final int SENSOR_DELAY_NORMAL = 3; - /** The values returned by this sensor cannot be trusted, calibration - * is needed or the environment doesn't allow readings */ + /** + * The values returned by this sensor cannot be trusted, calibration is + * needed or the environment doesn't allow readings + */ public static final int SENSOR_STATUS_UNRELIABLE = 0; - /** This sensor is reporting data with low accuracy, calibration with the - * environment is needed */ + /** + * This sensor is reporting data with low accuracy, calibration with the + * environment is needed + */ public static final int SENSOR_STATUS_ACCURACY_LOW = 1; - /** This sensor is reporting data with an average level of accuracy, - * calibration with the environment may improve the readings */ + /** + * This sensor is reporting data with an average level of accuracy, + * calibration with the environment may improve the readings + */ public static final int SENSOR_STATUS_ACCURACY_MEDIUM = 2; /** This sensor is reporting data with maximum accuracy */ @@ -259,7 +353,6 @@ public class SensorManager /*-----------------------------------------------------------------------*/ - private ISensorService mSensorService; Looper mMainLooper; @SuppressWarnings("deprecation") private HashMap<SensorListener, LegacyListener> mLegacyListenersMap = @@ -276,6 +369,7 @@ public class SensorManager /* The thread and the sensor list are global to the process * but the actual thread is spawned on demand */ private static SensorThread sSensorThread; + private static int sQueue; // Used within this module from outside SensorManager, don't make private static SparseArray<Sensor> sHandleToSensor = new SparseArray<Sensor>(); @@ -290,80 +384,41 @@ public class SensorManager boolean mSensorsReady; SensorThread() { - // this gets to the sensor module. We can have only one per process. - sensors_data_init(); } @Override protected void finalize() { - sensors_data_uninit(); } // must be called with sListeners lock - boolean startLocked(ISensorService service) { + boolean startLocked() { try { if (mThread == null) { - Bundle dataChannel = service.getDataChannel(); - if (dataChannel != null) { - mSensorsReady = false; - SensorThreadRunnable runnable = new SensorThreadRunnable(dataChannel); - Thread thread = new Thread(runnable, SensorThread.class.getName()); - thread.start(); - synchronized (runnable) { - while (mSensorsReady == false) { - runnable.wait(); - } + mSensorsReady = false; + SensorThreadRunnable runnable = new SensorThreadRunnable(); + Thread thread = new Thread(runnable, SensorThread.class.getName()); + thread.start(); + synchronized (runnable) { + while (mSensorsReady == false) { + runnable.wait(); } - mThread = thread; } + mThread = thread; } - } catch (RemoteException e) { - Log.e(TAG, "RemoteException in startLocked: ", e); } catch (InterruptedException e) { } return mThread == null ? false : true; } private class SensorThreadRunnable implements Runnable { - private Bundle mDataChannel; - SensorThreadRunnable(Bundle dataChannel) { - mDataChannel = dataChannel; + SensorThreadRunnable() { } private boolean open() { // NOTE: this cannot synchronize on sListeners, since // it's held in the main thread at least until we // return from here. - - // this thread is guaranteed to be unique - Parcelable[] pfds = mDataChannel.getParcelableArray("fds"); - FileDescriptor[] fds; - if (pfds != null) { - int length = pfds.length; - fds = new FileDescriptor[length]; - for (int i = 0; i < length; i++) { - ParcelFileDescriptor pfd = (ParcelFileDescriptor)pfds[i]; - fds[i] = pfd.getFileDescriptor(); - } - } else { - fds = null; - } - int[] ints = mDataChannel.getIntArray("ints"); - sensors_data_open(fds, ints); - if (pfds != null) { - try { - // close our copies of the file descriptors, - // since we are just passing these to the JNI code and not using them here. - for (int i = pfds.length - 1; i >= 0; i--) { - ParcelFileDescriptor pfd = (ParcelFileDescriptor)pfds[i]; - pfd.close(); - } - } catch (IOException e) { - // *shrug* - Log.e(TAG, "IOException: ", e); - } - } - mDataChannel = null; + sQueue = sensors_create_queue(); return true; } @@ -386,7 +441,7 @@ public class SensorManager while (true) { // wait for an event - final int sensor = sensors_data_poll(values, status, timestamp); + final int sensor = sensors_data_poll(sQueue, values, status, timestamp); int accuracy = status[0]; synchronized (sListeners) { @@ -398,7 +453,8 @@ public class SensorManager } // we have no more listeners or polling failed, terminate the thread - sensors_data_close(); + sensors_destroy_queue(sQueue); + sQueue = 0; mThread = null; break; } @@ -426,7 +482,7 @@ public class SensorManager /*-----------------------------------------------------------------------*/ - private class ListenerDelegate extends Binder { + private class ListenerDelegate { final SensorEventListener mSensorEventListener; private final ArrayList<Sensor> mSensorList = new ArrayList<Sensor>(); private final Handler mHandler; @@ -522,8 +578,6 @@ public class SensorManager * {@hide} */ public SensorManager(Looper mainLooper) { - mSensorService = ISensorService.Stub.asInterface( - ServiceManager.getService(Context.SENSOR_SERVICE)); mMainLooper = mainLooper; @@ -540,11 +594,11 @@ public class SensorManager // which won't get the rotated values try { sRotation = sWindowManager.watchRotation( - new IRotationWatcher.Stub() { - public void onRotationChanged(int rotation) { - SensorManager.this.onRotationChanged(rotation); + new IRotationWatcher.Stub() { + public void onRotationChanged(int rotation) { + SensorManager.this.onRotationChanged(rotation); + } } - } ); } catch (RemoteException e) { } @@ -586,9 +640,10 @@ public class SensorManager return 0; } - /** @return available sensors. + /** + * @return available sensors. * @deprecated This method is deprecated, use - * {@link SensorManager#getSensorList(int)} instead + * {@link SensorManager#getSensorList(int)} instead */ @Deprecated public int getSensors() { @@ -604,7 +659,7 @@ public class SensorManager break; case Sensor.TYPE_ORIENTATION: result |= SensorManager.SENSOR_ORIENTATION | - SensorManager.SENSOR_ORIENTATION_RAW; + SensorManager.SENSOR_ORIENTATION_RAW; break; } } @@ -612,13 +667,18 @@ public class SensorManager } /** - * Use this method to get the list of available sensors of a certain - * type. Make multiple calls to get sensors of different types or use - * {@link android.hardware.Sensor#TYPE_ALL Sensor.TYPE_ALL} to get all - * the sensors. + * Use this method to get the list of available sensors of a certain type. + * Make multiple calls to get sensors of different types or use + * {@link android.hardware.Sensor#TYPE_ALL Sensor.TYPE_ALL} to get all the + * sensors. + * + * @param type + * of sensors requested * - * @param type of sensors requested * @return a list of sensors matching the asked type. + * + * @see #getDefaultSensor(int) + * @see Sensor */ public List<Sensor> getSensorList(int type) { // cache the returned lists the first time @@ -644,14 +704,18 @@ public class SensorManager } /** - * Use this method to get the default sensor for a given type. Note that - * the returned sensor could be a composite sensor, and its data could be + * Use this method to get the default sensor for a given type. Note that the + * returned sensor could be a composite sensor, and its data could be * averaged or filtered. If you need to access the raw sensors use * {@link SensorManager#getSensorList(int) getSensorList}. * + * @param type + * of sensors requested * - * @param type of sensors requested * @return the default sensors matching the asked type. + * + * @see #getSensorList(int) + * @see Sensor */ public Sensor getDefaultSensor(int type) { // TODO: need to be smarter, for now, just return the 1st sensor @@ -659,17 +723,21 @@ public class SensorManager return l.isEmpty() ? null : l.get(0); } - /** * Registers a listener for given sensors. + * * @deprecated This method is deprecated, use - * {@link SensorManager#registerListener(SensorEventListener, Sensor, int)} - * instead. + * {@link SensorManager#registerListener(SensorEventListener, Sensor, int)} + * instead. * - * @param listener sensor listener object - * @param sensors a bit masks of the sensors to register to + * @param listener + * sensor listener object * - * @return true if the sensor is supported and successfully enabled + * @param sensors + * a bit masks of the sensors to register to + * + * @return <code>true</code> if the sensor is supported and successfully + * enabled */ @Deprecated public boolean registerListener(SensorListener listener, int sensors) { @@ -678,18 +746,26 @@ public class SensorManager /** * Registers a SensorListener for given sensors. + * * @deprecated This method is deprecated, use - * {@link SensorManager#registerListener(SensorEventListener, Sensor, int)} - * instead. + * {@link SensorManager#registerListener(SensorEventListener, Sensor, int)} + * instead. + * + * @param listener + * sensor listener object + * + * @param sensors + * a bit masks of the sensors to register to * - * @param listener sensor listener object - * @param sensors a bit masks of the sensors to register to - * @param rate rate of events. This is only a hint to the system. events - * may be received faster or slower than the specified rate. Usually events - * are received faster. The value must be one of {@link #SENSOR_DELAY_NORMAL}, - * {@link #SENSOR_DELAY_UI}, {@link #SENSOR_DELAY_GAME}, or {@link #SENSOR_DELAY_FASTEST}. + * @param rate + * rate of events. This is only a hint to the system. events may be + * received faster or slower than the specified rate. Usually events + * are received faster. The value must be one of + * {@link #SENSOR_DELAY_NORMAL}, {@link #SENSOR_DELAY_UI}, + * {@link #SENSOR_DELAY_GAME}, or {@link #SENSOR_DELAY_FASTEST}. * - * @return true if the sensor is supported and successfully enabled + * @return <code>true</code> if the sensor is supported and successfully + * enabled */ @Deprecated public boolean registerListener(SensorListener listener, int sensors, int rate) { @@ -747,12 +823,16 @@ public class SensorManager /** * Unregisters a listener for the sensors with which it is registered. + * * @deprecated This method is deprecated, use - * {@link SensorManager#unregisterListener(SensorEventListener, Sensor)} - * instead. + * {@link SensorManager#unregisterListener(SensorEventListener, Sensor)} + * instead. + * + * @param listener + * a SensorListener object * - * @param listener a SensorListener object - * @param sensors a bit masks of the sensors to unregister from + * @param sensors + * a bit masks of the sensors to unregister from */ @Deprecated public void unregisterListener(SensorListener listener, int sensors) { @@ -815,11 +895,13 @@ public class SensorManager /** * Unregisters a listener for all sensors. + * * @deprecated This method is deprecated, use - * {@link SensorManager#unregisterListener(SensorEventListener)} - * instead. + * {@link SensorManager#unregisterListener(SensorEventListener)} + * instead. * - * @param listener a SensorListener object + * @param listener + * a SensorListener object */ @Deprecated public void unregisterListener(SensorListener listener) { @@ -829,8 +911,14 @@ public class SensorManager /** * Unregisters a listener for the sensors with which it is registered. * - * @param listener a SensorEventListener object - * @param sensor the sensor to unregister from + * @param listener + * a SensorEventListener object + * + * @param sensor + * the sensor to unregister from + * + * @see #unregisterListener(SensorEventListener) + * @see #registerListener(SensorEventListener, Sensor, int) * */ public void unregisterListener(SensorEventListener listener, Sensor sensor) { @@ -840,27 +928,43 @@ public class SensorManager /** * Unregisters a listener for all sensors. * - * @param listener a SensorListener object + * @param listener + * a SensorListener object + * + * @see #unregisterListener(SensorEventListener, Sensor) + * @see #registerListener(SensorEventListener, Sensor, int) * */ public void unregisterListener(SensorEventListener listener) { unregisterListener((Object)listener); } - /** - * Registers a {@link android.hardware.SensorEventListener SensorEventListener} - * for the given sensor. + * Registers a {@link android.hardware.SensorEventListener + * SensorEventListener} for the given sensor. * - * @param listener A {@link android.hardware.SensorEventListener SensorEventListener} object. - * @param sensor The {@link android.hardware.Sensor Sensor} to register to. - * @param rate The rate {@link android.hardware.SensorEvent sensor events} are delivered at. - * This is only a hint to the system. Events may be received faster or - * slower than the specified rate. Usually events are received faster. The value must be - * one of {@link #SENSOR_DELAY_NORMAL}, {@link #SENSOR_DELAY_UI}, {@link #SENSOR_DELAY_GAME}, - * or {@link #SENSOR_DELAY_FASTEST}. + * @param listener + * A {@link android.hardware.SensorEventListener SensorEventListener} + * object. * - * @return true if the sensor is supported and successfully enabled. + * @param sensor + * The {@link android.hardware.Sensor Sensor} to register to. + * + * @param rate + * The rate {@link android.hardware.SensorEvent sensor events} are + * delivered at. This is only a hint to the system. Events may be + * received faster or slower than the specified rate. Usually events + * are received faster. The value must be one of + * {@link #SENSOR_DELAY_NORMAL}, {@link #SENSOR_DELAY_UI}, + * {@link #SENSOR_DELAY_GAME}, or {@link #SENSOR_DELAY_FASTEST} + * or, the desired delay between events in microsecond. + * + * @return <code>true</code> if the sensor is supported and successfully + * enabled. + * + * @see #registerListener(SensorEventListener, Sensor, int, Handler) + * @see #unregisterListener(SensorEventListener) + * @see #unregisterListener(SensorEventListener, Sensor) * */ public boolean registerListener(SensorEventListener listener, Sensor sensor, int rate) { @@ -868,21 +972,36 @@ public class SensorManager } /** - * Registers a {@link android.hardware.SensorEventListener SensorEventListener} - * for the given sensor. - * - * @param listener A {@link android.hardware.SensorEventListener SensorEventListener} object. - * @param sensor The {@link android.hardware.Sensor Sensor} to register to. - * @param rate The rate {@link android.hardware.SensorEvent sensor events} are delivered at. - * This is only a hint to the system. Events may be received faster or - * slower than the specified rate. Usually events are received faster. The value must be one - * of {@link #SENSOR_DELAY_NORMAL}, {@link #SENSOR_DELAY_UI}, {@link #SENSOR_DELAY_GAME}, or - * {@link #SENSOR_DELAY_FASTEST}. - * @param handler The {@link android.os.Handler Handler} the - * {@link android.hardware.SensorEvent sensor events} will be delivered to. + * Registers a {@link android.hardware.SensorEventListener + * SensorEventListener} for the given sensor. + * + * @param listener + * A {@link android.hardware.SensorEventListener SensorEventListener} + * object. + * + * @param sensor + * The {@link android.hardware.Sensor Sensor} to register to. + * + * @param rate + * The rate {@link android.hardware.SensorEvent sensor events} are + * delivered at. This is only a hint to the system. Events may be + * received faster or slower than the specified rate. Usually events + * are received faster. The value must be one of + * {@link #SENSOR_DELAY_NORMAL}, {@link #SENSOR_DELAY_UI}, + * {@link #SENSOR_DELAY_GAME}, or {@link #SENSOR_DELAY_FASTEST}. + * or, the desired delay between events in microsecond. + * + * @param handler + * The {@link android.os.Handler Handler} the + * {@link android.hardware.SensorEvent sensor events} will be + * delivered to. * * @return true if the sensor is supported and successfully enabled. * + * @see #registerListener(SensorEventListener, Sensor, int) + * @see #unregisterListener(SensorEventListener) + * @see #unregisterListener(SensorEventListener, Sensor) + * */ public boolean registerListener(SensorEventListener listener, Sensor sensor, int rate, Handler handler) { @@ -896,54 +1015,50 @@ public class SensorManager delay = 0; break; case SENSOR_DELAY_GAME: - delay = 20; + delay = 20000; break; case SENSOR_DELAY_UI: - delay = 60; + delay = 60000; break; case SENSOR_DELAY_NORMAL: - delay = 200; + delay = 200000; break; default: - return false; + delay = rate; + break; } - try { - synchronized (sListeners) { - ListenerDelegate l = null; - for (ListenerDelegate i : sListeners) { - if (i.getListener() == listener) { - l = i; - break; - } + synchronized (sListeners) { + ListenerDelegate l = null; + for (ListenerDelegate i : sListeners) { + if (i.getListener() == listener) { + l = i; + break; } + } - String name = sensor.getName(); - int handle = sensor.getHandle(); - if (l == null) { - result = false; - l = new ListenerDelegate(listener, sensor, handler); - sListeners.add(l); - if (!sListeners.isEmpty()) { - result = sSensorThread.startLocked(mSensorService); - if (result) { - result = mSensorService.enableSensor(l, name, handle, delay); - if (!result) { - // there was an error, remove the listeners - sListeners.remove(l); - } - } - } - } else { - result = mSensorService.enableSensor(l, name, handle, delay); + String name = sensor.getName(); + int handle = sensor.getHandle(); + if (l == null) { + result = false; + l = new ListenerDelegate(listener, sensor, handler); + sListeners.add(l); + if (!sListeners.isEmpty()) { + result = sSensorThread.startLocked(); if (result) { - l.addSensor(sensor); + result = sensors_enable_sensor(sQueue, name, handle, delay); + if (!result) { + // there was an error, remove the listeners + sListeners.remove(l); + } } } + } else { + result = sensors_enable_sensor(sQueue, name, handle, delay); + if (result) { + l.addSensor(sensor); + } } - } catch (RemoteException e) { - Log.e(TAG, "RemoteException in registerListener: ", e); - result = false; } return result; } @@ -952,27 +1067,23 @@ public class SensorManager if (listener == null || sensor == null) { return; } - try { - synchronized (sListeners) { - final int size = sListeners.size(); - for (int i=0 ; i<size ; i++) { - ListenerDelegate l = sListeners.get(i); - if (l.getListener() == listener) { - // disable these sensors - String name = sensor.getName(); - int handle = sensor.getHandle(); - mSensorService.enableSensor(l, name, handle, SENSOR_DISABLE); - // if we have no more sensors enabled on this listener, - // take it off the list. - if (l.removeSensor(sensor) == 0) { - sListeners.remove(i); - } - break; + synchronized (sListeners) { + final int size = sListeners.size(); + for (int i=0 ; i<size ; i++) { + ListenerDelegate l = sListeners.get(i); + if (l.getListener() == listener) { + // disable these sensors + String name = sensor.getName(); + int handle = sensor.getHandle(); + sensors_enable_sensor(sQueue, name, handle, SENSOR_DISABLE); + // if we have no more sensors enabled on this listener, + // take it off the list. + if (l.removeSensor(sensor) == 0) { + sListeners.remove(i); } + break; } } - } catch (RemoteException e) { - Log.e(TAG, "RemoteException in unregisterListener: ", e); } } @@ -980,83 +1091,102 @@ public class SensorManager if (listener == null) { return; } - try { - synchronized (sListeners) { - final int size = sListeners.size(); - for (int i=0 ; i<size ; i++) { - ListenerDelegate l = sListeners.get(i); - if (l.getListener() == listener) { - // disable all sensors for this listener - for (Sensor sensor : l.getSensors()) { - String name = sensor.getName(); - int handle = sensor.getHandle(); - mSensorService.enableSensor(l, name, handle, SENSOR_DISABLE); - } - sListeners.remove(i); - break; + synchronized (sListeners) { + final int size = sListeners.size(); + for (int i=0 ; i<size ; i++) { + ListenerDelegate l = sListeners.get(i); + if (l.getListener() == listener) { + // disable all sensors for this listener + for (Sensor sensor : l.getSensors()) { + String name = sensor.getName(); + int handle = sensor.getHandle(); + sensors_enable_sensor(sQueue, name, handle, SENSOR_DISABLE); } + sListeners.remove(i); + break; } } - } catch (RemoteException e) { - Log.e(TAG, "RemoteException in unregisterListener: ", e); } } /** - * Computes the inclination matrix <b>I</b> as well as the rotation - * matrix <b>R</b> transforming a vector from the - * device coordinate system to the world's coordinate system which is - * defined as a direct orthonormal basis, where: - * + * <p> + * Computes the inclination matrix <b>I</b> as well as the rotation matrix + * <b>R</b> transforming a vector from the device coordinate system to the + * world's coordinate system which is defined as a direct orthonormal basis, + * where: + * </p> + * + * <ul> * <li>X is defined as the vector product <b>Y.Z</b> (It is tangential to * the ground at the device's current location and roughly points East).</li> * <li>Y is tangential to the ground at the device's current location and * points towards the magnetic North Pole.</li> * <li>Z points towards the sky and is perpendicular to the ground.</li> + * </ul> + * + * <p> + * <center><img src="../../../images/axis_globe.png" + * alt="Sensors coordinate-system diagram." border="0" /></center> + * </p> + * * <p> * <hr> - * <p>By definition: - * <p>[0 0 g] = <b>R</b> * <b>gravity</b> (g = magnitude of gravity) - * <p>[0 m 0] = <b>I</b> * <b>R</b> * <b>geomagnetic</b> - * (m = magnitude of geomagnetic field) - * <p><b>R</b> is the identity matrix when the device is aligned with the + * <p> + * By definition: + * <p> + * [0 0 g] = <b>R</b> * <b>gravity</b> (g = magnitude of gravity) + * <p> + * [0 m 0] = <b>I</b> * <b>R</b> * <b>geomagnetic</b> (m = magnitude of + * geomagnetic field) + * <p> + * <b>R</b> is the identity matrix when the device is aligned with the * world's coordinate system, that is, when the device's X axis points * toward East, the Y axis points to the North Pole and the device is facing * the sky. * - * <p><b>I</b> is a rotation matrix transforming the geomagnetic - * vector into the same coordinate space as gravity (the world's coordinate - * space). <b>I</b> is a simple rotation around the X axis. - * The inclination angle in radians can be computed with - * {@link #getInclination}. + * <p> + * <b>I</b> is a rotation matrix transforming the geomagnetic vector into + * the same coordinate space as gravity (the world's coordinate space). + * <b>I</b> is a simple rotation around the X axis. The inclination angle in + * radians can be computed with {@link #getInclination}. * <hr> - * - * <p> Each matrix is returned either as a 3x3 or 4x4 row-major matrix - * depending on the length of the passed array: - * <p><u>If the array length is 16:</u> + * + * <p> + * Each matrix is returned either as a 3x3 or 4x4 row-major matrix depending + * on the length of the passed array: + * <p> + * <u>If the array length is 16:</u> + * * <pre> * / M[ 0] M[ 1] M[ 2] M[ 3] \ * | M[ 4] M[ 5] M[ 6] M[ 7] | * | M[ 8] M[ 9] M[10] M[11] | * \ M[12] M[13] M[14] M[15] / *</pre> - * This matrix is ready to be used by OpenGL ES's - * {@link javax.microedition.khronos.opengles.GL10#glLoadMatrixf(float[], int) - * glLoadMatrixf(float[], int)}. - * <p>Note that because OpenGL matrices are column-major matrices you must - * transpose the matrix before using it. However, since the matrix is a + * + * This matrix is ready to be used by OpenGL ES's + * {@link javax.microedition.khronos.opengles.GL10#glLoadMatrixf(float[], int) + * glLoadMatrixf(float[], int)}. + * <p> + * Note that because OpenGL matrices are column-major matrices you must + * transpose the matrix before using it. However, since the matrix is a * rotation matrix, its transpose is also its inverse, conveniently, it is * often the inverse of the rotation that is needed for rendering; it can * therefore be used with OpenGL ES directly. * <p> * Also note that the returned matrices always have this form: + * * <pre> * / M[ 0] M[ 1] M[ 2] 0 \ * | M[ 4] M[ 5] M[ 6] 0 | * | M[ 8] M[ 9] M[10] 0 | * \ 0 0 0 1 / *</pre> - * <p><u>If the array length is 9:</u> + * + * <p> + * <u>If the array length is 9:</u> + * * <pre> * / M[ 0] M[ 1] M[ 2] \ * | M[ 3] M[ 4] M[ 5] | @@ -1064,34 +1194,52 @@ public class SensorManager *</pre> * * <hr> - * <p>The inverse of each matrix can be computed easily by taking its + * <p> + * The inverse of each matrix can be computed easily by taking its * transpose. * - * <p>The matrices returned by this function are meaningful only when the - * device is not free-falling and it is not close to the magnetic north. - * If the device is accelerating, or placed into a strong magnetic field, - * the returned matrices may be inaccurate. - * - * @param R is an array of 9 floats holding the rotation matrix <b>R</b> - * when this function returns. R can be null.<p> - * @param I is an array of 9 floats holding the rotation matrix <b>I</b> - * when this function returns. I can be null.<p> - * @param gravity is an array of 3 floats containing the gravity vector - * expressed in the device's coordinate. You can simply use the - * {@link android.hardware.SensorEvent#values values} - * returned by a {@link android.hardware.SensorEvent SensorEvent} of a - * {@link android.hardware.Sensor Sensor} of type - * {@link android.hardware.Sensor#TYPE_ACCELEROMETER TYPE_ACCELEROMETER}.<p> - * @param geomagnetic is an array of 3 floats containing the geomagnetic - * vector expressed in the device's coordinate. You can simply use the - * {@link android.hardware.SensorEvent#values values} - * returned by a {@link android.hardware.SensorEvent SensorEvent} of a - * {@link android.hardware.Sensor Sensor} of type - * {@link android.hardware.Sensor#TYPE_MAGNETIC_FIELD TYPE_MAGNETIC_FIELD}. - * @return - * true on success<p> - * false on failure (for instance, if the device is in free fall). - * On failure the output matrices are not modified. + * <p> + * The matrices returned by this function are meaningful only when the + * device is not free-falling and it is not close to the magnetic north. If + * the device is accelerating, or placed into a strong magnetic field, the + * returned matrices may be inaccurate. + * + * @param R + * is an array of 9 floats holding the rotation matrix <b>R</b> when + * this function returns. R can be null. + * <p> + * + * @param I + * is an array of 9 floats holding the rotation matrix <b>I</b> when + * this function returns. I can be null. + * <p> + * + * @param gravity + * is an array of 3 floats containing the gravity vector expressed in + * the device's coordinate. You can simply use the + * {@link android.hardware.SensorEvent#values values} returned by a + * {@link android.hardware.SensorEvent SensorEvent} of a + * {@link android.hardware.Sensor Sensor} of type + * {@link android.hardware.Sensor#TYPE_ACCELEROMETER + * TYPE_ACCELEROMETER}. + * <p> + * + * @param geomagnetic + * is an array of 3 floats containing the geomagnetic vector + * expressed in the device's coordinate. You can simply use the + * {@link android.hardware.SensorEvent#values values} returned by a + * {@link android.hardware.SensorEvent SensorEvent} of a + * {@link android.hardware.Sensor Sensor} of type + * {@link android.hardware.Sensor#TYPE_MAGNETIC_FIELD + * TYPE_MAGNETIC_FIELD}. + * + * @return <code>true</code> on success, <code>false</code> on failure (for + * instance, if the device is in free fall). On failure the output + * matrices are not modified. + * + * @see #getInclination(float[]) + * @see #getOrientation(float[], float[]) + * @see #remapCoordinateSystem(float[], int, int, float[]) */ public static boolean getRotationMatrix(float[] R, float[] I, @@ -1160,64 +1308,98 @@ public class SensorManager /** * Computes the geomagnetic inclination angle in radians from the * inclination matrix <b>I</b> returned by {@link #getRotationMatrix}. - * @param I inclination matrix see {@link #getRotationMatrix}. + * + * @param I + * inclination matrix see {@link #getRotationMatrix}. + * * @return The geomagnetic inclination angle in radians. + * + * @see #getRotationMatrix(float[], float[], float[], float[]) + * @see #getOrientation(float[], float[]) + * @see GeomagneticField + * */ public static float getInclination(float[] I) { if (I.length == 9) { return (float)Math.atan2(I[5], I[4]); } else { - return (float)Math.atan2(I[6], I[5]); + return (float)Math.atan2(I[6], I[5]); } } /** - * Rotates the supplied rotation matrix so it is expressed in a - * different coordinate system. This is typically used when an application - * needs to compute the three orientation angles of the device (see + * <p> + * Rotates the supplied rotation matrix so it is expressed in a different + * coordinate system. This is typically used when an application needs to + * compute the three orientation angles of the device (see * {@link #getOrientation}) in a different coordinate system. - * - * <p>When the rotation matrix is used for drawing (for instance with - * OpenGL ES), it usually <b>doesn't need</b> to be transformed by this - * function, unless the screen is physically rotated, in which case you - * can use {@link android.view.Display#getRotation() Display.getRotation()} - * to retrieve the current rotation of the screen. Note that because the - * user is generally free to rotate their screen, you often should - * consider the rotation in deciding the parameters to use here. + * </p> * - * <p><u>Examples:</u><p> + * <p> + * When the rotation matrix is used for drawing (for instance with OpenGL + * ES), it usually <b>doesn't need</b> to be transformed by this function, + * unless the screen is physically rotated, in which case you can use + * {@link android.view.Display#getRotation() Display.getRotation()} to + * retrieve the current rotation of the screen. Note that because the user + * is generally free to rotate their screen, you often should consider the + * rotation in deciding the parameters to use here. + * </p> * - * <li>Using the camera (Y axis along the camera's axis) for an augmented - * reality application where the rotation angles are needed: </li><p> + * <p> + * <u>Examples:</u> + * <p> * - * <code>remapCoordinateSystem(inR, AXIS_X, AXIS_Z, outR);</code><p> + * <ul> + * <li>Using the camera (Y axis along the camera's axis) for an augmented + * reality application where the rotation angles are needed:</li> + * + * <p> + * <ul> + * <code>remapCoordinateSystem(inR, AXIS_X, AXIS_Z, outR);</code> + * </ul> + * </p> * * <li>Using the device as a mechanical compass when rotation is - * {@link android.view.Surface#ROTATION_90 Surface.ROTATION_90}:</li><p> - * - * <code>remapCoordinateSystem(inR, AXIS_Y, AXIS_MINUS_X, outR);</code><p> - * - * Beware of the above example. This call is needed only to account for - * a rotation from its natural orientation when calculating the - * rotation angles (see {@link #getOrientation}). - * If the rotation matrix is also used for rendering, it may not need to - * be transformed, for instance if your {@link android.app.Activity - * Activity} is running in landscape mode. - * - * <p>Since the resulting coordinate system is orthonormal, only two axes - * need to be specified. - * - * @param inR the rotation matrix to be transformed. Usually it is the - * matrix returned by {@link #getRotationMatrix}. - * @param X defines on which world axis and direction the X axis of the - * device is mapped. - * @param Y defines on which world axis and direction the Y axis of the - * device is mapped. - * @param outR the transformed rotation matrix. inR and outR can be the same + * {@link android.view.Surface#ROTATION_90 Surface.ROTATION_90}:</li> + * + * <p> + * <ul> + * <code>remapCoordinateSystem(inR, AXIS_Y, AXIS_MINUS_X, outR);</code> + * </ul> + * </p> + * + * Beware of the above example. This call is needed only to account for a + * rotation from its natural orientation when calculating the rotation + * angles (see {@link #getOrientation}). If the rotation matrix is also used + * for rendering, it may not need to be transformed, for instance if your + * {@link android.app.Activity Activity} is running in landscape mode. + * </ul> + * + * <p> + * Since the resulting coordinate system is orthonormal, only two axes need + * to be specified. + * + * @param inR + * the rotation matrix to be transformed. Usually it is the matrix + * returned by {@link #getRotationMatrix}. + * + * @param X + * defines on which world axis and direction the X axis of the device + * is mapped. + * + * @param Y + * defines on which world axis and direction the Y axis of the device + * is mapped. + * + * @param outR + * the transformed rotation matrix. inR and outR can be the same * array, but it is not recommended for performance reason. - * @return true on success. false if the input parameters are incorrect, for - * instance if X and Y define the same axis. Or if inR and outR don't have - * the same length. + * + * @return <code>true</code> on success. <code>false</code> if the input + * parameters are incorrect, for instance if X and Y define the same + * axis. Or if inR and outR don't have the same length. + * + * @see #getRotationMatrix(float[], float[], float[], float[]) */ public static boolean remapCoordinateSystem(float[] inR, int X, int Y, @@ -1301,17 +1483,31 @@ public class SensorManager /** * Computes the device's orientation based on the rotation matrix. - * <p> When it returns, the array values is filled with the result: + * <p> + * When it returns, the array values is filled with the result: + * <ul> * <li>values[0]: <i>azimuth</i>, rotation around the Z axis.</li> * <li>values[1]: <i>pitch</i>, rotation around the X axis.</li> * <li>values[2]: <i>roll</i>, rotation around the Y axis.</li> + * </ul> + * <p> + * <center><img src="../../../images/axis_device.png" + * alt="Sensors coordinate-system diagram." border="0" /></center> + * </p> * <p> * All three angles above are in <b>radians</b> and <b>positive</b> in the * <b>counter-clockwise</b> direction. - * - * @param R rotation matrix see {@link #getRotationMatrix}. - * @param values an array of 3 floats to hold the result. + * + * @param R + * rotation matrix see {@link #getRotationMatrix}. + * + * @param values + * an array of 3 floats to hold the result. + * * @return The array values passed as argument. + * + * @see #getRotationMatrix(float[], float[], float[], float[]) + * @see GeomagneticField */ public static float[] getOrientation(float[] R, float values[]) { /* @@ -1320,12 +1516,12 @@ public class SensorManager * | R[ 4] R[ 5] R[ 6] 0 | * | R[ 8] R[ 9] R[10] 0 | * \ 0 0 0 1 / - * + * * 3x3 (length=9) case: * / R[ 0] R[ 1] R[ 2] \ * | R[ 3] R[ 4] R[ 5] | * \ R[ 6] R[ 7] R[ 8] / - * + * */ if (R.length == 9) { values[0] = (float)Math.atan2(R[1], R[4]); @@ -1339,8 +1535,42 @@ public class SensorManager return values; } - /** + * Computes the Altitude in meters from the atmospheric pressure and the + * pressure at sea level. + * <p> + * Typically the atmospheric pressure is read from a + * {@link Sensor#TYPE_PRESSURE} sensor. The pressure at sea level must be + * known, usually it can be retrieved from airport databases in the + * vicinity. If unknown, you can use {@link #PRESSURE_STANDARD_ATMOSPHERE} + * as an approximation, but absolute altitudes won't be accurate. + * </p> + * <p> + * To calculate altitude differences, you must calculate the difference + * between the altitudes at both points. If you don't know the altitude + * as sea level, you can use {@link #PRESSURE_STANDARD_ATMOSPHERE} instead, + * which will give good results considering the range of pressure typically + * involved. + * </p> + * <p> + * <code><ul> + * float altitude_difference = + * getAltitude(SensorManager.PRESSURE_STANDARD_ATMOSPHERE, pressure_at_point2) + * - getAltitude(SensorManager.PRESSURE_STANDARD_ATMOSPHERE, pressure_at_point1); + * </ul></code> + * </p> + * + * @param p0 pressure at sea level + * @param p atmospheric pressure + * @return Altitude in meters + */ + public static float getAltitude(float p0, float p) { + final float coef = 1.0f / 5.255f; + return 44330.0f * (1.0f - (float)Math.pow(p/p0, coef)); + } + + + /** * {@hide} */ public void onRotationChanged(int rotation) { @@ -1487,7 +1717,7 @@ public class SensorManager } } } - + class LmsFilter { private static final int SENSORS_RATE_MS = 20; private static final int COUNT = 12; @@ -1555,16 +1785,192 @@ public class SensorManager } } - + + /** Helper function to compute the angle change between two rotation matrices. + * Given a current rotation matrix (R) and a previous rotation matrix + * (prevR) computes the rotation around the x,y, and z axes which + * transforms prevR to R. + * outputs a 3 element vector containing the x,y, and z angle + * change at indexes 0, 1, and 2 respectively. + * <p> Each input matrix is either as a 3x3 or 4x4 row-major matrix + * depending on the length of the passed array: + * <p>If the array length is 9, then the array elements represent this matrix + * <pre> + * / R[ 0] R[ 1] R[ 2] \ + * | R[ 3] R[ 4] R[ 5] | + * \ R[ 6] R[ 7] R[ 8] / + *</pre> + * <p>If the array length is 16, then the array elements represent this matrix + * <pre> + * / R[ 0] R[ 1] R[ 2] R[ 3] \ + * | R[ 4] R[ 5] R[ 6] R[ 7] | + * | R[ 8] R[ 9] R[10] R[11] | + * \ R[12] R[13] R[14] R[15] / + *</pre> + * @param R current rotation matrix + * @param prevR previous rotation matrix + * @param angleChange an array of floats in which the angle change is stored + */ + + public static void getAngleChange( float[] angleChange, float[] R, float[] prevR) { + float rd1=0,rd4=0, rd6=0,rd7=0, rd8=0; + float ri0=0,ri1=0,ri2=0,ri3=0,ri4=0,ri5=0,ri6=0,ri7=0,ri8=0; + float pri0=0, pri1=0, pri2=0, pri3=0, pri4=0, pri5=0, pri6=0, pri7=0, pri8=0; + int i, j, k; + + if(R.length == 9) { + ri0 = R[0]; + ri1 = R[1]; + ri2 = R[2]; + ri3 = R[3]; + ri4 = R[4]; + ri5 = R[5]; + ri6 = R[6]; + ri7 = R[7]; + ri8 = R[8]; + } else if(R.length == 16) { + ri0 = R[0]; + ri1 = R[1]; + ri2 = R[2]; + ri3 = R[4]; + ri4 = R[5]; + ri5 = R[6]; + ri6 = R[8]; + ri7 = R[9]; + ri8 = R[10]; + } + + if(prevR.length == 9) { + pri0 = R[0]; + pri1 = R[1]; + pri2 = R[2]; + pri3 = R[3]; + pri4 = R[4]; + pri5 = R[5]; + pri6 = R[6]; + pri7 = R[7]; + pri8 = R[8]; + } else if(prevR.length == 16) { + pri0 = R[0]; + pri1 = R[1]; + pri2 = R[2]; + pri3 = R[4]; + pri4 = R[5]; + pri5 = R[6]; + pri6 = R[8]; + pri7 = R[9]; + pri8 = R[10]; + } + + // calculate the parts of the rotation difference matrix we need + // rd[i][j] = pri[0][i] * ri[0][j] + pri[1][i] * ri[1][j] + pri[2][i] * ri[2][j]; + + rd1 = pri0 * ri1 + pri3 * ri4 + pri6 * ri7; //rd[0][1] + rd4 = pri1 * ri1 + pri4 * ri4 + pri7 * ri7; //rd[1][1] + rd6 = pri2 * ri0 + pri5 * ri3 + pri8 * ri6; //rd[2][0] + rd7 = pri2 * ri1 + pri5 * ri4 + pri8 * ri7; //rd[2][1] + rd8 = pri2 * ri2 + pri5 * ri5 + pri8 * ri8; //rd[2][2] + + angleChange[0] = (float)Math.atan2(rd1, rd4); + angleChange[1] = (float)Math.asin(-rd7); + angleChange[2] = (float)Math.atan2(-rd6, rd8); + + } + + /** Helper function to convert a rotation vector to a rotation matrix. + * Given a rotation vector (presumably from a ROTATION_VECTOR sensor), returns a + * 9 or 16 element rotation matrix in the array R. R must have length 9 or 16. + * If R.length == 9, the following matrix is returned: + * <pre> + * / R[ 0] R[ 1] R[ 2] \ + * | R[ 3] R[ 4] R[ 5] | + * \ R[ 6] R[ 7] R[ 8] / + *</pre> + * If R.length == 16, the following matrix is returned: + * <pre> + * / R[ 0] R[ 1] R[ 2] 0 \ + * | R[ 4] R[ 5] R[ 6] 0 | + * | R[ 8] R[ 9] R[10] 0 | + * \ 0 0 0 1 / + *</pre> + * @param rotationVector the rotation vector to convert + * @param R an array of floats in which to store the rotation matrix + */ + public static void getRotationMatrixFromVector(float[] R, float[] rotationVector) { + float q0 = (float)Math.sqrt(1 - rotationVector[0]*rotationVector[0] - + rotationVector[1]*rotationVector[1] - + rotationVector[2]*rotationVector[2]); + float q1 = rotationVector[0]; + float q2 = rotationVector[1]; + float q3 = rotationVector[2]; + + float sq_q1 = 2 * q1 * q1; + float sq_q2 = 2 * q2 * q2; + float sq_q3 = 2 * q3 * q3; + float q1_q2 = 2 * q1 * q2; + float q3_q0 = 2 * q3 * q0; + float q1_q3 = 2 * q1 * q3; + float q2_q0 = 2 * q2 * q0; + float q2_q3 = 2 * q2 * q3; + float q1_q0 = 2 * q1 * q0; + + if(R.length == 9) { + R[0] = 1 - sq_q2 - sq_q3; + R[1] = q1_q2 - q3_q0; + R[2] = q1_q3 + q2_q0; + + R[3] = q1_q2 + q3_q0; + R[4] = 1 - sq_q1 - sq_q3; + R[5] = q2_q3 - q1_q0; + + R[6] = q1_q3 - q2_q0; + R[7] = q2_q3 + q1_q0; + R[8] = 1 - sq_q1 - sq_q2; + } else if (R.length == 16) { + R[0] = 1 - sq_q2 - sq_q3; + R[1] = q1_q2 - q3_q0; + R[2] = q1_q3 + q2_q0; + R[3] = 0.0f; + + R[4] = q1_q2 + q3_q0; + R[5] = 1 - sq_q1 - sq_q3; + R[6] = q2_q3 - q1_q0; + R[7] = 0.0f; + + R[8] = q1_q3 - q2_q0; + R[9] = q2_q3 + q1_q0; + R[10] = 1 - sq_q1 - sq_q2; + R[11] = 0.0f; + + R[12] = R[13] = R[14] = 0.0f; + R[15] = 1.0f; + } + } + + /** Helper function to convert a rotation vector to a normalized quaternion. + * Given a rotation vector (presumably from a ROTATION_VECTOR sensor), returns a normalized + * quaternion in the array Q. The quaternion is stored as [w, x, y, z] + * @param rv the rotation vector to convert + * @param Q an array of floats in which to store the computed quaternion + */ + public static void getQuaternionFromVector(float[] Q, float[] rv) { + float w = (float)Math.sqrt(1 - rv[0]*rv[0] - rv[1]*rv[1] - rv[2]*rv[2]); + //In this case, the w component of the quaternion is known to be a positive number + + Q[0] = w; + Q[1] = rv[0]; + Q[2] = rv[1]; + Q[3] = rv[2]; + } + private static native void nativeClassInit(); private static native int sensors_module_init(); private static native int sensors_module_get_next_sensor(Sensor sensor, int next); // Used within this module from outside SensorManager, don't make private - static native int sensors_data_init(); - static native int sensors_data_uninit(); - static native int sensors_data_open(FileDescriptor[] fds, int[] ints); - static native int sensors_data_close(); - static native int sensors_data_poll(float[] values, int[] status, long[] timestamp); + static native int sensors_create_queue(); + static native void sensors_destroy_queue(int queue); + static native boolean sensors_enable_sensor(int queue, String name, int sensor, int enable); + static native int sensors_data_poll(int queue, float[] values, int[] status, long[] timestamp); } diff --git a/core/java/android/hardware/Usb.java b/core/java/android/hardware/Usb.java new file mode 100644 index 000000000000..57271d4b72f8 --- /dev/null +++ b/core/java/android/hardware/Usb.java @@ -0,0 +1,99 @@ +/* + * 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 android.hardware; + +/** + * Class for accessing USB state information. + * @hide + */ +public class Usb { + /** + * Broadcast Action: A broadcast for USB connected events. + * + * The extras bundle will name/value pairs with the name of the function + * and a value of either {@link #USB_FUNCTION_ENABLED} or {@link #USB_FUNCTION_DISABLED}. + * Possible USB function names include {@link #USB_FUNCTION_MASS_STORAGE}, + * {@link #USB_FUNCTION_ADB}, {@link #USB_FUNCTION_RNDIS} and {@link #USB_FUNCTION_MTP}. + */ + public static final String ACTION_USB_CONNECTED = + "android.hardware.action.USB_CONNECTED"; + + /** + * Broadcast Action: A broadcast for USB disconnected events. + */ + public static final String ACTION_USB_DISCONNECTED = + "android.hardware.action.USB_DISCONNECTED"; + + /** + * Broadcast Action: A sticky broadcast for USB state change events. + * + * This is a sticky broadcast for clients that are interested in both USB connect and + * disconnect events. If you are only concerned with one or the other, you can use + * {@link #ACTION_USB_CONNECTED} or {@link #ACTION_USB_DISCONNECTED} to avoid receiving + * unnecessary broadcasts. The boolean {@link #USB_CONNECTED} extra indicates whether + * USB is connected or disconnected. + * The extras bundle will also contain name/value pairs with the name of the function + * and a value of either {@link #USB_FUNCTION_ENABLED} or {@link #USB_FUNCTION_DISABLED}. + * Possible USB function names include {@link #USB_FUNCTION_MASS_STORAGE}, + * {@link #USB_FUNCTION_ADB}, {@link #USB_FUNCTION_RNDIS} and {@link #USB_FUNCTION_MTP}. + */ + public static final String ACTION_USB_STATE = + "android.hardware.action.USB_STATE"; + + /** + * Boolean extra indicating whether USB is connected or disconnected. + * Used in extras for the {@link #ACTION_USB_STATE} broadcast. + */ + public static final String USB_CONNECTED = "connected"; + + /** + * Name of the USB mass storage USB function. + * Used in extras for the {@link #ACTION_USB_CONNECTED} broadcast + */ + public static final String USB_FUNCTION_MASS_STORAGE = "mass_storage"; + + /** + * Name of the adb USB function. + * Used in extras for the {@link #ACTION_USB_CONNECTED} broadcast + */ + public static final String USB_FUNCTION_ADB = "adb"; + + /** + * Name of the RNDIS ethernet USB function. + * Used in extras for the {@link #ACTION_USB_CONNECTED} broadcast + */ + public static final String USB_FUNCTION_RNDIS = "rndis"; + + /** + * Name of the MTP USB function. + * Used in extras for the {@link #ACTION_USB_CONNECTED} broadcast + */ + public static final String USB_FUNCTION_MTP = "mtp"; + + /** + * Value indicating that a USB function is enabled. + * Used in extras for the {@link #ACTION_USB_CONNECTED} broadcast + */ + public static final String USB_FUNCTION_ENABLED = "enabled"; + + /** + * Value indicating that a USB function is disabled. + * Used in extras for the {@link #ACTION_USB_CONNECTED} broadcast + */ + public static final String USB_FUNCTION_DISABLED = "disabled"; +} diff --git a/core/java/android/inputmethodservice/ExtractEditText.java b/core/java/android/inputmethodservice/ExtractEditText.java index b7d53e26e543..8a52e4025b72 100644 --- a/core/java/android/inputmethodservice/ExtractEditText.java +++ b/core/java/android/inputmethodservice/ExtractEditText.java @@ -18,6 +18,7 @@ package android.inputmethodservice; import android.content.Context; import android.util.AttributeSet; +import android.view.ContextMenu; import android.view.inputmethod.ExtractedText; import android.widget.EditText; @@ -28,6 +29,7 @@ import android.widget.EditText; public class ExtractEditText extends EditText { private InputMethodService mIME; private int mSettingExtractedText; + private boolean mContextMenuShouldBeHandledBySuper = false; public ExtractEditText(Context context) { super(context, null); @@ -97,18 +99,26 @@ public class ExtractEditText extends EditText { return false; } + @Override + protected void onCreateContextMenu(ContextMenu menu) { + super.onCreateContextMenu(menu); + mContextMenuShouldBeHandledBySuper = true; + } + @Override public boolean onTextContextMenuItem(int id) { - if (mIME != null) { + if (mIME != null && !mContextMenuShouldBeHandledBySuper) { if (mIME.onExtractTextContextMenuItem(id)) { return true; } } + mContextMenuShouldBeHandledBySuper = false; return super.onTextContextMenuItem(id); } /** * We are always considered to be an input method target. */ + @Override public boolean isInputMethodTarget() { return true; } @@ -125,7 +135,7 @@ public class ExtractEditText extends EditText { * highlight and cursor will be displayed. */ @Override public boolean hasWindowFocus() { - return this.isEnabled() ? true : false; + return this.isEnabled(); } /** @@ -133,7 +143,7 @@ public class ExtractEditText extends EditText { * highlight and cursor will be displayed. */ @Override public boolean isFocused() { - return this.isEnabled() ? true : false; + return this.isEnabled(); } /** @@ -141,6 +151,6 @@ public class ExtractEditText extends EditText { * highlight and cursor will be displayed. */ @Override public boolean hasFocus() { - return this.isEnabled() ? true : false; + return this.isEnabled(); } } diff --git a/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java b/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java index 80e9865e5a81..44f30f7c5dac 100644 --- a/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java +++ b/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java @@ -47,9 +47,10 @@ class IInputMethodSessionWrapper extends IInputMethodSession.Stub private static final int DO_UPDATE_CURSOR = 95; private static final int DO_APP_PRIVATE_COMMAND = 100; private static final int DO_TOGGLE_SOFT_INPUT = 105; - - final HandlerCaller mCaller; - final InputMethodSession mInputMethodSession; + private static final int DO_FINISH_SESSION = 110; + + HandlerCaller mCaller; + InputMethodSession mInputMethodSession; // NOTE: we should have a cache of these. static class InputMethodEventCallbackWrapper implements InputMethodSession.EventCallback { @@ -127,6 +128,10 @@ class IInputMethodSessionWrapper extends IInputMethodSession.Stub mInputMethodSession.toggleSoftInput(msg.arg1, msg.arg2); return; } + case DO_FINISH_SESSION: { + mInputMethodSession = null; + return; + } } Log.w(TAG, "Unhandled message code: " + msg.what); } @@ -174,4 +179,8 @@ class IInputMethodSessionWrapper extends IInputMethodSession.Stub public void toggleSoftInput(int showFlags, int hideFlags) { mCaller.executeOrSendMessage(mCaller.obtainMessageII(DO_TOGGLE_SOFT_INPUT, showFlags, hideFlags)); } + + public void finishSession() { + mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_FINISH_SESSION)); + } } diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java index bfa82ee0d13c..35fd46f5a2f2 100644 --- a/core/java/android/inputmethodservice/IInputMethodWrapper.java +++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java @@ -39,6 +39,7 @@ import android.view.inputmethod.InputMethodSession; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.lang.ref.WeakReference; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -64,9 +65,9 @@ class IInputMethodWrapper extends IInputMethod.Stub private static final int DO_SHOW_SOFT_INPUT = 60; private static final int DO_HIDE_SOFT_INPUT = 70; - final AbstractInputMethodService mTarget; + final WeakReference<AbstractInputMethodService> mTarget; final HandlerCaller mCaller; - final InputMethod mInputMethod; + final WeakReference<InputMethod> mInputMethod; static class Notifier { boolean notified; @@ -96,21 +97,32 @@ class IInputMethodWrapper extends IInputMethod.Stub public IInputMethodWrapper(AbstractInputMethodService context, InputMethod inputMethod) { - mTarget = context; - mCaller = new HandlerCaller(context, this); - mInputMethod = inputMethod; + mTarget = new WeakReference<AbstractInputMethodService>(context); + mCaller = new HandlerCaller(context.getApplicationContext(), this); + mInputMethod = new WeakReference<InputMethod>(inputMethod); } public InputMethod getInternalInputMethod() { - return mInputMethod; + return mInputMethod.get(); } public void executeMessage(Message msg) { + InputMethod inputMethod = mInputMethod.get(); + // Need a valid reference to the inputMethod for everything except a dump. + if (inputMethod == null && msg.what != DO_DUMP) { + Log.w(TAG, "Input method reference was null, ignoring message: " + msg.what); + return; + } + switch (msg.what) { case DO_DUMP: { + AbstractInputMethodService target = mTarget.get(); + if (target == null) { + return; + } HandlerCaller.SomeArgs args = (HandlerCaller.SomeArgs)msg.obj; try { - mTarget.dump((FileDescriptor)args.arg1, + target.dump((FileDescriptor)args.arg1, (PrintWriter)args.arg2, (String[])args.arg3); } catch (RuntimeException e) { ((PrintWriter)args.arg2).println("Exception: " + e); @@ -122,22 +134,22 @@ class IInputMethodWrapper extends IInputMethod.Stub } case DO_ATTACH_TOKEN: { - mInputMethod.attachToken((IBinder)msg.obj); + inputMethod.attachToken((IBinder)msg.obj); return; } case DO_SET_INPUT_CONTEXT: { - mInputMethod.bindInput((InputBinding)msg.obj); + inputMethod.bindInput((InputBinding)msg.obj); return; } case DO_UNSET_INPUT_CONTEXT: - mInputMethod.unbindInput(); + inputMethod.unbindInput(); return; case DO_START_INPUT: { HandlerCaller.SomeArgs args = (HandlerCaller.SomeArgs)msg.obj; IInputContext inputContext = (IInputContext)args.arg1; InputConnection ic = inputContext != null ? new InputConnectionWrapper(inputContext) : null; - mInputMethod.startInput(ic, (EditorInfo)args.arg2); + inputMethod.startInput(ic, (EditorInfo)args.arg2); return; } case DO_RESTART_INPUT: { @@ -145,33 +157,37 @@ class IInputMethodWrapper extends IInputMethod.Stub IInputContext inputContext = (IInputContext)args.arg1; InputConnection ic = inputContext != null ? new InputConnectionWrapper(inputContext) : null; - mInputMethod.restartInput(ic, (EditorInfo)args.arg2); + inputMethod.restartInput(ic, (EditorInfo)args.arg2); return; } case DO_CREATE_SESSION: { - mInputMethod.createSession(new InputMethodSessionCallbackWrapper( + inputMethod.createSession(new InputMethodSessionCallbackWrapper( mCaller.mContext, (IInputMethodCallback)msg.obj)); return; } case DO_SET_SESSION_ENABLED: - mInputMethod.setSessionEnabled((InputMethodSession)msg.obj, + inputMethod.setSessionEnabled((InputMethodSession)msg.obj, msg.arg1 != 0); return; case DO_REVOKE_SESSION: - mInputMethod.revokeSession((InputMethodSession)msg.obj); + inputMethod.revokeSession((InputMethodSession)msg.obj); return; case DO_SHOW_SOFT_INPUT: - mInputMethod.showSoftInput(msg.arg1, (ResultReceiver)msg.obj); + inputMethod.showSoftInput(msg.arg1, (ResultReceiver)msg.obj); return; case DO_HIDE_SOFT_INPUT: - mInputMethod.hideSoftInput(msg.arg1, (ResultReceiver)msg.obj); + inputMethod.hideSoftInput(msg.arg1, (ResultReceiver)msg.obj); return; } Log.w(TAG, "Unhandled message code: " + msg.what); } @Override protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) { - if (mTarget.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) + AbstractInputMethodService target = mTarget.get(); + if (target == null) { + return; + } + if (target.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) != PackageManager.PERMISSION_GRANTED) { fout.println("Permission Denial: can't dump InputMethodManager from from pid=" diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index 8c8d3e59b8b4..1a261d3fa6ba 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -1988,15 +1988,19 @@ public class InputMethodService extends AbstractInputMethodService { ei.inputType != InputType.TYPE_NULL); if (hasAction) { mExtractAccessories.setVisibility(View.VISIBLE); - if (ei.actionLabel != null) { - mExtractAction.setText(ei.actionLabel); - } else { - mExtractAction.setText(getTextForImeAction(ei.imeOptions)); + if (mExtractAction != null) { + if (ei.actionLabel != null) { + mExtractAction.setText(ei.actionLabel); + } else { + mExtractAction.setText(getTextForImeAction(ei.imeOptions)); + } + mExtractAction.setOnClickListener(mActionClickListener); } - mExtractAction.setOnClickListener(mActionClickListener); } else { mExtractAccessories.setVisibility(View.GONE); - mExtractAction.setOnClickListener(null); + if (mExtractAction != null) { + mExtractAction.setOnClickListener(null); + } } } diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java index 280ded6bcc94..331ce10a64a2 100644 --- a/core/java/android/net/ConnectivityManager.java +++ b/core/java/android/net/ConnectivityManager.java @@ -102,6 +102,14 @@ public class ConnectivityManager * it with {@link android.content.Intent#getStringExtra(String)}. */ public static final String EXTRA_EXTRA_INFO = "extraInfo"; + /** + * 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)}. + * {@hide} + */ + public static final String EXTRA_INET_CONDITION = "inetCondition"; /** * Broadcast Action: The setting for background data usage has changed @@ -524,5 +532,17 @@ public class ConnectivityManager } catch (RemoteException e) { return TETHER_ERROR_SERVICE_UNAVAIL; } - } + } + + /** + * @param networkType The type of network you want to report on + * @param percentage The quality of the connection 0 is bad, 100 is good + * {@hide} + */ + public void reportInetCondition(int networkType, int percentage) { + try { + mService.reportInetCondition(networkType, percentage); + } catch (RemoteException e) { + } + } } diff --git a/core/java/android/net/DownloadManager.java b/core/java/android/net/DownloadManager.java new file mode 100644 index 000000000000..e8237c9d0072 --- /dev/null +++ b/core/java/android/net/DownloadManager.java @@ -0,0 +1,901 @@ +/* + * 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 android.net; + +import android.content.ContentResolver; +import android.content.ContentValues; +import android.database.Cursor; +import android.database.CursorWrapper; +import android.os.ParcelFileDescriptor; +import android.provider.BaseColumns; +import android.provider.Downloads; + +import java.io.File; +import java.io.FileNotFoundException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * The download manager is a system service that handles long-running HTTP downloads. Clients may + * request that a URI be downloaded to a particular destination file. The download manager will + * conduct the download in the background, taking care of HTTP interactions and retrying downloads + * after failures or across connectivity changes and system reboots. + * + * Instances of this class should be obtained through + * {@link android.content.Context#getSystemService(String)} by passing + * {@link android.content.Context#DOWNLOAD_SERVICE}. + */ +public class DownloadManager { + /** + * An identifier for a particular download, unique across the system. Clients use this ID to + * make subsequent calls related to the download. + */ + public final static String COLUMN_ID = BaseColumns._ID; + + /** + * The client-supplied title for this download. This will be displayed in system notifications. + * Defaults to the empty string. + */ + public final static String COLUMN_TITLE = "title"; + + /** + * The client-supplied description of this download. This will be displayed in system + * notifications. Defaults to the empty string. + */ + public final static String COLUMN_DESCRIPTION = "description"; + + /** + * URI to be downloaded. + */ + public final static String COLUMN_URI = "uri"; + + /** + * Internet Media Type of the downloaded file. If no value is provided upon creation, this will + * initially be null and will be filled in based on the server's response once the download has + * started. + * + * @see <a href="http://www.ietf.org/rfc/rfc1590.txt">RFC 1590, defining Media Types</a> + */ + public final static String COLUMN_MEDIA_TYPE = "media_type"; + + /** + * Total size of the download in bytes. This will initially be -1 and will be filled in once + * the download starts. + */ + public final static String COLUMN_TOTAL_SIZE_BYTES = "total_size"; + + /** + * Uri where downloaded file will be stored. If a destination is supplied by client, that URI + * will be used here. Otherwise, the value will initially be null and will be filled in with a + * generated URI once the download has started. + */ + public final static String COLUMN_LOCAL_URI = "local_uri"; + + /** + * Current status of the download, as one of the STATUS_* constants. + */ + public final static String COLUMN_STATUS = "status"; + + /** + * Indicates the type of error that occurred, when {@link #COLUMN_STATUS} is + * {@link #STATUS_FAILED}. If an HTTP error occurred, this will hold the HTTP status code as + * defined in RFC 2616. Otherwise, it will hold one of the ERROR_* constants. + * + * If {@link #COLUMN_STATUS} is not {@link #STATUS_FAILED}, this column's value is undefined. + * + * @see <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html#sec6.1.1">RFC 2616 + * status codes</a> + */ + public final static String COLUMN_ERROR_CODE = "error_code"; + + /** + * Number of bytes download so far. + */ + public final static String COLUMN_BYTES_DOWNLOADED_SO_FAR = "bytes_so_far"; + + /** + * Timestamp when the download was last modified, in {@link System#currentTimeMillis + * System.currentTimeMillis()} (wall clock time in UTC). + */ + public final static String COLUMN_LAST_MODIFIED_TIMESTAMP = "last_modified_timestamp"; + + + /** + * Value of {@link #COLUMN_STATUS} when the download is waiting to start. + */ + public final static int STATUS_PENDING = 1 << 0; + + /** + * Value of {@link #COLUMN_STATUS} when the download is currently running. + */ + public final static int STATUS_RUNNING = 1 << 1; + + /** + * Value of {@link #COLUMN_STATUS} when the download is waiting to retry or resume. + */ + public final static int STATUS_PAUSED = 1 << 2; + + /** + * Value of {@link #COLUMN_STATUS} when the download has successfully completed. + */ + public final static int STATUS_SUCCESSFUL = 1 << 3; + + /** + * Value of {@link #COLUMN_STATUS} when the download has failed (and will not be retried). + */ + public final static int STATUS_FAILED = 1 << 4; + + + /** + * Value of COLUMN_ERROR_CODE when the download has completed with an error that doesn't fit + * under any other error code. + */ + public final static int ERROR_UNKNOWN = 1000; + + /** + * Value of {@link #COLUMN_ERROR_CODE} when a storage issue arises which doesn't fit under any + * other error code. Use the more specific {@link #ERROR_INSUFFICIENT_SPACE} and + * {@link #ERROR_DEVICE_NOT_FOUND} when appropriate. + */ + public final static int ERROR_FILE_ERROR = 1001; + + /** + * Value of {@link #COLUMN_ERROR_CODE} when an HTTP code was received that download manager + * can't handle. + */ + public final static int ERROR_UNHANDLED_HTTP_CODE = 1002; + + /** + * Value of {@link #COLUMN_ERROR_CODE} when an error receiving or processing data occurred at + * the HTTP level. + */ + public final static int ERROR_HTTP_DATA_ERROR = 1004; + + /** + * Value of {@link #COLUMN_ERROR_CODE} when there were too many redirects. + */ + public final static int ERROR_TOO_MANY_REDIRECTS = 1005; + + /** + * Value of {@link #COLUMN_ERROR_CODE} when there was insufficient storage space. Typically, + * this is because the SD card is full. + */ + public final static int ERROR_INSUFFICIENT_SPACE = 1006; + + /** + * Value of {@link #COLUMN_ERROR_CODE} when no external storage device was found. Typically, + * this is because the SD card is not mounted. + */ + public final static int ERROR_DEVICE_NOT_FOUND = 1007; + + /** + * Value of {@link #COLUMN_ERROR_CODE} when some possibly transient error occurred but we can't + * resume the download. + */ + public final static int ERROR_CANNOT_RESUME = 1008; + + /** + * Broadcast intent action sent by the download manager when a download completes. + */ + public final static String ACTION_DOWNLOAD_COMPLETE = "android.intent.action.DOWNLOAD_COMPLETE"; + + /** + * Broadcast intent action sent by the download manager when a running download notification is + * clicked. + */ + public final static String ACTION_NOTIFICATION_CLICKED = + "android.intent.action.DOWNLOAD_NOTIFICATION_CLICKED"; + + /** + * Intent extra included with {@link #ACTION_DOWNLOAD_COMPLETE} intents, indicating the ID (as a + * long) of the download that just completed. + */ + public static final String EXTRA_DOWNLOAD_ID = "extra_download_id"; + + // this array must contain all public columns + private static final String[] COLUMNS = new String[] { + COLUMN_ID, + COLUMN_TITLE, + COLUMN_DESCRIPTION, + COLUMN_URI, + COLUMN_MEDIA_TYPE, + COLUMN_TOTAL_SIZE_BYTES, + COLUMN_LOCAL_URI, + COLUMN_STATUS, + COLUMN_ERROR_CODE, + COLUMN_BYTES_DOWNLOADED_SO_FAR, + COLUMN_LAST_MODIFIED_TIMESTAMP + }; + + // columns to request from DownloadProvider + private static final String[] UNDERLYING_COLUMNS = new String[] { + Downloads.Impl._ID, + Downloads.COLUMN_TITLE, + Downloads.COLUMN_DESCRIPTION, + Downloads.COLUMN_URI, + Downloads.COLUMN_MIME_TYPE, + Downloads.COLUMN_TOTAL_BYTES, + Downloads._DATA, + Downloads.COLUMN_STATUS, + Downloads.COLUMN_CURRENT_BYTES, + Downloads.COLUMN_LAST_MODIFICATION, + }; + + private static final Set<String> LONG_COLUMNS = new HashSet<String>( + Arrays.asList(COLUMN_ID, COLUMN_TOTAL_SIZE_BYTES, COLUMN_STATUS, COLUMN_ERROR_CODE, + COLUMN_BYTES_DOWNLOADED_SO_FAR, COLUMN_LAST_MODIFIED_TIMESTAMP)); + + /** + * This class contains all the information necessary to request a new download. The URI is the + * only required parameter. + */ + public static class Request { + /** + * Bit flag for {@link #setAllowedNetworkTypes} corresponding to + * {@link ConnectivityManager#TYPE_MOBILE}. + */ + public static final int NETWORK_MOBILE = 1 << 0; + + /** + * Bit flag for {@link #setAllowedNetworkTypes} corresponding to + * {@link ConnectivityManager#TYPE_WIFI}. + */ + public static final int NETWORK_WIFI = 1 << 1; + + /** + * Bit flag for {@link #setAllowedNetworkTypes} corresponding to + * {@link ConnectivityManager#TYPE_WIMAX}. + */ + public static final int NETWORK_WIMAX = 1 << 2; + + private Uri mUri; + private Uri mDestinationUri; + private Map<String, String> mRequestHeaders = new HashMap<String, String>(); + private String mTitle; + private String mDescription; + private boolean mShowNotification = true; + private String mMediaType; + private boolean mRoamingAllowed = true; + private int mAllowedNetworkTypes = ~0; // default to all network types allowed + private boolean mIsVisibleInDownloadsUi = true; + + /** + * @param uri the HTTP URI to download. + */ + public Request(Uri uri) { + if (uri == null) { + throw new NullPointerException(); + } + String scheme = uri.getScheme(); + if (scheme == null || !scheme.equals("http")) { + throw new IllegalArgumentException("Can only download HTTP URIs: " + uri); + } + mUri = uri; + } + + /** + * Set the local destination for the downloaded data. Must be a file URI to a path on + * external storage, and the calling application must have the WRITE_EXTERNAL_STORAGE + * permission. + * + * By default, downloads are saved to a generated file in the download cache and may be + * deleted by the download manager at any time. + * + * @return this object + */ + public Request setDestinationUri(Uri uri) { + mDestinationUri = uri; + return this; + } + + /** + * Set an HTTP header to be included with the download request. + * @param header HTTP header name + * @param value header value + * @return this object + */ + public Request setRequestHeader(String header, String value) { + mRequestHeaders.put(header, value); + return this; + } + + /** + * Set the title of this download, to be displayed in notifications (if enabled) + * @return this object + */ + public Request setTitle(String title) { + mTitle = title; + return this; + } + + /** + * Set a description of this download, to be displayed in notifications (if enabled) + * @return this object + */ + public Request setDescription(String description) { + mDescription = description; + return this; + } + + /** + * Set the Internet Media Type of this download. This will override the media type declared + * in the server's response. + * @see <a href="http://www.ietf.org/rfc/rfc1590.txt">RFC 1590, defining Media Types</a> + * @return this object + */ + public Request setMediaType(String mediaType) { + mMediaType = mediaType; + return this; + } + + /** + * Control whether a system notification is posted by the download manager while this + * download is running. If enabled, the download manager posts notifications about downloads + * through the system {@link android.app.NotificationManager}. By default, a notification is + * shown. + * + * If set to false, this requires the permission + * android.permission.DOWNLOAD_WITHOUT_NOTIFICATION. + * + * @param show whether the download manager should show a notification for this download. + * @return this object + * @hide + */ + public Request setShowRunningNotification(boolean show) { + mShowNotification = show; + return this; + } + + /** + * Restrict the types of networks over which this download may proceed. By default, all + * network types are allowed. + * @param flags any combination of the NETWORK_* bit flags. + * @return this object + */ + public Request setAllowedNetworkTypes(int flags) { + mAllowedNetworkTypes = flags; + return this; + } + + /** + * Set whether this download may proceed over a roaming connection. By default, roaming is + * allowed. + * @param allowed whether to allow a roaming connection to be used + * @return this object + */ + public Request setAllowedOverRoaming(boolean allowed) { + mRoamingAllowed = allowed; + return this; + } + + /** + * Set whether this download should be displayed in the system's Downloads UI. True by + * default. + * @param isVisible whether to display this download in the Downloads UI + * @return this object + */ + public Request setVisibleInDownloadsUi(boolean isVisible) { + mIsVisibleInDownloadsUi = isVisible; + return this; + } + + /** + * @return ContentValues to be passed to DownloadProvider.insert() + */ + ContentValues toContentValues(String packageName) { + ContentValues values = new ContentValues(); + assert mUri != null; + values.put(Downloads.COLUMN_URI, mUri.toString()); + values.put(Downloads.Impl.COLUMN_IS_PUBLIC_API, true); + values.put(Downloads.COLUMN_NOTIFICATION_PACKAGE, packageName); + + if (mDestinationUri != null) { + values.put(Downloads.COLUMN_DESTINATION, Downloads.Impl.DESTINATION_FILE_URI); + values.put(Downloads.COLUMN_FILE_NAME_HINT, mDestinationUri.toString()); + } else { + values.put(Downloads.COLUMN_DESTINATION, + Downloads.DESTINATION_CACHE_PARTITION_PURGEABLE); + } + + if (!mRequestHeaders.isEmpty()) { + encodeHttpHeaders(values); + } + + putIfNonNull(values, Downloads.COLUMN_TITLE, mTitle); + putIfNonNull(values, Downloads.COLUMN_DESCRIPTION, mDescription); + putIfNonNull(values, Downloads.COLUMN_MIME_TYPE, mMediaType); + + values.put(Downloads.COLUMN_VISIBILITY, + mShowNotification ? Downloads.VISIBILITY_VISIBLE + : Downloads.VISIBILITY_HIDDEN); + + values.put(Downloads.Impl.COLUMN_ALLOWED_NETWORK_TYPES, mAllowedNetworkTypes); + values.put(Downloads.Impl.COLUMN_ALLOW_ROAMING, mRoamingAllowed); + values.put(Downloads.Impl.COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI, mIsVisibleInDownloadsUi); + + return values; + } + + private void encodeHttpHeaders(ContentValues values) { + int index = 0; + for (Map.Entry<String, String> entry : mRequestHeaders.entrySet()) { + String headerString = entry.getKey() + ": " + entry.getValue(); + values.put(Downloads.Impl.RequestHeaders.INSERT_KEY_PREFIX + index, headerString); + index++; + } + } + + private void putIfNonNull(ContentValues contentValues, String key, String value) { + if (value != null) { + contentValues.put(key, value); + } + } + } + + /** + * This class may be used to filter download manager queries. + */ + public static class Query { + /** + * Constant for use with {@link #orderBy} + * @hide + */ + public static final int ORDER_ASCENDING = 1; + + /** + * Constant for use with {@link #orderBy} + * @hide + */ + public static final int ORDER_DESCENDING = 2; + + private Long mId = null; + private Integer mStatusFlags = null; + private String mOrderByColumn = Downloads.COLUMN_LAST_MODIFICATION; + private int mOrderDirection = ORDER_DESCENDING; + private boolean mOnlyIncludeVisibleInDownloadsUi = false; + + /** + * Include only the download with the given ID. + * @return this object + */ + public Query setFilterById(long id) { + mId = id; + return this; + } + + /** + * Include only downloads with status matching any the given status flags. + * @param flags any combination of the STATUS_* bit flags + * @return this object + */ + public Query setFilterByStatus(int flags) { + mStatusFlags = flags; + return this; + } + + /** + * Controls whether this query includes downloads not visible in the system's Downloads UI. + * @param value if true, this query will only include downloads that should be displayed in + * the system's Downloads UI; if false (the default), this query will include + * both visible and invisible downloads. + * @return this object + * @hide + */ + public Query setOnlyIncludeVisibleInDownloadsUi(boolean value) { + mOnlyIncludeVisibleInDownloadsUi = value; + return this; + } + + /** + * Change the sort order of the returned Cursor. + * + * @param column one of the COLUMN_* constants; currently, only + * {@link #COLUMN_LAST_MODIFIED_TIMESTAMP} and {@link #COLUMN_TOTAL_SIZE_BYTES} are + * supported. + * @param direction either {@link #ORDER_ASCENDING} or {@link #ORDER_DESCENDING} + * @return this object + * @hide + */ + public Query orderBy(String column, int direction) { + if (direction != ORDER_ASCENDING && direction != ORDER_DESCENDING) { + throw new IllegalArgumentException("Invalid direction: " + direction); + } + + if (column.equals(COLUMN_LAST_MODIFIED_TIMESTAMP)) { + mOrderByColumn = Downloads.COLUMN_LAST_MODIFICATION; + } else if (column.equals(COLUMN_TOTAL_SIZE_BYTES)) { + mOrderByColumn = Downloads.COLUMN_TOTAL_BYTES; + } else { + throw new IllegalArgumentException("Cannot order by " + column); + } + mOrderDirection = direction; + return this; + } + + /** + * Run this query using the given ContentResolver. + * @param projection the projection to pass to ContentResolver.query() + * @return the Cursor returned by ContentResolver.query() + */ + Cursor runQuery(ContentResolver resolver, String[] projection) { + Uri uri = Downloads.CONTENT_URI; + List<String> selectionParts = new ArrayList<String>(); + + if (mId != null) { + uri = Uri.withAppendedPath(uri, mId.toString()); + } + + if (mStatusFlags != null) { + List<String> parts = new ArrayList<String>(); + if ((mStatusFlags & STATUS_PENDING) != 0) { + parts.add(statusClause("=", Downloads.STATUS_PENDING)); + } + if ((mStatusFlags & STATUS_RUNNING) != 0) { + parts.add(statusClause("=", Downloads.STATUS_RUNNING)); + } + if ((mStatusFlags & STATUS_PAUSED) != 0) { + parts.add(statusClause("=", Downloads.STATUS_PENDING_PAUSED)); + parts.add(statusClause("=", Downloads.STATUS_RUNNING_PAUSED)); + } + if ((mStatusFlags & STATUS_SUCCESSFUL) != 0) { + parts.add(statusClause("=", Downloads.STATUS_SUCCESS)); + } + if ((mStatusFlags & STATUS_FAILED) != 0) { + parts.add("(" + statusClause(">=", 400) + + " AND " + statusClause("<", 600) + ")"); + } + selectionParts.add(joinStrings(" OR ", parts)); + } + + if (mOnlyIncludeVisibleInDownloadsUi) { + selectionParts.add(Downloads.Impl.COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI + " != '0'"); + } + + String selection = joinStrings(" AND ", selectionParts); + String orderDirection = (mOrderDirection == ORDER_ASCENDING ? "ASC" : "DESC"); + String orderBy = mOrderByColumn + " " + orderDirection; + + return resolver.query(uri, projection, selection, null, orderBy); + } + + private String joinStrings(String joiner, Iterable<String> parts) { + StringBuilder builder = new StringBuilder(); + boolean first = true; + for (String part : parts) { + if (!first) { + builder.append(joiner); + } + builder.append(part); + first = false; + } + return builder.toString(); + } + + private String statusClause(String operator, int value) { + return Downloads.COLUMN_STATUS + operator + "'" + value + "'"; + } + } + + private ContentResolver mResolver; + private String mPackageName; + + /** + * @hide + */ + public DownloadManager(ContentResolver resolver, String packageName) { + mResolver = resolver; + mPackageName = packageName; + } + + /** + * Enqueue a new download. The download will start automatically once the download manager is + * ready to execute it and connectivity is available. + * + * @param request the parameters specifying this download + * @return an ID for the download, unique across the system. This ID is used to make future + * calls related to this download. + */ + public long enqueue(Request request) { + ContentValues values = request.toContentValues(mPackageName); + Uri downloadUri = mResolver.insert(Downloads.CONTENT_URI, values); + long id = Long.parseLong(downloadUri.getLastPathSegment()); + return id; + } + + /** + * Cancel a download and remove it from the download manager. The download will be stopped if + * it was running, and it will no longer be accessible through the download manager. If a file + * was already downloaded, it will not be deleted. + * + * @param id the ID of the download + */ + public void remove(long id) { + int numDeleted = mResolver.delete(getDownloadUri(id), null, null); + if (numDeleted == 0) { + throw new IllegalArgumentException("Download " + id + " does not exist"); + } + } + + /** + * Query the download manager about downloads that have been requested. + * @param query parameters specifying filters for this query + * @return a Cursor over the result set of downloads, with columns consisting of all the + * COLUMN_* constants. + */ + public Cursor query(Query query) { + Cursor underlyingCursor = query.runQuery(mResolver, UNDERLYING_COLUMNS); + if (underlyingCursor == null) { + return null; + } + return new CursorTranslator(underlyingCursor); + } + + /** + * Open a downloaded file for reading. The download must have completed. + * @param id the ID of the download + * @return a read-only {@link ParcelFileDescriptor} + * @throws FileNotFoundException if the destination file does not already exist + */ + public ParcelFileDescriptor openDownloadedFile(long id) throws FileNotFoundException { + return mResolver.openFileDescriptor(getDownloadUri(id), "r"); + } + + /** + * Restart the given download, which must have already completed (successfully or not). This + * method will only work when called from within the download manager's process. + * @param id the ID of the download + * @hide + */ + public void restartDownload(long id) { + Cursor cursor = query(new Query().setFilterById(id)); + try { + if (!cursor.moveToFirst()) { + throw new IllegalArgumentException("No download with id " + id); + } + int status = cursor.getInt(cursor.getColumnIndex(COLUMN_STATUS)); + if (status != STATUS_SUCCESSFUL && status != STATUS_FAILED) { + throw new IllegalArgumentException("Cannot restart incomplete download: " + id); + } + } finally { + cursor.close(); + } + + ContentValues values = new ContentValues(); + values.put(Downloads.Impl.COLUMN_CURRENT_BYTES, 0); + values.put(Downloads.Impl.COLUMN_TOTAL_BYTES, -1); + values.putNull(Downloads.Impl._DATA); + values.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_PENDING); + mResolver.update(getDownloadUri(id), values, null, null); + } + + /** + * Get the DownloadProvider URI for the download with the given ID. + */ + private Uri getDownloadUri(long id) { + Uri downloadUri = Uri.withAppendedPath(Downloads.CONTENT_URI, Long.toString(id)); + return downloadUri; + } + + /** + * This class wraps a cursor returned by DownloadProvider -- the "underlying cursor" -- and + * presents a different set of columns, those defined in the DownloadManager.COLUMN_* constants. + * Some columns correspond directly to underlying values while others are computed from + * underlying data. + */ + private static class CursorTranslator extends CursorWrapper { + public CursorTranslator(Cursor cursor) { + super(cursor); + } + + @Override + public int getColumnIndex(String columnName) { + return Arrays.asList(COLUMNS).indexOf(columnName); + } + + @Override + public int getColumnIndexOrThrow(String columnName) throws IllegalArgumentException { + int index = getColumnIndex(columnName); + if (index == -1) { + throw new IllegalArgumentException("No such column: " + columnName); + } + return index; + } + + @Override + public String getColumnName(int columnIndex) { + int numColumns = COLUMNS.length; + if (columnIndex < 0 || columnIndex >= numColumns) { + throw new IllegalArgumentException("Invalid column index " + columnIndex + ", " + + numColumns + " columns exist"); + } + return COLUMNS[columnIndex]; + } + + @Override + public String[] getColumnNames() { + String[] returnColumns = new String[COLUMNS.length]; + System.arraycopy(COLUMNS, 0, returnColumns, 0, COLUMNS.length); + return returnColumns; + } + + @Override + public int getColumnCount() { + return COLUMNS.length; + } + + @Override + public byte[] getBlob(int columnIndex) { + throw new UnsupportedOperationException(); + } + + @Override + public double getDouble(int columnIndex) { + return getLong(columnIndex); + } + + private boolean isLongColumn(String column) { + return LONG_COLUMNS.contains(column); + } + + @Override + public float getFloat(int columnIndex) { + return (float) getDouble(columnIndex); + } + + @Override + public int getInt(int columnIndex) { + return (int) getLong(columnIndex); + } + + @Override + public long getLong(int columnIndex) { + return translateLong(getColumnName(columnIndex)); + } + + @Override + public short getShort(int columnIndex) { + return (short) getLong(columnIndex); + } + + @Override + public String getString(int columnIndex) { + return translateString(getColumnName(columnIndex)); + } + + private String translateString(String column) { + if (isLongColumn(column)) { + return Long.toString(translateLong(column)); + } + if (column.equals(COLUMN_TITLE)) { + return getUnderlyingString(Downloads.COLUMN_TITLE); + } + if (column.equals(COLUMN_DESCRIPTION)) { + return getUnderlyingString(Downloads.COLUMN_DESCRIPTION); + } + if (column.equals(COLUMN_URI)) { + return getUnderlyingString(Downloads.COLUMN_URI); + } + if (column.equals(COLUMN_MEDIA_TYPE)) { + return getUnderlyingString(Downloads.COLUMN_MIME_TYPE); + } + + assert column.equals(COLUMN_LOCAL_URI); + String localUri = getUnderlyingString(Downloads._DATA); + if (localUri == null) { + return null; + } + return Uri.fromFile(new File(localUri)).toString(); + } + + private long translateLong(String column) { + if (!isLongColumn(column)) { + // mimic behavior of underlying cursor -- most likely, throw NumberFormatException + return Long.valueOf(translateString(column)); + } + + if (column.equals(COLUMN_ID)) { + return getUnderlyingLong(Downloads.Impl._ID); + } + if (column.equals(COLUMN_TOTAL_SIZE_BYTES)) { + return getUnderlyingLong(Downloads.COLUMN_TOTAL_BYTES); + } + if (column.equals(COLUMN_STATUS)) { + return translateStatus((int) getUnderlyingLong(Downloads.COLUMN_STATUS)); + } + if (column.equals(COLUMN_ERROR_CODE)) { + return translateErrorCode((int) getUnderlyingLong(Downloads.COLUMN_STATUS)); + } + if (column.equals(COLUMN_BYTES_DOWNLOADED_SO_FAR)) { + return getUnderlyingLong(Downloads.COLUMN_CURRENT_BYTES); + } + assert column.equals(COLUMN_LAST_MODIFIED_TIMESTAMP); + return getUnderlyingLong(Downloads.COLUMN_LAST_MODIFICATION); + } + + private long translateErrorCode(int status) { + if (translateStatus(status) != STATUS_FAILED) { + return 0; // arbitrary value when status is not an error + } + if ((400 <= status && status < Downloads.Impl.MIN_ARTIFICIAL_ERROR_STATUS) + || (500 <= status && status < 600)) { + // HTTP status code + return status; + } + + switch (status) { + case Downloads.STATUS_FILE_ERROR: + return ERROR_FILE_ERROR; + + case Downloads.STATUS_UNHANDLED_HTTP_CODE: + case Downloads.STATUS_UNHANDLED_REDIRECT: + return ERROR_UNHANDLED_HTTP_CODE; + + case Downloads.STATUS_HTTP_DATA_ERROR: + return ERROR_HTTP_DATA_ERROR; + + case Downloads.STATUS_TOO_MANY_REDIRECTS: + return ERROR_TOO_MANY_REDIRECTS; + + case Downloads.STATUS_INSUFFICIENT_SPACE_ERROR: + return ERROR_INSUFFICIENT_SPACE; + + case Downloads.STATUS_DEVICE_NOT_FOUND_ERROR: + return ERROR_DEVICE_NOT_FOUND; + + case Downloads.Impl.STATUS_CANNOT_RESUME: + return ERROR_CANNOT_RESUME; + + default: + return ERROR_UNKNOWN; + } + } + + private long getUnderlyingLong(String column) { + return super.getLong(super.getColumnIndex(column)); + } + + private String getUnderlyingString(String column) { + return super.getString(super.getColumnIndex(column)); + } + + private long translateStatus(int status) { + switch (status) { + case Downloads.STATUS_PENDING: + return STATUS_PENDING; + + case Downloads.STATUS_RUNNING: + return STATUS_RUNNING; + + case Downloads.STATUS_PENDING_PAUSED: + case Downloads.STATUS_RUNNING_PAUSED: + return STATUS_PAUSED; + + case Downloads.STATUS_SUCCESS: + return STATUS_SUCCESSFUL; + + default: + assert Downloads.isStatusError(status); + return STATUS_FAILED; + } + } + } +} diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl index b05c2edd103b..b734ac7ebb9c 100644 --- a/core/java/android/net/IConnectivityManager.aidl +++ b/core/java/android/net/IConnectivityManager.aidl @@ -72,4 +72,6 @@ interface IConnectivityManager String[] getTetherableUsbRegexs(); String[] getTetherableWifiRegexs(); + + void reportInetCondition(int networkType, int percentage); } diff --git a/core/java/android/net/MobileDataStateTracker.java b/core/java/android/net/MobileDataStateTracker.java index 98f32b36e99e..eb7117bdee5d 100644 --- a/core/java/android/net/MobileDataStateTracker.java +++ b/core/java/android/net/MobileDataStateTracker.java @@ -53,6 +53,10 @@ public class MobileDataStateTracker extends NetworkStateTracker { private boolean mEnabled; private BroadcastReceiver mStateReceiver; + // DEFAULT and HIPRI are the same connection. If we're one of these we need to check if + // the other is also disconnected before we reset sockets + private boolean mIsDefaultOrHipri = false; + /** * Create a new MobileDataStateTracker * @param context the application context of the caller @@ -71,6 +75,10 @@ public class MobileDataStateTracker extends NetworkStateTracker { } else { mApnTypeToWatchFor = mApnType; } + if (netType == ConnectivityManager.TYPE_MOBILE || + netType == ConnectivityManager.TYPE_MOBILE_HIPRI) { + mIsDefaultOrHipri = true; + } mPhoneService = null; if(netType == ConnectivityManager.TYPE_MOBILE) { @@ -138,6 +146,7 @@ public class MobileDataStateTracker extends NetworkStateTracker { } private class MobileDataStateReceiver extends BroadcastReceiver { + ConnectivityManager mConnectivityManager; public void onReceive(Context context, Intent intent) { synchronized(this) { if (intent.getAction().equals(TelephonyIntents. @@ -190,7 +199,26 @@ public class MobileDataStateTracker extends NetworkStateTracker { } setDetailedState(DetailedState.DISCONNECTED, reason, apnName); - if (mInterfaceName != null) { + boolean doReset = true; + if (mIsDefaultOrHipri == true) { + // both default and hipri must go down before we reset + int typeToCheck = (Phone.APN_TYPE_DEFAULT.equals(mApnType) ? + ConnectivityManager.TYPE_MOBILE_HIPRI : + ConnectivityManager.TYPE_MOBILE); + if (mConnectivityManager == null) { + mConnectivityManager = + (ConnectivityManager)context.getSystemService( + Context.CONNECTIVITY_SERVICE); + } + if (mConnectivityManager != null) { + NetworkInfo info = mConnectivityManager.getNetworkInfo( + typeToCheck); + if (info != null && info.isConnected() == true) { + doReset = false; + } + } + } + if (doReset && mInterfaceName != null) { NetworkUtils.resetConnections(mInterfaceName); } // can't do this here - ConnectivityService needs it to clear stuff @@ -212,6 +240,7 @@ public class MobileDataStateTracker extends NetworkStateTracker { if (mInterfaceName == null) { Log.d(TAG, "CONNECTED event did not supply interface name."); } + mDefaultGatewayAddr = intent.getIntExtra(Phone.DATA_GATEWAY_KEY, 0); setDetailedState(DetailedState.CONNECTED, reason, apnName); break; } @@ -310,6 +339,9 @@ public class MobileDataStateTracker extends NetworkStateTracker { case TelephonyManager.NETWORK_TYPE_EVDO_A: networkTypeStr = "evdo"; break; + case TelephonyManager.NETWORK_TYPE_EVDO_B: + networkTypeStr = "evdo"; + break; } return "net.tcp.buffersize." + networkTypeStr; } diff --git a/core/java/android/net/NetworkStateTracker.java b/core/java/android/net/NetworkStateTracker.java index 1fb014452f4c..c5a327744bb8 100644 --- a/core/java/android/net/NetworkStateTracker.java +++ b/core/java/android/net/NetworkStateTracker.java @@ -45,7 +45,6 @@ public abstract class NetworkStateTracker extends Handler { protected String[] mDnsPropNames; private boolean mPrivateDnsRouteSet; protected int mDefaultGatewayAddr; - private boolean mDefaultRouteSet; private boolean mTeardownRequested; private static boolean DBG = true; @@ -63,6 +62,15 @@ public abstract class NetworkStateTracker extends Handler { public static final int EVENT_ROAMING_CHANGED = 5; public static final int EVENT_NETWORK_SUBTYPE_CHANGED = 6; public static final int EVENT_RESTORE_DEFAULT_NETWORK = 7; + /** + * arg1: network type + * arg2: condition (0 bad, 100 good) + */ + public static final int EVENT_INET_CONDITION_CHANGE = 8; + /** + * arg1: network type + */ + public static final int EVENT_INET_CONDITION_HOLD_END = 9; public NetworkStateTracker(Context context, Handler target, @@ -153,25 +161,22 @@ public abstract class NetworkStateTracker extends Handler { } public void addDefaultRoute() { - if ((mInterfaceName != null) && (mDefaultGatewayAddr != 0) && - mDefaultRouteSet == false) { + if ((mInterfaceName != null) && (mDefaultGatewayAddr != 0)) { if (DBG) { Log.d(TAG, "addDefaultRoute for " + mNetworkInfo.getTypeName() + " (" + mInterfaceName + "), GatewayAddr=" + mDefaultGatewayAddr); } NetworkUtils.setDefaultRoute(mInterfaceName, mDefaultGatewayAddr); - mDefaultRouteSet = true; } } public void removeDefaultRoute() { - if (mInterfaceName != null && mDefaultRouteSet == true) { + if (mInterfaceName != null) { if (DBG) { Log.d(TAG, "removeDefaultRoute for " + mNetworkInfo.getTypeName() + " (" + mInterfaceName + ")"); } NetworkUtils.removeDefaultRoute(mInterfaceName); - mDefaultRouteSet = false; } } diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java index a3ae01b5597b..e4f3d5c0f104 100644 --- a/core/java/android/net/NetworkUtils.java +++ b/core/java/android/net/NetworkUtils.java @@ -128,4 +128,19 @@ public class NetworkUtils { | (addrBytes[0] & 0xff); return addr; } + + public static int v4StringToInt(String str) { + int result = 0; + String[] array = str.split("\\."); + if (array.length != 4) return 0; + try { + result = Integer.parseInt(array[3]); + result = (result << 8) + Integer.parseInt(array[2]); + result = (result << 8) + Integer.parseInt(array[1]); + result = (result << 8) + Integer.parseInt(array[0]); + } catch (NumberFormatException e) { + return 0; + } + return result; + } } diff --git a/core/java/android/net/SSLCertificateSocketFactory.java b/core/java/android/net/SSLCertificateSocketFactory.java index 31acb5b177e8..9166019fc9cd 100644 --- a/core/java/android/net/SSLCertificateSocketFactory.java +++ b/core/java/android/net/SSLCertificateSocketFactory.java @@ -46,9 +46,9 @@ import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509TrustManager; +import org.apache.harmony.xnet.provider.jsse.OpenSSLContextImpl; import org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl; import org.apache.harmony.xnet.provider.jsse.SSLClientSessionCache; -import org.apache.harmony.xnet.provider.jsse.SSLContextImpl; import org.apache.harmony.xnet.provider.jsse.SSLParameters; /** @@ -210,7 +210,7 @@ public class SSLCertificateSocketFactory extends SSLSocketFactory { private SSLSocketFactory makeSocketFactory(TrustManager[] trustManagers) { try { - SSLContextImpl sslContext = new SSLContextImpl(); + OpenSSLContextImpl sslContext = new OpenSSLContextImpl(); sslContext.engineInit(null, trustManagers, null, mSessionCache, null); return sslContext.engineGetSocketFactory(); } catch (KeyManagementException e) { diff --git a/core/java/android/net/http/HttpsConnection.java b/core/java/android/net/http/HttpsConnection.java index e512a1dfc595..8c9d013f3955 100644 --- a/core/java/android/net/http/HttpsConnection.java +++ b/core/java/android/net/http/HttpsConnection.java @@ -19,8 +19,8 @@ package android.net.http; import android.content.Context; import android.util.Log; import org.apache.harmony.xnet.provider.jsse.FileClientSessionCache; +import org.apache.harmony.xnet.provider.jsse.OpenSSLContextImpl; import org.apache.harmony.xnet.provider.jsse.SSLClientSessionCache; -import org.apache.harmony.xnet.provider.jsse.SSLContextImpl; import org.apache.http.Header; import org.apache.http.HttpException; import org.apache.http.HttpHost; @@ -79,7 +79,7 @@ public class HttpsConnection extends Connection { cache = FileClientSessionCache.usingDirectory(sessionDir); } - SSLContextImpl sslContext = new SSLContextImpl(); + OpenSSLContextImpl sslContext = new OpenSSLContextImpl(); // here, trust managers is a single trust-all manager TrustManager[] trustManagers = new TrustManager[] { diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index d114bffe813d..f182a7ad6d71 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -23,6 +23,7 @@ import java.util.Map; import android.util.Log; import android.util.Printer; import android.util.SparseArray; +import android.util.TimeUtils; /** * A class providing access to battery usage statistics, including information on @@ -51,57 +52,43 @@ public abstract class BatteryStats implements Parcelable { /** * A constant indicating a sensor timer. - * - * {@hide} */ public static final int SENSOR = 3; /** * A constant indicating a a wifi turn on timer - * - * {@hide} */ public static final int WIFI_TURNED_ON = 4; /** * A constant indicating a full wifi lock timer - * - * {@hide} */ public static final int FULL_WIFI_LOCK = 5; /** * A constant indicating a scan wifi lock timer - * - * {@hide} */ public static final int SCAN_WIFI_LOCK = 6; /** * A constant indicating a wifi multicast timer - * - * {@hide} */ public static final int WIFI_MULTICAST_ENABLED = 7; /** * A constant indicating an audio turn on timer - * - * {@hide} */ public static final int AUDIO_TURNED_ON = 7; /** * A constant indicating a video turn on timer - * - * {@hide} */ public static final int VIDEO_TURNED_ON = 8; /** * Include all of the data in the stats, including previously saved data. */ - public static final int STATS_TOTAL = 0; + public static final int STATS_SINCE_CHARGED = 0; /** * Include only the last run in the stats. @@ -116,11 +103,11 @@ public abstract class BatteryStats implements Parcelable { /** * Include only the run since the last time the device was unplugged in the stats. */ - public static final int STATS_UNPLUGGED = 3; + public static final int STATS_SINCE_UNPLUGGED = 3; // NOTE: Update this list if you add/change any stats above. // These characters are supposed to represent "total", "last", "current", - // and "unplugged". They were shortened for effeciency sake. + // and "unplugged". They were shortened for efficiency sake. private static final String[] STAT_NAMES = { "t", "l", "c", "u" }; /** @@ -229,6 +216,11 @@ public abstract class BatteryStats implements Parcelable { public abstract Map<Integer, ? extends Sensor> getSensorStats(); /** + * Returns a mapping containing active process data. + */ + public abstract SparseArray<? extends Pid> getPidStats(); + + /** * Returns a mapping containing process statistics. * * @return a Map from Strings to Uid.Proc objects. @@ -299,11 +291,21 @@ public abstract class BatteryStats implements Parcelable { public abstract Timer getSensorTime(); } + public class Pid { + public long mWakeSum; + public long mWakeStart; + } + /** * The statistics associated with a particular process. */ public static abstract class Proc { + public static class ExcessiveWake { + public long overTime; + public long usedTime; + } + /** * Returns the total time (in 1/100 sec) spent executing in user code. * @@ -340,6 +342,10 @@ public abstract class BatteryStats implements Parcelable { * @see BatteryStats#getCpuSpeedSteps() */ public abstract long getTimeAtCpuSpeedStep(int speedStep, int which); + + public abstract int countExcessiveWakes(); + + public abstract ExcessiveWake getExcessiveWake(int i); } /** @@ -391,6 +397,145 @@ public abstract class BatteryStats implements Parcelable { } } + public final class HistoryItem implements Parcelable { + public HistoryItem next; + + public long time; + + public static final byte CMD_UPDATE = 0; + public static final byte CMD_START = 1; + public static final byte CMD_OVERFLOW = 2; + + public byte cmd; + + public byte batteryLevel; + public byte batteryStatus; + public byte batteryHealth; + public byte batteryPlugType; + + public char batteryTemperature; + public char batteryVoltage; + + // Constants from SCREEN_BRIGHTNESS_* + public static final int STATE_BRIGHTNESS_MASK = 0x000000f; + public static final int STATE_BRIGHTNESS_SHIFT = 0; + // Constants from SIGNAL_STRENGTH_* + public static final int STATE_SIGNAL_STRENGTH_MASK = 0x00000f0; + public static final int STATE_SIGNAL_STRENGTH_SHIFT = 4; + // Constants from ServiceState.STATE_* + public static final int STATE_PHONE_STATE_MASK = 0x0000f00; + public static final int STATE_PHONE_STATE_SHIFT = 8; + // Constants from DATA_CONNECTION_* + public static final int STATE_DATA_CONNECTION_MASK = 0x000f000; + public static final int STATE_DATA_CONNECTION_SHIFT = 12; + + public static final int STATE_BATTERY_PLUGGED_FLAG = 1<<30; + public static final int STATE_SCREEN_ON_FLAG = 1<<29; + public static final int STATE_GPS_ON_FLAG = 1<<28; + public static final int STATE_PHONE_IN_CALL_FLAG = 1<<27; + public static final int STATE_PHONE_SCANNING_FLAG = 1<<26; + public static final int STATE_WIFI_ON_FLAG = 1<<25; + public static final int STATE_WIFI_RUNNING_FLAG = 1<<24; + public static final int STATE_WIFI_FULL_LOCK_FLAG = 1<<23; + public static final int STATE_WIFI_SCAN_LOCK_FLAG = 1<<22; + public static final int STATE_WIFI_MULTICAST_ON_FLAG = 1<<21; + public static final int STATE_BLUETOOTH_ON_FLAG = 1<<20; + public static final int STATE_AUDIO_ON_FLAG = 1<<19; + public static final int STATE_VIDEO_ON_FLAG = 1<<18; + public static final int STATE_WAKE_LOCK_FLAG = 1<<17; + public static final int STATE_SENSOR_ON_FLAG = 1<<16; + + public int states; + + public HistoryItem() { + } + + public HistoryItem(long time, Parcel src) { + this.time = time; + int bat = src.readInt(); + cmd = (byte)(bat&0xff); + batteryLevel = (byte)((bat>>8)&0xff); + batteryStatus = (byte)((bat>>16)&0xf); + batteryHealth = (byte)((bat>>20)&0xf); + batteryPlugType = (byte)((bat>>24)&0xf); + bat = src.readInt(); + batteryTemperature = (char)(bat&0xffff); + batteryVoltage = (char)((bat>>16)&0xffff); + states = src.readInt(); + } + + public int describeContents() { + return 0; + } + + public void writeToParcel(Parcel dest, int flags) { + dest.writeLong(time); + int bat = (((int)cmd)&0xff) + | ((((int)batteryLevel)<<8)&0xff00) + | ((((int)batteryStatus)<<16)&0xf0000) + | ((((int)batteryHealth)<<20)&0xf00000) + | ((((int)batteryPlugType)<<24)&0xf000000); + dest.writeInt(bat); + bat = (((int)batteryTemperature)&0xffff) + | ((((int)batteryVoltage)<<16)&0xffff0000); + dest.writeInt(bat); + dest.writeInt(states); + } + + public void setTo(long time, byte cmd, HistoryItem o) { + this.time = time; + this.cmd = cmd; + batteryLevel = o.batteryLevel; + batteryStatus = o.batteryStatus; + batteryHealth = o.batteryHealth; + batteryPlugType = o.batteryPlugType; + batteryTemperature = o.batteryTemperature; + batteryVoltage = o.batteryVoltage; + states = o.states; + } + + public boolean same(HistoryItem o) { + return batteryLevel == o.batteryLevel + && batteryStatus == o.batteryStatus + && batteryHealth == o.batteryHealth + && batteryPlugType == o.batteryPlugType + && batteryTemperature == o.batteryTemperature + && batteryVoltage == o.batteryVoltage + && states == o.states; + } + } + + public static final class BitDescription { + public final int mask; + public final int shift; + public final String name; + public final String[] values; + + public BitDescription(int mask, String name) { + this.mask = mask; + this.shift = -1; + this.name = name; + this.values = null; + } + + public BitDescription(int mask, int shift, String name, String[] values) { + this.mask = mask; + this.shift = shift; + this.name = name; + this.values = values; + } + } + + /** + * Return the current history of battery state changes. + */ + public abstract HistoryItem getHistory(); + + /** + * Return the base time offset for the battery history. + */ + public abstract long getHistoryBaseTime(); + /** * Returns the number of times the device has been started. */ @@ -476,13 +621,23 @@ public abstract class BatteryStats implements Parcelable { public static final int DATA_CONNECTION_GPRS = 1; public static final int DATA_CONNECTION_EDGE = 2; public static final int DATA_CONNECTION_UMTS = 3; - public static final int DATA_CONNECTION_OTHER = 4; + public static final int DATA_CONNECTION_CDMA = 4; + public static final int DATA_CONNECTION_EVDO_0 = 5; + public static final int DATA_CONNECTION_EVDO_A = 6; + public static final int DATA_CONNECTION_1xRTT = 7; + public static final int DATA_CONNECTION_HSDPA = 8; + public static final int DATA_CONNECTION_HSUPA = 9; + public static final int DATA_CONNECTION_HSPA = 10; + public static final int DATA_CONNECTION_IDEN = 11; + public static final int DATA_CONNECTION_EVDO_B = 12; + public static final int DATA_CONNECTION_OTHER = 13; static final String[] DATA_CONNECTION_NAMES = { - "none", "gprs", "edge", "umts", "other" + "none", "gprs", "edge", "umts", "cdma", "evdo_0", "evdo_A", + "1xrtt", "hsdpa", "hsupa", "hspa", "iden", "evdo_b", "other" }; - public static final int NUM_DATA_CONNECTION_TYPES = 5; + public static final int NUM_DATA_CONNECTION_TYPES = DATA_CONNECTION_OTHER+1; /** * Returns the time in microseconds that the phone has been running with @@ -500,6 +655,37 @@ public abstract class BatteryStats implements Parcelable { * {@hide} */ public abstract int getPhoneDataConnectionCount(int dataType, int which); + + public static final BitDescription[] HISTORY_STATE_DESCRIPTIONS + = new BitDescription[] { + new BitDescription(HistoryItem.STATE_BATTERY_PLUGGED_FLAG, "plugged"), + new BitDescription(HistoryItem.STATE_SCREEN_ON_FLAG, "screen"), + new BitDescription(HistoryItem.STATE_GPS_ON_FLAG, "gps"), + new BitDescription(HistoryItem.STATE_PHONE_IN_CALL_FLAG, "phone_in_call"), + new BitDescription(HistoryItem.STATE_PHONE_SCANNING_FLAG, "phone_scanning"), + new BitDescription(HistoryItem.STATE_WIFI_ON_FLAG, "wifi"), + new BitDescription(HistoryItem.STATE_WIFI_RUNNING_FLAG, "wifi_running"), + new BitDescription(HistoryItem.STATE_WIFI_FULL_LOCK_FLAG, "wifi_full_lock"), + new BitDescription(HistoryItem.STATE_WIFI_SCAN_LOCK_FLAG, "wifi_scan_lock"), + new BitDescription(HistoryItem.STATE_WIFI_MULTICAST_ON_FLAG, "wifi_multicast"), + new BitDescription(HistoryItem.STATE_BLUETOOTH_ON_FLAG, "bluetooth"), + new BitDescription(HistoryItem.STATE_AUDIO_ON_FLAG, "audio"), + new BitDescription(HistoryItem.STATE_VIDEO_ON_FLAG, "video"), + new BitDescription(HistoryItem.STATE_WAKE_LOCK_FLAG, "wake_lock"), + new BitDescription(HistoryItem.STATE_SENSOR_ON_FLAG, "sensor"), + new BitDescription(HistoryItem.STATE_BRIGHTNESS_MASK, + HistoryItem.STATE_BRIGHTNESS_SHIFT, "brightness", + SCREEN_BRIGHTNESS_NAMES), + new BitDescription(HistoryItem.STATE_SIGNAL_STRENGTH_MASK, + HistoryItem.STATE_SIGNAL_STRENGTH_SHIFT, "signal_strength", + SIGNAL_STRENGTH_NAMES), + new BitDescription(HistoryItem.STATE_PHONE_STATE_MASK, + HistoryItem.STATE_PHONE_STATE_SHIFT, "phone_state", + new String[] {"in", "out", "emergency", "off"}), + new BitDescription(HistoryItem.STATE_DATA_CONNECTION_MASK, + HistoryItem.STATE_DATA_CONNECTION_SHIFT, "data_conn", + DATA_CONNECTION_NAMES), + }; /** * Returns the time in microseconds that wifi has been on while the device was @@ -575,6 +761,18 @@ public abstract class BatteryStats implements Parcelable { public abstract int getDischargeCurrentLevel(); /** + * Get the amount the battery has discharged since the stats were + * last reset after charging, as a lower-end approximation. + */ + public abstract int getLowDischargeAmountSinceCharge(); + + /** + * Get the amount the battery has discharged since the stats were + * last reset after charging, as an upper-end approximation. + */ + public abstract int getHighDischargeAmountSinceCharge(); + + /** * Returns the total, last, or current battery uptime in microseconds. * * @param curTime the elapsed realtime in microseconds. @@ -791,7 +989,7 @@ public abstract class BatteryStats implements Parcelable { // Dump "battery" stat dumpLine(pw, 0 /* uid */, category, BATTERY_DATA, - which == STATS_TOTAL ? getStartCount() : "N/A", + which == STATS_SINCE_CHARGED ? getStartCount() : "N/A", whichBatteryRealtime / 1000, whichBatteryUptime / 1000, totalRealtime / 1000, totalUptime / 1000); @@ -864,7 +1062,7 @@ public abstract class BatteryStats implements Parcelable { } dumpLine(pw, 0 /* uid */, category, DATA_CONNECTION_COUNT_DATA, args); - if (which == STATS_UNPLUGGED) { + if (which == STATS_SINCE_UNPLUGGED) { dumpLine(pw, 0 /* uid */, category, BATTERY_LEVEL_DATA, getDischargeStartLevel(), getDischargeCurrentLevel()); } @@ -1095,10 +1293,9 @@ public abstract class BatteryStats implements Parcelable { linePrefix); if (!linePrefix.equals(": ")) { sb.append(" realtime"); - } else { - sb.append(": (nothing executed)"); + // Only print out wake locks that were held + pw.println(sb.toString()); } - pw.println(sb.toString()); } } } @@ -1212,7 +1409,7 @@ public abstract class BatteryStats implements Parcelable { pw.println(" "); - if (which == STATS_UNPLUGGED) { + if (which == STATS_SINCE_UNPLUGGED) { if (getIsOnBattery()) { pw.print(prefix); pw.println(" Device is currently unplugged"); pw.print(prefix); pw.print(" Discharge cycle start level: "); @@ -1227,6 +1424,13 @@ public abstract class BatteryStats implements Parcelable { pw.println(getDischargeCurrentLevel()); } pw.println(" "); + } else { + pw.print(prefix); pw.println(" Device battery use since last full charge"); + pw.print(prefix); pw.print(" Amount discharged (lower bound): "); + pw.println(getLowDischargeAmountSinceCharge()); + pw.print(prefix); pw.print(" Amount discharged (upper bound): "); + pw.println(getHighDischargeAmountSinceCharge()); + pw.println(" "); } @@ -1311,11 +1515,10 @@ public abstract class BatteryStats implements Parcelable { "window", which, linePrefix); if (!linePrefix.equals(": ")) { sb.append(" realtime"); - } else { - sb.append(": (nothing executed)"); + // Only print out wake locks that were held + pw.println(sb.toString()); + uidActivity = true; } - pw.println(sb.toString()); - uidActivity = true; } } @@ -1368,21 +1571,39 @@ public abstract class BatteryStats implements Parcelable { long userTime; long systemTime; int starts; + int numExcessive; userTime = ps.getUserTime(which); systemTime = ps.getSystemTime(which); starts = ps.getStarts(which); + numExcessive = which == STATS_SINCE_CHARGED + ? ps.countExcessiveWakes() : 0; - if (userTime != 0 || systemTime != 0 || starts != 0) { + if (userTime != 0 || systemTime != 0 || starts != 0 + || numExcessive != 0) { sb.setLength(0); sb.append(prefix); sb.append(" Proc "); sb.append(ent.getKey()); sb.append(":\n"); sb.append(prefix); sb.append(" CPU: "); formatTime(sb, userTime); sb.append("usr + "); - formatTime(sb, systemTime); sb.append("krn\n"); - sb.append(prefix); sb.append(" "); sb.append(starts); - sb.append(" proc starts"); + formatTime(sb, systemTime); sb.append("krn"); + if (starts != 0) { + sb.append("\n"); sb.append(prefix); sb.append(" "); + sb.append(starts); sb.append(" proc starts"); + } pw.println(sb.toString()); + for (int e=0; e<numExcessive; e++) { + Uid.Proc.ExcessiveWake ew = ps.getExcessiveWake(e); + if (ew != null) { + pw.print(prefix); pw.print(" * Killed for wake lock use: "); + TimeUtils.formatDuration(ew.usedTime, pw); + pw.print(" over "); + TimeUtils.formatDuration(ew.overTime, pw); + pw.print(" ("); + pw.print((ew.usedTime*100)/ew.overTime); + pw.println("%)"); + } + } uidActivity = true; } } @@ -1436,6 +1657,30 @@ public abstract class BatteryStats implements Parcelable { } } + void printBitDescriptions(PrintWriter pw, int oldval, int newval, BitDescription[] descriptions) { + int diff = oldval ^ newval; + if (diff == 0) return; + for (int i=0; i<descriptions.length; i++) { + BitDescription bd = descriptions[i]; + if ((diff&bd.mask) != 0) { + if (bd.shift < 0) { + pw.print((newval&bd.mask) != 0 ? " +" : " -"); + pw.print(bd.name); + } else { + pw.print(" "); + pw.print(bd.name); + pw.print("="); + int val = (newval&bd.mask)>>bd.shift; + if (bd.values != null && val >= 0 && val < bd.values.length) { + pw.print(bd.values[val]); + } else { + pw.print(val); + } + } + } + } + } + /** * Dumps a human-readable summary of the battery statistics to the given PrintWriter. * @@ -1443,19 +1688,161 @@ public abstract class BatteryStats implements Parcelable { */ @SuppressWarnings("unused") public void dumpLocked(PrintWriter pw) { - pw.println("Total Statistics (Current and Historic):"); + HistoryItem rec = getHistory(); + if (rec != null) { + pw.println("Battery History:"); + long now = getHistoryBaseTime() + SystemClock.elapsedRealtime(); + int oldState = 0; + int oldStatus = -1; + int oldHealth = -1; + int oldPlug = -1; + int oldTemp = -1; + int oldVolt = -1; + while (rec != null) { + pw.print(" "); + TimeUtils.formatDuration(rec.time-now, pw, TimeUtils.HUNDRED_DAY_FIELD_LEN); + pw.print(" "); + if (rec.cmd == HistoryItem.CMD_START) { + pw.println(" START"); + } else if (rec.cmd == HistoryItem.CMD_OVERFLOW) { + pw.println(" *OVERFLOW*"); + } else { + if (rec.batteryLevel < 10) pw.print("00"); + else if (rec.batteryLevel < 100) pw.print("0"); + pw.print(rec.batteryLevel); + pw.print(" "); + if (rec.states < 0x10) pw.print("0000000"); + else if (rec.states < 0x100) pw.print("000000"); + else if (rec.states < 0x1000) pw.print("00000"); + else if (rec.states < 0x10000) pw.print("0000"); + else if (rec.states < 0x100000) pw.print("000"); + else if (rec.states < 0x1000000) pw.print("00"); + else if (rec.states < 0x10000000) pw.print("0"); + pw.print(Integer.toHexString(rec.states)); + if (oldStatus != rec.batteryStatus) { + oldStatus = rec.batteryStatus; + pw.print(" status="); + switch (oldStatus) { + case BatteryManager.BATTERY_STATUS_UNKNOWN: + pw.print("unknown"); + break; + case BatteryManager.BATTERY_STATUS_CHARGING: + pw.print("charging"); + break; + case BatteryManager.BATTERY_STATUS_DISCHARGING: + pw.print("discharging"); + break; + case BatteryManager.BATTERY_STATUS_NOT_CHARGING: + pw.print("not-charging"); + break; + case BatteryManager.BATTERY_STATUS_FULL: + pw.print("full"); + break; + default: + pw.print(oldStatus); + break; + } + } + if (oldHealth != rec.batteryHealth) { + oldHealth = rec.batteryHealth; + pw.print(" health="); + switch (oldHealth) { + case BatteryManager.BATTERY_HEALTH_UNKNOWN: + pw.print("unknown"); + break; + case BatteryManager.BATTERY_HEALTH_GOOD: + pw.print("good"); + break; + case BatteryManager.BATTERY_HEALTH_OVERHEAT: + pw.print("overheat"); + break; + case BatteryManager.BATTERY_HEALTH_DEAD: + pw.print("dead"); + break; + case BatteryManager.BATTERY_HEALTH_OVER_VOLTAGE: + pw.print("over-voltage"); + break; + case BatteryManager.BATTERY_HEALTH_UNSPECIFIED_FAILURE: + pw.print("failure"); + break; + default: + pw.print(oldHealth); + break; + } + } + if (oldPlug != rec.batteryPlugType) { + oldPlug = rec.batteryPlugType; + pw.print(" plug="); + switch (oldPlug) { + case 0: + pw.print("none"); + break; + case BatteryManager.BATTERY_PLUGGED_AC: + pw.print("ac"); + break; + case BatteryManager.BATTERY_PLUGGED_USB: + pw.print("usb"); + break; + default: + pw.print(oldPlug); + break; + } + } + if (oldTemp != rec.batteryTemperature) { + oldTemp = rec.batteryTemperature; + pw.print(" temp="); + pw.print(oldTemp); + } + if (oldVolt != rec.batteryVoltage) { + oldVolt = rec.batteryVoltage; + pw.print(" volt="); + pw.print(oldVolt); + } + printBitDescriptions(pw, oldState, rec.states, + HISTORY_STATE_DESCRIPTIONS); + pw.println(); + } + oldState = rec.states; + rec = rec.next; + } + pw.println(""); + } + + SparseArray<? extends Uid> uidStats = getUidStats(); + final int NU = uidStats.size(); + boolean didPid = false; + long nowRealtime = SystemClock.elapsedRealtime(); + StringBuilder sb = new StringBuilder(64); + for (int i=0; i<NU; i++) { + Uid uid = uidStats.valueAt(i); + SparseArray<? extends Uid.Pid> pids = uid.getPidStats(); + if (pids != null) { + for (int j=0; j<pids.size(); j++) { + Uid.Pid pid = pids.valueAt(j); + if (!didPid) { + pw.println("Per-PID Stats:"); + didPid = true; + } + long time = pid.mWakeSum + (pid.mWakeStart != 0 + ? (nowRealtime - pid.mWakeStart) : 0); + pw.print(" PID "); pw.print(pids.keyAt(j)); + pw.print(" wake time: "); + TimeUtils.formatDuration(time, pw); + pw.println(""); + } + } + } + if (didPid) { + pw.println(""); + } + + pw.println("Statistics since last charge:"); pw.println(" System starts: " + getStartCount() + ", currently on battery: " + getIsOnBattery()); - dumpLocked(pw, "", STATS_TOTAL, -1); + dumpLocked(pw, "", STATS_SINCE_CHARGED, -1); pw.println(""); - pw.println("Last Run Statistics (Previous run of system):"); - dumpLocked(pw, "", STATS_LAST, -1); - pw.println(""); - pw.println("Current Battery Statistics (Currently running system):"); - dumpLocked(pw, "", STATS_CURRENT, -1); - pw.println(""); - pw.println("Unplugged Statistics (Since last unplugged from power):"); - dumpLocked(pw, "", STATS_UNPLUGGED, -1); + pw.println("Statistics since last unplugged:"); + dumpLocked(pw, "", STATS_SINCE_UNPLUGGED, -1); } @SuppressWarnings("unused") @@ -1470,14 +1857,11 @@ public abstract class BatteryStats implements Parcelable { } if (isUnpluggedOnly) { - dumpCheckinLocked(pw, STATS_UNPLUGGED, -1); + dumpCheckinLocked(pw, STATS_SINCE_UNPLUGGED, -1); } else { - dumpCheckinLocked(pw, STATS_TOTAL, -1); - dumpCheckinLocked(pw, STATS_LAST, -1); - dumpCheckinLocked(pw, STATS_UNPLUGGED, -1); - dumpCheckinLocked(pw, STATS_CURRENT, -1); + dumpCheckinLocked(pw, STATS_SINCE_CHARGED, -1); + dumpCheckinLocked(pw, STATS_SINCE_UNPLUGGED, -1); } } - } diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java index c9df5671ddf2..f8260cababeb 100644 --- a/core/java/android/os/Binder.java +++ b/core/java/android/os/Binder.java @@ -101,7 +101,30 @@ public class Binder implements IBinder { * @see #clearCallingIdentity */ public static final native void restoreCallingIdentity(long token); - + + /** + * Sets the native thread-local StrictMode policy mask. + * + * <p>The StrictMode settings are kept in two places: a Java-level + * threadlocal for libcore/Dalvik, and a native threadlocal (set + * here) for propagation via Binder calls. This is a little + * unfortunate, but necessary to break otherwise more unfortunate + * dependencies either of Dalvik on Android, or Android + * native-only code on Dalvik. + * + * @see StrictMode + * @hide + */ + public static final native void setThreadStrictModePolicy(int policyMask); + + /** + * Gets the current native thread-local StrictMode policy mask. + * + * @see #setThreadStrictModePolicy + * @hide + */ + public static final native int getThreadStrictModePolicy(); + /** * Flush any Binder commands pending in the current thread to the kernel * driver. This can be @@ -203,9 +226,16 @@ public class Binder implements IBinder { try { fd.close(); } catch (IOException e) { + // swallowed, not propagated back to the caller } } } + // Write the StrictMode header. + if (reply != null) { + reply.writeNoException(); + } else { + StrictMode.clearGatheredViolations(); + } return true; } return false; @@ -276,6 +306,8 @@ public class Binder implements IBinder { private native final void init(); private native final void destroy(); + + // Entry point from android_util_Binder.cpp's onTransact private boolean execTransact(int code, int dataObj, int replyObj, int flags) { Parcel data = Parcel.obtain(dataObj); @@ -316,12 +348,15 @@ final class BinderProxy implements IBinder { public void dump(FileDescriptor fd, String[] args) throws RemoteException { Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); data.writeFileDescriptor(fd); data.writeStringArray(args); try { - transact(DUMP_TRANSACTION, data, null, 0); + transact(DUMP_TRANSACTION, data, reply, 0); + reply.readException(); } finally { data.recycle(); + reply.recycle(); } } diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java index 3e9fd420a3fe..8925bd05cd83 100644 --- a/core/java/android/os/Build.java +++ b/core/java/android/os/Build.java @@ -62,6 +62,9 @@ public class Build { /** The name of the hardware (from the kernel command line or /proc). */ public static final String HARDWARE = getString("ro.hardware"); + /** A hardware serial number, if available. Alphanumeric only, case-insensitive. */ + public static final String SERIAL = getString("ro.serialno"); + /** Various version strings. */ public static class VERSION { /** @@ -179,7 +182,23 @@ public class Build { */ public static final int ECLAIR_MR1 = 7; + /** + * June 2010: Android 2.2 + */ public static final int FROYO = 8; + + /** + * Next version of Android. + * + * <p>Applications targeting this or a later release will get these + * new changes in behavior:</p> + * <ul> + * <li> The status bar is now dark. Targeting this version allows + * the platform to perform performing compatibility on status bar + * graphics to ensure they look okay on a dark background. + * </ul> + */ + public static final int GINGERBREAD = CUR_DEVELOPMENT; } /** The type of build, like "user" or "eng". */ diff --git a/core/java/android/os/DropBoxManager.java b/core/java/android/os/DropBoxManager.java index a9f9bd74de63..23fdb0bd7045 100644 --- a/core/java/android/os/DropBoxManager.java +++ b/core/java/android/os/DropBoxManager.java @@ -267,7 +267,7 @@ public class DropBoxManager { if (file == null) throw new NullPointerException("file == null"); Entry entry = new Entry(tag, 0, file, flags); try { - mService.add(new Entry(tag, 0, file, flags)); + mService.add(entry); } catch (RemoteException e) { // ignore } finally { diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java index 812391c48e5f..c7cbed6e4ced 100644 --- a/core/java/android/os/Environment.java +++ b/core/java/android/os/Environment.java @@ -28,6 +28,8 @@ public class Environment { private static final File ROOT_DIRECTORY = getDirectory("ANDROID_ROOT", "/system"); + private static final String SYSTEM_PROPERTY_EFS_ENABLED = "persist.security.efs.enabled"; + private static IMountService mMntSvc = null; /** @@ -37,9 +39,55 @@ public class Environment { return ROOT_DIRECTORY; } + /** + * Gets the system directory available for secure storage. + * If Encrypted File system is enabled, it returns an encrypted directory (/data/secure/system). + * Otherwise, it returns the unencrypted /data/system directory. + * @return File object representing the secure storage system directory. + * @hide + */ + public static File getSystemSecureDirectory() { + if (isEncryptedFilesystemEnabled()) { + return new File(SECURE_DATA_DIRECTORY, "system"); + } else { + return new File(DATA_DIRECTORY, "system"); + } + } + + /** + * Gets the data directory for secure storage. + * If Encrypted File system is enabled, it returns an encrypted directory (/data/secure). + * Otherwise, it returns the unencrypted /data directory. + * @return File object representing the data directory for secure storage. + * @hide + */ + public static File getSecureDataDirectory() { + if (isEncryptedFilesystemEnabled()) { + return SECURE_DATA_DIRECTORY; + } else { + return DATA_DIRECTORY; + } + } + + /** + * Returns whether the Encrypted File System feature is enabled on the device or not. + * @return <code>true</code> if Encrypted File System feature is enabled, <code>false</code> + * if disabled. + * @hide + */ + public static boolean isEncryptedFilesystemEnabled() { + return SystemProperties.getBoolean(SYSTEM_PROPERTY_EFS_ENABLED, false); + } + private static final File DATA_DIRECTORY = getDirectory("ANDROID_DATA", "/data"); + /** + * @hide + */ + private static final File SECURE_DATA_DIRECTORY + = getDirectory("ANDROID_SECURE_DATA", "/data/secure"); + private static final File EXTERNAL_STORAGE_DIRECTORY = getDirectory("EXTERNAL_STORAGE", "/sdcard"); diff --git a/core/java/android/os/Handler.java b/core/java/android/os/Handler.java index 2a32e543f19f..3b2bf1e81c80 100644 --- a/core/java/android/os/Handler.java +++ b/core/java/android/os/Handler.java @@ -563,13 +563,13 @@ public class Handler { return mMessenger; } } - + private final class MessengerImpl extends IMessenger.Stub { public void send(Message msg) { Handler.this.sendMessage(msg); } } - + private final Message getPostMessage(Runnable r) { Message m = Message.obtain(); m.callback = r; diff --git a/core/java/android/os/IPowerManager.aidl b/core/java/android/os/IPowerManager.aidl index 01cc408074d0..0067e9407777 100644 --- a/core/java/android/os/IPowerManager.aidl +++ b/core/java/android/os/IPowerManager.aidl @@ -17,10 +17,13 @@ package android.os; +import android.os.WorkSource; + /** @hide */ interface IPowerManager { - void acquireWakeLock(int flags, IBinder lock, String tag); + void acquireWakeLock(int flags, IBinder lock, String tag, in WorkSource ws); + void updateWakeLockWorkSource(IBinder lock, in WorkSource ws); void goToSleep(long time); void goToSleepWithReason(long time, int reason); void releaseWakeLock(IBinder lock, int flags); diff --git a/core/java/android/os/Looper.java b/core/java/android/os/Looper.java index 69b35404a61b..d360140d0c10 100644 --- a/core/java/android/os/Looper.java +++ b/core/java/android/os/Looper.java @@ -180,6 +180,11 @@ public class Looper { return mThread; } + /** @hide */ + public MessageQueue getQueue() { + return mQueue; + } + public void dump(Printer pw, String prefix) { pw.println(prefix + this); pw.println(prefix + "mRun=" + mRun); @@ -187,10 +192,11 @@ public class Looper { pw.println(prefix + "mQueue=" + ((mQueue != null) ? mQueue : "(null")); if (mQueue != null) { synchronized (mQueue) { + long now = SystemClock.uptimeMillis(); Message msg = mQueue.mMessages; int n = 0; while (msg != null) { - pw.println(prefix + " Message " + n + ": " + msg); + pw.println(prefix + " Message " + n + ": " + msg.toString(now)); n++; msg = msg.next; } diff --git a/core/java/android/os/Message.java b/core/java/android/os/Message.java index 476da1df32db..49b72fee22d3 100644 --- a/core/java/android/os/Message.java +++ b/core/java/android/os/Message.java @@ -19,6 +19,7 @@ package android.os; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; +import android.util.TimeUtils; /** * @@ -366,13 +367,17 @@ public final class Message implements Parcelable { } public String toString() { + return toString(SystemClock.uptimeMillis()); + } + + String toString(long now) { StringBuilder b = new StringBuilder(); b.append("{ what="); b.append(what); b.append(" when="); - b.append(when); + TimeUtils.formatDuration(when-now, b); if (arg1 != 0) { b.append(" arg1="); diff --git a/core/java/android/os/MessageQueue.java b/core/java/android/os/MessageQueue.java index bc653d64864d..281386305311 100644 --- a/core/java/android/os/MessageQueue.java +++ b/core/java/android/os/MessageQueue.java @@ -16,13 +16,11 @@ package android.os; -import java.util.ArrayList; - import android.util.AndroidRuntimeException; import android.util.Config; import android.util.Log; -import com.android.internal.os.RuntimeInit; +import java.util.ArrayList; /** * Low-level class holding the list of messages to be dispatched by a @@ -34,10 +32,18 @@ import com.android.internal.os.RuntimeInit; */ public class MessageQueue { Message mMessages; - private final ArrayList mIdleHandlers = new ArrayList(); + private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>(); private boolean mQuiting = false; boolean mQuitAllowed = true; + + @SuppressWarnings("unused") + private int mPtr; // used by native code + private native void nativeInit(); + private native void nativeDestroy(); + private native boolean nativePollOnce(int timeoutMillis); + private native void nativeWake(); + /** * Callback interface for discovering when a thread is going to block * waiting for more messages. @@ -84,22 +90,39 @@ public class MessageQueue { mIdleHandlers.remove(handler); } } - + MessageQueue() { + nativeInit(); + } + + @Override + protected void finalize() throws Throwable { + try { + nativeDestroy(); + } finally { + super.finalize(); + } } final Message next() { boolean tryIdle = true; + // when we start out, we'll just touch the input pipes and then go from there + int timeToNextEventMillis = 0; while (true) { long now; Object[] idlers = null; - + + boolean dispatched = nativePollOnce(timeToNextEventMillis); + // Try to retrieve the next message, returning if found. synchronized (this) { now = SystemClock.uptimeMillis(); Message msg = pullNextLocked(now); - if (msg != null) return msg; + if (msg != null) { + return msg; + } + if (tryIdle && mIdleHandlers.size() > 0) { idlers = mIdleHandlers.toArray(); } @@ -135,20 +158,22 @@ public class MessageQueue { synchronized (this) { // No messages, nobody to tell about it... time to wait! - try { - if (mMessages != null) { - if (mMessages.when-now > 0) { - Binder.flushPendingCommands(); - this.wait(mMessages.when-now); - } - } else { + if (mMessages != null) { + long longTimeToNextEventMillis = mMessages.when - now; + + if (longTimeToNextEventMillis > 0) { Binder.flushPendingCommands(); - this.wait(); + timeToNextEventMillis = (int) Math.min(longTimeToNextEventMillis, + Integer.MAX_VALUE); + } else { + timeToNextEventMillis = 0; } - } - catch (InterruptedException e) { + } else { + Binder.flushPendingCommands(); + timeToNextEventMillis = -1; } } + // loop to the while(true) and do the appropriate nativeWait(when) } } @@ -190,7 +215,6 @@ public class MessageQueue { if (p == null || when == 0 || when < p.when) { msg.next = p; mMessages = msg; - this.notify(); } else { Message prev = null; while (p != null && p.when <= when) { @@ -199,9 +223,9 @@ public class MessageQueue { } msg.next = prev.next; prev.next = msg; - this.notify(); } } + nativeWake(); return true; } @@ -317,11 +341,4 @@ public class MessageQueue { } } */ - - void poke() - { - synchronized (this) { - this.notify(); - } - } } diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java index bb6357e55b19..31f87198878a 100644 --- a/core/java/android/os/Parcel.java +++ b/core/java/android/os/Parcel.java @@ -176,6 +176,7 @@ import java.util.Set; */ public final class Parcel { private static final boolean DEBUG_RECYCLE = false; + private static final String TAG = "Parcel"; @SuppressWarnings({"UnusedDeclaration"}) private int mObject; // used by native code @@ -214,12 +215,14 @@ public final class Parcel { private static final int VAL_BOOLEANARRAY = 23; private static final int VAL_CHARSEQUENCEARRAY = 24; + // The initial int32 in a Binder call's reply Parcel header: private static final int EX_SECURITY = -1; private static final int EX_BAD_PARCELABLE = -2; private static final int EX_ILLEGAL_ARGUMENT = -3; private static final int EX_NULL_POINTER = -4; private static final int EX_ILLEGAL_STATE = -5; - + private static final int EX_HAS_REPLY_HEADER = -128; // special; see below + public final static Parcelable.Creator<String> STRING_CREATOR = new Parcelable.Creator<String>() { public String createFromParcel(Parcel source) { @@ -1212,6 +1215,7 @@ public final class Parcel { code = EX_ILLEGAL_STATE; } writeInt(code); + StrictMode.clearGatheredViolations(); if (code == 0) { if (e instanceof RuntimeException) { throw (RuntimeException) e; @@ -1229,7 +1233,31 @@ public final class Parcel { * @see #readException */ public final void writeNoException() { - writeInt(0); + // Despite the name of this function ("write no exception"), + // it should instead be thought of as "write the RPC response + // header", but because this function name is written out by + // the AIDL compiler, we're not going to rename it. + // + // The response header, in the non-exception case (see also + // writeException above, also called by the AIDL compiler), is + // either a 0 (the default case), or EX_HAS_REPLY_HEADER if + // StrictMode has gathered up violations that have occurred + // during a Binder call, in which case we write out the number + // of violations and their details, serialized, before the + // actual RPC respons data. The receiving end of this is + // readException(), below. + if (StrictMode.hasGatheredViolations()) { + writeInt(EX_HAS_REPLY_HEADER); + final int sizePosition = dataPosition(); + writeInt(0); // total size of fat header, to be filled in later + StrictMode.writeGatheredViolationsToParcel(this); + final int payloadPosition = dataPosition(); + setDataPosition(sizePosition); + writeInt(payloadPosition - sizePosition); // header size + setDataPosition(payloadPosition); + } else { + writeInt(0); + } } /** @@ -1242,10 +1270,44 @@ public final class Parcel { * @see #writeNoException */ public final void readException() { + int code = readExceptionCode(); + if (code != 0) { + String msg = readString(); + readException(code, msg); + } + } + + /** + * Parses the header of a Binder call's response Parcel and + * returns the exception code. Deals with lite or fat headers. + * In the common successful case, this header is generally zero. + * In less common cases, it's a small negative number and will be + * followed by an error string. + * + * This exists purely for android.database.DatabaseUtils and + * insulating it from having to handle fat headers as returned by + * e.g. StrictMode-induced RPC responses. + * + * @hide + */ + public final int readExceptionCode() { int code = readInt(); - if (code == 0) return; - String msg = readString(); - readException(code, msg); + if (code == EX_HAS_REPLY_HEADER) { + int headerSize = readInt(); + if (headerSize == 0) { + Log.e(TAG, "Unexpected zero-sized Parcel reply header."); + } else { + // Currently the only thing in the header is StrictMode stacks, + // but discussions around event/RPC tracing suggest we might + // put that here too. If so, switch on sub-header tags here. + // But for now, just parse out the StrictMode stuff. + StrictMode.readAndHandleBinderCallViolations(this); + } + // And fat response headers are currently only used when + // there are no exceptions, so return no error: + return 0; + } + return code; } /** @@ -1885,13 +1947,13 @@ public final class Parcel { creator = (Parcelable.Creator)f.get(null); } catch (IllegalAccessException e) { - Log.e("Parcel", "Class not found when unmarshalling: " + Log.e(TAG, "Class not found when unmarshalling: " + name + ", e: " + e); throw new BadParcelableException( "IllegalAccessException when unmarshalling: " + name); } catch (ClassNotFoundException e) { - Log.e("Parcel", "Class not found when unmarshalling: " + Log.e(TAG, "Class not found when unmarshalling: " + name + ", e: " + e); throw new BadParcelableException( "ClassNotFoundException when unmarshalling: " + name); @@ -1996,7 +2058,7 @@ public final class Parcel { if (DEBUG_RECYCLE) { mStack = new RuntimeException(); } - //Log.i("Parcel", "Initializing obj=0x" + Integer.toHexString(obj), mStack); + //Log.i(TAG, "Initializing obj=0x" + Integer.toHexString(obj), mStack); init(obj); } @@ -2004,7 +2066,7 @@ public final class Parcel { protected void finalize() throws Throwable { if (DEBUG_RECYCLE) { if (mStack != null) { - Log.w("Parcel", "Client did not call Parcel.recycle()", mStack); + Log.w(TAG, "Client did not call Parcel.recycle()", mStack); } } destroy(); @@ -2028,7 +2090,7 @@ public final class Parcel { ClassLoader loader) { while (N > 0) { Object value = readValue(loader); - //Log.d("Parcel", "Unmarshalling value=" + value); + //Log.d(TAG, "Unmarshalling value=" + value); outVal.add(value); N--; } @@ -2038,7 +2100,7 @@ public final class Parcel { ClassLoader loader) { for (int i = 0; i < N; i++) { Object value = readValue(loader); - //Log.d("Parcel", "Unmarshalling value=" + value); + //Log.d(TAG, "Unmarshalling value=" + value); outVal[i] = value; } } @@ -2048,7 +2110,7 @@ public final class Parcel { while (N > 0) { int key = readInt(); Object value = readValue(loader); - //Log.i("Parcel", "Unmarshalling key=" + key + " value=" + value); + //Log.i(TAG, "Unmarshalling key=" + key + " value=" + value); outVal.append(key, value); N--; } @@ -2059,7 +2121,7 @@ public final class Parcel { while (N > 0) { int key = readInt(); boolean value = this.readByte() == 1; - //Log.i("Parcel", "Unmarshalling key=" + key + " value=" + value); + //Log.i(TAG, "Unmarshalling key=" + key + " value=" + value); outVal.append(key, value); N--; } diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java index e8fe302e28d3..9d213b344dc8 100644 --- a/core/java/android/os/ParcelFileDescriptor.java +++ b/core/java/android/os/ParcelFileDescriptor.java @@ -179,7 +179,7 @@ public class ParcelFileDescriptor implements Parcelable { /** * An InputStream you can create on a ParcelFileDescriptor, which will * take care of calling {@link ParcelFileDescriptor#close - * ParcelFileDescritor.close()} for you when the stream is closed. + * ParcelFileDescriptor.close()} for you when the stream is closed. */ public static class AutoCloseInputStream extends FileInputStream { private final ParcelFileDescriptor mFd; @@ -198,7 +198,7 @@ public class ParcelFileDescriptor implements Parcelable { /** * An OutputStream you can create on a ParcelFileDescriptor, which will * take care of calling {@link ParcelFileDescriptor#close - * ParcelFileDescritor.close()} for you when the stream is closed. + * ParcelFileDescriptor.close()} for you when the stream is closed. */ public static class AutoCloseOutputStream extends FileOutputStream { private final ParcelFileDescriptor mFd; diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java index f4ca8bc85469..3876a3e55873 100644 --- a/core/java/android/os/PowerManager.java +++ b/core/java/android/os/PowerManager.java @@ -209,6 +209,7 @@ public class PowerManager int mCount = 0; boolean mRefCounted = true; boolean mHeld = false; + WorkSource mWorkSource; WakeLock(int flags, String tag) { @@ -247,7 +248,7 @@ public class PowerManager synchronized (mToken) { if (!mRefCounted || mCount++ == 0) { try { - mService.acquireWakeLock(mFlags, mToken, mTag); + mService.acquireWakeLock(mFlags, mToken, mTag, mWorkSource); } catch (RemoteException e) { } mHeld = true; @@ -313,6 +314,32 @@ public class PowerManager } } + public void setWorkSource(WorkSource ws) { + synchronized (mToken) { + if (ws != null && ws.size() == 0) { + ws = null; + } + boolean changed = true; + if (ws == null) { + mWorkSource = null; + } else if (mWorkSource == null) { + changed = mWorkSource != null; + mWorkSource = new WorkSource(ws); + } else { + changed = mWorkSource.diff(ws); + if (changed) { + mWorkSource.set(ws); + } + } + if (changed && mHeld) { + try { + mService.updateWakeLockWorkSource(mToken, mWorkSource); + } catch (RemoteException e) { + } + } + } + } + public String toString() { synchronized (mToken) { return "WakeLock{" diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java index 5640a0627721..f695dbb4cdd4 100644 --- a/core/java/android/os/Process.java +++ b/core/java/android/os/Process.java @@ -626,6 +626,15 @@ public class Process { throws IllegalArgumentException, SecurityException; /** + * Call with 'false' to cause future calls to {@link #setThreadPriority(int)} to + * throw an exception if passed a background-level thread priority. This is only + * effective if the JNI layer is built with GUARD_THREAD_PRIORITY defined to 1. + * + * @hide + */ + public static final native void setCanSelfBackground(boolean backgroundOk); + + /** * Sets the scheduling group for a thread. * @hide * @param tid The indentifier of the thread/process to change. diff --git a/core/java/android/os/RecoverySystem.java b/core/java/android/os/RecoverySystem.java index b6dc1b52dffa..5fea6fec453f 100644 --- a/core/java/android/os/RecoverySystem.java +++ b/core/java/android/os/RecoverySystem.java @@ -348,6 +348,23 @@ public class RecoverySystem { } /** + * Reboot into the recovery system to wipe the /data partition and toggle + * Encrypted File Systems on/off. + * @param extras to add to the RECOVERY_COMPLETED intent after rebooting. + * @throws IOException if something goes wrong. + * + * @hide + */ + public static void rebootToggleEFS(Context context, boolean efsEnabled) + throws IOException { + if (efsEnabled) { + bootCommand(context, "--set_encrypted_filesystem=on"); + } else { + bootCommand(context, "--set_encrypted_filesystem=off"); + } + } + + /** * Reboot into the recovery system with the supplied argument. * @param arg to pass to the recovery utility. * @throws IOException if something goes wrong. diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java new file mode 100644 index 000000000000..7f7b02b5f1bd --- /dev/null +++ b/core/java/android/os/StrictMode.java @@ -0,0 +1,657 @@ +/* + * 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 android.os; + +import android.app.ActivityManagerNative; +import android.app.ApplicationErrorReport; +import android.util.Log; +import android.util.Printer; + +import com.android.internal.os.RuntimeInit; + +import dalvik.system.BlockGuard; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.HashMap; + +/** + * <p>StrictMode lets you impose stricter rules under which your + * application runs.</p> + */ +public final class StrictMode { + private static final String TAG = "StrictMode"; + private static final boolean LOG_V = false; + + // Only log a duplicate stack trace to the logs every second. + private static final long MIN_LOG_INTERVAL_MS = 1000; + + // Only show an annoying dialog at most every 30 seconds + private static final long MIN_DIALOG_INTERVAL_MS = 30000; + + private StrictMode() {} + + public static final int DISALLOW_DISK_WRITE = 0x01; + public static final int DISALLOW_DISK_READ = 0x02; + public static final int DISALLOW_NETWORK = 0x04; + + /** @hide */ + public static final int DISALLOW_MASK = + DISALLOW_DISK_WRITE | DISALLOW_DISK_READ | DISALLOW_NETWORK; + + /** + * Flag to log to the system log. + */ + public static final int PENALTY_LOG = 0x10; // normal android.util.Log + + /** + * Show an annoying dialog to the user. Will be rate-limited to be only + * a little annoying. + */ + public static final int PENALTY_DIALOG = 0x20; + + /** + * Crash hard if policy is violated. + */ + public static final int PENALTY_DEATH = 0x40; + + /** + * Log a stacktrace to the DropBox on policy violation. + */ + public static final int PENALTY_DROPBOX = 0x80; + + /** + * Non-public penalty mode which overrides all the other penalty + * bits and signals that we're in a Binder call and we should + * ignore the other penalty bits and instead serialize back all + * our offending stack traces to the caller to ultimately handle + * in the originating process. + * + * This must be kept in sync with the constant in libs/binder/Parcel.cpp + * + * @hide + */ + public static final int PENALTY_GATHER = 0x100; + + /** @hide */ + public static final int PENALTY_MASK = + PENALTY_LOG | PENALTY_DIALOG | + PENALTY_DROPBOX | PENALTY_DEATH; + + /** + * Log of strict mode violation stack traces that have occurred + * during a Binder call, to be serialized back later to the caller + * via Parcel.writeNoException() (amusingly) where the caller can + * choose how to react. + */ + private static final ThreadLocal<ArrayList<ViolationInfo>> gatheredViolations = + new ThreadLocal<ArrayList<ViolationInfo>>() { + @Override protected ArrayList<ViolationInfo> initialValue() { + // Starts null to avoid unnecessary allocations when + // checking whether there are any violations or not in + // hasGatheredViolations() below. + return null; + } + }; + + /** + * Sets the policy for what actions the current thread is denied, + * as well as the penalty for violating the policy. + * + * @param policyMask a bitmask of DISALLOW_* and PENALTY_* values. + */ + public static void setThreadPolicy(final int policyMask) { + // In addition to the Java-level thread-local in Dalvik's + // BlockGuard, we also need to keep a native thread-local in + // Binder in order to propagate the value across Binder calls, + // even across native-only processes. The two are kept in + // sync via the callback to onStrictModePolicyChange, below. + setBlockGuardPolicy(policyMask); + + // And set the Android native version... + Binder.setThreadStrictModePolicy(policyMask); + } + + // Sets the policy in Dalvik/libcore (BlockGuard) + private static void setBlockGuardPolicy(final int policyMask) { + if (policyMask == 0) { + BlockGuard.setThreadPolicy(BlockGuard.LAX_POLICY); + return; + } + BlockGuard.Policy policy = BlockGuard.getThreadPolicy(); + if (!(policy instanceof AndroidBlockGuardPolicy)) { + BlockGuard.setThreadPolicy(new AndroidBlockGuardPolicy(policyMask)); + } else { + AndroidBlockGuardPolicy androidPolicy = (AndroidBlockGuardPolicy) policy; + androidPolicy.setPolicyMask(policyMask); + } + } + + private static class StrictModeNetworkViolation extends BlockGuard.BlockGuardPolicyException { + public StrictModeNetworkViolation(int policyMask) { + super(policyMask, DISALLOW_NETWORK); + } + } + + private static class StrictModeDiskReadViolation extends BlockGuard.BlockGuardPolicyException { + public StrictModeDiskReadViolation(int policyMask) { + super(policyMask, DISALLOW_DISK_READ); + } + } + + private static class StrictModeDiskWriteViolation extends BlockGuard.BlockGuardPolicyException { + public StrictModeDiskWriteViolation(int policyMask) { + super(policyMask, DISALLOW_DISK_WRITE); + } + } + + /** + * Returns the bitmask of the current thread's blocking policy. + * + * @return the bitmask of all the DISALLOW_* and PENALTY_* bits currently enabled + */ + public static int getThreadPolicy() { + return BlockGuard.getThreadPolicy().getPolicyMask(); + } + + /** + * Updates the current thread's policy mask to allow reading & + * writing to disk. + * + * @return the old policy mask, to be passed to setThreadPolicy to + * restore the policy. + */ + public static int allowThreadDiskWrites() { + int oldPolicy = getThreadPolicy(); + int newPolicy = oldPolicy & ~(DISALLOW_DISK_WRITE | DISALLOW_DISK_READ); + if (newPolicy != oldPolicy) { + setThreadPolicy(newPolicy); + } + return oldPolicy; + } + + /** + * Updates the current thread's policy mask to allow reading from + * disk. + * + * @return the old policy mask, to be passed to setThreadPolicy to + * restore the policy. + */ + public static int allowThreadDiskReads() { + int oldPolicy = getThreadPolicy(); + int newPolicy = oldPolicy & ~(DISALLOW_DISK_READ); + if (newPolicy != oldPolicy) { + setThreadPolicy(newPolicy); + } + return oldPolicy; + } + + /** + * Parses the BlockGuard policy mask out from the Exception's + * getMessage() String value. Kinda gross, but least + * invasive. :/ + * + * Input is of form "policy=137 violation=64" + * + * Returns 0 on failure, which is a valid policy, but not a + * valid policy during a violation (else there must've been + * some policy in effect to violate). + */ + private static int parsePolicyFromMessage(String message) { + if (message == null || !message.startsWith("policy=")) { + return 0; + } + int spaceIndex = message.indexOf(' '); + if (spaceIndex == -1) { + return 0; + } + String policyString = message.substring(7, spaceIndex); + try { + return Integer.valueOf(policyString).intValue(); + } catch (NumberFormatException e) { + return 0; + } + } + + /** + * Like parsePolicyFromMessage(), but returns the violation. + */ + private static int parseViolationFromMessage(String message) { + if (message == null) { + return 0; + } + int violationIndex = message.indexOf("violation="); + if (violationIndex == -1) { + return 0; + } + String violationString = message.substring(violationIndex + 10); + try { + return Integer.valueOf(violationString).intValue(); + } catch (NumberFormatException e) { + return 0; + } + } + + private static class AndroidBlockGuardPolicy implements BlockGuard.Policy { + private int mPolicyMask; + + // Map from violation stacktrace hashcode -> uptimeMillis of + // last violation. No locking needed, as this is only + // accessed by the same thread. + private final HashMap<Integer, Long> mLastViolationTime = new HashMap<Integer, Long>(); + + public AndroidBlockGuardPolicy(final int policyMask) { + mPolicyMask = policyMask; + } + + @Override + public String toString() { + return "AndroidBlockGuardPolicy; mPolicyMask=" + mPolicyMask; + } + + // Part of BlockGuard.Policy interface: + public int getPolicyMask() { + return mPolicyMask; + } + + // Part of BlockGuard.Policy interface: + public void onWriteToDisk() { + if ((mPolicyMask & DISALLOW_DISK_WRITE) == 0) { + return; + } + BlockGuard.BlockGuardPolicyException e = new StrictModeDiskWriteViolation(mPolicyMask); + e.fillInStackTrace(); + startHandlingViolationException(e); + } + + // Part of BlockGuard.Policy interface: + public void onReadFromDisk() { + if ((mPolicyMask & DISALLOW_DISK_READ) == 0) { + return; + } + BlockGuard.BlockGuardPolicyException e = new StrictModeDiskReadViolation(mPolicyMask); + e.fillInStackTrace(); + startHandlingViolationException(e); + } + + // Part of BlockGuard.Policy interface: + public void onNetwork() { + if ((mPolicyMask & DISALLOW_NETWORK) == 0) { + return; + } + BlockGuard.BlockGuardPolicyException e = new StrictModeNetworkViolation(mPolicyMask); + e.fillInStackTrace(); + startHandlingViolationException(e); + } + + public void setPolicyMask(int policyMask) { + mPolicyMask = policyMask; + } + + // Start handling a violation that just started and hasn't + // actually run yet (e.g. no disk write or network operation + // has yet occurred). This sees if we're in an event loop + // thread and, if so, uses it to roughly measure how long the + // violation took. + void startHandlingViolationException(BlockGuard.BlockGuardPolicyException e) { + final ViolationInfo info = new ViolationInfo(e, e.getPolicy()); + info.violationUptimeMillis = SystemClock.uptimeMillis(); + handleViolationWithTimingAttempt(info); + } + + private static final ThreadLocal<ArrayList<ViolationInfo>> violationsBeingTimed = + new ThreadLocal<ArrayList<ViolationInfo>>() { + @Override protected ArrayList<ViolationInfo> initialValue() { + return new ArrayList<ViolationInfo>(); + } + }; + + // Attempts to fill in the provided ViolationInfo's + // durationMillis field if this thread has a Looper we can use + // to measure with. We measure from the time of violation + // until the time the looper is idle again (right before + // the next epoll_wait) + void handleViolationWithTimingAttempt(final ViolationInfo info) { + Looper looper = Looper.myLooper(); + + // Without a Looper, we're unable to time how long the + // violation takes place. This case should be rare, as + // most users will care about timing violations that + // happen on their main UI thread. Note that this case is + // also hit when a violation takes place in a Binder + // thread, in "gather" mode. In this case, the duration + // of the violation is computed by the ultimate caller and + // its Looper, if any. + // TODO: if in gather mode, ignore Looper.myLooper() and always + // go into this immediate mode? + if (looper == null) { + info.durationMillis = -1; // unknown (redundant, already set) + handleViolation(info); + return; + } + + MessageQueue queue = Looper.myQueue(); + final ArrayList<ViolationInfo> records = violationsBeingTimed.get(); + if (records.size() >= 10) { + // Not worth measuring. Too many offenses in one loop. + return; + } + records.add(info); + if (records.size() > 1) { + // There's already been a violation this loop, so we've already + // registered an idle handler to process the list of violations + // at the end of this Looper's loop. + return; + } + + queue.addIdleHandler(new MessageQueue.IdleHandler() { + public boolean queueIdle() { + long loopFinishTime = SystemClock.uptimeMillis(); + for (int n = 0; n < records.size(); ++n) { + ViolationInfo v = records.get(n); + v.violationNumThisLoop = n + 1; + v.durationMillis = + (int) (loopFinishTime - v.violationUptimeMillis); + handleViolation(v); + } + records.clear(); + return false; // remove this idle handler from the array + } + }); + } + + // Note: It's possible (even quite likely) that the + // thread-local policy mask has changed from the time the + // violation fired and now (after the violating code ran) due + // to people who push/pop temporary policy in regions of code, + // hence the policy being passed around. + void handleViolation(final ViolationInfo info) { + if (info == null || info.crashInfo == null || info.crashInfo.stackTrace == null) { + Log.wtf(TAG, "unexpected null stacktrace"); + return; + } + + if (LOG_V) Log.d(TAG, "handleViolation; policy=" + info.policy); + + if ((info.policy & PENALTY_GATHER) != 0) { + ArrayList<ViolationInfo> violations = gatheredViolations.get(); + if (violations == null) { + violations = new ArrayList<ViolationInfo>(1); + gatheredViolations.set(violations); + } else if (violations.size() >= 5) { + // Too many. In a loop or something? Don't gather them all. + return; + } + for (ViolationInfo previous : violations) { + if (info.crashInfo.stackTrace.equals(previous.crashInfo.stackTrace)) { + // Duplicate. Don't log. + return; + } + } + violations.add(info); + return; + } + + // Not perfect, but fast and good enough for dup suppression. + Integer crashFingerprint = info.crashInfo.stackTrace.hashCode(); + long lastViolationTime = 0; + if (mLastViolationTime.containsKey(crashFingerprint)) { + lastViolationTime = mLastViolationTime.get(crashFingerprint); + } + long now = SystemClock.uptimeMillis(); + mLastViolationTime.put(crashFingerprint, now); + long timeSinceLastViolationMillis = lastViolationTime == 0 ? + Long.MAX_VALUE : (now - lastViolationTime); + + if ((info.policy & PENALTY_LOG) != 0 && + timeSinceLastViolationMillis > MIN_LOG_INTERVAL_MS) { + if (info.durationMillis != -1) { + Log.d(TAG, "StrictMode policy violation; ~duration=" + + info.durationMillis + " ms: " + info.crashInfo.stackTrace); + } else { + Log.d(TAG, "StrictMode policy violation: " + info.crashInfo.stackTrace); + } + } + + // The violationMask, passed to ActivityManager, is a + // subset of the original StrictMode policy bitmask, with + // only the bit violated and penalty bits to be executed + // by the ActivityManagerService remaining set. + int violationMaskSubset = 0; + + if ((info.policy & PENALTY_DIALOG) != 0 && + timeSinceLastViolationMillis > MIN_DIALOG_INTERVAL_MS) { + violationMaskSubset |= PENALTY_DIALOG; + } + + if ((info.policy & PENALTY_DROPBOX) != 0 && lastViolationTime == 0) { + violationMaskSubset |= PENALTY_DROPBOX; + } + + if (violationMaskSubset != 0) { + int violationBit = parseViolationFromMessage(info.crashInfo.exceptionMessage); + violationMaskSubset |= violationBit; + final int savedPolicy = getThreadPolicy(); + try { + // First, remove any policy before we call into the Activity Manager, + // otherwise we'll infinite recurse as we try to log policy violations + // to disk, thus violating policy, thus requiring logging, etc... + // We restore the current policy below, in the finally block. + setThreadPolicy(0); + + ActivityManagerNative.getDefault().handleApplicationStrictModeViolation( + RuntimeInit.getApplicationObject(), + violationMaskSubset, + info); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException trying to handle StrictMode violation", e); + } finally { + // Restore the policy. + setThreadPolicy(savedPolicy); + } + } + + if ((info.policy & PENALTY_DEATH) != 0) { + System.err.println("StrictMode policy violation with POLICY_DEATH; shutting down."); + Process.killProcess(Process.myPid()); + System.exit(10); + } + } + } + + /** + * Called from Parcel.writeNoException() + */ + /* package */ static boolean hasGatheredViolations() { + return gatheredViolations.get() != null; + } + + /** + * Called from Parcel.writeException(), so we drop this memory and + * don't incorrectly attribute it to the wrong caller on the next + * Binder call on this thread. + */ + /* package */ static void clearGatheredViolations() { + gatheredViolations.set(null); + } + + /** + * Called from Parcel.writeNoException() + */ + /* package */ static void writeGatheredViolationsToParcel(Parcel p) { + ArrayList<ViolationInfo> violations = gatheredViolations.get(); + if (violations == null) { + p.writeInt(0); + } else { + p.writeInt(violations.size()); + for (int i = 0; i < violations.size(); ++i) { + violations.get(i).writeToParcel(p, 0 /* unused flags? */); + } + if (LOG_V) Log.d(TAG, "wrote violations to response parcel; num=" + violations.size()); + violations.clear(); // somewhat redundant, as we're about to null the threadlocal + } + gatheredViolations.set(null); + } + + private static class LogStackTrace extends Exception {} + + /** + * Called from Parcel.readException() when the exception is EX_STRICT_MODE_VIOLATIONS, + * we here read back all the encoded violations. + */ + /* package */ static void readAndHandleBinderCallViolations(Parcel p) { + // Our own stack trace to append + StringWriter sw = new StringWriter(); + new LogStackTrace().printStackTrace(new PrintWriter(sw)); + String ourStack = sw.toString(); + + int policyMask = getThreadPolicy(); + boolean currentlyGathering = (policyMask & PENALTY_GATHER) != 0; + + int numViolations = p.readInt(); + for (int i = 0; i < numViolations; ++i) { + if (LOG_V) Log.d(TAG, "strict mode violation stacks read from binder call. i=" + i); + ViolationInfo info = new ViolationInfo(p, !currentlyGathering); + info.crashInfo.stackTrace += "# via Binder call with stack:\n" + ourStack; + BlockGuard.Policy policy = BlockGuard.getThreadPolicy(); + if (policy instanceof AndroidBlockGuardPolicy) { + ((AndroidBlockGuardPolicy) policy).handleViolationWithTimingAttempt(info); + } + } + } + + /** + * Called from android_util_Binder.cpp's + * android_os_Parcel_enforceInterface when an incoming Binder call + * requires changing the StrictMode policy mask. The role of this + * function is to ask Binder for its current (native) thread-local + * policy value and synchronize it to libcore's (Java) + * thread-local policy value. + */ + private static void onBinderStrictModePolicyChange(int newPolicy) { + setBlockGuardPolicy(newPolicy); + } + + /** + * Parcelable that gets sent in Binder call headers back to callers + * to report violations that happened during a cross-process call. + * + * @hide + */ + public static class ViolationInfo { + /** + * Stack and other stuff info. + */ + public final ApplicationErrorReport.CrashInfo crashInfo; + + /** + * The strict mode policy mask at the time of violation. + */ + public final int policy; + + /** + * The wall time duration of the violation, when known. -1 when + * not known. + */ + public int durationMillis = -1; + + /** + * Which violation number this was (1-based) since the last Looper loop, + * from the perspective of the root caller (if it crossed any processes + * via Binder calls). The value is 0 if the root caller wasn't on a Looper + * thread. + */ + public int violationNumThisLoop; + + /** + * The time (in terms of SystemClock.uptimeMillis()) that the + * violation occurred. + */ + public long violationUptimeMillis; + + /** + * Create an uninitialized instance of ViolationInfo + */ + public ViolationInfo() { + crashInfo = null; + policy = 0; + } + + /** + * Create an instance of ViolationInfo initialized from an exception. + */ + public ViolationInfo(Throwable tr, int policy) { + crashInfo = new ApplicationErrorReport.CrashInfo(tr); + violationUptimeMillis = SystemClock.uptimeMillis(); + this.policy = policy; + } + + /** + * Create an instance of ViolationInfo initialized from a Parcel. + */ + public ViolationInfo(Parcel in) { + this(in, false); + } + + /** + * Create an instance of ViolationInfo initialized from a Parcel. + * + * @param unsetGatheringBit if true, the caller is the root caller + * and the gathering penalty should be removed. + */ + public ViolationInfo(Parcel in, boolean unsetGatheringBit) { + crashInfo = new ApplicationErrorReport.CrashInfo(in); + int rawPolicy = in.readInt(); + if (unsetGatheringBit) { + policy = rawPolicy & ~PENALTY_GATHER; + } else { + policy = rawPolicy; + } + durationMillis = in.readInt(); + violationNumThisLoop = in.readInt(); + violationUptimeMillis = in.readLong(); + } + + /** + * Save a ViolationInfo instance to a parcel. + */ + public void writeToParcel(Parcel dest, int flags) { + crashInfo.writeToParcel(dest, flags); + dest.writeInt(policy); + dest.writeInt(durationMillis); + dest.writeInt(violationNumThisLoop); + dest.writeLong(violationUptimeMillis); + } + + + /** + * Dump a ViolationInfo instance to a Printer. + */ + public void dump(Printer pw, String prefix) { + crashInfo.dump(pw, prefix); + pw.println(prefix + "policy: " + policy); + if (durationMillis != -1) { + pw.println(prefix + "durationMillis: " + durationMillis); + } + if (violationNumThisLoop != 0) { + pw.println(prefix + "violationNumThisLoop: " + violationNumThisLoop); + } + pw.println(prefix + "violationUptimeMillis: " + violationUptimeMillis); + } + + } +} diff --git a/core/java/android/os/WorkSource.aidl b/core/java/android/os/WorkSource.aidl new file mode 100644 index 000000000000..1e7fabcbd889 --- /dev/null +++ b/core/java/android/os/WorkSource.aidl @@ -0,0 +1,18 @@ +/* Copyright 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 android.os; + +parcelable WorkSource; diff --git a/core/java/android/os/WorkSource.java b/core/java/android/os/WorkSource.java new file mode 100644 index 000000000000..bba1984367d7 --- /dev/null +++ b/core/java/android/os/WorkSource.java @@ -0,0 +1,311 @@ +package android.os; + +/** + * Describes the source of some work that may be done by someone else. + * Currently the public representation of what a work source is is not + * defined; this is an opaque container. + */ +public class WorkSource implements Parcelable { + int mNum; + int[] mUids; + + /** + * Internal statics to avoid object allocations in some operations. + * The WorkSource object itself is not thread safe, but we need to + * hold sTmpWorkSource lock while working with these statics. + */ + static final WorkSource sTmpWorkSource = new WorkSource(0); + /** + * For returning newbie work from a modification operation. + */ + static WorkSource sNewbWork; + /** + * For returning gone work form a modification operation. + */ + static WorkSource sGoneWork; + + /** + * Create an empty work source. + */ + public WorkSource() { + mNum = 0; + } + + /** + * Create a new WorkSource that is a copy of an existing one. + * If <var>orig</var> is null, an empty WorkSource is created. + */ + public WorkSource(WorkSource orig) { + if (orig == null) { + mNum = 0; + return; + } + mNum = orig.mNum; + if (orig.mUids != null) { + mUids = orig.mUids.clone(); + } else { + mUids = null; + } + } + + /** @hide */ + public WorkSource(int uid) { + mNum = 1; + mUids = new int[] { uid, 0 }; + } + + WorkSource(Parcel in) { + mNum = in.readInt(); + mUids = in.createIntArray(); + } + + /** @hide */ + public int size() { + return mNum; + } + + /** @hide */ + public int get(int index) { + return mUids[index]; + } + + /** + * Clear this WorkSource to be empty. + */ + public void clear() { + mNum = 0; + } + + /** + * Compare this WorkSource with another. + * @param other The WorkSource to compare against. + * @return If there is a difference, true is returned. + */ + public boolean diff(WorkSource other) { + int N = mNum; + if (N != other.mNum) { + return true; + } + final int[] uids1 = mUids; + final int[] uids2 = other.mUids; + for (int i=0; i<N; i++) { + if (uids1[i] != uids2[i]) { + return true; + } + } + return false; + } + + /** + * Replace the current contents of this work source with the given + * work source. If <var>other</var> is null, the current work source + * will be made empty. + */ + public void set(WorkSource other) { + if (other == null) { + mNum = 0; + return; + } + mNum = other.mNum; + if (other.mUids != null) { + if (mUids != null && mUids.length >= mNum) { + System.arraycopy(other.mUids, 0, mUids, 0, mNum); + } else { + mUids = other.mUids.clone(); + } + } else { + mUids = null; + } + } + + /** @hide */ + public void set(int uid) { + mNum = 1; + if (mUids == null) mUids = new int[2]; + mUids[0] = uid; + } + + /** @hide */ + public WorkSource[] setReturningDiffs(WorkSource other) { + synchronized (sTmpWorkSource) { + sNewbWork = null; + sGoneWork = null; + updateLocked(other, true, true); + if (sNewbWork != null || sGoneWork != null) { + WorkSource[] diffs = new WorkSource[2]; + diffs[0] = sNewbWork; + diffs[1] = sGoneWork; + return diffs; + } + return null; + } + } + + /** + * Merge the contents of <var>other</var> WorkSource in to this one. + * + * @param other The other WorkSource whose contents are to be merged. + * @return Returns true if any new sources were added. + */ + public boolean add(WorkSource other) { + synchronized (sTmpWorkSource) { + return updateLocked(other, false, false); + } + } + + /** @hide */ + public WorkSource addReturningNewbs(WorkSource other) { + synchronized (sTmpWorkSource) { + sNewbWork = null; + updateLocked(other, false, true); + return sNewbWork; + } + } + + /** @hide */ + public boolean add(int uid) { + synchronized (sTmpWorkSource) { + sTmpWorkSource.mUids[0] = uid; + return updateLocked(sTmpWorkSource, false, false); + } + } + + /** @hide */ + public WorkSource addReturningNewbs(int uid) { + synchronized (sTmpWorkSource) { + sNewbWork = null; + sTmpWorkSource.mUids[0] = uid; + updateLocked(sTmpWorkSource, false, true); + return sNewbWork; + } + } + + public boolean remove(WorkSource other) { + int N1 = mNum; + final int[] uids1 = mUids; + final int N2 = other.mNum; + final int[] uids2 = other.mUids; + boolean changed = false; + int i1 = 0; + for (int i2=0; i2<N2 && i1<N1; i2++) { + if (uids2[i2] == uids1[i1]) { + N1--; + if (i1 < N1) System.arraycopy(uids1, i1, uids1, i1-1, N1-i1); + } + while (i1 < N1 && uids2[i2] > uids1[i1]) { + i1++; + } + } + + mNum = N1; + + return changed; + } + + private boolean updateLocked(WorkSource other, boolean set, boolean returnNewbs) { + int N1 = mNum; + int[] uids1 = mUids; + final int N2 = other.mNum; + final int[] uids2 = other.mUids; + boolean changed = false; + int i1 = 0; + for (int i2=0; i2<N2; i2++) { + if (i1 >= N1 || uids2[i2] < uids1[i1]) { + // Need to insert a new uid. + changed = true; + if (uids1 == null) { + uids1 = new int[4]; + uids1[0] = uids2[i2]; + } else if (i1 >= uids1.length) { + int[] newuids = new int[(uids1.length*3)/2]; + if (i1 > 0) System.arraycopy(uids1, 0, newuids, 0, i1); + if (i1 < N1) System.arraycopy(uids1, i1, newuids, i1+1, N1-i1); + uids1 = newuids; + uids1[i1] = uids2[i2]; + } else { + if (i1 < N1) System.arraycopy(uids1, i1, uids1, i1+1, N1-i1); + uids1[i1] = uids2[i2]; + } + if (returnNewbs) { + if (sNewbWork == null) { + sNewbWork = new WorkSource(uids2[i2]); + } else { + sNewbWork.addLocked(uids2[i2]); + } + } + N1++; + i1++; + } else { + if (!set) { + // Skip uids that already exist or are not in 'other'. + do { + i1++; + } while (i1 < N1 && uids2[i2] >= uids1[i1]); + } else { + // Remove any uids that don't exist in 'other'. + int start = i1; + while (i1 < N1 && uids2[i2] > uids1[i1]) { + if (sGoneWork == null) { + sGoneWork = new WorkSource(uids1[i1]); + } else { + sGoneWork.addLocked(uids1[i1]); + } + i1++; + } + if (start < i1) { + System.arraycopy(uids1, i1, uids1, start, i1-start); + N1 -= i1-start; + i1 = start; + } + // If there is a matching uid, skip it. + if (i1 < N1 && uids2[i1] == uids1[i1]) { + i1++; + } + } + } + } + + mNum = N1; + mUids = uids1; + + return changed; + } + + private void addLocked(int uid) { + if (mUids == null) { + mUids = new int[4]; + mUids[0] = uid; + mNum = 1; + return; + } + if (mNum >= mUids.length) { + int[] newuids = new int[(mNum*3)/2]; + System.arraycopy(mUids, 0, newuids, 0, mNum); + mUids = newuids; + } + + mUids[mNum] = uid; + mNum++; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mNum); + dest.writeIntArray(mUids); + } + + public static final Parcelable.Creator<WorkSource> CREATOR + = new Parcelable.Creator<WorkSource>() { + public WorkSource createFromParcel(Parcel in) { + return new WorkSource(in); + } + public WorkSource[] newArray(int size) { + return new WorkSource[size]; + } + }; +} diff --git a/core/java/android/os/storage/IMountService.aidl b/core/java/android/os/storage/IMountService.aidl deleted file mode 100644 index 4862f80d82c6..000000000000 --- a/core/java/android/os/storage/IMountService.aidl +++ /dev/null @@ -1,155 +0,0 @@ -/* //device/java/android/android/os/IUsb.aidl -** -** Copyright 2007, 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.os.storage; - -import android.os.storage.IMountServiceListener; -import android.os.storage.IMountShutdownObserver; - -/** WARNING! Update IMountService.h and IMountService.cpp if you change this file. - * In particular, the ordering of the methods below must match the - * _TRANSACTION enum in IMountService.cpp - * @hide - Applications should use android.os.storage.StorageManager to access - * storage functions. - */ -interface IMountService -{ - /** - * Registers an IMountServiceListener for receiving async - * notifications. - */ - void registerListener(IMountServiceListener listener); - - /** - * Unregisters an IMountServiceListener - */ - void unregisterListener(IMountServiceListener listener); - - /** - * Returns true if a USB mass storage host is connected - */ - boolean isUsbMassStorageConnected(); - - /** - * Enables / disables USB mass storage. - * The caller should check actual status of enabling/disabling - * USB mass storage via StorageEventListener. - */ - void setUsbMassStorageEnabled(boolean enable); - - /** - * Returns true if a USB mass storage host is enabled (media is shared) - */ - boolean isUsbMassStorageEnabled(); - - /** - * Mount external storage at given mount point. - * Returns an int consistent with MountServiceResultCode - */ - int mountVolume(String mountPoint); - - /** - * Safely unmount external storage at given mount point. - * The unmount is an asynchronous operation. Applications - * should register StorageEventListener for storage related - * status changes. - * - */ - void unmountVolume(String mountPoint, boolean force); - - /** - * Format external storage given a mount point. - * Returns an int consistent with MountServiceResultCode - */ - int formatVolume(String mountPoint); - - /** - * Returns an array of pids with open files on - * the specified path. - */ - int[] getStorageUsers(String path); - - /** - * Gets the state of a volume via its mountpoint. - */ - String getVolumeState(String mountPoint); - - /* - * Creates a secure container with the specified parameters. - * Returns an int consistent with MountServiceResultCode - */ - int createSecureContainer(String id, int sizeMb, String fstype, String key, int ownerUid); - - /* - * Finalize a container which has just been created and populated. - * After finalization, the container is immutable. - * Returns an int consistent with MountServiceResultCode - */ - int finalizeSecureContainer(String id); - - /* - * Destroy a secure container, and free up all resources associated with it. - * NOTE: Ensure all references are released prior to deleting. - * Returns an int consistent with MountServiceResultCode - */ - int destroySecureContainer(String id, boolean force); - - /* - * Mount a secure container with the specified key and owner UID. - * Returns an int consistent with MountServiceResultCode - */ - int mountSecureContainer(String id, String key, int ownerUid); - - /* - * Unount a secure container. - * Returns an int consistent with MountServiceResultCode - */ - int unmountSecureContainer(String id, boolean force); - - /* - * Returns true if the specified container is mounted - */ - boolean isSecureContainerMounted(String id); - - /* - * Rename an unmounted secure container. - * Returns an int consistent with MountServiceResultCode - */ - int renameSecureContainer(String oldId, String newId); - - /* - * Returns the filesystem path of a mounted secure container. - */ - String getSecureContainerPath(String id); - - /** - * Gets an Array of currently known secure container IDs - */ - String[] getSecureContainerList(); - - /** - * Shuts down the MountService and gracefully unmounts all external media. - * Invokes call back once the shutdown is complete. - */ - void shutdown(IMountShutdownObserver observer); - - /** - * Call into MountService by PackageManager to notify that its done - * processing the media status update request. - */ - void finishMediaUpdate(); -} diff --git a/core/java/android/os/storage/IMountService.java b/core/java/android/os/storage/IMountService.java new file mode 100644 index 000000000000..60ea95c459f7 --- /dev/null +++ b/core/java/android/os/storage/IMountService.java @@ -0,0 +1,1046 @@ +/* + * 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 android.os.storage; + +import android.os.Binder; +import android.os.IBinder; +import android.os.IInterface; +import android.os.Parcel; +import android.os.RemoteException; + +/** + * WARNING! Update IMountService.h and IMountService.cpp if you change this + * file. In particular, the ordering of the methods below must match the + * _TRANSACTION enum in IMountService.cpp + * + * @hide - Applications should use android.os.storage.StorageManager to access + * storage functions. + */ +public interface IMountService extends IInterface { + /** Local-side IPC implementation stub class. */ + public static abstract class Stub extends Binder implements IMountService { + private static class Proxy implements IMountService { + private IBinder mRemote; + + Proxy(IBinder remote) { + mRemote = remote; + } + + public IBinder asBinder() { + return mRemote; + } + + public String getInterfaceDescriptor() { + return DESCRIPTOR; + } + + /** + * Registers an IMountServiceListener for receiving async + * notifications. + */ + public void registerListener(IMountServiceListener listener) throws RemoteException { + Parcel _data = Parcel.obtain(); + Parcel _reply = Parcel.obtain(); + try { + _data.writeInterfaceToken(DESCRIPTOR); + _data.writeStrongBinder((listener != null ? listener.asBinder() : null)); + mRemote.transact(Stub.TRANSACTION_registerListener, _data, _reply, 0); + _reply.readException(); + } finally { + _reply.recycle(); + _data.recycle(); + } + } + + /** + * Unregisters an IMountServiceListener + */ + public void unregisterListener(IMountServiceListener listener) throws RemoteException { + Parcel _data = Parcel.obtain(); + Parcel _reply = Parcel.obtain(); + try { + _data.writeInterfaceToken(DESCRIPTOR); + _data.writeStrongBinder((listener != null ? listener.asBinder() : null)); + mRemote.transact(Stub.TRANSACTION_unregisterListener, _data, _reply, 0); + _reply.readException(); + } finally { + _reply.recycle(); + _data.recycle(); + } + } + + /** + * Returns true if a USB mass storage host is connected + */ + public boolean isUsbMassStorageConnected() throws RemoteException { + Parcel _data = Parcel.obtain(); + Parcel _reply = Parcel.obtain(); + boolean _result; + try { + _data.writeInterfaceToken(DESCRIPTOR); + mRemote.transact(Stub.TRANSACTION_isUsbMassStorageConnected, _data, _reply, 0); + _reply.readException(); + _result = 0 != _reply.readInt(); + } finally { + _reply.recycle(); + _data.recycle(); + } + return _result; + } + + /** + * Enables / disables USB mass storage. The caller should check + * actual status of enabling/disabling USB mass storage via + * StorageEventListener. + */ + public void setUsbMassStorageEnabled(boolean enable) throws RemoteException { + Parcel _data = Parcel.obtain(); + Parcel _reply = Parcel.obtain(); + try { + _data.writeInterfaceToken(DESCRIPTOR); + _data.writeInt((enable ? 1 : 0)); + mRemote.transact(Stub.TRANSACTION_setUsbMassStorageEnabled, _data, _reply, 0); + _reply.readException(); + } finally { + _reply.recycle(); + _data.recycle(); + } + } + + /** + * Returns true if a USB mass storage host is enabled (media is + * shared) + */ + public boolean isUsbMassStorageEnabled() throws RemoteException { + Parcel _data = Parcel.obtain(); + Parcel _reply = Parcel.obtain(); + boolean _result; + try { + _data.writeInterfaceToken(DESCRIPTOR); + mRemote.transact(Stub.TRANSACTION_isUsbMassStorageEnabled, _data, _reply, 0); + _reply.readException(); + _result = 0 != _reply.readInt(); + } finally { + _reply.recycle(); + _data.recycle(); + } + return _result; + } + + /** + * Mount external storage at given mount point. Returns an int + * consistent with MountServiceResultCode + */ + public int mountVolume(String mountPoint) throws RemoteException { + Parcel _data = Parcel.obtain(); + Parcel _reply = Parcel.obtain(); + int _result; + try { + _data.writeInterfaceToken(DESCRIPTOR); + _data.writeString(mountPoint); + mRemote.transact(Stub.TRANSACTION_mountVolume, _data, _reply, 0); + _reply.readException(); + _result = _reply.readInt(); + } finally { + _reply.recycle(); + _data.recycle(); + } + return _result; + } + + /** + * Safely unmount external storage at given mount point. The unmount + * is an asynchronous operation. Applications should register + * StorageEventListener for storage related status changes. + */ + public void unmountVolume(String mountPoint, boolean force) throws RemoteException { + Parcel _data = Parcel.obtain(); + Parcel _reply = Parcel.obtain(); + try { + _data.writeInterfaceToken(DESCRIPTOR); + _data.writeString(mountPoint); + _data.writeInt((force ? 1 : 0)); + mRemote.transact(Stub.TRANSACTION_unmountVolume, _data, _reply, 0); + _reply.readException(); + } finally { + _reply.recycle(); + _data.recycle(); + } + } + + /** + * Format external storage given a mount point. Returns an int + * consistent with MountServiceResultCode + */ + public int formatVolume(String mountPoint) throws RemoteException { + Parcel _data = Parcel.obtain(); + Parcel _reply = Parcel.obtain(); + int _result; + try { + _data.writeInterfaceToken(DESCRIPTOR); + _data.writeString(mountPoint); + mRemote.transact(Stub.TRANSACTION_formatVolume, _data, _reply, 0); + _reply.readException(); + _result = _reply.readInt(); + } finally { + _reply.recycle(); + _data.recycle(); + } + return _result; + } + + /** + * Returns an array of pids with open files on the specified path. + */ + public int[] getStorageUsers(String path) throws RemoteException { + Parcel _data = Parcel.obtain(); + Parcel _reply = Parcel.obtain(); + int[] _result; + try { + _data.writeInterfaceToken(DESCRIPTOR); + _data.writeString(path); + mRemote.transact(Stub.TRANSACTION_getStorageUsers, _data, _reply, 0); + _reply.readException(); + _result = _reply.createIntArray(); + } finally { + _reply.recycle(); + _data.recycle(); + } + return _result; + } + + /** + * Gets the state of a volume via its mountpoint. + */ + public String getVolumeState(String mountPoint) throws RemoteException { + Parcel _data = Parcel.obtain(); + Parcel _reply = Parcel.obtain(); + String _result; + try { + _data.writeInterfaceToken(DESCRIPTOR); + _data.writeString(mountPoint); + mRemote.transact(Stub.TRANSACTION_getVolumeState, _data, _reply, 0); + _reply.readException(); + _result = _reply.readString(); + } finally { + _reply.recycle(); + _data.recycle(); + } + return _result; + } + + /* + * Creates a secure container with the specified parameters. Returns + * an int consistent with MountServiceResultCode + */ + public int createSecureContainer(String id, int sizeMb, String fstype, String key, + int ownerUid) throws RemoteException { + Parcel _data = Parcel.obtain(); + Parcel _reply = Parcel.obtain(); + int _result; + try { + _data.writeInterfaceToken(DESCRIPTOR); + _data.writeString(id); + _data.writeInt(sizeMb); + _data.writeString(fstype); + _data.writeString(key); + _data.writeInt(ownerUid); + mRemote.transact(Stub.TRANSACTION_createSecureContainer, _data, _reply, 0); + _reply.readException(); + _result = _reply.readInt(); + } finally { + _reply.recycle(); + _data.recycle(); + } + return _result; + } + + /* + * Destroy a secure container, and free up all resources associated + * with it. NOTE: Ensure all references are released prior to + * deleting. Returns an int consistent with MountServiceResultCode + */ + public int destroySecureContainer(String id, boolean force) throws RemoteException { + Parcel _data = Parcel.obtain(); + Parcel _reply = Parcel.obtain(); + int _result; + try { + _data.writeInterfaceToken(DESCRIPTOR); + _data.writeString(id); + _data.writeInt((force ? 1 : 0)); + mRemote.transact(Stub.TRANSACTION_destroySecureContainer, _data, _reply, 0); + _reply.readException(); + _result = _reply.readInt(); + } finally { + _reply.recycle(); + _data.recycle(); + } + return _result; + } + + /* + * Finalize a container which has just been created and populated. + * After finalization, the container is immutable. Returns an int + * consistent with MountServiceResultCode + */ + public int finalizeSecureContainer(String id) throws RemoteException { + Parcel _data = Parcel.obtain(); + Parcel _reply = Parcel.obtain(); + int _result; + try { + _data.writeInterfaceToken(DESCRIPTOR); + _data.writeString(id); + mRemote.transact(Stub.TRANSACTION_finalizeSecureContainer, _data, _reply, 0); + _reply.readException(); + _result = _reply.readInt(); + } finally { + _reply.recycle(); + _data.recycle(); + } + return _result; + } + + /* + * Mount a secure container with the specified key and owner UID. + * Returns an int consistent with MountServiceResultCode + */ + public int mountSecureContainer(String id, String key, int ownerUid) + throws RemoteException { + Parcel _data = Parcel.obtain(); + Parcel _reply = Parcel.obtain(); + int _result; + try { + _data.writeInterfaceToken(DESCRIPTOR); + _data.writeString(id); + _data.writeString(key); + _data.writeInt(ownerUid); + mRemote.transact(Stub.TRANSACTION_mountSecureContainer, _data, _reply, 0); + _reply.readException(); + _result = _reply.readInt(); + } finally { + _reply.recycle(); + _data.recycle(); + } + return _result; + } + + /* + * Unount a secure container. Returns an int consistent with + * MountServiceResultCode + */ + public int unmountSecureContainer(String id, boolean force) throws RemoteException { + Parcel _data = Parcel.obtain(); + Parcel _reply = Parcel.obtain(); + int _result; + try { + _data.writeInterfaceToken(DESCRIPTOR); + _data.writeString(id); + _data.writeInt((force ? 1 : 0)); + mRemote.transact(Stub.TRANSACTION_unmountSecureContainer, _data, _reply, 0); + _reply.readException(); + _result = _reply.readInt(); + } finally { + _reply.recycle(); + _data.recycle(); + } + return _result; + } + + /* + * Returns true if the specified container is mounted + */ + public boolean isSecureContainerMounted(String id) throws RemoteException { + Parcel _data = Parcel.obtain(); + Parcel _reply = Parcel.obtain(); + boolean _result; + try { + _data.writeInterfaceToken(DESCRIPTOR); + _data.writeString(id); + mRemote.transact(Stub.TRANSACTION_isSecureContainerMounted, _data, _reply, 0); + _reply.readException(); + _result = 0 != _reply.readInt(); + } finally { + _reply.recycle(); + _data.recycle(); + } + return _result; + } + + /* + * Rename an unmounted secure container. Returns an int consistent + * with MountServiceResultCode + */ + public int renameSecureContainer(String oldId, String newId) throws RemoteException { + Parcel _data = Parcel.obtain(); + Parcel _reply = Parcel.obtain(); + int _result; + try { + _data.writeInterfaceToken(DESCRIPTOR); + _data.writeString(oldId); + _data.writeString(newId); + mRemote.transact(Stub.TRANSACTION_renameSecureContainer, _data, _reply, 0); + _reply.readException(); + _result = _reply.readInt(); + } finally { + _reply.recycle(); + _data.recycle(); + } + return _result; + } + + /* + * Returns the filesystem path of a mounted secure container. + */ + public String getSecureContainerPath(String id) throws RemoteException { + Parcel _data = Parcel.obtain(); + Parcel _reply = Parcel.obtain(); + String _result; + try { + _data.writeInterfaceToken(DESCRIPTOR); + _data.writeString(id); + mRemote.transact(Stub.TRANSACTION_getSecureContainerPath, _data, _reply, 0); + _reply.readException(); + _result = _reply.readString(); + } finally { + _reply.recycle(); + _data.recycle(); + } + return _result; + } + + /** + * Gets an Array of currently known secure container IDs + */ + public String[] getSecureContainerList() throws RemoteException { + Parcel _data = Parcel.obtain(); + Parcel _reply = Parcel.obtain(); + String[] _result; + try { + _data.writeInterfaceToken(DESCRIPTOR); + mRemote.transact(Stub.TRANSACTION_getSecureContainerList, _data, _reply, 0); + _reply.readException(); + _result = _reply.createStringArray(); + } finally { + _reply.recycle(); + _data.recycle(); + } + return _result; + } + + /** + * Shuts down the MountService and gracefully unmounts all external + * media. Invokes call back once the shutdown is complete. + */ + public void shutdown(IMountShutdownObserver observer) + throws RemoteException { + Parcel _data = Parcel.obtain(); + Parcel _reply = Parcel.obtain(); + try { + _data.writeInterfaceToken(DESCRIPTOR); + _data.writeStrongBinder((observer != null ? observer.asBinder() : null)); + mRemote.transact(Stub.TRANSACTION_shutdown, _data, _reply, 0); + _reply.readException(); + } finally { + _reply.recycle(); + _data.recycle(); + } + } + + /** + * Call into MountService by PackageManager to notify that its done + * processing the media status update request. + */ + public void finishMediaUpdate() throws RemoteException { + Parcel _data = Parcel.obtain(); + Parcel _reply = Parcel.obtain(); + try { + _data.writeInterfaceToken(DESCRIPTOR); + mRemote.transact(Stub.TRANSACTION_finishMediaUpdate, _data, _reply, 0); + _reply.readException(); + } finally { + _reply.recycle(); + _data.recycle(); + } + } + + /** + * Mounts an Opaque Binary Blob (OBB) with the specified decryption + * key and only allows the calling process's UID access to the + * contents. MountService will call back to the supplied + * IObbActionListener to inform it of the terminal state of the + * call. + */ + public void mountObb(String filename, String key, IObbActionListener token) + throws RemoteException { + Parcel _data = Parcel.obtain(); + Parcel _reply = Parcel.obtain(); + try { + _data.writeInterfaceToken(DESCRIPTOR); + _data.writeString(filename); + _data.writeString(key); + _data.writeStrongBinder((token != null ? token.asBinder() : null)); + mRemote.transact(Stub.TRANSACTION_mountObb, _data, _reply, 0); + _reply.readException(); + } finally { + _reply.recycle(); + _data.recycle(); + } + } + + /** + * Unmounts an Opaque Binary Blob (OBB). When the force flag is + * specified, any program using it will be forcibly killed to + * unmount the image. MountService will call back to the supplied + * IObbActionListener to inform it of the terminal state of the + * call. + */ + public void unmountObb(String filename, boolean force, IObbActionListener token) + throws RemoteException { + Parcel _data = Parcel.obtain(); + Parcel _reply = Parcel.obtain(); + try { + _data.writeInterfaceToken(DESCRIPTOR); + _data.writeString(filename); + _data.writeInt((force ? 1 : 0)); + _data.writeStrongBinder((token != null ? token.asBinder() : null)); + mRemote.transact(Stub.TRANSACTION_unmountObb, _data, _reply, 0); + _reply.readException(); + } finally { + _reply.recycle(); + _data.recycle(); + } + } + + /** + * Checks whether the specified Opaque Binary Blob (OBB) is mounted + * somewhere. + */ + public boolean isObbMounted(String filename) throws RemoteException { + Parcel _data = Parcel.obtain(); + Parcel _reply = Parcel.obtain(); + boolean _result; + try { + _data.writeInterfaceToken(DESCRIPTOR); + _data.writeString(filename); + mRemote.transact(Stub.TRANSACTION_isObbMounted, _data, _reply, 0); + _reply.readException(); + _result = 0 != _reply.readInt(); + } finally { + _reply.recycle(); + _data.recycle(); + } + return _result; + } + + /** + * Gets the path to the mounted Opaque Binary Blob (OBB). + */ + public String getMountedObbPath(String filename) throws RemoteException { + Parcel _data = Parcel.obtain(); + Parcel _reply = Parcel.obtain(); + String _result; + try { + _data.writeInterfaceToken(DESCRIPTOR); + _data.writeString(filename); + mRemote.transact(Stub.TRANSACTION_getMountedObbPath, _data, _reply, 0); + _reply.readException(); + _result = _reply.readString(); + } finally { + _reply.recycle(); + _data.recycle(); + } + return _result; + } + } + + private static final String DESCRIPTOR = "IMountService"; + + static final int TRANSACTION_registerListener = IBinder.FIRST_CALL_TRANSACTION + 0; + + static final int TRANSACTION_unregisterListener = IBinder.FIRST_CALL_TRANSACTION + 1; + + static final int TRANSACTION_isUsbMassStorageConnected = IBinder.FIRST_CALL_TRANSACTION + 2; + + static final int TRANSACTION_setUsbMassStorageEnabled = IBinder.FIRST_CALL_TRANSACTION + 3; + + static final int TRANSACTION_isUsbMassStorageEnabled = IBinder.FIRST_CALL_TRANSACTION + 4; + + static final int TRANSACTION_mountVolume = IBinder.FIRST_CALL_TRANSACTION + 5; + + static final int TRANSACTION_unmountVolume = IBinder.FIRST_CALL_TRANSACTION + 6; + + static final int TRANSACTION_formatVolume = IBinder.FIRST_CALL_TRANSACTION + 7; + + static final int TRANSACTION_getStorageUsers = IBinder.FIRST_CALL_TRANSACTION + 8; + + static final int TRANSACTION_getVolumeState = IBinder.FIRST_CALL_TRANSACTION + 9; + + static final int TRANSACTION_createSecureContainer = IBinder.FIRST_CALL_TRANSACTION + 10; + + static final int TRANSACTION_finalizeSecureContainer = IBinder.FIRST_CALL_TRANSACTION + 11; + + static final int TRANSACTION_destroySecureContainer = IBinder.FIRST_CALL_TRANSACTION + 12; + + static final int TRANSACTION_mountSecureContainer = IBinder.FIRST_CALL_TRANSACTION + 13; + + static final int TRANSACTION_unmountSecureContainer = IBinder.FIRST_CALL_TRANSACTION + 14; + + static final int TRANSACTION_isSecureContainerMounted = IBinder.FIRST_CALL_TRANSACTION + 15; + + static final int TRANSACTION_renameSecureContainer = IBinder.FIRST_CALL_TRANSACTION + 16; + + static final int TRANSACTION_getSecureContainerPath = IBinder.FIRST_CALL_TRANSACTION + 17; + + static final int TRANSACTION_getSecureContainerList = IBinder.FIRST_CALL_TRANSACTION + 18; + + static final int TRANSACTION_shutdown = IBinder.FIRST_CALL_TRANSACTION + 19; + + static final int TRANSACTION_finishMediaUpdate = IBinder.FIRST_CALL_TRANSACTION + 20; + + static final int TRANSACTION_mountObb = IBinder.FIRST_CALL_TRANSACTION + 21; + + static final int TRANSACTION_unmountObb = IBinder.FIRST_CALL_TRANSACTION + 22; + + static final int TRANSACTION_isObbMounted = IBinder.FIRST_CALL_TRANSACTION + 23; + + static final int TRANSACTION_getMountedObbPath = IBinder.FIRST_CALL_TRANSACTION + 24; + + /** + * Cast an IBinder object into an IMountService interface, generating a + * proxy if needed. + */ + public static IMountService asInterface(IBinder obj) { + if (obj == null) { + return null; + } + IInterface iin = obj.queryLocalInterface(DESCRIPTOR); + if (iin != null && iin instanceof IMountService) { + return (IMountService) iin; + } + return new IMountService.Stub.Proxy(obj); + } + + /** Construct the stub at attach it to the interface. */ + public Stub() { + attachInterface(this, DESCRIPTOR); + } + + public IBinder asBinder() { + return this; + } + + @Override + public boolean onTransact(int code, Parcel data, Parcel reply, + int flags) throws RemoteException { + switch (code) { + case INTERFACE_TRANSACTION: { + reply.writeString(DESCRIPTOR); + return true; + } + case TRANSACTION_registerListener: { + data.enforceInterface(DESCRIPTOR); + IMountServiceListener listener; + listener = IMountServiceListener.Stub.asInterface(data.readStrongBinder()); + registerListener(listener); + reply.writeNoException(); + return true; + } + case TRANSACTION_unregisterListener: { + data.enforceInterface(DESCRIPTOR); + IMountServiceListener listener; + listener = IMountServiceListener.Stub.asInterface(data.readStrongBinder()); + unregisterListener(listener); + reply.writeNoException(); + return true; + } + case TRANSACTION_isUsbMassStorageConnected: { + data.enforceInterface(DESCRIPTOR); + boolean result = isUsbMassStorageConnected(); + reply.writeNoException(); + reply.writeInt((result ? 1 : 0)); + return true; + } + case TRANSACTION_setUsbMassStorageEnabled: { + data.enforceInterface(DESCRIPTOR); + boolean enable; + enable = 0 != data.readInt(); + setUsbMassStorageEnabled(enable); + reply.writeNoException(); + return true; + } + case TRANSACTION_isUsbMassStorageEnabled: { + data.enforceInterface(DESCRIPTOR); + boolean result = isUsbMassStorageEnabled(); + reply.writeNoException(); + reply.writeInt((result ? 1 : 0)); + return true; + } + case TRANSACTION_mountVolume: { + data.enforceInterface(DESCRIPTOR); + String mountPoint; + mountPoint = data.readString(); + int resultCode = mountVolume(mountPoint); + reply.writeNoException(); + reply.writeInt(resultCode); + return true; + } + case TRANSACTION_unmountVolume: { + data.enforceInterface(DESCRIPTOR); + String mountPoint; + mountPoint = data.readString(); + boolean force; + force = 0 != data.readInt(); + unmountVolume(mountPoint, force); + reply.writeNoException(); + return true; + } + case TRANSACTION_formatVolume: { + data.enforceInterface(DESCRIPTOR); + String mountPoint; + mountPoint = data.readString(); + int result = formatVolume(mountPoint); + reply.writeNoException(); + reply.writeInt(result); + return true; + } + case TRANSACTION_getStorageUsers: { + data.enforceInterface(DESCRIPTOR); + String path; + path = data.readString(); + int[] pids = getStorageUsers(path); + reply.writeNoException(); + reply.writeIntArray(pids); + return true; + } + case TRANSACTION_getVolumeState: { + data.enforceInterface(DESCRIPTOR); + String mountPoint; + mountPoint = data.readString(); + String state = getVolumeState(mountPoint); + reply.writeNoException(); + reply.writeString(state); + return true; + } + case TRANSACTION_createSecureContainer: { + data.enforceInterface(DESCRIPTOR); + String id; + id = data.readString(); + int sizeMb; + sizeMb = data.readInt(); + String fstype; + fstype = data.readString(); + String key; + key = data.readString(); + int ownerUid; + ownerUid = data.readInt(); + int resultCode = createSecureContainer(id, sizeMb, fstype, key, ownerUid); + reply.writeNoException(); + reply.writeInt(resultCode); + return true; + } + case TRANSACTION_finalizeSecureContainer: { + data.enforceInterface(DESCRIPTOR); + String id; + id = data.readString(); + int resultCode = finalizeSecureContainer(id); + reply.writeNoException(); + reply.writeInt(resultCode); + return true; + } + case TRANSACTION_destroySecureContainer: { + data.enforceInterface(DESCRIPTOR); + String id; + id = data.readString(); + boolean force; + force = 0 != data.readInt(); + int resultCode = destroySecureContainer(id, force); + reply.writeNoException(); + reply.writeInt(resultCode); + return true; + } + case TRANSACTION_mountSecureContainer: { + data.enforceInterface(DESCRIPTOR); + String id; + id = data.readString(); + String key; + key = data.readString(); + int ownerUid; + ownerUid = data.readInt(); + int resultCode = mountSecureContainer(id, key, ownerUid); + reply.writeNoException(); + reply.writeInt(resultCode); + return true; + } + case TRANSACTION_unmountSecureContainer: { + data.enforceInterface(DESCRIPTOR); + String id; + id = data.readString(); + boolean force; + force = 0 != data.readInt(); + int resultCode = unmountSecureContainer(id, force); + reply.writeNoException(); + reply.writeInt(resultCode); + return true; + } + case TRANSACTION_isSecureContainerMounted: { + data.enforceInterface(DESCRIPTOR); + String id; + id = data.readString(); + boolean status = isSecureContainerMounted(id); + reply.writeNoException(); + reply.writeInt((status ? 1 : 0)); + return true; + } + case TRANSACTION_renameSecureContainer: { + data.enforceInterface(DESCRIPTOR); + String oldId; + oldId = data.readString(); + String newId; + newId = data.readString(); + int resultCode = renameSecureContainer(oldId, newId); + reply.writeNoException(); + reply.writeInt(resultCode); + return true; + } + case TRANSACTION_getSecureContainerPath: { + data.enforceInterface(DESCRIPTOR); + String id; + id = data.readString(); + String path = getSecureContainerPath(id); + reply.writeNoException(); + reply.writeString(path); + return true; + } + case TRANSACTION_getSecureContainerList: { + data.enforceInterface(DESCRIPTOR); + String[] ids = getSecureContainerList(); + reply.writeNoException(); + reply.writeStringArray(ids); + return true; + } + case TRANSACTION_shutdown: { + data.enforceInterface(DESCRIPTOR); + IMountShutdownObserver observer; + observer = IMountShutdownObserver.Stub.asInterface(data + .readStrongBinder()); + shutdown(observer); + reply.writeNoException(); + return true; + } + case TRANSACTION_finishMediaUpdate: { + data.enforceInterface(DESCRIPTOR); + finishMediaUpdate(); + reply.writeNoException(); + return true; + } + case TRANSACTION_mountObb: { + data.enforceInterface(DESCRIPTOR); + String filename; + filename = data.readString(); + String key; + key = data.readString(); + IObbActionListener observer; + observer = IObbActionListener.Stub.asInterface(data.readStrongBinder()); + mountObb(filename, key, observer); + reply.writeNoException(); + return true; + } + case TRANSACTION_unmountObb: { + data.enforceInterface(DESCRIPTOR); + String filename; + filename = data.readString(); + boolean force; + force = 0 != data.readInt(); + IObbActionListener observer; + observer = IObbActionListener.Stub.asInterface(data.readStrongBinder()); + unmountObb(filename, force, observer); + reply.writeNoException(); + return true; + } + case TRANSACTION_isObbMounted: { + data.enforceInterface(DESCRIPTOR); + String filename; + filename = data.readString(); + boolean status = isObbMounted(filename); + reply.writeNoException(); + reply.writeInt((status ? 1 : 0)); + return true; + } + case TRANSACTION_getMountedObbPath: { + data.enforceInterface(DESCRIPTOR); + String filename; + filename = data.readString(); + String mountedPath = getMountedObbPath(filename); + reply.writeNoException(); + reply.writeString(mountedPath); + return true; + } + } + return super.onTransact(code, data, reply, flags); + } + } + + /* + * Creates a secure container with the specified parameters. Returns an int + * consistent with MountServiceResultCode + */ + public int createSecureContainer(String id, int sizeMb, String fstype, String key, int ownerUid) + throws RemoteException; + + /* + * Destroy a secure container, and free up all resources associated with it. + * NOTE: Ensure all references are released prior to deleting. Returns an + * int consistent with MountServiceResultCode + */ + public int destroySecureContainer(String id, boolean force) throws RemoteException; + + /* + * Finalize a container which has just been created and populated. After + * finalization, the container is immutable. Returns an int consistent with + * MountServiceResultCode + */ + public int finalizeSecureContainer(String id) throws RemoteException; + + /** + * Call into MountService by PackageManager to notify that its done + * processing the media status update request. + */ + public void finishMediaUpdate() throws RemoteException; + + /** + * Format external storage given a mount point. Returns an int consistent + * with MountServiceResultCode + */ + public int formatVolume(String mountPoint) throws RemoteException; + + /** + * Gets the path to the mounted Opaque Binary Blob (OBB). + */ + public String getMountedObbPath(String filename) throws RemoteException; + + /** + * Gets an Array of currently known secure container IDs + */ + public String[] getSecureContainerList() throws RemoteException; + + /* + * Returns the filesystem path of a mounted secure container. + */ + public String getSecureContainerPath(String id) throws RemoteException; + + /** + * Returns an array of pids with open files on the specified path. + */ + public int[] getStorageUsers(String path) throws RemoteException; + + /** + * Gets the state of a volume via its mountpoint. + */ + public String getVolumeState(String mountPoint) throws RemoteException; + + /** + * Checks whether the specified Opaque Binary Blob (OBB) is mounted + * somewhere. + */ + public boolean isObbMounted(String filename) throws RemoteException; + + /* + * Returns true if the specified container is mounted + */ + public boolean isSecureContainerMounted(String id) throws RemoteException; + + /** + * Returns true if a USB mass storage host is connected + */ + public boolean isUsbMassStorageConnected() throws RemoteException; + + /** + * Returns true if a USB mass storage host is enabled (media is shared) + */ + public boolean isUsbMassStorageEnabled() throws RemoteException; + + /** + * Mounts an Opaque Binary Blob (OBB) with the specified decryption key and + * only allows the calling process's UID access to the contents. + * MountService will call back to the supplied IObbActionListener to inform + * it of the terminal state of the call. + */ + public void mountObb(String filename, String key, IObbActionListener token) + throws RemoteException; + + /* + * Mount a secure container with the specified key and owner UID. Returns an + * int consistent with MountServiceResultCode + */ + public int mountSecureContainer(String id, String key, int ownerUid) throws RemoteException; + + /** + * Mount external storage at given mount point. Returns an int consistent + * with MountServiceResultCode + */ + public int mountVolume(String mountPoint) throws RemoteException; + + /** + * Registers an IMountServiceListener for receiving async notifications. + */ + public void registerListener(IMountServiceListener listener) throws RemoteException; + + /* + * Rename an unmounted secure container. Returns an int consistent with + * MountServiceResultCode + */ + public int renameSecureContainer(String oldId, String newId) throws RemoteException; + + /** + * Enables / disables USB mass storage. The caller should check actual + * status of enabling/disabling USB mass storage via StorageEventListener. + */ + public void setUsbMassStorageEnabled(boolean enable) throws RemoteException; + + /** + * Shuts down the MountService and gracefully unmounts all external media. + * Invokes call back once the shutdown is complete. + */ + public void shutdown(IMountShutdownObserver observer) throws RemoteException; + + /** + * Unmounts an Opaque Binary Blob (OBB). When the force flag is specified, + * any program using it will be forcibly killed to unmount the image. + * MountService will call back to the supplied IObbActionListener to inform + * it of the terminal state of the call. + */ + public void unmountObb(String filename, boolean force, IObbActionListener token) + throws RemoteException; + + /* + * Unount a secure container. Returns an int consistent with + * MountServiceResultCode + */ + public int unmountSecureContainer(String id, boolean force) throws RemoteException; + + /** + * Safely unmount external storage at given mount point. The unmount is an + * asynchronous operation. Applications should register StorageEventListener + * for storage related status changes. + */ + public void unmountVolume(String mountPoint, boolean force) throws RemoteException; + + /** + * Unregisters an IMountServiceListener + */ + public void unregisterListener(IMountServiceListener listener) throws RemoteException; +} diff --git a/core/java/android/os/storage/IMountServiceListener.aidl b/core/java/android/os/storage/IMountServiceListener.aidl deleted file mode 100644 index 883413ad103e..000000000000 --- a/core/java/android/os/storage/IMountServiceListener.aidl +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2009 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.os.storage; - -/** - * Callback class for receiving events from MountService. - * - * @hide - Applications should use android.os.storage.IStorageEventListener - * for storage event callbacks. - */ -interface IMountServiceListener { - /** - * Detection state of USB Mass Storage has changed - * - * @param available true if a UMS host is connected. - */ - void onUsbMassStorageConnectionChanged(boolean connected); - - /** - * Storage state has changed. - * - * @param path The volume mount path. - * @param oldState The old state of the volume. - * @param newState The new state of the volume. - * - * Note: State is one of the values returned by Environment.getExternalStorageState() - */ - void onStorageStateChanged(String path, String oldState, String newState); -} diff --git a/core/java/android/os/storage/IMountServiceListener.java b/core/java/android/os/storage/IMountServiceListener.java new file mode 100644 index 000000000000..d5c5fa579802 --- /dev/null +++ b/core/java/android/os/storage/IMountServiceListener.java @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2009 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.os.storage; + +import android.os.Binder; +import android.os.IBinder; +import android.os.IInterface; +import android.os.Parcel; +import android.os.RemoteException; + +/** + * Callback class for receiving events from MountService. + * + * @hide - Applications should use IStorageEventListener for storage event + * callbacks. + */ +public interface IMountServiceListener extends IInterface { + /** Local-side IPC implementation stub class. */ + public static abstract class Stub extends Binder implements IMountServiceListener { + private static final String DESCRIPTOR = "IMountServiceListener"; + + /** Construct the stub at attach it to the interface. */ + public Stub() { + this.attachInterface(this, DESCRIPTOR); + } + + /** + * Cast an IBinder object into an IMountServiceListener interface, + * generating a proxy if needed. + */ + public static IMountServiceListener asInterface(IBinder obj) { + if ((obj == null)) { + return null; + } + IInterface iin = (IInterface) obj.queryLocalInterface(DESCRIPTOR); + if (((iin != null) && (iin instanceof IMountServiceListener))) { + return ((IMountServiceListener) iin); + } + return new IMountServiceListener.Stub.Proxy(obj); + } + + public IBinder asBinder() { + return this; + } + + @Override + public boolean onTransact(int code, Parcel data, Parcel reply, int flags) + throws RemoteException { + switch (code) { + case INTERFACE_TRANSACTION: { + reply.writeString(DESCRIPTOR); + return true; + } + case TRANSACTION_onUsbMassStorageConnectionChanged: { + data.enforceInterface(DESCRIPTOR); + boolean connected; + connected = (0 != data.readInt()); + this.onUsbMassStorageConnectionChanged(connected); + reply.writeNoException(); + return true; + } + case TRANSACTION_onStorageStateChanged: { + data.enforceInterface(DESCRIPTOR); + String path; + path = data.readString(); + String oldState; + oldState = data.readString(); + String newState; + newState = data.readString(); + this.onStorageStateChanged(path, oldState, newState); + reply.writeNoException(); + return true; + } + } + return super.onTransact(code, data, reply, flags); + } + + private static class Proxy implements IMountServiceListener { + private IBinder mRemote; + + Proxy(IBinder remote) { + mRemote = remote; + } + + public IBinder asBinder() { + return mRemote; + } + + public String getInterfaceDescriptor() { + return DESCRIPTOR; + } + + /** + * Detection state of USB Mass Storage has changed + * + * @param available true if a UMS host is connected. + */ + public void onUsbMassStorageConnectionChanged(boolean connected) throws RemoteException { + Parcel _data = Parcel.obtain(); + Parcel _reply = Parcel.obtain(); + try { + _data.writeInterfaceToken(DESCRIPTOR); + _data.writeInt(((connected) ? (1) : (0))); + mRemote.transact(Stub.TRANSACTION_onUsbMassStorageConnectionChanged, _data, + _reply, 0); + _reply.readException(); + } finally { + _reply.recycle(); + _data.recycle(); + } + } + + /** + * Storage state has changed. + * + * @param path The volume mount path. + * @param oldState The old state of the volume. + * @param newState The new state of the volume. Note: State is one + * of the values returned by + * Environment.getExternalStorageState() + */ + public void onStorageStateChanged(String path, String oldState, String newState) + throws RemoteException { + Parcel _data = Parcel.obtain(); + Parcel _reply = Parcel.obtain(); + try { + _data.writeInterfaceToken(DESCRIPTOR); + _data.writeString(path); + _data.writeString(oldState); + _data.writeString(newState); + mRemote.transact(Stub.TRANSACTION_onStorageStateChanged, _data, _reply, 0); + _reply.readException(); + } finally { + _reply.recycle(); + _data.recycle(); + } + } + } + + static final int TRANSACTION_onUsbMassStorageConnectionChanged = (IBinder.FIRST_CALL_TRANSACTION + 0); + + static final int TRANSACTION_onStorageStateChanged = (IBinder.FIRST_CALL_TRANSACTION + 1); + } + + /** + * Detection state of USB Mass Storage has changed + * + * @param available true if a UMS host is connected. + */ + public void onUsbMassStorageConnectionChanged(boolean connected) throws RemoteException; + + /** + * Storage state has changed. + * + * @param path The volume mount path. + * @param oldState The old state of the volume. + * @param newState The new state of the volume. Note: State is one of the + * values returned by Environment.getExternalStorageState() + */ + public void onStorageStateChanged(String path, String oldState, String newState) + throws RemoteException; +} diff --git a/core/java/android/os/storage/IMountShutdownObserver.java b/core/java/android/os/storage/IMountShutdownObserver.java new file mode 100644 index 000000000000..d946e1a7cba5 --- /dev/null +++ b/core/java/android/os/storage/IMountShutdownObserver.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2009 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.os.storage; + +import android.os.Binder; +import android.os.IBinder; +import android.os.IInterface; +import android.os.Parcel; +import android.os.RemoteException; + +/** + * Callback class for receiving events related to shutdown. + * + * @hide - For internal consumption only. + */ +public interface IMountShutdownObserver extends IInterface { + /** Local-side IPC implementation stub class. */ + public static abstract class Stub extends Binder implements IMountShutdownObserver { + private static final java.lang.String DESCRIPTOR = "IMountShutdownObserver"; + + /** Construct the stub at attach it to the interface. */ + public Stub() { + this.attachInterface(this, DESCRIPTOR); + } + + /** + * Cast an IBinder object into an IMountShutdownObserver interface, + * generating a proxy if needed. + */ + public static IMountShutdownObserver asInterface(IBinder obj) { + if ((obj == null)) { + return null; + } + IInterface iin = (IInterface) obj.queryLocalInterface(DESCRIPTOR); + if (((iin != null) && (iin instanceof IMountShutdownObserver))) { + return ((IMountShutdownObserver) iin); + } + return new IMountShutdownObserver.Stub.Proxy(obj); + } + + public IBinder asBinder() { + return this; + } + + @Override + public boolean onTransact(int code, Parcel data, Parcel reply, int flags) + throws RemoteException { + switch (code) { + case INTERFACE_TRANSACTION: { + reply.writeString(DESCRIPTOR); + return true; + } + case TRANSACTION_onShutDownComplete: { + data.enforceInterface(DESCRIPTOR); + int statusCode; + statusCode = data.readInt(); + this.onShutDownComplete(statusCode); + reply.writeNoException(); + return true; + } + } + return super.onTransact(code, data, reply, flags); + } + + private static class Proxy implements IMountShutdownObserver { + private IBinder mRemote; + + Proxy(IBinder remote) { + mRemote = remote; + } + + public IBinder asBinder() { + return mRemote; + } + + public java.lang.String getInterfaceDescriptor() { + return DESCRIPTOR; + } + + /** + * This method is called when the shutdown of MountService + * completed. + * + * @param statusCode indicates success or failure of the shutdown. + */ + public void onShutDownComplete(int statusCode) throws RemoteException { + Parcel _data = Parcel.obtain(); + Parcel _reply = Parcel.obtain(); + try { + _data.writeInterfaceToken(DESCRIPTOR); + _data.writeInt(statusCode); + mRemote.transact(Stub.TRANSACTION_onShutDownComplete, _data, _reply, 0); + _reply.readException(); + } finally { + _reply.recycle(); + _data.recycle(); + } + } + } + + static final int TRANSACTION_onShutDownComplete = (IBinder.FIRST_CALL_TRANSACTION + 0); + } + + /** + * This method is called when the shutdown of MountService completed. + * + * @param statusCode indicates success or failure of the shutdown. + */ + public void onShutDownComplete(int statusCode) throws RemoteException; +} diff --git a/core/java/android/os/storage/IObbActionListener.java b/core/java/android/os/storage/IObbActionListener.java new file mode 100644 index 000000000000..2c098ac6c911 --- /dev/null +++ b/core/java/android/os/storage/IObbActionListener.java @@ -0,0 +1,130 @@ +/* + * 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 android.os.storage; + +import android.os.Binder; +import android.os.IBinder; +import android.os.IInterface; +import android.os.Parcel; +import android.os.RemoteException; + +/** + * Callback class for receiving events from MountService about Opaque Binary + * Blobs (OBBs). + * + * @hide - Applications should use StorageManager to interact with OBBs. + */ +public interface IObbActionListener extends IInterface { + /** Local-side IPC implementation stub class. */ + public static abstract class Stub extends Binder implements IObbActionListener { + private static final String DESCRIPTOR = "IObbActionListener"; + + /** Construct the stub at attach it to the interface. */ + public Stub() { + this.attachInterface(this, DESCRIPTOR); + } + + /** + * Cast an IBinder object into an IObbActionListener interface, + * generating a proxy if needed. + */ + public static IObbActionListener asInterface(IBinder obj) { + if ((obj == null)) { + return null; + } + IInterface iin = (IInterface) obj.queryLocalInterface(DESCRIPTOR); + if (((iin != null) && (iin instanceof IObbActionListener))) { + return ((IObbActionListener) iin); + } + return new IObbActionListener.Stub.Proxy(obj); + } + + public IBinder asBinder() { + return this; + } + + @Override + public boolean onTransact(int code, Parcel data, Parcel reply, int flags) + throws RemoteException { + switch (code) { + case INTERFACE_TRANSACTION: { + reply.writeString(DESCRIPTOR); + return true; + } + case TRANSACTION_onObbResult: { + data.enforceInterface(DESCRIPTOR); + String filename; + filename = data.readString(); + String status; + status = data.readString(); + this.onObbResult(filename, status); + reply.writeNoException(); + return true; + } + } + return super.onTransact(code, data, reply, flags); + } + + private static class Proxy implements IObbActionListener { + private IBinder mRemote; + + Proxy(IBinder remote) { + mRemote = remote; + } + + public IBinder asBinder() { + return mRemote; + } + + public String getInterfaceDescriptor() { + return DESCRIPTOR; + } + + /** + * Return from an OBB action result. + * + * @param filename the path to the OBB the operation was performed + * on + * @param returnCode status of the operation + */ + public void onObbResult(String filename, String status) throws RemoteException { + Parcel _data = Parcel.obtain(); + Parcel _reply = Parcel.obtain(); + try { + _data.writeInterfaceToken(DESCRIPTOR); + _data.writeString(filename); + _data.writeString(status); + mRemote.transact(Stub.TRANSACTION_onObbResult, _data, _reply, 0); + _reply.readException(); + } finally { + _reply.recycle(); + _data.recycle(); + } + } + } + + static final int TRANSACTION_onObbResult = (IBinder.FIRST_CALL_TRANSACTION + 0); + } + + /** + * Return from an OBB action result. + * + * @param filename the path to the OBB the operation was performed on + * @param returnCode status of the operation + */ + public void onObbResult(String filename, String status) throws RemoteException; +} diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index a12603ce42ee..df0b69c9fe24 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -16,37 +16,20 @@ package android.os.storage; -import android.content.Context; -import android.os.Binder; -import android.os.Bundle; -import android.os.Looper; -import android.os.Parcelable; -import android.os.ParcelFileDescriptor; -import android.os.Process; -import android.os.RemoteException; import android.os.Handler; +import android.os.Looper; import android.os.Message; +import android.os.RemoteException; import android.os.ServiceManager; -import android.os.storage.IMountService; -import android.os.storage.IMountServiceListener; import android.util.Log; -import android.util.SparseArray; -import java.io.FileDescriptor; -import java.io.IOException; import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; /** * StorageManager is the interface to the systems storage service. * Get an instance of this class by calling * {@link android.content.Context#getSystemService(java.lang.String)} with an argument * of {@link android.content.Context#STORAGE_SERVICE}. - * - * @hide - * */ public class StorageManager @@ -90,6 +73,17 @@ public class StorageManager } /** + * Binder listener for OBB action results. + */ + private final ObbActionBinderListener mObbActionListener = new ObbActionBinderListener(); + private class ObbActionBinderListener extends IObbActionListener.Stub { + @Override + public void onObbResult(String filename, String status) throws RemoteException { + Log.i(TAG, "filename = " + filename + ", result = " + status); + } + } + + /** * Private base class for messages sent between the callback thread * and the target looper handler. */ @@ -209,6 +203,7 @@ public class StorageManager * * @param listener A {@link android.os.storage.StorageEventListener StorageEventListener} object. * + * @hide */ public void registerListener(StorageEventListener listener) { if (listener == null) { @@ -225,6 +220,7 @@ public class StorageManager * * @param listener A {@link android.os.storage.StorageEventListener StorageEventListener} object. * + * @hide */ public void unregisterListener(StorageEventListener listener) { if (listener == null) { @@ -245,6 +241,8 @@ public class StorageManager /** * Enables USB Mass Storage (UMS) on the device. + * + * @hide */ public void enableUsbMassStorage() { try { @@ -256,6 +254,8 @@ public class StorageManager /** * Disables USB Mass Storage (UMS) on the device. + * + * @hide */ public void disableUsbMassStorage() { try { @@ -268,6 +268,8 @@ public class StorageManager /** * Query if a USB Mass Storage (UMS) host is connected. * @return true if UMS host is connected. + * + * @hide */ public boolean isUsbMassStorageConnected() { try { @@ -281,6 +283,8 @@ public class StorageManager /** * Query if a USB Mass Storage (UMS) is enabled on the device. * @return true if UMS host is enabled. + * + * @hide */ public boolean isUsbMassStorageEnabled() { try { @@ -290,4 +294,96 @@ public class StorageManager } return false; } + + /** + * Mount an Opaque Binary Blob (OBB) file. If a <code>key</code> is + * specified, it is supplied to the mounting process to be used in any + * encryption used in the OBB. + * <p> + * <em>Note:</em> you can only mount OBB files for which the OBB tag on the + * file matches a package ID that is owned by the calling program's UID. + * That is, shared UID applications can obtain access to any other + * application's OBB that shares its UID. + * <p> + * STOPSHIP document more; discuss lack of guarantees of security + * + * @param filename the path to the OBB file + * @param key decryption key + * @return whether the mount call was successfully queued or not + */ + public boolean mountObb(String filename, String key) { + try { + mMountService.mountObb(filename, key, mObbActionListener); + return true; + } catch (RemoteException e) { + Log.e(TAG, "Failed to mount OBB", e); + } + + return false; + } + + /** + * Unmount an Opaque Binary Blob (OBB) file. If the <code>force</code> flag + * is true, it will kill any application needed to unmount the given OBB. + * <p> + * <em>Note:</em> you can only mount OBB files for which the OBB tag on the + * file matches a package ID that is owned by the calling program's UID. + * That is, shared UID applications can obtain access to any other + * application's OBB that shares its UID. + * <p> + * STOPSHIP document more; discuss lack of guarantees of security + * + * @param filename path to the OBB file + * @param force whether to kill any programs using this in order to unmount + * it + * @return whether the unmount call was successfully queued or not + * @throws IllegalArgumentException when OBB is not already mounted + */ + public boolean unmountObb(String filename, boolean force) throws IllegalArgumentException { + try { + mMountService.unmountObb(filename, force, mObbActionListener); + return true; + } catch (RemoteException e) { + Log.e(TAG, "Failed to mount OBB", e); + } + + return false; + } + + /** + * Check whether an Opaque Binary Blob (OBB) is mounted or not. + * + * @param filename path to OBB image + * @return true if OBB is mounted; false if not mounted or on error + */ + public boolean isObbMounted(String filename) throws IllegalArgumentException { + try { + return mMountService.isObbMounted(filename); + } catch (RemoteException e) { + Log.e(TAG, "Failed to check if OBB is mounted", e); + } + + return false; + } + + /** + * Check the mounted path of an Opaque Binary Blob (OBB) file. This will + * give you the path to where you can obtain access to the internals of the + * OBB. + * + * @param filename path to OBB image + * @return absolute path to mounted OBB image data or <code>null</code> if + * not mounted or exception encountered trying to read status + */ + public String getMountedObbPath(String filename) { + try { + return mMountService.getMountedObbPath(filename); + } catch (RemoteException e) { + Log.e(TAG, "Failed to find mounted path for OBB", e); + } catch (IllegalArgumentException e) { + Log.d(TAG, "Couldn't read OBB file", e); + } + + return null; + } } diff --git a/core/java/android/pim/vcard/JapaneseUtils.java b/core/java/android/pim/vcard/JapaneseUtils.java index 875c29ef1b11..dcfe980bfcaf 100644 --- a/core/java/android/pim/vcard/JapaneseUtils.java +++ b/core/java/android/pim/vcard/JapaneseUtils.java @@ -27,7 +27,6 @@ import java.util.Map; new HashMap<Character, String>(); static { - // There's no logical mapping rule in Unicode. Sigh. sHalfWidthMap.put('\u3001', "\uFF64"); sHalfWidthMap.put('\u3002', "\uFF61"); sHalfWidthMap.put('\u300C', "\uFF62"); @@ -366,11 +365,11 @@ import java.util.Map; } /** - * Return half-width version of that character if possible. Return null if not possible + * Returns half-width version of that character if possible. Returns null if not possible * @param ch input character * @return CharSequence object if the mapping for ch exists. Return null otherwise. */ - public static String tryGetHalfWidthText(char ch) { + public static String tryGetHalfWidthText(final char ch) { if (sHalfWidthMap.containsKey(ch)) { return sHalfWidthMap.get(ch); } else { diff --git a/core/java/android/pim/vcard/VCardBuilder.java b/core/java/android/pim/vcard/VCardBuilder.java index 0a6415dd200c..b2007f287836 100644 --- a/core/java/android/pim/vcard/VCardBuilder.java +++ b/core/java/android/pim/vcard/VCardBuilder.java @@ -30,11 +30,10 @@ import android.provider.ContactsContract.CommonDataKinds.StructuredPostal; import android.provider.ContactsContract.CommonDataKinds.Website; import android.telephony.PhoneNumberUtils; import android.text.TextUtils; +import android.util.Base64; import android.util.CharsetUtils; import android.util.Log; -import org.apache.commons.codec.binary.Base64; - import java.io.UnsupportedEncodingException; import java.nio.charset.UnsupportedCharsetException; import java.util.ArrayList; @@ -47,7 +46,23 @@ import java.util.Map; import java.util.Set; /** - * The class which lets users create their own vCard String. + * <p> + * The class which lets users create their own vCard String. Typical usage is as follows: + * </p> + * <pre class="prettyprint">final VCardBuilder builder = new VCardBuilder(vcardType); + * builder.appendNameProperties(contentValuesListMap.get(StructuredName.CONTENT_ITEM_TYPE)) + * .appendNickNames(contentValuesListMap.get(Nickname.CONTENT_ITEM_TYPE)) + * .appendPhones(contentValuesListMap.get(Phone.CONTENT_ITEM_TYPE)) + * .appendEmails(contentValuesListMap.get(Email.CONTENT_ITEM_TYPE)) + * .appendPostals(contentValuesListMap.get(StructuredPostal.CONTENT_ITEM_TYPE)) + * .appendOrganizations(contentValuesListMap.get(Organization.CONTENT_ITEM_TYPE)) + * .appendWebsites(contentValuesListMap.get(Website.CONTENT_ITEM_TYPE)) + * .appendPhotos(contentValuesListMap.get(Photo.CONTENT_ITEM_TYPE)) + * .appendNotes(contentValuesListMap.get(Note.CONTENT_ITEM_TYPE)) + * .appendEvents(contentValuesListMap.get(Event.CONTENT_ITEM_TYPE)) + * .appendIms(contentValuesListMap.get(Im.CONTENT_ITEM_TYPE)) + * .appendRelation(contentValuesListMap.get(Relation.CONTENT_ITEM_TYPE)); + * return builder.toString();</pre> */ public class VCardBuilder { private static final String LOG_TAG = "VCardBuilder"; @@ -75,81 +90,129 @@ public class VCardBuilder { private static final String VCARD_WS = " "; private static final String VCARD_PARAM_EQUAL = "="; - private static final String VCARD_PARAM_ENCODING_QP = "ENCODING=QUOTED-PRINTABLE"; - - private static final String VCARD_PARAM_ENCODING_BASE64_V21 = "ENCODING=BASE64"; - private static final String VCARD_PARAM_ENCODING_BASE64_V30 = "ENCODING=b"; + private static final String VCARD_PARAM_ENCODING_QP = + "ENCODING=" + VCardConstants.PARAM_ENCODING_QP; + private static final String VCARD_PARAM_ENCODING_BASE64_V21 = + "ENCODING=" + VCardConstants.PARAM_ENCODING_BASE64; + private static final String VCARD_PARAM_ENCODING_BASE64_AS_B = + "ENCODING=" + VCardConstants.PARAM_ENCODING_B; private static final String SHIFT_JIS = "SHIFT_JIS"; - private static final String UTF_8 = "UTF-8"; private final int mVCardType; - private final boolean mIsV30; + private final boolean mIsV30OrV40; private final boolean mIsJapaneseMobilePhone; private final boolean mOnlyOneNoteFieldIsAvailable; private final boolean mIsDoCoMo; private final boolean mShouldUseQuotedPrintable; private final boolean mUsesAndroidProperty; private final boolean mUsesDefactProperty; - private final boolean mUsesUtf8; - private final boolean mUsesShiftJis; private final boolean mAppendTypeParamName; private final boolean mRefrainsQPToNameProperties; private final boolean mNeedsToConvertPhoneticString; private final boolean mShouldAppendCharsetParam; - private final String mCharsetString; + private final String mCharset; private final String mVCardCharsetParameter; private StringBuilder mBuilder; private boolean mEndAppended; public VCardBuilder(final int vcardType) { + // Default charset should be used + this(vcardType, null); + } + + /** + * @param vcardType + * @param charset If null, we use default charset for export. + * @hide + */ + public VCardBuilder(final int vcardType, String charset) { mVCardType = vcardType; - mIsV30 = VCardConfig.isV30(vcardType); + Log.w(LOG_TAG, + "Should not use vCard 4.0 when building vCard. " + + "It is not officially published yet."); + + mIsV30OrV40 = VCardConfig.isVersion30(vcardType) || VCardConfig.isVersion40(vcardType); mShouldUseQuotedPrintable = VCardConfig.shouldUseQuotedPrintable(vcardType); mIsDoCoMo = VCardConfig.isDoCoMo(vcardType); mIsJapaneseMobilePhone = VCardConfig.needsToConvertPhoneticString(vcardType); mOnlyOneNoteFieldIsAvailable = VCardConfig.onlyOneNoteFieldIsAvailable(vcardType); mUsesAndroidProperty = VCardConfig.usesAndroidSpecificProperty(vcardType); mUsesDefactProperty = VCardConfig.usesDefactProperty(vcardType); - mUsesUtf8 = VCardConfig.usesUtf8(vcardType); - mUsesShiftJis = VCardConfig.usesShiftJis(vcardType); mRefrainsQPToNameProperties = VCardConfig.shouldRefrainQPToNameProperties(vcardType); mAppendTypeParamName = VCardConfig.appendTypeParamName(vcardType); mNeedsToConvertPhoneticString = VCardConfig.needsToConvertPhoneticString(vcardType); - mShouldAppendCharsetParam = !(mIsV30 && mUsesUtf8); - - if (mIsDoCoMo) { - String charset; - try { - charset = CharsetUtils.charsetForVendor(SHIFT_JIS, "docomo").name(); - } catch (UnsupportedCharsetException e) { - Log.e(LOG_TAG, "DoCoMo-specific SHIFT_JIS was not found. Use SHIFT_JIS as is."); - charset = SHIFT_JIS; - } - mCharsetString = charset; - // Do not use mCharsetString bellow since it is different from "SHIFT_JIS" but - // may be "DOCOMO_SHIFT_JIS" or something like that (internal expression used in - // Android, not shown to the public). - mVCardCharsetParameter = "CHARSET=" + SHIFT_JIS; - } else if (mUsesShiftJis) { - String charset; - try { - charset = CharsetUtils.charsetForVendor(SHIFT_JIS).name(); - } catch (UnsupportedCharsetException e) { - Log.e(LOG_TAG, "Vendor-specific SHIFT_JIS was not found. Use SHIFT_JIS as is."); - charset = SHIFT_JIS; - } - mCharsetString = charset; + // vCard 2.1 requires charset. + // vCard 3.0 does not allow it but we found some devices use it to determine + // the exact charset. + // We currently append it only when charset other than UTF_8 is used. + mShouldAppendCharsetParam = + !(VCardConfig.isVersion30(vcardType) && "UTF-8".equalsIgnoreCase(charset)); + + if (VCardConfig.isDoCoMo(vcardType)) { + if (!SHIFT_JIS.equalsIgnoreCase(charset)) { + Log.w(LOG_TAG, + "The charset \"" + charset + "\" is used while " + + SHIFT_JIS + " is needed to be used."); + if (TextUtils.isEmpty(charset)) { + mCharset = SHIFT_JIS; + } else { + try { + charset = CharsetUtils.charsetForVendor(charset).name(); + } catch (UnsupportedCharsetException e) { + Log.i(LOG_TAG, + "Career-specific \"" + charset + "\" was not found (as usual). " + + "Use it as is."); + } + mCharset = charset; + } + } else { + if (mIsDoCoMo) { + try { + charset = CharsetUtils.charsetForVendor(SHIFT_JIS, "docomo").name(); + } catch (UnsupportedCharsetException e) { + Log.e(LOG_TAG, + "DoCoMo-specific SHIFT_JIS was not found. " + + "Use SHIFT_JIS as is."); + charset = SHIFT_JIS; + } + } else { + try { + charset = CharsetUtils.charsetForVendor(SHIFT_JIS).name(); + } catch (UnsupportedCharsetException e) { + Log.e(LOG_TAG, + "Career-specific SHIFT_JIS was not found. " + + "Use SHIFT_JIS as is."); + charset = SHIFT_JIS; + } + } + mCharset = charset; + } mVCardCharsetParameter = "CHARSET=" + SHIFT_JIS; } else { - mCharsetString = UTF_8; - mVCardCharsetParameter = "CHARSET=" + UTF_8; + if (TextUtils.isEmpty(charset)) { + Log.i(LOG_TAG, + "Use the charset \"" + VCardConfig.DEFAULT_EXPORT_CHARSET + + "\" for export."); + mCharset = VCardConfig.DEFAULT_EXPORT_CHARSET; + mVCardCharsetParameter = "CHARSET=" + VCardConfig.DEFAULT_EXPORT_CHARSET; + } else { + try { + charset = CharsetUtils.charsetForVendor(charset).name(); + } catch (UnsupportedCharsetException e) { + Log.i(LOG_TAG, + "Career-specific \"" + charset + "\" was not found (as usual). " + + "Use it as is."); + } + mCharset = charset; + mVCardCharsetParameter = "CHARSET=" + charset; + } } clear(); } @@ -158,9 +221,14 @@ public class VCardBuilder { mBuilder = new StringBuilder(); mEndAppended = false; appendLine(VCardConstants.PROPERTY_BEGIN, VCARD_DATA_VCARD); - if (mIsV30) { + if (VCardConfig.isVersion40(mVCardType)) { + appendLine(VCardConstants.PROPERTY_VERSION, VCardConstants.VERSION_V40); + } else if (VCardConfig.isVersion30(mVCardType)) { appendLine(VCardConstants.PROPERTY_VERSION, VCardConstants.VERSION_V30); } else { + if (!VCardConfig.isVersion21(mVCardType)) { + Log.w(LOG_TAG, "Unknown vCard version detected."); + } appendLine(VCardConstants.PROPERTY_VERSION, VCardConstants.VERSION_V21); } } @@ -227,18 +295,127 @@ public class VCardBuilder { } /** + * To avoid unnecessary complication in logic, we use this method to construct N, FN + * properties for vCard 4.0. + */ + private VCardBuilder appendNamePropertiesV40(final List<ContentValues> contentValuesList) { + if (mIsDoCoMo || mNeedsToConvertPhoneticString) { + // Ignore all flags that look stale from the view of vCard 4.0 to + // simplify construction algorithm. Actually we don't have any vCard file + // available from real world yet, so we may need to re-enable some of these + // in the future. + Log.w(LOG_TAG, "Invalid flag is used in vCard 4.0 construction. Ignored."); + } + + if (contentValuesList == null || contentValuesList.isEmpty()) { + appendLine(VCardConstants.PROPERTY_FN, ""); + return this; + } + + // We have difficulty here. How can we appropriately handle StructuredName with + // missing parts necessary for displaying while it has suppremental information. + // + // e.g. How to handle non-empty phonetic names with empty structured names? + + final ContentValues contentValues = getPrimaryContentValue(contentValuesList); + String familyName = contentValues.getAsString(StructuredName.FAMILY_NAME); + final String middleName = contentValues.getAsString(StructuredName.MIDDLE_NAME); + final String givenName = contentValues.getAsString(StructuredName.GIVEN_NAME); + final String prefix = contentValues.getAsString(StructuredName.PREFIX); + final String suffix = contentValues.getAsString(StructuredName.SUFFIX); + final String formattedName = contentValues.getAsString(StructuredName.DISPLAY_NAME); + if (TextUtils.isEmpty(familyName) + && TextUtils.isEmpty(givenName) + && TextUtils.isEmpty(middleName) + && TextUtils.isEmpty(prefix) + && TextUtils.isEmpty(suffix)) { + if (TextUtils.isEmpty(formattedName)) { + appendLine(VCardConstants.PROPERTY_FN, ""); + return this; + } + familyName = formattedName; + } + + final String phoneticFamilyName = + contentValues.getAsString(StructuredName.PHONETIC_FAMILY_NAME); + final String phoneticMiddleName = + contentValues.getAsString(StructuredName.PHONETIC_MIDDLE_NAME); + final String phoneticGivenName = + contentValues.getAsString(StructuredName.PHONETIC_GIVEN_NAME); + final String escapedFamily = escapeCharacters(familyName); + final String escapedGiven = escapeCharacters(givenName); + final String escapedMiddle = escapeCharacters(middleName); + final String escapedPrefix = escapeCharacters(prefix); + final String escapedSuffix = escapeCharacters(suffix); + + mBuilder.append(VCardConstants.PROPERTY_N); + + if (!(TextUtils.isEmpty(phoneticFamilyName) && + TextUtils.isEmpty(phoneticMiddleName) && + TextUtils.isEmpty(phoneticGivenName))) { + mBuilder.append(VCARD_PARAM_SEPARATOR); + final String sortAs = escapeCharacters(phoneticFamilyName) + + ';' + escapeCharacters(phoneticGivenName) + + ';' + escapeCharacters(phoneticMiddleName); + mBuilder.append("SORT-AS=").append( + VCardUtils.toStringAsV40ParamValue(sortAs)); + } + + mBuilder.append(VCARD_DATA_SEPARATOR); + mBuilder.append(escapedFamily); + mBuilder.append(VCARD_ITEM_SEPARATOR); + mBuilder.append(escapedGiven); + mBuilder.append(VCARD_ITEM_SEPARATOR); + mBuilder.append(escapedMiddle); + mBuilder.append(VCARD_ITEM_SEPARATOR); + mBuilder.append(escapedPrefix); + mBuilder.append(VCARD_ITEM_SEPARATOR); + mBuilder.append(escapedSuffix); + mBuilder.append(VCARD_END_OF_LINE); + + if (TextUtils.isEmpty(formattedName)) { + // Note: + // DISPLAY_NAME doesn't exist while some other elements do, which is usually + // weird in Android, as DISPLAY_NAME should (usually) be constructed + // from the others using locale information and its code points. + Log.w(LOG_TAG, "DISPLAY_NAME is empty."); + + final String escaped = escapeCharacters(VCardUtils.constructNameFromElements( + VCardConfig.getNameOrderType(mVCardType), + familyName, middleName, givenName, prefix, suffix)); + appendLine(VCardConstants.PROPERTY_FN, escaped); + } else { + final String escapedFormatted = escapeCharacters(formattedName); + mBuilder.append(VCardConstants.PROPERTY_FN); + mBuilder.append(VCARD_DATA_SEPARATOR); + mBuilder.append(escapedFormatted); + mBuilder.append(VCARD_END_OF_LINE); + } + + // We may need X- properties for phonetic names. + appendPhoneticNameFields(contentValues); + return this; + } + + /** * For safety, we'll emit just one value around StructuredName, as external importers * may get confused with multiple "N", "FN", etc. properties, though it is valid in * vCard spec. */ public VCardBuilder appendNameProperties(final List<ContentValues> contentValuesList) { + if (VCardConfig.isVersion40(mVCardType)) { + return appendNamePropertiesV40(contentValuesList); + } + if (contentValuesList == null || contentValuesList.isEmpty()) { - if (mIsDoCoMo) { - appendLine(VCardConstants.PROPERTY_N, ""); - } else if (mIsV30) { + if (VCardConfig.isVersion30(mVCardType)) { // vCard 3.0 requires "N" and "FN" properties. + // vCard 4.0 does NOT require N, but we take care of possible backward + // compatibility issues. appendLine(VCardConstants.PROPERTY_N, ""); appendLine(VCardConstants.PROPERTY_FN, ""); + } else if (mIsDoCoMo) { + appendLine(VCardConstants.PROPERTY_N, ""); } return this; } @@ -360,6 +537,7 @@ public class VCardBuilder { encodeQuotedPrintable(displayName) : escapeCharacters(displayName); + // N mBuilder.append(VCardConstants.PROPERTY_N); if (shouldAppendCharsetParam(displayName)) { mBuilder.append(VCARD_PARAM_SEPARATOR); @@ -376,11 +554,13 @@ public class VCardBuilder { mBuilder.append(VCARD_ITEM_SEPARATOR); mBuilder.append(VCARD_ITEM_SEPARATOR); mBuilder.append(VCARD_END_OF_LINE); + + // FN mBuilder.append(VCardConstants.PROPERTY_FN); // Note: "CHARSET" param is not allowed in vCard 3.0, but we may add it - // when it would be useful for external importers, assuming no external - // importer allows this vioration. + // when it would be useful or necessary for external importers, + // assuming the external importer allows this vioration of the spec. if (shouldAppendCharsetParam(displayName)) { mBuilder.append(VCARD_PARAM_SEPARATOR); mBuilder.append(mVCardCharsetParameter); @@ -388,8 +568,7 @@ public class VCardBuilder { mBuilder.append(VCARD_DATA_SEPARATOR); mBuilder.append(encodedDisplayName); mBuilder.append(VCARD_END_OF_LINE); - } else if (mIsV30) { - // vCard 3.0 specification requires these fields. + } else if (VCardConfig.isVersion30(mVCardType)) { appendLine(VCardConstants.PROPERTY_N, ""); appendLine(VCardConstants.PROPERTY_FN, ""); } else if (mIsDoCoMo) { @@ -400,6 +579,9 @@ public class VCardBuilder { return this; } + /** + * Emits SOUND;IRMC, SORT-STRING, and de-fact values for phonetic names like X-PHONETIC-FAMILY. + */ private void appendPhoneticNameFields(final ContentValues contentValues) { final String phoneticFamilyName; final String phoneticMiddleName; @@ -439,13 +621,18 @@ public class VCardBuilder { return; } - // Try to emit the field(s) related to phonetic name. - if (mIsV30) { - final String sortString = VCardUtils - .constructNameFromElements(mVCardType, + if (VCardConfig.isVersion40(mVCardType)) { + // We don't want SORT-STRING anyway. + } else if (VCardConfig.isVersion30(mVCardType)) { + final String sortString = + VCardUtils.constructNameFromElements(mVCardType, phoneticFamilyName, phoneticMiddleName, phoneticGivenName); mBuilder.append(VCardConstants.PROPERTY_SORT_STRING); - if (shouldAppendCharsetParam(sortString)) { + if (VCardConfig.isVersion30(mVCardType) && shouldAppendCharsetParam(sortString)) { + // vCard 3.0 does not force us to use UTF-8 and actually we see some + // programs which emit this value. It is incorrect from the view of + // specification, but actually necessary for parsing vCard with non-UTF-8 + // charsets, expecting other parsers not get confused with this value. mBuilder.append(VCARD_PARAM_SEPARATOR); mBuilder.append(mVCardCharsetParameter); } @@ -454,18 +641,18 @@ public class VCardBuilder { mBuilder.append(VCARD_END_OF_LINE); } else if (mIsJapaneseMobilePhone) { // Note: There is no appropriate property for expressing - // phonetic name in vCard 2.1, while there is in + // phonetic name (Yomigana in Japanese) in vCard 2.1, while there is in // vCard 3.0 (SORT-STRING). - // We chose to use DoCoMo's way when the device is Japanese one - // since it is supported by - // a lot of Japanese mobile phones. This is "X-" property, so - // any parser hopefully would not get confused with this. + // We use DoCoMo's way when the device is Japanese one since it is already + // supported by a lot of Japanese mobile phones. + // This is "X-" property, so any parser hopefully would not get + // confused with this. // // Also, DoCoMo's specification requires vCard composer to use just the first // column. // i.e. - // o SOUND;X-IRMC-N:Miyakawa Daisuke;;;; - // x SOUND;X-IRMC-N:Miyakawa;Daisuke;;; + // good: SOUND;X-IRMC-N:Miyakawa Daisuke;;;; + // bad : SOUND;X-IRMC-N:Miyakawa;Daisuke;;; mBuilder.append(VCardConstants.PROPERTY_SOUND); mBuilder.append(VCARD_PARAM_SEPARATOR); mBuilder.append(VCardConstants.PARAM_TYPE_X_IRMC_N); @@ -519,13 +706,14 @@ public class VCardBuilder { mBuilder.append(encodedPhoneticGivenName); } } - mBuilder.append(VCARD_ITEM_SEPARATOR); - mBuilder.append(VCARD_ITEM_SEPARATOR); - mBuilder.append(VCARD_ITEM_SEPARATOR); - mBuilder.append(VCARD_ITEM_SEPARATOR); + mBuilder.append(VCARD_ITEM_SEPARATOR); // family;given + mBuilder.append(VCARD_ITEM_SEPARATOR); // given;middle + mBuilder.append(VCARD_ITEM_SEPARATOR); // middle;prefix + mBuilder.append(VCARD_ITEM_SEPARATOR); // prefix;suffix mBuilder.append(VCARD_END_OF_LINE); } + Log.d("@@@", "hoge"); if (mUsesDefactProperty) { if (!TextUtils.isEmpty(phoneticGivenName)) { final boolean reallyUseQuotedPrintable = @@ -549,7 +737,7 @@ public class VCardBuilder { mBuilder.append(VCARD_DATA_SEPARATOR); mBuilder.append(encodedPhoneticGivenName); mBuilder.append(VCARD_END_OF_LINE); - } + } // if (!TextUtils.isEmpty(phoneticGivenName)) if (!TextUtils.isEmpty(phoneticMiddleName)) { final boolean reallyUseQuotedPrintable = (mShouldUseQuotedPrintable && @@ -572,7 +760,7 @@ public class VCardBuilder { mBuilder.append(VCARD_DATA_SEPARATOR); mBuilder.append(encodedPhoneticMiddleName); mBuilder.append(VCARD_END_OF_LINE); - } + } // if (!TextUtils.isEmpty(phoneticGivenName)) if (!TextUtils.isEmpty(phoneticFamilyName)) { final boolean reallyUseQuotedPrintable = (mShouldUseQuotedPrintable && @@ -595,13 +783,13 @@ public class VCardBuilder { mBuilder.append(VCARD_DATA_SEPARATOR); mBuilder.append(encodedPhoneticFamilyName); mBuilder.append(VCARD_END_OF_LINE); - } + } // if (!TextUtils.isEmpty(phoneticFamilyName)) } } public VCardBuilder appendNickNames(final List<ContentValues> contentValuesList) { final boolean useAndroidProperty; - if (mIsV30) { + if (mIsV30OrV40) { // These specifications have NICKNAME property. useAndroidProperty = false; } else if (mUsesAndroidProperty) { useAndroidProperty = true; @@ -642,22 +830,18 @@ public class VCardBuilder { if (TextUtils.isEmpty(phoneNumber)) { continue; } - int type = (typeAsObject != null ? typeAsObject : DEFAULT_PHONE_TYPE); - if (type == Phone.TYPE_PAGER) { + + // PAGER number needs unformatted "phone number". + final int type = (typeAsObject != null ? typeAsObject : DEFAULT_PHONE_TYPE); + if (type == Phone.TYPE_PAGER || + VCardConfig.refrainPhoneNumberFormatting(mVCardType)) { phoneLineExists = true; if (!phoneSet.contains(phoneNumber)) { phoneSet.add(phoneNumber); appendTelLine(type, label, phoneNumber, isPrimary); } } else { - // The entry "may" have several phone numbers when the contact entry is - // corrupted because of its original source. - // - // e.g. I encountered the entry like the following. - // "111-222-3333 (Miami)\n444-555-6666 (Broward; 305-653-6796 (Miami); ..." - // This kind of entry is not able to be inserted via Android devices, but - // possible if the source of the data is already corrupted. - List<String> phoneNumberList = splitIfSeveralPhoneNumbersExist(phoneNumber); + final List<String> phoneNumberList = splitAndTrimPhoneNumbers(phoneNumber); if (phoneNumberList.isEmpty()) { continue; } @@ -670,7 +854,7 @@ public class VCardBuilder { phoneSet.add(actualPhoneNumber); appendTelLine(type, label, formattedPhoneNumber, isPrimary); } - } + } // for (String actualPhoneNumber : phoneNumberList) { } } } @@ -682,15 +866,38 @@ public class VCardBuilder { return this; } - private List<String> splitIfSeveralPhoneNumbersExist(final String phoneNumber) { - List<String> phoneList = new ArrayList<String>(); + /** + * <p> + * Splits a given string expressing phone numbers into several strings, and remove + * unnecessary characters inside them. The size of a returned list becomes 1 when + * no split is needed. + * </p> + * <p> + * The given number "may" have several phone numbers when the contact entry is corrupted + * because of its original source. + * e.g. "111-222-3333 (Miami)\n444-555-6666 (Broward; 305-653-6796 (Miami)" + * </p> + * <p> + * This kind of "phone numbers" will not be created with Android vCard implementation, + * but we may encounter them if the source of the input data has already corrupted + * implementation. + * </p> + * <p> + * To handle this case, this method first splits its input into multiple parts + * (e.g. "111-222-3333 (Miami)", "444-555-6666 (Broward", and 305653-6796 (Miami)") and + * removes unnecessary strings like "(Miami)". + * </p> + * <p> + * Do not call this method when trimming is inappropriate for its receivers. + * </p> + */ + private List<String> splitAndTrimPhoneNumbers(final String phoneNumber) { + final List<String> phoneList = new ArrayList<String>(); StringBuilder builder = new StringBuilder(); final int length = phoneNumber.length(); for (int i = 0; i < length; i++) { final char ch = phoneNumber.charAt(i); - // TODO: add a test case for string with '+', and care the other possible issues - // which may happen by ignoring non-digits other than '+'. if (Character.isDigit(ch) || ch == '+') { builder.append(ch); } else if ((ch == ';' || ch == '\n') && builder.length() > 0) { @@ -903,21 +1110,21 @@ public class VCardBuilder { encodedCountry = escapeCharacters(rawCountry); encodedNeighborhood = escapeCharacters(rawNeighborhood); } - final StringBuffer addressBuffer = new StringBuffer(); - addressBuffer.append(encodedPoBox); - addressBuffer.append(VCARD_ITEM_SEPARATOR); - addressBuffer.append(VCARD_ITEM_SEPARATOR); - addressBuffer.append(encodedStreet); - addressBuffer.append(VCARD_ITEM_SEPARATOR); - addressBuffer.append(encodedLocality); - addressBuffer.append(VCARD_ITEM_SEPARATOR); - addressBuffer.append(encodedRegion); - addressBuffer.append(VCARD_ITEM_SEPARATOR); - addressBuffer.append(encodedPostalCode); - addressBuffer.append(VCARD_ITEM_SEPARATOR); - addressBuffer.append(encodedCountry); + final StringBuilder addressBuilder = new StringBuilder(); + addressBuilder.append(encodedPoBox); + addressBuilder.append(VCARD_ITEM_SEPARATOR); // PO BOX ; Extended Address + addressBuilder.append(VCARD_ITEM_SEPARATOR); // Extended Address : Street + addressBuilder.append(encodedStreet); + addressBuilder.append(VCARD_ITEM_SEPARATOR); // Street : Locality + addressBuilder.append(encodedLocality); + addressBuilder.append(VCARD_ITEM_SEPARATOR); // Locality : Region + addressBuilder.append(encodedRegion); + addressBuilder.append(VCARD_ITEM_SEPARATOR); // Region : Postal Code + addressBuilder.append(encodedPostalCode); + addressBuilder.append(VCARD_ITEM_SEPARATOR); // Postal Code : Country + addressBuilder.append(encodedCountry); return new PostalStruct( - reallyUseQuotedPrintable, appendCharset, addressBuffer.toString()); + reallyUseQuotedPrintable, appendCharset, addressBuilder.toString()); } else { // VCardUtils.areAllEmpty(rawAddressArray) == true // Try to use FORMATTED_ADDRESS instead. final String rawFormattedAddress = @@ -940,16 +1147,16 @@ public class VCardBuilder { // We use the second value ("Extended Address") just because Japanese mobile phones // do so. If the other importer expects the value be in the other field, some flag may // be needed. - final StringBuffer addressBuffer = new StringBuffer(); - addressBuffer.append(VCARD_ITEM_SEPARATOR); - addressBuffer.append(encodedFormattedAddress); - addressBuffer.append(VCARD_ITEM_SEPARATOR); - addressBuffer.append(VCARD_ITEM_SEPARATOR); - addressBuffer.append(VCARD_ITEM_SEPARATOR); - addressBuffer.append(VCARD_ITEM_SEPARATOR); - addressBuffer.append(VCARD_ITEM_SEPARATOR); + final StringBuilder addressBuilder = new StringBuilder(); + addressBuilder.append(VCARD_ITEM_SEPARATOR); // PO BOX ; Extended Address + addressBuilder.append(encodedFormattedAddress); + addressBuilder.append(VCARD_ITEM_SEPARATOR); // Extended Address : Street + addressBuilder.append(VCARD_ITEM_SEPARATOR); // Street : Locality + addressBuilder.append(VCARD_ITEM_SEPARATOR); // Locality : Region + addressBuilder.append(VCARD_ITEM_SEPARATOR); // Region : Postal Code + addressBuilder.append(VCARD_ITEM_SEPARATOR); // Postal Code : Country return new PostalStruct( - reallyUseQuotedPrintable, appendCharset, addressBuffer.toString()); + reallyUseQuotedPrintable, appendCharset, addressBuilder.toString()); } } @@ -1089,7 +1296,8 @@ public class VCardBuilder { Log.d(LOG_TAG, "Unknown photo type. Ignored."); continue; } - final String photoString = new String(Base64.encodeBase64(data)); + // TODO: check this works fine. + final String photoString = new String(Base64.encode(data, Base64.NO_WRAP)); if (!TextUtils.isEmpty(photoString)) { appendPhotoLine(photoString, photoType); } @@ -1146,6 +1354,8 @@ public class VCardBuilder { } public VCardBuilder appendEvents(final List<ContentValues> contentValuesList) { + // There's possibility where a given object may have more than one birthday, which + // is inappropriate. We just build one birthday. if (contentValuesList != null) { String primaryBirthday = null; String secondaryBirthday = null; @@ -1213,16 +1423,19 @@ public class VCardBuilder { return this; } + /** + * @param emitEveryTime If true, builder builds the line even when there's no entry. + */ public void appendPostalLine(final int type, final String label, final ContentValues contentValues, - final boolean isPrimary, final boolean emitLineEveryTime) { + final boolean isPrimary, final boolean emitEveryTime) { final boolean reallyUseQuotedPrintable; final boolean appendCharset; final String addressValue; { PostalStruct postalStruct = tryConstructPostalStruct(contentValues); if (postalStruct == null) { - if (emitLineEveryTime) { + if (emitEveryTime) { reallyUseQuotedPrintable = false; appendCharset = false; addressValue = ""; @@ -1444,6 +1657,9 @@ public class VCardBuilder { parameterList.add(VCardConstants.PARAM_TYPE_VOICE); } else if (VCardUtils.isMobilePhoneLabel(label)) { parameterList.add(VCardConstants.PARAM_TYPE_CELL); + } else if (mIsV30OrV40) { + // This label is appropriately encoded in appendTypeParameters. + parameterList.add(label); } else { final String upperLabel = label.toUpperCase(); if (VCardUtils.isValidInV21ButUnknownToContactsPhoteType(upperLabel)) { @@ -1504,8 +1720,8 @@ public class VCardBuilder { StringBuilder tmpBuilder = new StringBuilder(); tmpBuilder.append(VCardConstants.PROPERTY_PHOTO); tmpBuilder.append(VCARD_PARAM_SEPARATOR); - if (mIsV30) { - tmpBuilder.append(VCARD_PARAM_ENCODING_BASE64_V30); + if (mIsV30OrV40) { + tmpBuilder.append(VCARD_PARAM_ENCODING_BASE64_AS_B); } else { tmpBuilder.append(VCARD_PARAM_ENCODING_BASE64_V21); } @@ -1537,7 +1753,8 @@ public class VCardBuilder { mBuilder.append(VCARD_END_OF_LINE); } - public void appendAndroidSpecificProperty(final String mimeType, ContentValues contentValues) { + public void appendAndroidSpecificProperty( + final String mimeType, ContentValues contentValues) { if (!sAllowedAndroidPropertySet.contains(mimeType)) { return; } @@ -1659,7 +1876,7 @@ public class VCardBuilder { encodedValue = encodeQuotedPrintable(rawValue); } else { // TODO: one line may be too huge, which may be invalid in vCard spec, though - // several (even well-known) applications do not care this. + // several (even well-known) applications do not care that violation. encodedValue = escapeCharacters(rawValue); } @@ -1722,21 +1939,35 @@ public class VCardBuilder { // which would be recommended way in vcard 3.0 though not valid in vCard 2.1. boolean first = true; for (final String typeValue : types) { - // Note: vCard 3.0 specifies the different type of acceptable type Strings, but - // we don't emit that kind of vCard 3.0 specific type since there should be - // high probabilyty in which external importers cannot understand them. - // - // e.g. TYPE="\u578B\u306B\u3087" (vCard 3.0 allows non-Ascii characters if they - // are quoted.) - if (!VCardUtils.isV21Word(typeValue)) { - continue; - } - if (first) { - first = false; - } else { - mBuilder.append(VCARD_PARAM_SEPARATOR); + if (VCardConfig.isVersion30(mVCardType)) { + final String encoded = VCardUtils.toStringAsV30ParamValue(typeValue); + if (TextUtils.isEmpty(encoded)) { + continue; + } + + // Note: vCard 3.0 specifies the different type of acceptable type Strings, but + // we don't emit that kind of vCard 3.0 specific type since there should be + // high probabilyty in which external importers cannot understand them. + // + // e.g. TYPE="\u578B\u306B\u3087" (vCard 3.0 allows non-Ascii characters if they + // are quoted.) + if (first) { + first = false; + } else { + mBuilder.append(VCARD_PARAM_SEPARATOR); + } + appendTypeParameter(encoded); + } else { // vCard 2.1 + if (!VCardUtils.isV21Word(typeValue)) { + continue; + } + if (first) { + first = false; + } else { + mBuilder.append(VCARD_PARAM_SEPARATOR); + } + appendTypeParameter(typeValue); } - appendTypeParameter(typeValue); } } @@ -1752,7 +1983,8 @@ public class VCardBuilder { // device is DoCoMo's (just for safety). // // Note: In vCard 3.0, Type strings also can be like this: "TYPE=HOME,PREF" - if ((mIsV30 || mAppendTypeParamName) && !mIsDoCoMo) { + if (VCardConfig.isVersion40(mVCardType) || + ((VCardConfig.isVersion30(mVCardType) || mAppendTypeParamName) && !mIsDoCoMo)) { builder.append(VCardConstants.PARAM_TYPE).append(VCARD_PARAM_EQUAL); } builder.append(type); @@ -1794,9 +2026,9 @@ public class VCardBuilder { byte[] strArray = null; try { - strArray = str.getBytes(mCharsetString); + strArray = str.getBytes(mCharset); } catch (UnsupportedEncodingException e) { - Log.e(LOG_TAG, "Charset " + mCharsetString + " cannot be used. " + Log.e(LOG_TAG, "Charset " + mCharset + " cannot be used. " + "Try default charset"); strArray = str.getBytes(); } @@ -1862,7 +2094,7 @@ public class VCardBuilder { break; } case '\\': { - if (mIsV30) { + if (mIsV30OrV40) { tmpBuilder.append("\\\\"); break; } else { @@ -1880,7 +2112,7 @@ public class VCardBuilder { break; } case ',': { - if (mIsV30) { + if (mIsV30OrV40) { tmpBuilder.append("\\,"); } else { tmpBuilder.append(ch); diff --git a/core/java/android/pim/vcard/VCardComposer.java b/core/java/android/pim/vcard/VCardComposer.java index 0e8b66566f32..193cf1e32b92 100644 --- a/core/java/android/pim/vcard/VCardComposer.java +++ b/core/java/android/pim/vcard/VCardComposer.java @@ -19,16 +19,12 @@ import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.content.Entity; -import android.content.EntityIterator; import android.content.Entity.NamedContentValues; +import android.content.EntityIterator; import android.database.Cursor; import android.database.sqlite.SQLiteException; import android.net.Uri; import android.pim.vcard.exception.VCardException; -import android.provider.ContactsContract.Contacts; -import android.provider.ContactsContract.Data; -import android.provider.ContactsContract.RawContacts; -import android.provider.ContactsContract.RawContactsEntity; import android.provider.ContactsContract.CommonDataKinds.Email; import android.provider.ContactsContract.CommonDataKinds.Event; import android.provider.ContactsContract.CommonDataKinds.Im; @@ -41,6 +37,11 @@ import android.provider.ContactsContract.CommonDataKinds.Relation; import android.provider.ContactsContract.CommonDataKinds.StructuredName; import android.provider.ContactsContract.CommonDataKinds.StructuredPostal; import android.provider.ContactsContract.CommonDataKinds.Website; +import android.provider.ContactsContract.Contacts; +import android.provider.ContactsContract.Data; +import android.provider.ContactsContract.RawContacts; +import android.provider.ContactsContract.RawContactsEntity; +import android.text.TextUtils; import android.util.CharsetUtils; import android.util.Log; @@ -61,15 +62,11 @@ import java.util.Map; /** * <p> - * The class for composing VCard from Contacts information. Note that this is - * completely differnt implementation from - * android.syncml.pim.vcard.VCardComposer, which is not maintained anymore. + * The class for composing vCard from Contacts information. * </p> - * * <p> * Usually, this class should be used like this. * </p> - * * <pre class="prettyprint">VCardComposer composer = null; * try { * composer = new VCardComposer(context); @@ -93,15 +90,18 @@ import java.util.Map; * if (composer != null) { * composer.terminate(); * } - * } </pre> + * }</pre> + * <p> + * Users have to manually take care of memory efficiency. Even one vCard may contain + * image of non-trivial size for mobile devices. + * </p> + * <p> + * {@link VCardBuilder} is used to build each vCard. + * </p> */ public class VCardComposer { private static final String LOG_TAG = "VCardComposer"; - public static final int DEFAULT_PHONE_TYPE = Phone.TYPE_HOME; - public static final int DEFAULT_POSTAL_TYPE = StructuredPostal.TYPE_HOME; - public static final int DEFAULT_EMAIL_TYPE = Email.TYPE_OTHER; - public static final String FAILURE_REASON_FAILED_TO_GET_DATABASE_INFO = "Failed to get database information"; @@ -119,6 +119,8 @@ public class VCardComposer { public static final String VCARD_TYPE_STRING_DOCOMO = "docomo"; + // Strictly speaking, "Shift_JIS" is the most appropriate, but we use upper version here, + // since usual vCard devices for Japanese devices already use it. private static final String SHIFT_JIS = "SHIFT_JIS"; private static final String UTF_8 = "UTF-8"; @@ -141,7 +143,7 @@ public class VCardComposer { sImMap.put(Im.PROTOCOL_ICQ, VCardConstants.PROPERTY_X_ICQ); sImMap.put(Im.PROTOCOL_JABBER, VCardConstants.PROPERTY_X_JABBER); sImMap.put(Im.PROTOCOL_SKYPE, VCardConstants.PROPERTY_X_SKYPE_USERNAME); - // Google talk is a special case. + // We don't add Google talk here since it has to be handled separately. } public static interface OneEntryHandler { @@ -152,37 +154,37 @@ public class VCardComposer { /** * <p> - * An useful example handler, which emits VCard String to outputstream one by one. + * An useful handler for emitting vCard String to an OutputStream object one by one. * </p> * <p> * The input OutputStream object is closed() on {@link #onTerminate()}. - * Must not close the stream outside. + * Must not close the stream outside this class. * </p> */ - public class HandlerForOutputStream implements OneEntryHandler { + public final class HandlerForOutputStream implements OneEntryHandler { @SuppressWarnings("hiding") - private static final String LOG_TAG = "vcard.VCardComposer.HandlerForOutputStream"; - - final private OutputStream mOutputStream; // mWriter will close this. - private Writer mWriter; + private static final String LOG_TAG = "VCardComposer.HandlerForOutputStream"; private boolean mOnTerminateIsCalled = false; + private final OutputStream mOutputStream; // mWriter will close this. + private Writer mWriter; + /** * Input stream will be closed on the detruction of this object. */ - public HandlerForOutputStream(OutputStream outputStream) { + public HandlerForOutputStream(final OutputStream outputStream) { mOutputStream = outputStream; } - public boolean onInit(Context context) { + public boolean onInit(final Context context) { try { mWriter = new BufferedWriter(new OutputStreamWriter( - mOutputStream, mCharsetString)); + mOutputStream, mCharset)); } catch (UnsupportedEncodingException e1) { - Log.e(LOG_TAG, "Unsupported charset: " + mCharsetString); + Log.e(LOG_TAG, "Unsupported charset: " + mCharset); mErrorReason = "Encoding is not supported (usually this does not happen!): " - + mCharsetString; + + mCharset; return false; } @@ -235,14 +237,19 @@ public class VCardComposer { "IOException during closing the output stream: " + e.getMessage()); } finally { - try { - mWriter.close(); - } catch (IOException e) { - } + closeOutputStream(); } } } + public void closeOutputStream() { + try { + mWriter.close(); + } catch (IOException e) { + Log.w(LOG_TAG, "IOException is thrown during close(). Ignoring."); + } + } + @Override public void finalize() { if (!mOnTerminateIsCalled) { @@ -257,11 +264,10 @@ public class VCardComposer { private final ContentResolver mContentResolver; private final boolean mIsDoCoMo; - private final boolean mUsesShiftJis; private Cursor mCursor; private int mIdColumn; - private final String mCharsetString; + private final String mCharset; private boolean mTerminateIsCalled; private final List<OneEntryHandler> mHandlerList; @@ -272,52 +278,107 @@ public class VCardComposer { }; public VCardComposer(Context context) { - this(context, VCardConfig.VCARD_TYPE_DEFAULT, true); + this(context, VCardConfig.VCARD_TYPE_DEFAULT, null, true); } + /** + * The variant which sets charset to null and sets careHandlerErrors to true. + */ public VCardComposer(Context context, int vcardType) { - this(context, vcardType, true); + this(context, vcardType, null, true); } - public VCardComposer(Context context, String vcardTypeStr, boolean careHandlerErrors) { - this(context, VCardConfig.getVCardTypeFromString(vcardTypeStr), careHandlerErrors); + public VCardComposer(Context context, int vcardType, String charset) { + this(context, vcardType, charset, true); } /** - * Construct for supporting call log entry vCard composing. + * The variant which sets charset to null. */ public VCardComposer(final Context context, final int vcardType, final boolean careHandlerErrors) { + this(context, vcardType, null, careHandlerErrors); + } + + /** + * Construct for supporting call log entry vCard composing. + * + * @param context Context to be used during the composition. + * @param vcardType The type of vCard, typically available via {@link VCardConfig}. + * @param charset The charset to be used. Use null when you don't need the charset. + * @param careHandlerErrors If true, This object returns false everytime + * a Handler object given via {{@link #addHandler(OneEntryHandler)} returns false. + * If false, this ignores those errors. + */ + public VCardComposer(final Context context, final int vcardType, String charset, + final boolean careHandlerErrors) { mContext = context; mVCardType = vcardType; mCareHandlerErrors = careHandlerErrors; mContentResolver = context.getContentResolver(); mIsDoCoMo = VCardConfig.isDoCoMo(vcardType); - mUsesShiftJis = VCardConfig.usesShiftJis(vcardType); mHandlerList = new ArrayList<OneEntryHandler>(); - if (mIsDoCoMo) { - String charset; - try { - charset = CharsetUtils.charsetForVendor(SHIFT_JIS, "docomo").name(); - } catch (UnsupportedCharsetException e) { - Log.e(LOG_TAG, "DoCoMo-specific SHIFT_JIS was not found. Use SHIFT_JIS as is."); - charset = SHIFT_JIS; - } - mCharsetString = charset; - } else if (mUsesShiftJis) { - String charset; - try { - charset = CharsetUtils.charsetForVendor(SHIFT_JIS).name(); - } catch (UnsupportedCharsetException e) { - Log.e(LOG_TAG, "Vendor-specific SHIFT_JIS was not found. Use SHIFT_JIS as is."); - charset = SHIFT_JIS; + charset = (TextUtils.isEmpty(charset) ? VCardConfig.DEFAULT_EXPORT_CHARSET : charset); + final boolean shouldAppendCharsetParam = !( + VCardConfig.isVersion30(vcardType) && UTF_8.equalsIgnoreCase(charset)); + + if (mIsDoCoMo || shouldAppendCharsetParam) { + if (SHIFT_JIS.equalsIgnoreCase(charset)) { + if (mIsDoCoMo) { + try { + charset = CharsetUtils.charsetForVendor(SHIFT_JIS, "docomo").name(); + } catch (UnsupportedCharsetException e) { + Log.e(LOG_TAG, + "DoCoMo-specific SHIFT_JIS was not found. " + + "Use SHIFT_JIS as is."); + charset = SHIFT_JIS; + } + } else { + try { + charset = CharsetUtils.charsetForVendor(SHIFT_JIS).name(); + } catch (UnsupportedCharsetException e) { + Log.e(LOG_TAG, + "Career-specific SHIFT_JIS was not found. " + + "Use SHIFT_JIS as is."); + charset = SHIFT_JIS; + } + } + mCharset = charset; + } else { + Log.w(LOG_TAG, + "The charset \"" + charset + "\" is used while " + + SHIFT_JIS + " is needed to be used."); + if (TextUtils.isEmpty(charset)) { + mCharset = SHIFT_JIS; + } else { + try { + charset = CharsetUtils.charsetForVendor(charset).name(); + } catch (UnsupportedCharsetException e) { + Log.i(LOG_TAG, + "Career-specific \"" + charset + "\" was not found (as usual). " + + "Use it as is."); + } + mCharset = charset; + } } - mCharsetString = charset; } else { - mCharsetString = UTF_8; + if (TextUtils.isEmpty(charset)) { + mCharset = UTF_8; + } else { + try { + charset = CharsetUtils.charsetForVendor(charset).name(); + } catch (UnsupportedCharsetException e) { + Log.i(LOG_TAG, + "Career-specific \"" + charset + "\" was not found (as usual). " + + "Use it as is."); + } + mCharset = charset; + } } + + Log.d(LOG_TAG, "Use the charset \"" + mCharset + "\""); } /** @@ -351,7 +412,7 @@ public class VCardComposer { } if (mCareHandlerErrors) { - List<OneEntryHandler> finishedList = new ArrayList<OneEntryHandler>( + final List<OneEntryHandler> finishedList = new ArrayList<OneEntryHandler>( mHandlerList.size()); for (OneEntryHandler handler : mHandlerList) { if (!handler.onInit(mContext)) { @@ -414,7 +475,7 @@ public class VCardComposer { mErrorReason = FAILURE_REASON_NOT_INITIALIZED; return false; } - String vcard; + final String vcard; try { if (mIdColumn >= 0) { vcard = createOneEntryInternal(mCursor.getString(mIdColumn), @@ -437,8 +498,7 @@ public class VCardComposer { mCursor.moveToNext(); } - // This function does not care the OutOfMemoryError on the handler side - // :-P + // This function does not care the OutOfMemoryError on the handler side :-P if (mCareHandlerErrors) { List<OneEntryHandler> finishedList = new ArrayList<OneEntryHandler>( mHandlerList.size()); @@ -457,7 +517,7 @@ public class VCardComposer { } private String createOneEntryInternal(final String contactId, - Method getEntityIteratorMethod) throws VCardException { + final Method getEntityIteratorMethod) throws VCardException { final Map<String, List<ContentValues>> contentValuesListMap = new HashMap<String, List<ContentValues>>(); // The resolver may return the entity iterator with no data. It is possible. @@ -466,12 +526,13 @@ public class VCardComposer { EntityIterator entityIterator = null; try { final Uri uri = RawContactsEntity.CONTENT_URI.buildUpon() + // .appendQueryParameter("for_export_only", "1") .appendQueryParameter(Data.FOR_EXPORT_ONLY, "1") .build(); final String selection = Data.CONTACT_ID + "=?"; final String[] selectionArgs = new String[] {contactId}; if (getEntityIteratorMethod != null) { - // Please note that this branch is executed by some tests only + // Please note that this branch is executed by unit tests only try { entityIterator = (EntityIterator)getEntityIteratorMethod.invoke(null, mContentResolver, uri, selection, selectionArgs, null); @@ -527,22 +588,35 @@ public class VCardComposer { } } - final VCardBuilder builder = new VCardBuilder(mVCardType); - builder.appendNameProperties(contentValuesListMap.get(StructuredName.CONTENT_ITEM_TYPE)) - .appendNickNames(contentValuesListMap.get(Nickname.CONTENT_ITEM_TYPE)) - .appendPhones(contentValuesListMap.get(Phone.CONTENT_ITEM_TYPE)) - .appendEmails(contentValuesListMap.get(Email.CONTENT_ITEM_TYPE)) - .appendPostals(contentValuesListMap.get(StructuredPostal.CONTENT_ITEM_TYPE)) - .appendOrganizations(contentValuesListMap.get(Organization.CONTENT_ITEM_TYPE)) - .appendWebsites(contentValuesListMap.get(Website.CONTENT_ITEM_TYPE)); - if ((mVCardType & VCardConfig.FLAG_REFRAIN_IMAGE_EXPORT) == 0) { - builder.appendPhotos(contentValuesListMap.get(Photo.CONTENT_ITEM_TYPE)); + return buildVCard(contentValuesListMap); + } + + /** + * Builds and returns vCard using given map, whose key is CONTENT_ITEM_TYPE defined in + * {ContactsContract}. Developers can override this method to customize the output. + */ + public String buildVCard(final Map<String, List<ContentValues>> contentValuesListMap) { + if (contentValuesListMap == null) { + Log.e(LOG_TAG, "The given map is null. Ignore and return empty String"); + return ""; + } else { + final VCardBuilder builder = new VCardBuilder(mVCardType, mCharset); + builder.appendNameProperties(contentValuesListMap.get(StructuredName.CONTENT_ITEM_TYPE)) + .appendNickNames(contentValuesListMap.get(Nickname.CONTENT_ITEM_TYPE)) + .appendPhones(contentValuesListMap.get(Phone.CONTENT_ITEM_TYPE)) + .appendEmails(contentValuesListMap.get(Email.CONTENT_ITEM_TYPE)) + .appendPostals(contentValuesListMap.get(StructuredPostal.CONTENT_ITEM_TYPE)) + .appendOrganizations(contentValuesListMap.get(Organization.CONTENT_ITEM_TYPE)) + .appendWebsites(contentValuesListMap.get(Website.CONTENT_ITEM_TYPE)); + if ((mVCardType & VCardConfig.FLAG_REFRAIN_IMAGE_EXPORT) == 0) { + builder.appendPhotos(contentValuesListMap.get(Photo.CONTENT_ITEM_TYPE)); + } + builder.appendNotes(contentValuesListMap.get(Note.CONTENT_ITEM_TYPE)) + .appendEvents(contentValuesListMap.get(Event.CONTENT_ITEM_TYPE)) + .appendIms(contentValuesListMap.get(Im.CONTENT_ITEM_TYPE)) + .appendRelation(contentValuesListMap.get(Relation.CONTENT_ITEM_TYPE)); + return builder.toString(); } - builder.appendNotes(contentValuesListMap.get(Note.CONTENT_ITEM_TYPE)) - .appendEvents(contentValuesListMap.get(Event.CONTENT_ITEM_TYPE)) - .appendIms(contentValuesListMap.get(Im.CONTENT_ITEM_TYPE)) - .appendRelation(contentValuesListMap.get(Relation.CONTENT_ITEM_TYPE)); - return builder.toString(); } public void terminate() { @@ -565,26 +639,38 @@ public class VCardComposer { @Override public void finalize() { if (!mTerminateIsCalled) { + Log.w(LOG_TAG, "terminate() is not called yet. We call it in finalize() step."); terminate(); } } + /** + * @return returns the number of available entities. The return value is undefined + * when this object is not ready yet (typically when {{@link #init()} is not called + * or when {@link #terminate()} is already called). + */ public int getCount() { if (mCursor == null) { + Log.w(LOG_TAG, "This object is not ready yet."); return 0; } return mCursor.getCount(); } + /** + * @return true when there's no entity to be built. The return value is undefined + * when this object is not ready yet. + */ public boolean isAfterLast() { if (mCursor == null) { + Log.w(LOG_TAG, "This object is not ready yet."); return false; } return mCursor.isAfterLast(); } /** - * @return Return the error reason if possible. + * @return Returns the error reason. */ public String getErrorReason() { return mErrorReason; diff --git a/core/java/android/pim/vcard/VCardConfig.java b/core/java/android/pim/vcard/VCardConfig.java index 3409be6b91e1..8e759e3960b2 100644 --- a/core/java/android/pim/vcard/VCardConfig.java +++ b/core/java/android/pim/vcard/VCardConfig.java @@ -15,6 +15,7 @@ */ package android.pim.vcard; +import android.telephony.PhoneNumberUtils; import android.util.Log; import java.util.HashMap; @@ -37,20 +38,43 @@ public class VCardConfig { /* package */ static final int LOG_LEVEL = LOG_LEVEL_NONE; - /* package */ static final int PARSE_TYPE_UNKNOWN = 0; - /* package */ static final int PARSE_TYPE_APPLE = 1; - /* package */ static final int PARSE_TYPE_MOBILE_PHONE_JP = 2; // For Japanese mobile phones. - /* package */ static final int PARSE_TYPE_FOMA = 3; // For Japanese FOMA mobile phones. - /* package */ static final int PARSE_TYPE_WINDOWS_MOBILE_JP = 4; + /** + * <p> + * The charset used during import. + * </p> + * <p> + * We cannot determine which charset should be used to interpret lines in vCard, + * while Java requires us to specify it when InputStream is used. + * We need to rely on the mechanism due to some performance reason. + * </p> + * <p> + * In order to avoid "misinterpretation" of charset and lose any data in vCard, + * "ISO-8859-1" is first used for reading the stream. + * When a charset is specified in a property (with "CHARSET=..." parameter), + * the string is decoded to raw bytes and encoded into the specific charset, + * </p> + * <p> + * Unicode specification there's a one to one mapping between each byte in ISO-8859-1 + * and a codepoint, and Java specification requires runtime must have the charset. + * Thus, ISO-8859-1 is one effective mapping for intermediate mapping. + * </p> + */ + public static final String DEFAULT_INTERMEDIATE_CHARSET = "ISO-8859-1"; - // Assumes that "iso-8859-1" is able to map "all" 8bit characters to some unicode and - // decode the unicode to the original charset. If not, this setting will cause some bug. - public static final String DEFAULT_CHARSET = "iso-8859-1"; - - public static final int FLAG_V21 = 0; - public static final int FLAG_V30 = 1; + /** + * The charset used when there's no information affbout what charset should be used to + * encode the binary given from vCard. + */ + public static final String DEFAULT_IMPORT_CHARSET = "UTF-8"; + public static final String DEFAULT_EXPORT_CHARSET = "UTF-8"; - // 0x2 is reserved for the future use ... + /** + * Do not use statically like "version == VERSION_V21" + */ + public static final int VERSION_21 = 0; + public static final int VERSION_30 = 1; + public static final int VERSION_40 = 2; + public static final int VERSION_MASK = 3; public static final int NAME_ORDER_DEFAULT = 0; public static final int NAME_ORDER_EUROPE = 0x4; @@ -58,311 +82,340 @@ public class VCardConfig { private static final int NAME_ORDER_MASK = 0xC; // 0x10 is reserved for safety - - private static final int FLAG_CHARSET_UTF8 = 0; - private static final int FLAG_CHARSET_SHIFT_JIS = 0x100; - private static final int FLAG_CHARSET_MASK = 0xF00; /** + * <p> * The flag indicating the vCard composer will add some "X-" properties used only in Android * when the formal vCard specification does not have appropriate fields for that data. - * + * </p> + * <p> * For example, Android accepts nickname information while vCard 2.1 does not. * When this flag is on, vCard composer emits alternative "X-" property (like "X-NICKNAME") * instead of just dropping it. - * + * </p> + * <p> * vCard parser code automatically parses the field emitted even when this flag is off. - * - * Note that this flag does not assure all the information must be hold in the emitted vCard. + * </p> */ private static final int FLAG_USE_ANDROID_PROPERTY = 0x80000000; /** + * <p> * The flag indicating the vCard composer will add some "X-" properties seen in the * vCard data emitted by the other softwares/devices when the formal vCard specification - * does not have appropriate field(s) for that data. - * + * does not have appropriate field(s) for that data. + * </p> + * <p> * One example is X-PHONETIC-FIRST-NAME/X-PHONETIC-MIDDLE-NAME/X-PHONETIC-LAST-NAME, which are * for phonetic name (how the name is pronounced), seen in the vCard emitted by some other * non-Android devices/softwares. We chose to enable the vCard composer to use those * defact properties since they are also useful for Android devices. - * + * </p> + * <p> * Note for developers: only "X-" properties should be added with this flag. vCard 2.1/3.0 * allows any kind of "X-" properties but does not allow non-"X-" properties (except IANA tokens * in vCard 3.0). Some external parsers may get confused with non-valid, non-"X-" properties. + * </p> */ private static final int FLAG_USE_DEFACT_PROPERTY = 0x40000000; /** - * The flag indicating some specific dialect seen in vcard of DoCoMo (one of Japanese + * <p> + * The flag indicating some specific dialect seen in vCard of DoCoMo (one of Japanese * mobile careers) should be used. This flag does not include any other information like * that "the vCard is for Japanese". So it is "possible" that "the vCard should have DoCoMo's * dialect but the name order should be European", but it is not recommended. + * </p> */ private static final int FLAG_DOCOMO = 0x20000000; /** - * <P> + * <p> * The flag indicating the vCard composer does "NOT" use Quoted-Printable toward "primary" * properties even though it is required by vCard 2.1 (QP is prohibited in vCard 3.0). - * </P> - * <P> + * </p> + * <p> * We actually cannot define what is the "primary" property. Note that this is NOT defined * in vCard specification either. Also be aware that it is NOT related to "primary" notion * used in {@link android.provider.ContactsContract}. * This notion is just for vCard composition in Android. - * </P> - * <P> + * </p> + * <p> * We added this Android-specific notion since some (incomplete) vCard exporters for vCard 2.1 * do NOT use Quoted-Printable encoding toward some properties related names like "N", "FN", etc. * even when their values contain non-ascii or/and CR/LF, while they use the encoding in the * other properties like "ADR", "ORG", etc. - * <P> + * <p> * We are afraid of the case where some vCard importer also forget handling QP presuming QP is * not used in such fields. - * </P> - * <P> + * </p> + * <p> * This flag is useful when some target importer you are going to focus on does not accept * such properties with Quoted-Printable encoding. - * </P> - * <P> + * </p> + * <p> * Again, we should not use this flag at all for complying vCard 2.1 spec. - * </P> - * <P> + * </p> + * <p> * In vCard 3.0, Quoted-Printable is explicitly "prohibitted", so we don't need to care this * kind of problem (hopefully). - * </P> + * </p> + * @hide */ public static final int FLAG_REFRAIN_QP_TO_NAME_PROPERTIES = 0x10000000; /** - * <P> + * <p> * The flag indicating that phonetic name related fields must be converted to * appropriate form. Note that "appropriate" is not defined in any vCard specification. * This is Android-specific. - * </P> - * <P> + * </p> + * <p> * One typical (and currently sole) example where we need this flag is the time when * we need to emit Japanese phonetic names into vCard entries. The property values * should be encoded into half-width katakana when the target importer is Japanese mobile * phones', which are probably not able to parse full-width hiragana/katakana for * historical reasons, while the vCard importers embedded to softwares for PC should be * able to parse them as we expect. - * </P> + * </p> */ - public static final int FLAG_CONVERT_PHONETIC_NAME_STRINGS = 0x0800000; + public static final int FLAG_CONVERT_PHONETIC_NAME_STRINGS = 0x08000000; /** - * <P> + * <p> * The flag indicating the vCard composer "for 2.1" emits "TYPE=" string toward TYPE params * every time possible. The default behavior does not emit it and is valid in the spec. * In vCrad 3.0, this flag is unnecessary, since "TYPE=" is MUST in vCard 3.0 specification. - * </P> - * <P> + * </p> + * <p> * Detail: * How more than one TYPE fields are expressed is different between vCard 2.1 and vCard 3.0. * </p> - * <P> - * e.g.<BR /> - * 1) Probably valid in both vCard 2.1 and vCard 3.0: "ADR;TYPE=DOM;TYPE=HOME:..."<BR /> - * 2) Valid in vCard 2.1 but not in vCard 3.0: "ADR;DOM;HOME:..."<BR /> - * 3) Valid in vCard 3.0 but not in vCard 2.1: "ADR;TYPE=DOM,HOME:..."<BR /> - * </P> - * <P> - * 2) had been the default of VCard exporter/importer in Android, but it is found that - * some external exporter is not able to parse the type format like 2) but only 3). - * </P> - * <P> + * <p> + * e.g. + * </p> + * <ol> + * <li>Probably valid in both vCard 2.1 and vCard 3.0: "ADR;TYPE=DOM;TYPE=HOME:..."</li> + * <li>Valid in vCard 2.1 but not in vCard 3.0: "ADR;DOM;HOME:..."</li> + * <li>Valid in vCard 3.0 but not in vCard 2.1: "ADR;TYPE=DOM,HOME:..."</li> + * </ol> + * <p> * If you are targeting to the importer which cannot accept TYPE params without "TYPE=" * strings (which should be rare though), please use this flag. - * </P> - * <P> - * Example usage: int vcardType = (VCARD_TYPE_V21_GENERIC | FLAG_APPEND_TYPE_PARAM); - * </P> + * </p> + * <p> + * Example usage: + * <pre class="prettyprint">int type = (VCARD_TYPE_V21_GENERIC | FLAG_APPEND_TYPE_PARAM);</pre> + * </p> */ public static final int FLAG_APPEND_TYPE_PARAM = 0x04000000; /** - * <P> - * The flag asking exporter to refrain image export. - * </P> - * @hide will be deleted in the near future. + * <p> + * The flag indicating the vCard composer does touch nothing toward phone number Strings + * but leave it as is. + * </p> + * <p> + * The vCard specifications mention nothing toward phone numbers, while some devices + * do (wrongly, but with innevitable reasons). + * For example, there's a possibility Japanese mobile phones are expected to have + * just numbers, hypens, plus, etc. but not usual alphabets, while US mobile phones + * should get such characters. To make exported vCard simple for external parsers, + * we have used {@link PhoneNumberUtils#formatNumber(String)} during export, and + * removed unnecessary characters inside the number (e.g. "111-222-3333 (Miami)" + * becomes "111-222-3333"). + * Unfortunate side effect of that use was some control characters used in the other + * areas may be badly affected by the formatting. + * </p> + * <p> + * This flag disables that formatting, affecting both importer and exporter. + * If the user is aware of some side effects due to the implicit formatting, use this flag. + * </p> + */ + public static final int FLAG_REFRAIN_PHONE_NUMBER_FORMATTING = 0x02000000; + + /** + * <p> + * For importer only. Ignored in exporter. + * </p> + * <p> + * The flag indicating the parser should handle a nested vCard, in which vCard clause starts + * in another vCard clause. Here's a typical example. + * </p> + * <pre class="prettyprint">BEGIN:VCARD + * BEGIN:VCARD + * VERSION:2.1 + * ... + * END:VCARD + * END:VCARD</pre> + * <p> + * The vCard 2.1 specification allows the nest, but also let parsers ignore nested entries, + * while some mobile devices emit nested ones as primary data to be imported. + * </p> + * <p> + * This flag forces a vCard parser to torelate such a nest and understand its content. + * </p> */ - public static final int FLAG_REFRAIN_IMAGE_EXPORT = 0x02000000; + public static final int FLAG_TORELATE_NEST = 0x01000000; //// The followings are VCard types available from importer/exporter. //// + public static final int FLAG_REFRAIN_IMAGE_EXPORT = 0x00800000; + /** - * <P> - * Generic vCard format with the vCard 2.1. Uses UTF-8 for the charset. - * When composing a vCard entry, the US convension will be used toward formatting - * some values. - * </P> - * <P> + * <p> + * The type indicating nothing. Used by {@link VCardSourceDetector} when it + * was not able to guess the exact vCard type. + * </p> + */ + public static final int VCARD_TYPE_UNKNOWN = 0; + + /** + * <p> + * Generic vCard format with the vCard 2.1. When composing a vCard entry, + * the US convension will be used toward formatting some values. + * </p> + * <p> * e.g. The order of the display name would be "Prefix Given Middle Family Suffix", * while it should be "Prefix Family Middle Given Suffix" in Japan for example. - * </P> + * </p> + * <p> + * Uses UTF-8 for the charset as a charset for exporting. Note that old vCard importer + * outside Android cannot accept it since vCard 2.1 specifically does not allow + * that charset, while we need to use it to support various languages around the world. + * </p> + * <p> + * If you want to use alternative charset, you should notify the charset to the other + * compontent to be used. + * </p> */ - public static final int VCARD_TYPE_V21_GENERIC_UTF8 = - (FLAG_V21 | NAME_ORDER_DEFAULT | FLAG_CHARSET_UTF8 | - FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY); + public static final int VCARD_TYPE_V21_GENERIC = + (VERSION_21 | NAME_ORDER_DEFAULT | FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY); - /* package */ static String VCARD_TYPE_V21_GENERIC_UTF8_STR = "v21_generic"; + /* package */ static String VCARD_TYPE_V21_GENERIC_STR = "v21_generic"; /** - * <P> + * <p> * General vCard format with the version 3.0. Uses UTF-8 for the charset. - * </P> - * <P> + * </p> + * <p> * Not fully ready yet. Use with caution when you use this. - * </P> + * </p> */ - public static final int VCARD_TYPE_V30_GENERIC_UTF8 = - (FLAG_V30 | NAME_ORDER_DEFAULT | FLAG_CHARSET_UTF8 | - FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY); + public static final int VCARD_TYPE_V30_GENERIC = + (VERSION_30 | NAME_ORDER_DEFAULT | FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY); + + /* package */ static final String VCARD_TYPE_V30_GENERIC_STR = "v30_generic"; + + /** + * General vCard format with the version 4.0. + * @hide vCard 4.0 is not published yet. + */ + public static final int VCARD_TYPE_V40_GENERIC = + (VERSION_40 | NAME_ORDER_DEFAULT | FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY); + + /* package */ static final String VCARD_TYPE_V40_GENERIC_STR = "v40_generic"; - /* package */ static final String VCARD_TYPE_V30_GENERIC_UTF8_STR = "v30_generic"; - /** - * <P> + * <p> * General vCard format for the vCard 2.1 with some Europe convension. Uses Utf-8. * Currently, only name order is considered ("Prefix Middle Given Family Suffix") - * </P> + * </p> */ - public static final int VCARD_TYPE_V21_EUROPE_UTF8 = - (FLAG_V21 | NAME_ORDER_EUROPE | FLAG_CHARSET_UTF8 | - FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY); - - /* package */ static final String VCARD_TYPE_V21_EUROPE_UTF8_STR = "v21_europe"; + public static final int VCARD_TYPE_V21_EUROPE = + (VERSION_21 | NAME_ORDER_EUROPE | FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY); + + /* package */ static final String VCARD_TYPE_V21_EUROPE_STR = "v21_europe"; /** - * <P> + * <p> * General vCard format with the version 3.0 with some Europe convension. Uses UTF-8. - * </P> - * <P> + * </p> + * <p> * Not ready yet. Use with caution when you use this. - * </P> + * </p> */ - public static final int VCARD_TYPE_V30_EUROPE_UTF8 = - (FLAG_V30 | NAME_ORDER_EUROPE | FLAG_CHARSET_UTF8 | - FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY); + public static final int VCARD_TYPE_V30_EUROPE = + (VERSION_30 | NAME_ORDER_EUROPE | FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY); /* package */ static final String VCARD_TYPE_V30_EUROPE_STR = "v30_europe"; /** - * <P> + * <p> * The vCard 2.1 format for miscellaneous Japanese devices, using UTF-8 as default charset. - * </P> - * <P> + * </p> + * <p> * Not ready yet. Use with caution when you use this. - * </P> + * </p> */ - public static final int VCARD_TYPE_V21_JAPANESE_UTF8 = - (FLAG_V21 | NAME_ORDER_JAPANESE | FLAG_CHARSET_UTF8 | - FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY); - - /* package */ static final String VCARD_TYPE_V21_JAPANESE_UTF8_STR = "v21_japanese_utf8"; + public static final int VCARD_TYPE_V21_JAPANESE = + (VERSION_21 | NAME_ORDER_JAPANESE | FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY); - /** - * <P> - * vCard 2.1 format for miscellaneous Japanese devices. Shift_Jis is used for - * parsing/composing the vCard data. - * </P> - * <P> - * Not ready yet. Use with caution when you use this. - * </P> - */ - public static final int VCARD_TYPE_V21_JAPANESE_SJIS = - (FLAG_V21 | NAME_ORDER_JAPANESE | FLAG_CHARSET_SHIFT_JIS | - FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY); + /* package */ static final String VCARD_TYPE_V21_JAPANESE_STR = "v21_japanese_utf8"; - /* package */ static final String VCARD_TYPE_V21_JAPANESE_SJIS_STR = "v21_japanese_sjis"; - - /** - * <P> - * vCard format for miscellaneous Japanese devices, using Shift_Jis for - * parsing/composing the vCard data. - * </P> - * <P> - * Not ready yet. Use with caution when you use this. - * </P> - */ - public static final int VCARD_TYPE_V30_JAPANESE_SJIS = - (FLAG_V30 | NAME_ORDER_JAPANESE | FLAG_CHARSET_SHIFT_JIS | - FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY); - - /* package */ static final String VCARD_TYPE_V30_JAPANESE_SJIS_STR = "v30_japanese_sjis"; - /** - * <P> + * <p> * The vCard 3.0 format for miscellaneous Japanese devices, using UTF-8 as default charset. - * </P> - * <P> + * </p> + * <p> * Not ready yet. Use with caution when you use this. - * </P> + * </p> */ - public static final int VCARD_TYPE_V30_JAPANESE_UTF8 = - (FLAG_V30 | NAME_ORDER_JAPANESE | FLAG_CHARSET_UTF8 | - FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY); + public static final int VCARD_TYPE_V30_JAPANESE = + (VERSION_30 | NAME_ORDER_JAPANESE | FLAG_USE_DEFACT_PROPERTY | FLAG_USE_ANDROID_PROPERTY); - /* package */ static final String VCARD_TYPE_V30_JAPANESE_UTF8_STR = "v30_japanese_utf8"; + /* package */ static final String VCARD_TYPE_V30_JAPANESE_STR = "v30_japanese_utf8"; /** - * <P> + * <p> * The vCard 2.1 based format which (partially) considers the convention in Japanese * mobile phones, where phonetic names are translated to half-width katakana if - * possible, etc. - * </P> - * <P> - * Not ready yet. Use with caution when you use this. - * </P> + * possible, etc. It would be better to use Shift_JIS as a charset for maximum + * compatibility. + * </p> + * @hide Should not be available world wide. */ public static final int VCARD_TYPE_V21_JAPANESE_MOBILE = - (FLAG_V21 | NAME_ORDER_JAPANESE | FLAG_CHARSET_SHIFT_JIS | - FLAG_CONVERT_PHONETIC_NAME_STRINGS | - FLAG_REFRAIN_QP_TO_NAME_PROPERTIES); + (VERSION_21 | NAME_ORDER_JAPANESE | + FLAG_CONVERT_PHONETIC_NAME_STRINGS | FLAG_REFRAIN_QP_TO_NAME_PROPERTIES); /* package */ static final String VCARD_TYPE_V21_JAPANESE_MOBILE_STR = "v21_japanese_mobile"; /** - * <P> - * VCard format used in DoCoMo, which is one of Japanese mobile phone careers. + * <p> + * The vCard format used in DoCoMo, which is one of Japanese mobile phone careers. * </p> - * <P> + * <p> * Base version is vCard 2.1, but the data has several DoCoMo-specific convensions. * No Android-specific property nor defact property is included. The "Primary" properties * are NOT encoded to Quoted-Printable. - * </P> + * </p> + * @hide Should not be available world wide. */ public static final int VCARD_TYPE_DOCOMO = (VCARD_TYPE_V21_JAPANESE_MOBILE | FLAG_DOCOMO); /* package */ static final String VCARD_TYPE_DOCOMO_STR = "docomo"; - public static int VCARD_TYPE_DEFAULT = VCARD_TYPE_V21_GENERIC_UTF8; + public static int VCARD_TYPE_DEFAULT = VCARD_TYPE_V21_GENERIC; private static final Map<String, Integer> sVCardTypeMap; private static final Set<Integer> sJapaneseMobileTypeSet; static { sVCardTypeMap = new HashMap<String, Integer>(); - sVCardTypeMap.put(VCARD_TYPE_V21_GENERIC_UTF8_STR, VCARD_TYPE_V21_GENERIC_UTF8); - sVCardTypeMap.put(VCARD_TYPE_V30_GENERIC_UTF8_STR, VCARD_TYPE_V30_GENERIC_UTF8); - sVCardTypeMap.put(VCARD_TYPE_V21_EUROPE_UTF8_STR, VCARD_TYPE_V21_EUROPE_UTF8); - sVCardTypeMap.put(VCARD_TYPE_V30_EUROPE_STR, VCARD_TYPE_V30_EUROPE_UTF8); - sVCardTypeMap.put(VCARD_TYPE_V21_JAPANESE_SJIS_STR, VCARD_TYPE_V21_JAPANESE_SJIS); - sVCardTypeMap.put(VCARD_TYPE_V21_JAPANESE_UTF8_STR, VCARD_TYPE_V21_JAPANESE_UTF8); - sVCardTypeMap.put(VCARD_TYPE_V30_JAPANESE_SJIS_STR, VCARD_TYPE_V30_JAPANESE_SJIS); - sVCardTypeMap.put(VCARD_TYPE_V30_JAPANESE_UTF8_STR, VCARD_TYPE_V30_JAPANESE_UTF8); + sVCardTypeMap.put(VCARD_TYPE_V21_GENERIC_STR, VCARD_TYPE_V21_GENERIC); + sVCardTypeMap.put(VCARD_TYPE_V30_GENERIC_STR, VCARD_TYPE_V30_GENERIC); + sVCardTypeMap.put(VCARD_TYPE_V21_EUROPE_STR, VCARD_TYPE_V21_EUROPE); + sVCardTypeMap.put(VCARD_TYPE_V30_EUROPE_STR, VCARD_TYPE_V30_EUROPE); + sVCardTypeMap.put(VCARD_TYPE_V21_JAPANESE_STR, VCARD_TYPE_V21_JAPANESE); + sVCardTypeMap.put(VCARD_TYPE_V30_JAPANESE_STR, VCARD_TYPE_V30_JAPANESE); sVCardTypeMap.put(VCARD_TYPE_V21_JAPANESE_MOBILE_STR, VCARD_TYPE_V21_JAPANESE_MOBILE); sVCardTypeMap.put(VCARD_TYPE_DOCOMO_STR, VCARD_TYPE_DOCOMO); sJapaneseMobileTypeSet = new HashSet<Integer>(); - sJapaneseMobileTypeSet.add(VCARD_TYPE_V21_JAPANESE_SJIS); - sJapaneseMobileTypeSet.add(VCARD_TYPE_V21_JAPANESE_UTF8); - sJapaneseMobileTypeSet.add(VCARD_TYPE_V21_JAPANESE_SJIS); - sJapaneseMobileTypeSet.add(VCARD_TYPE_V30_JAPANESE_SJIS); - sJapaneseMobileTypeSet.add(VCARD_TYPE_V30_JAPANESE_UTF8); + sJapaneseMobileTypeSet.add(VCARD_TYPE_V21_JAPANESE); + sJapaneseMobileTypeSet.add(VCARD_TYPE_V30_JAPANESE); sJapaneseMobileTypeSet.add(VCARD_TYPE_V21_JAPANESE_MOBILE); sJapaneseMobileTypeSet.add(VCARD_TYPE_DOCOMO); } @@ -379,20 +432,20 @@ public class VCardConfig { } } - public static boolean isV30(final int vcardType) { - return ((vcardType & FLAG_V30) != 0); + public static boolean isVersion21(final int vcardType) { + return (vcardType & VERSION_MASK) == VERSION_21; } - public static boolean shouldUseQuotedPrintable(final int vcardType) { - return !isV30(vcardType); + public static boolean isVersion30(final int vcardType) { + return (vcardType & VERSION_MASK) == VERSION_30; } - public static boolean usesUtf8(final int vcardType) { - return ((vcardType & FLAG_CHARSET_MASK) == FLAG_CHARSET_UTF8); + public static boolean isVersion40(final int vcardType) { + return (vcardType & VERSION_MASK) == VERSION_40; } - public static boolean usesShiftJis(final int vcardType) { - return ((vcardType & FLAG_CHARSET_MASK) == FLAG_CHARSET_SHIFT_JIS); + public static boolean shouldUseQuotedPrintable(final int vcardType) { + return !isVersion30(vcardType); } public static int getNameOrderType(final int vcardType) { @@ -417,7 +470,7 @@ public class VCardConfig { } public static boolean appendTypeParamName(final int vcardType) { - return (isV30(vcardType) || ((vcardType & FLAG_APPEND_TYPE_PARAM) != 0)); + return (isVersion30(vcardType) || ((vcardType & FLAG_APPEND_TYPE_PARAM) != 0)); } /** @@ -431,6 +484,10 @@ public class VCardConfig { return sJapaneseMobileTypeSet.contains(vcardType); } + /* package */ static boolean refrainPhoneNumberFormatting(final int vcardType) { + return ((vcardType & FLAG_REFRAIN_PHONE_NUMBER_FORMATTING) != 0); + } + public static boolean needsToConvertPhoneticString(final int vcardType) { return ((vcardType & FLAG_CONVERT_PHONETIC_NAME_STRINGS) != 0); } diff --git a/core/java/android/pim/vcard/VCardConstants.java b/core/java/android/pim/vcard/VCardConstants.java index 8c07126d56bd..76371ef9b02a 100644 --- a/core/java/android/pim/vcard/VCardConstants.java +++ b/core/java/android/pim/vcard/VCardConstants.java @@ -21,6 +21,7 @@ package android.pim.vcard; public class VCardConstants { public static final String VERSION_V21 = "2.1"; public static final String VERSION_V30 = "3.0"; + public static final String VERSION_V40 = "4.0"; // The property names valid both in vCard 2.1 and 3.0. public static final String PROPERTY_BEGIN = "BEGIN"; @@ -38,25 +39,30 @@ public class VCardConstants { public static final String PROPERTY_PHOTO = "PHOTO"; public static final String PROPERTY_LOGO = "LOGO"; public static final String PROPERTY_URL = "URL"; - public static final String PROPERTY_BDAY = "BDAY"; // Birthday + public static final String PROPERTY_BDAY = "BDAY"; // Birthday (3.0, 4.0) + public static final String PROPERTY_BIRTH = "BIRTH"; // Place of birth (4.0) + public static final String PROPERTY_ANNIVERSARY = "ANNIVERSARY"; // Date of marriage (4.0) + public static final String PROPERTY_NAME = "NAME"; // (3.0, 4,0) + public static final String PROPERTY_NICKNAME = "NICKNAME"; // (3.0, 4.0) + public static final String PROPERTY_SORT_STRING = "SORT-STRING"; // (3.0, 4.0) public static final String PROPERTY_END = "END"; - // Valid property names not supported (not appropriately handled) by our vCard importer now. + // Valid property names not supported (not appropriately handled) by our importer. + // TODO: Should be removed from the view of memory efficiency? public static final String PROPERTY_REV = "REV"; - public static final String PROPERTY_AGENT = "AGENT"; + public static final String PROPERTY_AGENT = "AGENT"; // (3.0) + public static final String PROPERTY_DDAY = "DDAY"; // Date of death (4.0) + public static final String PROPERTY_DEATH = "DEATH"; // Place of death (4.0) // Available in vCard 3.0. Shoud not use when composing vCard 2.1 file. - public static final String PROPERTY_NAME = "NAME"; - public static final String PROPERTY_NICKNAME = "NICKNAME"; - public static final String PROPERTY_SORT_STRING = "SORT-STRING"; // De-fact property values expressing phonetic names. public static final String PROPERTY_X_PHONETIC_FIRST_NAME = "X-PHONETIC-FIRST-NAME"; public static final String PROPERTY_X_PHONETIC_MIDDLE_NAME = "X-PHONETIC-MIDDLE-NAME"; public static final String PROPERTY_X_PHONETIC_LAST_NAME = "X-PHONETIC-LAST-NAME"; - // Properties both ContactsStruct in Eclair and de-fact vCard extensions - // shown in http://en.wikipedia.org/wiki/VCard support are defined here. + // Properties both ContactsStruct and de-fact vCard extensions + // Shown in http://en.wikipedia.org/wiki/VCard support are defined here. public static final String PROPERTY_X_AIM = "X-AIM"; public static final String PROPERTY_X_MSN = "X-MSN"; public static final String PROPERTY_X_YAHOO = "X-YAHOO"; @@ -89,6 +95,9 @@ public class VCardConstants { public static final String PARAM_TYPE_VOICE = "VOICE"; public static final String PARAM_TYPE_INTERNET = "INTERNET"; + public static final String PARAM_CHARSET = "CHARSET"; + public static final String PARAM_ENCODING = "ENCODING"; + // Abbreviation of "prefered" according to vCard 2.1 specification. // We interpret this value as "primary" property during import/export. // @@ -109,6 +118,12 @@ public class VCardConstants { public static final String PARAM_TYPE_BBS = "BBS"; public static final String PARAM_TYPE_VIDEO = "VIDEO"; + public static final String PARAM_ENCODING_7BIT = "7BIT"; + public static final String PARAM_ENCODING_8BIT = "8BIT"; + public static final String PARAM_ENCODING_QP = "QUOTED-PRINTABLE"; + public static final String PARAM_ENCODING_BASE64 = "BASE64"; // Available in vCard 2.1 + public static final String PARAM_ENCODING_B = "B"; // Available in vCard 3.0 + // TYPE parameters for Phones, which are not formally valid in vCard (at least 2.1). // These types are basically encoded to "X-" parameters when composing vCard. // Parser passes these when "X-" is added to the parameter or not. @@ -126,14 +141,15 @@ public class VCardConstants { public static final String PARAM_ADR_TYPE_DOM = "DOM"; public static final String PARAM_ADR_TYPE_INTL = "INTL"; + public static final String PARAM_LANGUAGE = "LANGUAGE"; + + // SORT-AS parameter introduced in vCard 4.0 (as of rev.13) + public static final String PARAM_SORT_AS = "SORT-AS"; + // TYPE parameters not officially valid but used in some vCard exporter. // Do not use in composer side. public static final String PARAM_EXTRA_TYPE_COMPANY = "COMPANY"; - // DoCoMo specific type parameter. Used with "SOUND" property, which is alternate of SORT-STRING in - // vCard 3.0. - public static final String PARAM_TYPE_X_IRMC_N = "X-IRMC-N"; - public interface ImportOnly { public static final String PROPERTY_X_NICKNAME = "X-NICKNAME"; // Some device emits this "X-" parameter for expressing Google Talk, @@ -142,7 +158,14 @@ public class VCardConstants { public static final String PROPERTY_X_GOOGLE_TALK_WITH_SPACE = "X-GOOGLE TALK"; } - /* package */ static final int MAX_DATA_COLUMN = 15; + //// Mainly for package constants. + + // DoCoMo specific type parameter. Used with "SOUND" property, which is alternate of + // SORT-STRING invCard 3.0. + /* package */ static final String PARAM_TYPE_X_IRMC_N = "X-IRMC-N"; + + // Used in unit test. + public static final int MAX_DATA_COLUMN = 15; /* package */ static final int MAX_CHARACTER_NUMS_QP = 76; static final int MAX_CHARACTER_NUMS_BASE64_V30 = 75; diff --git a/core/java/android/pim/vcard/VCardEntry.java b/core/java/android/pim/vcard/VCardEntry.java index 13277704bdcf..94f7c5fe1b0d 100644 --- a/core/java/android/pim/vcard/VCardEntry.java +++ b/core/java/android/pim/vcard/VCardEntry.java @@ -20,13 +20,11 @@ import android.content.ContentProviderOperation; import android.content.ContentProviderResult; import android.content.ContentResolver; import android.content.OperationApplicationException; -import android.database.Cursor; import android.net.Uri; import android.os.RemoteException; import android.provider.ContactsContract; import android.provider.ContactsContract.Contacts; import android.provider.ContactsContract.Data; -import android.provider.ContactsContract.Groups; import android.provider.ContactsContract.RawContacts; import android.provider.ContactsContract.CommonDataKinds.Email; import android.provider.ContactsContract.CommonDataKinds.Event; @@ -61,9 +59,6 @@ public class VCardEntry { private final static int DEFAULT_ORGANIZATION_TYPE = Organization.TYPE_WORK; - private static final String ACCOUNT_TYPE_GOOGLE = "com.google"; - private static final String GOOGLE_MY_CONTACTS_GROUP = "System Group: My Contacts"; - private static final Map<String, Integer> sImMap = new HashMap<String, Integer>(); static { @@ -78,12 +73,12 @@ public class VCardEntry { Im.PROTOCOL_GOOGLE_TALK); } - static public class PhoneData { + public static class PhoneData { public final int type; public final String data; public final String label; - // isPrimary is changable only when there's no appropriate one existing in - // the original VCard. + // isPrimary is (not final but) changable, only when there's no appropriate one existing + // in the original VCard. public boolean isPrimary; public PhoneData(int type, String data, String label, boolean isPrimary) { this.type = type; @@ -109,13 +104,11 @@ public class VCardEntry { } } - static public class EmailData { + public static class EmailData { public final int type; public final String data; // Used only when TYPE is TYPE_CUSTOM. public final String label; - // isPrimary is changable only when there's no appropriate one existing in - // the original VCard. public boolean isPrimary; public EmailData(int type, String data, String label, boolean isPrimary) { this.type = type; @@ -141,9 +134,9 @@ public class VCardEntry { } } - static public class PostalData { - // Determined by vCard spec. - // PO Box, Extended Addr, Street, Locality, Region, Postal Code, Country Name + public static class PostalData { + // Determined by vCard specification. + // - PO Box, Extended Addr, Street, Locality, Region, Postal Code, Country Name public static final int ADDR_MAX_DATA_SIZE = 7; private final String[] dataArray; public final String pobox; @@ -248,24 +241,28 @@ public class VCardEntry { } } - static public class OrganizationData { + public static class OrganizationData { public final int type; // non-final is Intentional: we may change the values since this info is separated into - // two parts in vCard: "ORG" + "TITLE". + // two parts in vCard: "ORG" + "TITLE", and we have to cope with each field in + // different timing. public String companyName; public String departmentName; public String titleName; + public final String phoneticName; // We won't have this in "TITLE" property. public boolean isPrimary; public OrganizationData(int type, - String companyName, - String departmentName, - String titleName, - boolean isPrimary) { + final String companyName, + final String departmentName, + final String titleName, + final String phoneticName, + final boolean isPrimary) { this.type = type; this.companyName = companyName; this.departmentName = departmentName; this.titleName = titleName; + this.phoneticName = phoneticName; this.isPrimary = isPrimary; } @@ -313,7 +310,7 @@ public class VCardEntry { } } - static public class ImData { + public static class ImData { public final int protocol; public final String customProtocol; public final int type; @@ -434,6 +431,14 @@ public class VCardEntry { } } + // TODO(dmiyakawa): vCard 4.0 logically has multiple formatted names and we need to + // select the most preferable one using PREF parameter. + // + // e.g. (based on rev.13) + // FN;PREF=1:John M. Doe + // FN;PREF=2:John Doe + // FN;PREF=3;John + private String mFamilyName; private String mGivenName; private String mMiddleName; @@ -441,7 +446,7 @@ public class VCardEntry { private String mSuffix; // Used only when no family nor given name is found. - private String mFullName; + private String mFormattedName; private String mPhoneticFamilyName; private String mPhoneticGivenName; @@ -454,6 +459,7 @@ public class VCardEntry { private String mDisplayName; private String mBirthday; + private String mAnniversary; private List<String> mNoteList; private List<PhoneData> mPhoneList; @@ -469,7 +475,7 @@ public class VCardEntry { private final Account mAccount; public VCardEntry() { - this(VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8); + this(VCardConfig.VCARD_TYPE_V21_GENERIC); } public VCardEntry(int vcardType) { @@ -488,7 +494,7 @@ public class VCardEntry { final StringBuilder builder = new StringBuilder(); final String trimed = data.trim(); final String formattedNumber; - if (type == Phone.TYPE_PAGER) { + if (type == Phone.TYPE_PAGER || VCardConfig.refrainPhoneNumberFormatting(mVCardType)) { formattedNumber = trimed; } else { final int length = trimed.length(); @@ -499,9 +505,7 @@ public class VCardEntry { } } - // Use NANP in default when there's no information about locale. - final int formattingType = (VCardConfig.isJapaneseDevice(mVCardType) ? - PhoneNumberUtils.FORMAT_JAPAN : PhoneNumberUtils.FORMAT_NANP); + final int formattingType = VCardUtils.getPhoneNumberFormat(mVCardType); formattedNumber = PhoneNumberUtils.formatNumber(builder.toString(), formattingType); } PhoneData phoneData = new PhoneData(type, formattedNumber, label, isPrimary); @@ -530,22 +534,44 @@ public class VCardEntry { } /** - * Should be called via {@link #handleOrgValue(int, List, boolean)} or + * Should be called via {@link #handleOrgValue(int, List, Map, boolean) or * {@link #handleTitleValue(String)}. */ private void addNewOrganization(int type, final String companyName, final String departmentName, - final String titleName, boolean isPrimary) { + final String titleName, + final String phoneticName, + final boolean isPrimary) { if (mOrganizationList == null) { mOrganizationList = new ArrayList<OrganizationData>(); } mOrganizationList.add(new OrganizationData(type, companyName, - departmentName, titleName, isPrimary)); + departmentName, titleName, phoneticName, isPrimary)); } private static final List<String> sEmptyList = Collections.unmodifiableList(new ArrayList<String>(0)); + private String buildSinglePhoneticNameFromSortAsParam(Map<String, Collection<String>> paramMap) { + final Collection<String> sortAsCollection = paramMap.get(VCardConstants.PARAM_SORT_AS); + if (sortAsCollection != null && sortAsCollection.size() != 0) { + if (sortAsCollection.size() > 1) { + Log.w(LOG_TAG, "Incorrect multiple SORT_AS parameters detected: " + + Arrays.toString(sortAsCollection.toArray())); + } + final List<String> sortNames = + VCardUtils.constructListFromValue(sortAsCollection.iterator().next(), + mVCardType); + final StringBuilder builder = new StringBuilder(); + for (final String elem : sortNames) { + builder.append(elem); + } + return builder.toString(); + } else { + return null; + } + } + /** * Set "ORG" related values to the appropriate data. If there's more than one * {@link OrganizationData} objects, this input data are attached to the last one which @@ -553,7 +579,9 @@ public class VCardEntry { * {@link OrganizationData} object, a new {@link OrganizationData} is created, * whose title is set to null. */ - private void handleOrgValue(final int type, List<String> orgList, boolean isPrimary) { + private void handleOrgValue(final int type, List<String> orgList, + Map<String, Collection<String>> paramMap, boolean isPrimary) { + final String phoneticName = buildSinglePhoneticNameFromSortAsParam(paramMap); if (orgList == null) { orgList = sEmptyList; } @@ -588,7 +616,7 @@ public class VCardEntry { if (mOrganizationList == null) { // Create new first organization entry, with "null" title which may be // added via handleTitleValue(). - addNewOrganization(type, companyName, departmentName, null, isPrimary); + addNewOrganization(type, companyName, departmentName, null, phoneticName, isPrimary); return; } for (OrganizationData organizationData : mOrganizationList) { @@ -606,7 +634,7 @@ public class VCardEntry { } // No OrganizatioData is available. Create another one, with "null" title, which may be // added via handleTitleValue(). - addNewOrganization(type, companyName, departmentName, null, isPrimary); + addNewOrganization(type, companyName, departmentName, null, phoneticName, isPrimary); } /** @@ -620,7 +648,7 @@ public class VCardEntry { if (mOrganizationList == null) { // Create new first organization entry, with "null" other info, which may be // added via handleOrgValue(). - addNewOrganization(DEFAULT_ORGANIZATION_TYPE, null, null, title, false); + addNewOrganization(DEFAULT_ORGANIZATION_TYPE, null, null, title, null, false); return; } for (OrganizationData organizationData : mOrganizationList) { @@ -631,7 +659,7 @@ public class VCardEntry { } // No Organization is available. Create another one, with "null" other info, which may be // added via handleOrgValue(). - addNewOrganization(DEFAULT_ORGANIZATION_TYPE, null, null, title, false); + addNewOrganization(DEFAULT_ORGANIZATION_TYPE, null, null, title, null, false); } private void addIm(int protocol, String customProtocol, int type, @@ -657,11 +685,54 @@ public class VCardEntry { mPhotoList.add(photoData); } + /** + * Tries to extract paramMap, constructs SORT-AS parameter values, and store them in + * appropriate phonetic name variables. + * + * This method does not care the vCard version. Even when we have SORT-AS parameters in + * invalid versions (i.e. 2.1 and 3.0), we scilently accept them so that we won't drop + * meaningful information. If we had this parameter in the N field of vCard 3.0, and + * the contact data also have SORT-STRING, we will prefer SORT-STRING, since it is + * regitimate property to be understood. + */ + private void tryHandleSortAsName(final Map<String, Collection<String>> paramMap) { + if (VCardConfig.isVersion30(mVCardType) && + !(TextUtils.isEmpty(mPhoneticFamilyName) && + TextUtils.isEmpty(mPhoneticMiddleName) && + TextUtils.isEmpty(mPhoneticGivenName))) { + return; + } + + final Collection<String> sortAsCollection = paramMap.get(VCardConstants.PARAM_SORT_AS); + if (sortAsCollection != null && sortAsCollection.size() != 0) { + if (sortAsCollection.size() > 1) { + Log.w(LOG_TAG, "Incorrect multiple SORT_AS parameters detected: " + + Arrays.toString(sortAsCollection.toArray())); + } + final List<String> sortNames = + VCardUtils.constructListFromValue(sortAsCollection.iterator().next(), + mVCardType); + int size = sortNames.size(); + if (size > 3) { + size = 3; + } + switch (size) { + case 3: mPhoneticMiddleName = sortNames.get(2); //$FALL-THROUGH$ + case 2: mPhoneticGivenName = sortNames.get(1); //$FALL-THROUGH$ + default: mPhoneticFamilyName = sortNames.get(0); break; + } + } + } + @SuppressWarnings("fallthrough") - private void handleNProperty(List<String> elems) { + private void handleNProperty(final List<String> paramValues, + Map<String, Collection<String>> paramMap) { + // in vCard 4.0, SORT-AS parameter is available. + tryHandleSortAsName(paramMap); + // Family, Given, Middle, Prefix, Suffix. (1 - 5) int size; - if (elems == null || (size = elems.size()) < 1) { + if (paramValues == null || (size = paramValues.size()) < 1) { return; } if (size > 5) { @@ -669,12 +740,12 @@ public class VCardEntry { } switch (size) { - // fallthrough - case 5: mSuffix = elems.get(4); - case 4: mPrefix = elems.get(3); - case 3: mMiddleName = elems.get(2); - case 2: mGivenName = elems.get(1); - default: mFamilyName = elems.get(0); + // Fall-through. + case 5: mSuffix = paramValues.get(4); + case 4: mPrefix = paramValues.get(3); + case 3: mMiddleName = paramValues.get(2); + case 2: mGivenName = paramValues.get(1); + default: mFamilyName = paramValues.get(0); } } @@ -755,13 +826,13 @@ public class VCardEntry { if (propName.equals(VCardConstants.PROPERTY_VERSION)) { // vCard version. Ignore this. } else if (propName.equals(VCardConstants.PROPERTY_FN)) { - mFullName = propValue; - } else if (propName.equals(VCardConstants.PROPERTY_NAME) && mFullName == null) { + mFormattedName = propValue; + } else if (propName.equals(VCardConstants.PROPERTY_NAME) && mFormattedName == null) { // Only in vCard 3.0. Use this if FN, which must exist in vCard 3.0 but may not // actually exist in the real vCard data, does not exist. - mFullName = propValue; + mFormattedName = propValue; } else if (propName.equals(VCardConstants.PROPERTY_N)) { - handleNProperty(propValueList); + handleNProperty(propValueList, paramMap); } else if (propName.equals(VCardConstants.PROPERTY_SORT_STRING)) { mPhoneticFullName = propValue; } else if (propName.equals(VCardConstants.PROPERTY_NICKNAME) || @@ -776,8 +847,7 @@ public class VCardEntry { // which is correct behavior from the view of vCard 2.1. // But we want it to be separated, so do the separation here. final List<String> phoneticNameList = - VCardUtils.constructListFromValue(propValue, - VCardConfig.isV30(mVCardType)); + VCardUtils.constructListFromValue(propValue, mVCardType); handlePhoneticNameFromSound(phoneticNameList); } else { // Ignore this field since Android cannot understand what it is. @@ -878,7 +948,7 @@ public class VCardEntry { } } } - handleOrgValue(type, propValueList, isPrimary); + handleOrgValue(type, propValueList, paramMap, isPrimary); } else if (propName.equals(VCardConstants.PROPERTY_TITLE)) { handleTitleValue(propValue); } else if (propName.equals(VCardConstants.PROPERTY_ROLE)) { @@ -955,7 +1025,7 @@ public class VCardEntry { } } if (type < 0) { - type = Phone.TYPE_HOME; + type = Im.TYPE_HOME; } addIm(protocol, null, type, propValue, isPrimary); } else if (propName.equals(VCardConstants.PROPERTY_NOTE)) { @@ -967,6 +1037,8 @@ public class VCardEntry { mWebsiteList.add(propValue); } else if (propName.equals(VCardConstants.PROPERTY_BDAY)) { mBirthday = propValue; + } else if (propName.equals(VCardConstants.PROPERTY_ANNIVERSARY)) { + mAnniversary = propValue; } else if (propName.equals(VCardConstants.PROPERTY_X_PHONETIC_FIRST_NAME)) { mPhoneticGivenName = propValue; } else if (propName.equals(VCardConstants.PROPERTY_X_PHONETIC_MIDDLE_NAME)) { @@ -975,33 +1047,9 @@ public class VCardEntry { mPhoneticFamilyName = propValue; } else if (propName.equals(VCardConstants.PROPERTY_X_ANDROID_CUSTOM)) { final List<String> customPropertyList = - VCardUtils.constructListFromValue(propValue, - VCardConfig.isV30(mVCardType)); + VCardUtils.constructListFromValue(propValue, mVCardType); handleAndroidCustomProperty(customPropertyList); - /*} else if (propName.equals("REV")) { - // Revision of this VCard entry. I think we can ignore this. - } else if (propName.equals("UID")) { - } else if (propName.equals("KEY")) { - // Type is X509 or PGP? I don't know how to handle this... - } else if (propName.equals("MAILER")) { - } else if (propName.equals("TZ")) { - } else if (propName.equals("GEO")) { - } else if (propName.equals("CLASS")) { - // vCard 3.0 only. - // e.g. CLASS:CONFIDENTIAL - } else if (propName.equals("PROFILE")) { - // VCard 3.0 only. Must be "VCARD". I think we can ignore this. - } else if (propName.equals("CATEGORIES")) { - // VCard 3.0 only. - // e.g. CATEGORIES:INTERNET,IETF,INDUSTRY,INFORMATION TECHNOLOGY - } else if (propName.equals("SOURCE")) { - // VCard 3.0 only. - } else if (propName.equals("PRODID")) { - // VCard 3.0 only. - // To specify the identifier for the product that created - // the vCard object.*/ } else { - // Unknown X- words and IANA token. } } @@ -1017,8 +1065,8 @@ public class VCardEntry { */ private void constructDisplayName() { // FullName (created via "FN" or "NAME" field) is prefered. - if (!TextUtils.isEmpty(mFullName)) { - mDisplayName = mFullName; + if (!TextUtils.isEmpty(mFormattedName)) { + mDisplayName = mFormattedName; } else if (!(TextUtils.isEmpty(mFamilyName) && TextUtils.isEmpty(mGivenName))) { mDisplayName = VCardUtils.constructNameFromElements(mVCardType, mFamilyName, mMiddleName, mGivenName, mPrefix, mSuffix); @@ -1063,23 +1111,6 @@ public class VCardEntry { if (mAccount != null) { builder.withValue(RawContacts.ACCOUNT_NAME, mAccount.name); builder.withValue(RawContacts.ACCOUNT_TYPE, mAccount.type); - - // Assume that caller side creates this group if it does not exist. - if (ACCOUNT_TYPE_GOOGLE.equals(mAccount.type)) { - final Cursor cursor = resolver.query(Groups.CONTENT_URI, new String[] { - Groups.SOURCE_ID }, - Groups.TITLE + "=?", new String[] { - GOOGLE_MY_CONTACTS_GROUP }, null); - try { - if (cursor != null && cursor.moveToFirst()) { - myGroupsId = cursor.getString(0); - } - } finally { - if (cursor != null) { - cursor.close(); - } - } - } } else { builder.withValue(RawContacts.ACCOUNT_NAME, null); builder.withValue(RawContacts.ACCOUNT_TYPE, null); @@ -1155,6 +1186,9 @@ public class VCardEntry { if (organizationData.titleName != null) { builder.withValue(Organization.TITLE, organizationData.titleName); } + if (organizationData.phoneticName != null) { + builder.withValue(Organization.PHONETIC_NAME, organizationData.phoneticName); + } if (organizationData.isPrimary) { builder.withValue(Organization.IS_PRIMARY, 1); } @@ -1196,12 +1230,14 @@ public class VCardEntry { builder.withValue(Data.MIMETYPE, Im.CONTENT_ITEM_TYPE); builder.withValue(Im.TYPE, imData.type); builder.withValue(Im.PROTOCOL, imData.protocol); + builder.withValue(Im.DATA, imData.data); if (imData.protocol == Im.PROTOCOL_CUSTOM) { builder.withValue(Im.CUSTOM_PROTOCOL, imData.customProtocol); } if (imData.isPrimary) { builder.withValue(Data.IS_PRIMARY, 1); } + operationList.add(builder.build()); } } @@ -1250,6 +1286,15 @@ public class VCardEntry { operationList.add(builder.build()); } + if (!TextUtils.isEmpty(mAnniversary)) { + builder = ContentProviderOperation.newInsert(Data.CONTENT_URI); + builder.withValueBackReference(Event.RAW_CONTACT_ID, 0); + builder.withValue(Data.MIMETYPE, Event.CONTENT_ITEM_TYPE); + builder.withValue(Event.START_DATE, mAnniversary); + builder.withValue(Event.TYPE, Event.TYPE_ANNIVERSARY); + operationList.add(builder.build()); + } + if (mAndroidCustomPropertyList != null) { for (List<String> customPropertyList : mAndroidCustomPropertyList) { int size = customPropertyList.size(); @@ -1321,7 +1366,7 @@ public class VCardEntry { && TextUtils.isEmpty(mGivenName) && TextUtils.isEmpty(mPrefix) && TextUtils.isEmpty(mSuffix) - && TextUtils.isEmpty(mFullName) + && TextUtils.isEmpty(mFormattedName) && TextUtils.isEmpty(mPhoneticFamilyName) && TextUtils.isEmpty(mPhoneticMiddleName) && TextUtils.isEmpty(mPhoneticGivenName) @@ -1380,7 +1425,7 @@ public class VCardEntry { } public String getFullName() { - return mFullName; + return mFormattedName; } public String getPhoneticFamilyName() { diff --git a/core/java/android/pim/vcard/VCardEntryCommitter.java b/core/java/android/pim/vcard/VCardEntryCommitter.java index 59a2baf13179..a8c8057a58b6 100644 --- a/core/java/android/pim/vcard/VCardEntryCommitter.java +++ b/core/java/android/pim/vcard/VCardEntryCommitter.java @@ -52,9 +52,9 @@ public class VCardEntryCommitter implements VCardEntryHandler { } } - public void onEntryCreated(final VCardEntry contactStruct) { + public void onEntryCreated(final VCardEntry vcardEntry) { long start = System.currentTimeMillis(); - mCreatedUris.add(contactStruct.pushIntoContentResolver(mContentResolver)); + mCreatedUris.add(vcardEntry.pushIntoContentResolver(mContentResolver)); mTimeToCommit += System.currentTimeMillis() - start; } diff --git a/core/java/android/pim/vcard/VCardEntryConstructor.java b/core/java/android/pim/vcard/VCardEntryConstructor.java index 290ca2bfec14..aa3e3e2f585f 100644 --- a/core/java/android/pim/vcard/VCardEntryConstructor.java +++ b/core/java/android/pim/vcard/VCardEntryConstructor.java @@ -16,78 +16,82 @@ package android.pim.vcard; import android.accounts.Account; +import android.text.TextUtils; +import android.util.Base64; import android.util.CharsetUtils; import android.util.Log; -import org.apache.commons.codec.DecoderException; -import org.apache.commons.codec.binary.Base64; -import org.apache.commons.codec.net.QuotedPrintableCodec; - -import java.io.UnsupportedEncodingException; -import java.nio.ByteBuffer; -import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Collection; import java.util.List; +/** + * <p> + * The {@link VCardInterpreter} implementation which enables {@link VCardEntryHandler} objects + * to easily handle each vCard entry. + * </p> + * <p> + * This class understand details inside vCard and translates it to {@link VCardEntry}. + * Then the class throw it to {@link VCardEntryHandler} registered via + * {@link #addEntryHandler(VCardEntryHandler)}, so that all those registered objects + * are able to handle the {@link VCardEntry} object. + * </p> + * <p> + * If you want to know the detail inside vCard, it would be better to implement + * {@link VCardInterpreter} directly, instead of relying on this class and + * {@link VCardEntry} created by the object. + * </p> + */ public class VCardEntryConstructor implements VCardInterpreter { private static String LOG_TAG = "VCardEntryConstructor"; - /** - * If there's no other information available, this class uses this charset for encoding - * byte arrays to String. - */ - /* package */ static final String DEFAULT_CHARSET_FOR_DECODED_BYTES = "UTF-8"; - private VCardEntry.Property mCurrentProperty = new VCardEntry.Property(); - private VCardEntry mCurrentContactStruct; + private VCardEntry mCurrentVCardEntry; private String mParamType; - /** - * The charset using which {@link VCardInterpreter} parses the text. - */ - private String mInputCharset; - - /** - * The charset with which byte array is encoded to String. - */ - final private String mCharsetForDecodedBytes; - final private boolean mStrictLineBreakParsing; - final private int mVCardType; - final private Account mAccount; + // The charset using which {@link VCardInterpreter} parses the text. + // Each String is first decoded into binary stream with this charset, and encoded back + // to "target charset", which may be explicitly specified by the vCard with "CHARSET" + // property or implicitly mentioned by its version (e.g. vCard 3.0 recommends UTF-8). + private final String mSourceCharset; + + private final boolean mStrictLineBreaking; + private final int mVCardType; + private final Account mAccount; - /** For measuring performance. */ + // For measuring performance. private long mTimePushIntoContentResolver; - final private List<VCardEntryHandler> mEntryHandlers = new ArrayList<VCardEntryHandler>(); + private final List<VCardEntryHandler> mEntryHandlers = new ArrayList<VCardEntryHandler>(); public VCardEntryConstructor() { - this(null, null, false, VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8, null); + this(VCardConfig.VCARD_TYPE_V21_GENERIC, null); } public VCardEntryConstructor(final int vcardType) { - this(null, null, false, vcardType, null); + this(vcardType, null, null, false); + } + + public VCardEntryConstructor(final int vcardType, final Account account) { + this(vcardType, account, null, false); } - public VCardEntryConstructor(final String charset, final boolean strictLineBreakParsing, - final int vcardType, final Account account) { - this(null, charset, strictLineBreakParsing, vcardType, account); + public VCardEntryConstructor(final int vcardType, final Account account, + final String inputCharset) { + this(vcardType, account, inputCharset, false); } - public VCardEntryConstructor(final String inputCharset, final String charsetForDetodedBytes, - final boolean strictLineBreakParsing, final int vcardType, - final Account account) { + /** + * @hide Just for testing. + */ + public VCardEntryConstructor(final int vcardType, final Account account, + final String inputCharset, final boolean strictLineBreakParsing) { if (inputCharset != null) { - mInputCharset = inputCharset; - } else { - mInputCharset = VCardConfig.DEFAULT_CHARSET; - } - if (charsetForDetodedBytes != null) { - mCharsetForDecodedBytes = charsetForDetodedBytes; + mSourceCharset = inputCharset; } else { - mCharsetForDecodedBytes = DEFAULT_CHARSET_FOR_DECODED_BYTES; + mSourceCharset = VCardConfig.DEFAULT_INTERMEDIATE_CHARSET; } - mStrictLineBreakParsing = strictLineBreakParsing; + mStrictLineBreaking = strictLineBreakParsing; mVCardType = vcardType; mAccount = account; } @@ -95,60 +99,63 @@ public class VCardEntryConstructor implements VCardInterpreter { public void addEntryHandler(VCardEntryHandler entryHandler) { mEntryHandlers.add(entryHandler); } - + + @Override public void start() { for (VCardEntryHandler entryHandler : mEntryHandlers) { entryHandler.onStart(); } } + @Override public void end() { for (VCardEntryHandler entryHandler : mEntryHandlers) { entryHandler.onEnd(); } } - /** - * Called when the parse failed between {@link #startEntry()} and {@link #endEntry()}. - */ public void clear() { - mCurrentContactStruct = null; + mCurrentVCardEntry = null; mCurrentProperty = new VCardEntry.Property(); } - /** - * Assume that VCard is not nested. In other words, this code does not accept - */ + @Override public void startEntry() { - if (mCurrentContactStruct != null) { + if (mCurrentVCardEntry != null) { Log.e(LOG_TAG, "Nested VCard code is not supported now."); } - mCurrentContactStruct = new VCardEntry(mVCardType, mAccount); + mCurrentVCardEntry = new VCardEntry(mVCardType, mAccount); } + @Override public void endEntry() { - mCurrentContactStruct.consolidateFields(); + mCurrentVCardEntry.consolidateFields(); for (VCardEntryHandler entryHandler : mEntryHandlers) { - entryHandler.onEntryCreated(mCurrentContactStruct); + entryHandler.onEntryCreated(mCurrentVCardEntry); } - mCurrentContactStruct = null; + mCurrentVCardEntry = null; } + @Override public void startProperty() { mCurrentProperty.clear(); } + @Override public void endProperty() { - mCurrentContactStruct.addProperty(mCurrentProperty); + mCurrentVCardEntry.addProperty(mCurrentProperty); } + @Override public void propertyName(String name) { mCurrentProperty.setPropertyName(name); } + @Override public void propertyGroup(String group) { } + @Override public void propertyParamType(String type) { if (mParamType != null) { Log.e(LOG_TAG, "propertyParamType() is called more than once " + @@ -157,122 +164,41 @@ public class VCardEntryConstructor implements VCardInterpreter { mParamType = type; } + @Override public void propertyParamValue(String value) { if (mParamType == null) { // From vCard 2.1 specification. vCard 3.0 formally does not allow this case. mParamType = "TYPE"; } + if (!VCardUtils.containsOnlyAlphaDigitHyphen(value)) { + value = VCardUtils.convertStringCharset( + value, mSourceCharset, VCardConfig.DEFAULT_IMPORT_CHARSET); + } mCurrentProperty.addParameter(mParamType, value); mParamType = null; } - private String encodeString(String originalString, String charsetForDecodedBytes) { - if (mInputCharset.equalsIgnoreCase(charsetForDecodedBytes)) { - return originalString; - } - Charset charset = Charset.forName(mInputCharset); - ByteBuffer byteBuffer = charset.encode(originalString); - // byteBuffer.array() "may" return byte array which is larger than - // byteBuffer.remaining(). Here, we keep on the safe side. - byte[] bytes = new byte[byteBuffer.remaining()]; - byteBuffer.get(bytes); - try { - return new String(bytes, charsetForDecodedBytes); - } catch (UnsupportedEncodingException e) { - Log.e(LOG_TAG, "Failed to encode: charset=" + charsetForDecodedBytes); - return null; + private String handleOneValue(String value, + String sourceCharset, String targetCharset, String encoding) { + // It is possible when some of multiple values are empty. + // e.g. N:;a;;; -> values are "", "a", "", "", and "". + if (TextUtils.isEmpty(value)) { + return ""; } - } - private String handleOneValue(String value, String charsetForDecodedBytes, String encoding) { if (encoding != null) { if (encoding.equals("BASE64") || encoding.equals("B")) { - mCurrentProperty.setPropertyBytes(Base64.decodeBase64(value.getBytes())); + mCurrentProperty.setPropertyBytes(Base64.decode(value.getBytes(), Base64.DEFAULT)); return value; } else if (encoding.equals("QUOTED-PRINTABLE")) { - // "= " -> " ", "=\t" -> "\t". - // Previous code had done this replacement. Keep on the safe side. - StringBuilder builder = new StringBuilder(); - int length = value.length(); - for (int i = 0; i < length; i++) { - char ch = value.charAt(i); - if (ch == '=' && i < length - 1) { - char nextCh = value.charAt(i + 1); - if (nextCh == ' ' || nextCh == '\t') { - - builder.append(nextCh); - i++; - continue; - } - } - builder.append(ch); - } - String quotedPrintable = builder.toString(); - - String[] lines; - if (mStrictLineBreakParsing) { - lines = quotedPrintable.split("\r\n"); - } else { - builder = new StringBuilder(); - length = quotedPrintable.length(); - ArrayList<String> list = new ArrayList<String>(); - for (int i = 0; i < length; i++) { - char ch = quotedPrintable.charAt(i); - if (ch == '\n') { - list.add(builder.toString()); - builder = new StringBuilder(); - } else if (ch == '\r') { - list.add(builder.toString()); - builder = new StringBuilder(); - if (i < length - 1) { - char nextCh = quotedPrintable.charAt(i + 1); - if (nextCh == '\n') { - i++; - } - } - } else { - builder.append(ch); - } - } - String finalLine = builder.toString(); - if (finalLine.length() > 0) { - list.add(finalLine); - } - lines = list.toArray(new String[0]); - } - - builder = new StringBuilder(); - for (String line : lines) { - if (line.endsWith("=")) { - line = line.substring(0, line.length() - 1); - } - builder.append(line); - } - byte[] bytes; - try { - bytes = builder.toString().getBytes(mInputCharset); - } catch (UnsupportedEncodingException e1) { - Log.e(LOG_TAG, "Failed to encode: charset=" + mInputCharset); - bytes = builder.toString().getBytes(); - } - - try { - bytes = QuotedPrintableCodec.decodeQuotedPrintable(bytes); - } catch (DecoderException e) { - Log.e(LOG_TAG, "Failed to decode quoted-printable: " + e); - return ""; - } - - try { - return new String(bytes, charsetForDecodedBytes); - } catch (UnsupportedEncodingException e) { - Log.e(LOG_TAG, "Failed to encode: charset=" + charsetForDecodedBytes); - return new String(bytes); - } + return VCardUtils.parseQuotedPrintable( + value, mStrictLineBreaking, sourceCharset, targetCharset); } - // Unknown encoding. Fall back to default. + Log.w(LOG_TAG, "Unknown encoding. Fall back to default."); } - return encodeString(value, charsetForDecodedBytes); + + // Just translate the charset of a given String from inputCharset to a system one. + return VCardUtils.convertStringCharset(value, sourceCharset, targetCharset); } public void propertyValues(List<String> values) { @@ -280,24 +206,27 @@ public class VCardEntryConstructor implements VCardInterpreter { return; } - final Collection<String> charsetCollection = mCurrentProperty.getParameters("CHARSET"); - final String charset = - ((charsetCollection != null) ? charsetCollection.iterator().next() : null); - final Collection<String> encodingCollection = mCurrentProperty.getParameters("ENCODING"); + final Collection<String> charsetCollection = + mCurrentProperty.getParameters(VCardConstants.PARAM_CHARSET); + final Collection<String> encodingCollection = + mCurrentProperty.getParameters(VCardConstants.PARAM_ENCODING); final String encoding = ((encodingCollection != null) ? encodingCollection.iterator().next() : null); - - String charsetForDecodedBytes = CharsetUtils.nameForDefaultVendor(charset); - if (charsetForDecodedBytes == null || charsetForDecodedBytes.length() == 0) { - charsetForDecodedBytes = mCharsetForDecodedBytes; + String targetCharset = CharsetUtils.nameForDefaultVendor( + ((charsetCollection != null) ? charsetCollection.iterator().next() : null)); + if (TextUtils.isEmpty(targetCharset)) { + targetCharset = VCardConfig.DEFAULT_IMPORT_CHARSET; } for (final String value : values) { mCurrentProperty.addToPropertyValueList( - handleOneValue(value, charsetForDecodedBytes, encoding)); + handleOneValue(value, mSourceCharset, targetCharset, encoding)); } } + /** + * @hide + */ public void showPerformanceInfo() { Log.d(LOG_TAG, "time for insert ContactStruct to database: " + mTimePushIntoContentResolver + " ms"); diff --git a/core/java/android/pim/vcard/VCardEntryHandler.java b/core/java/android/pim/vcard/VCardEntryHandler.java index 83a67fe2c6f8..56bf69d5d371 100644 --- a/core/java/android/pim/vcard/VCardEntryHandler.java +++ b/core/java/android/pim/vcard/VCardEntryHandler.java @@ -16,8 +16,13 @@ package android.pim.vcard; /** - * The interface called by {@link VCardEntryConstructor}. Useful when you don't want to - * handle detailed information as what {@link VCardParser} provides via {@link VCardInterpreter}. + * <p> + * The interface called by {@link VCardEntryConstructor}. + * </p> + * <p> + * This class is useful when you don't want to know vCard data in detail. If you want to know + * it, it would be better to consider using {@link VCardInterpreter}. + * </p> */ public interface VCardEntryHandler { /** diff --git a/core/java/android/pim/vcard/VCardInterpreter.java b/core/java/android/pim/vcard/VCardInterpreter.java index b5237c066bf0..03704a22a962 100644 --- a/core/java/android/pim/vcard/VCardInterpreter.java +++ b/core/java/android/pim/vcard/VCardInterpreter.java @@ -20,7 +20,7 @@ import java.util.List; /** * <P> * The interface which should be implemented by the classes which have to analyze each - * vCard entry more minutely than {@link VCardEntry} class analysis. + * vCard entry minutely. * </P> * <P> * Here, there are several terms specific to vCard (and this library). diff --git a/core/java/android/pim/vcard/VCardInterpreterCollection.java b/core/java/android/pim/vcard/VCardInterpreterCollection.java index 99f81f704d77..77e44a02b2e0 100644 --- a/core/java/android/pim/vcard/VCardInterpreterCollection.java +++ b/core/java/android/pim/vcard/VCardInterpreterCollection.java @@ -23,9 +23,9 @@ import java.util.List; * {@link VCardInterpreter} objects and make a user object treat them as one * {@link VCardInterpreter} object. */ -public class VCardInterpreterCollection implements VCardInterpreter { +public final class VCardInterpreterCollection implements VCardInterpreter { private final Collection<VCardInterpreter> mInterpreterCollection; - + public VCardInterpreterCollection(Collection<VCardInterpreter> interpreterCollection) { mInterpreterCollection = interpreterCollection; } diff --git a/core/java/android/pim/vcard/VCardParser.java b/core/java/android/pim/vcard/VCardParser.java index 57c52a646cdd..31b9369231f5 100644 --- a/core/java/android/pim/vcard/VCardParser.java +++ b/core/java/android/pim/vcard/VCardParser.java @@ -20,82 +20,36 @@ import android.pim.vcard.exception.VCardException; import java.io.IOException; import java.io.InputStream; -public abstract class VCardParser { - protected final int mParseType; - protected boolean mCanceled; - - public VCardParser() { - this(VCardConfig.PARSE_TYPE_UNKNOWN); - } - - public VCardParser(int parseType) { - mParseType = parseType; - } - +public interface VCardParser { /** - * <P> - * Parses the given stream and send the VCard data into VCardBuilderBase object. - * </P. - * <P> + * <p> + * Parses the given stream and send the vCard data into VCardBuilderBase object. + * </p>. + * <p> * Note that vCard 2.1 specification allows "CHARSET" parameter, and some career sets * local encoding to it. For example, Japanese phone career uses Shift_JIS, which is - * formally allowed in VCard 2.1, but not recommended in VCard 3.0. In VCard 2.1, - * In some exreme case, some VCard may have different charsets in one VCard (though - * we do not see any device which emits such kind of malicious data) - * </P> - * <P> - * In order to avoid "misunderstanding" charset as much as possible, this method - * use "ISO-8859-1" for reading the stream. When charset is specified in some property - * (with "CHARSET=..." parameter), the string is decoded to raw bytes and encoded to - * the charset. This method assumes that "ISO-8859-1" has 1 to 1 mapping in all 8bit - * characters, which is not completely sure. In some cases, this "decoding-encoding" - * scheme may fail. To avoid the case, - * </P> - * <P> - * We recommend you to use {@link VCardSourceDetector} and detect which kind of source the - * VCard comes from and explicitly specify a charset using the result. - * </P> + * formally allowed in vCard 2.1, but not allowed in vCard 3.0. In vCard 2.1, + * In some exreme case, it is allowed for vCard to have different charsets in one vCard. + * </p> + * <p> + * We recommend you use {@link VCardSourceDetector} and detect which kind of source the + * vCard comes from and explicitly specify a charset using the result. + * </p> * * @param is The source to parse. * @param interepreter A {@link VCardInterpreter} object which used to construct data. - * @return Returns true for success. Otherwise returns false. - * @throws IOException, VCardException - */ - public abstract boolean parse(InputStream is, VCardInterpreter interepreter) - throws IOException, VCardException; - - /** - * <P> - * The method variants which accept charset. - * </P> - * <P> - * RFC 2426 "recommends" (not forces) to use UTF-8, so it may be OK to use - * UTF-8 as an encoding when parsing vCard 3.0. But note that some Japanese - * phone uses Shift_JIS as a charset (e.g. W61SH), and another uses - * "CHARSET=SHIFT_JIS", which is explicitly prohibited in vCard 3.0 specification (e.g. W53K). - * </P> - * - * @param is The source to parse. - * @param charset Charset to be used. - * @param builder The VCardBuilderBase object. - * @return Returns true when successful. Otherwise returns false. * @throws IOException, VCardException */ - public abstract boolean parse(InputStream is, String charset, VCardInterpreter builder) + public void parse(InputStream is, VCardInterpreter interepreter) throws IOException, VCardException; - - /** - * The method variants which tells this object the operation is already canceled. - */ - public abstract void parse(InputStream is, String charset, - VCardInterpreter builder, boolean canceled) - throws IOException, VCardException; - + /** - * Cancel parsing. - * Actual cancel is done after the end of the current one vcard entry parsing. + * <p> + * Cancel parsing vCard. Useful when you want to stop the parse in the other threads. + * </p> + * <p> + * Actual cancel is done after parsing the current vcard. + * </p> */ - public void cancel() { - mCanceled = true; - } + public abstract void cancel(); } diff --git a/core/java/android/pim/vcard/VCardParserImpl_V21.java b/core/java/android/pim/vcard/VCardParserImpl_V21.java new file mode 100644 index 000000000000..8b365eb417ad --- /dev/null +++ b/core/java/android/pim/vcard/VCardParserImpl_V21.java @@ -0,0 +1,1046 @@ +/* + * 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 android.pim.vcard; + +import android.pim.vcard.exception.VCardAgentNotSupportedException; +import android.pim.vcard.exception.VCardException; +import android.pim.vcard.exception.VCardInvalidCommentLineException; +import android.pim.vcard.exception.VCardInvalidLineException; +import android.pim.vcard.exception.VCardNestedException; +import android.pim.vcard.exception.VCardVersionException; +import android.text.TextUtils; +import android.util.Log; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * <p> + * Basic implementation achieving vCard parsing. Based on vCard 2.1, + * </p> + * @hide + */ +/* package */ class VCardParserImpl_V21 { + private static final String LOG_TAG = "VCardParserImpl_V21"; + + private static final class EmptyInterpreter implements VCardInterpreter { + @Override + public void end() { + } + @Override + public void endEntry() { + } + @Override + public void endProperty() { + } + @Override + public void propertyGroup(String group) { + } + @Override + public void propertyName(String name) { + } + @Override + public void propertyParamType(String type) { + } + @Override + public void propertyParamValue(String value) { + } + @Override + public void propertyValues(List<String> values) { + } + @Override + public void start() { + } + @Override + public void startEntry() { + } + @Override + public void startProperty() { + } + } + + protected static final class CustomBufferedReader extends BufferedReader { + private long mTime; + + /** + * Needed since "next line" may be null due to end of line. + */ + private boolean mNextLineIsValid; + private String mNextLine; + + public CustomBufferedReader(Reader in) { + super(in); + } + + @Override + public String readLine() throws IOException { + if (mNextLineIsValid) { + final String ret = mNextLine; + mNextLine = null; + mNextLineIsValid = false; + return ret; + } + + long start = System.currentTimeMillis(); + final String line = super.readLine(); + long end = System.currentTimeMillis(); + mTime += end - start; + return line; + } + + /** + * Read one line, but make this object store it in its queue. + */ + public String peekLine() throws IOException { + if (!mNextLineIsValid) { + long start = System.currentTimeMillis(); + final String line = super.readLine(); + long end = System.currentTimeMillis(); + mTime += end - start; + + mNextLine = line; + mNextLineIsValid = true; + } + + return mNextLine; + } + + public long getTotalmillisecond() { + return mTime; + } + } + + private static final String DEFAULT_ENCODING = "8BIT"; + + protected boolean mCanceled; + protected VCardInterpreter mInterpreter; + + protected final String mIntermediateCharset; + + /** + * <p> + * The encoding type for deconding byte streams. This member variable is + * reset to a default encoding every time when a new item comes. + * </p> + * <p> + * "Encoding" in vCard is different from "Charset". It is mainly used for + * addresses, notes, images. "7BIT", "8BIT", "BASE64", and + * "QUOTED-PRINTABLE" are known examples. + * </p> + */ + protected String mCurrentEncoding; + + /** + * <p> + * The reader object to be used internally. + * </p> + * <p> + * Developers should not directly read a line from this object. Use + * getLine() unless there some reason. + * </p> + */ + protected CustomBufferedReader mReader; + + /** + * <p> + * Set for storing unkonwn TYPE attributes, which is not acceptable in vCard + * specification, but happens to be seen in real world vCard. + * </p> + */ + protected final Set<String> mUnknownTypeSet = new HashSet<String>(); + + /** + * <p> + * Set for storing unkonwn VALUE attributes, which is not acceptable in + * vCard specification, but happens to be seen in real world vCard. + * </p> + */ + protected final Set<String> mUnknownValueSet = new HashSet<String>(); + + + // In some cases, vCard is nested. Currently, we only consider the most + // interior vCard data. + // See v21_foma_1.vcf in test directory for more information. + // TODO: Don't ignore by using count, but read all of information outside vCard. + private int mNestCount; + + // Used only for parsing END:VCARD. + private String mPreviousLine; + + // For measuring performance. + private long mTimeTotal; + private long mTimeReadStartRecord; + private long mTimeReadEndRecord; + private long mTimeStartProperty; + private long mTimeEndProperty; + private long mTimeParseItems; + private long mTimeParseLineAndHandleGroup; + private long mTimeParsePropertyValues; + private long mTimeParseAdrOrgN; + private long mTimeHandleMiscPropertyValue; + private long mTimeHandleQuotedPrintable; + private long mTimeHandleBase64; + + public VCardParserImpl_V21() { + this(VCardConfig.VCARD_TYPE_DEFAULT); + } + + public VCardParserImpl_V21(int vcardType) { + if ((vcardType & VCardConfig.FLAG_TORELATE_NEST) != 0) { + mNestCount = 1; + } + + mIntermediateCharset = VCardConfig.DEFAULT_INTERMEDIATE_CHARSET; + } + + /** + * <p> + * Parses the file at the given position. + * </p> + */ + // <pre class="prettyprint">vcard_file = [wsls] vcard [wsls]</pre> + protected void parseVCardFile() throws IOException, VCardException { + boolean readingFirstFile = true; + while (true) { + if (mCanceled) { + break; + } + if (!parseOneVCard(readingFirstFile)) { + break; + } + readingFirstFile = false; + } + + if (mNestCount > 0) { + boolean useCache = true; + for (int i = 0; i < mNestCount; i++) { + readEndVCard(useCache, true); + useCache = false; + } + } + } + + /** + * @return true when a given property name is a valid property name. + */ + protected boolean isValidPropertyName(final String propertyName) { + if (!(getKnownPropertyNameSet().contains(propertyName.toUpperCase()) || + propertyName.startsWith("X-")) + && !mUnknownTypeSet.contains(propertyName)) { + mUnknownTypeSet.add(propertyName); + Log.w(LOG_TAG, "Property name unsupported by vCard 2.1: " + propertyName); + } + return true; + } + + /** + * @return String. It may be null, or its length may be 0 + * @throws IOException + */ + protected String getLine() throws IOException { + return mReader.readLine(); + } + + protected String peekLine() throws IOException { + return mReader.peekLine(); + } + + /** + * @return String with it's length > 0 + * @throws IOException + * @throws VCardException when the stream reached end of line + */ + protected String getNonEmptyLine() throws IOException, VCardException { + String line; + while (true) { + line = getLine(); + if (line == null) { + throw new VCardException("Reached end of buffer."); + } else if (line.trim().length() > 0) { + return line; + } + } + } + + /* + * vcard = "BEGIN" [ws] ":" [ws] "VCARD" [ws] 1*CRLF + * items *CRLF + * "END" [ws] ":" [ws] "VCARD" + */ + private boolean parseOneVCard(boolean firstRead) throws IOException, VCardException { + boolean allowGarbage = false; + if (firstRead) { + if (mNestCount > 0) { + for (int i = 0; i < mNestCount; i++) { + if (!readBeginVCard(allowGarbage)) { + return false; + } + allowGarbage = true; + } + } + } + + if (!readBeginVCard(allowGarbage)) { + return false; + } + final long beforeStartEntry = System.currentTimeMillis(); + mInterpreter.startEntry(); + mTimeReadStartRecord += System.currentTimeMillis() - beforeStartEntry; + + final long beforeParseItems = System.currentTimeMillis(); + parseItems(); + mTimeParseItems += System.currentTimeMillis() - beforeParseItems; + + readEndVCard(true, false); + + final long beforeEndEntry = System.currentTimeMillis(); + mInterpreter.endEntry(); + mTimeReadEndRecord += System.currentTimeMillis() - beforeEndEntry; + return true; + } + + /** + * @return True when successful. False when reaching the end of line + * @throws IOException + * @throws VCardException + */ + protected boolean readBeginVCard(boolean allowGarbage) throws IOException, VCardException { + String line; + do { + while (true) { + line = getLine(); + if (line == null) { + return false; + } else if (line.trim().length() > 0) { + break; + } + } + final String[] strArray = line.split(":", 2); + final int length = strArray.length; + + // Although vCard 2.1/3.0 specification does not allow lower cases, + // we found vCard file emitted by some external vCard expoter have such + // invalid Strings. + // So we allow it. + // e.g. + // BEGIN:vCard + if (length == 2 && strArray[0].trim().equalsIgnoreCase("BEGIN") + && strArray[1].trim().equalsIgnoreCase("VCARD")) { + return true; + } else if (!allowGarbage) { + if (mNestCount > 0) { + mPreviousLine = line; + return false; + } else { + throw new VCardException("Expected String \"BEGIN:VCARD\" did not come " + + "(Instead, \"" + line + "\" came)"); + } + } + } while (allowGarbage); + + throw new VCardException("Reached where must not be reached."); + } + + /** + * <p> + * The arguments useCache and allowGarbase are usually true and false + * accordingly when this function is called outside this function itself. + * </p> + * + * @param useCache When true, line is obtained from mPreviousline. + * Otherwise, getLine() is used. + * @param allowGarbage When true, ignore non "END:VCARD" line. + * @throws IOException + * @throws VCardException + */ + protected void readEndVCard(boolean useCache, boolean allowGarbage) throws IOException, + VCardException { + String line; + do { + if (useCache) { + // Though vCard specification does not allow lower cases, + // some data may have them, so we allow it. + line = mPreviousLine; + } else { + while (true) { + line = getLine(); + if (line == null) { + throw new VCardException("Expected END:VCARD was not found."); + } else if (line.trim().length() > 0) { + break; + } + } + } + + String[] strArray = line.split(":", 2); + if (strArray.length == 2 && strArray[0].trim().equalsIgnoreCase("END") + && strArray[1].trim().equalsIgnoreCase("VCARD")) { + return; + } else if (!allowGarbage) { + throw new VCardException("END:VCARD != \"" + mPreviousLine + "\""); + } + useCache = false; + } while (allowGarbage); + } + + /* + * items = *CRLF item / item + */ + protected void parseItems() throws IOException, VCardException { + boolean ended = false; + + final long beforeBeginProperty = System.currentTimeMillis(); + mInterpreter.startProperty(); + mTimeStartProperty += System.currentTimeMillis() - beforeBeginProperty; + ended = parseItem(); + if (!ended) { + final long beforeEndProperty = System.currentTimeMillis(); + mInterpreter.endProperty(); + mTimeEndProperty += System.currentTimeMillis() - beforeEndProperty; + } + + while (!ended) { + final long beforeStartProperty = System.currentTimeMillis(); + mInterpreter.startProperty(); + mTimeStartProperty += System.currentTimeMillis() - beforeStartProperty; + try { + ended = parseItem(); + } catch (VCardInvalidCommentLineException e) { + Log.e(LOG_TAG, "Invalid line which looks like some comment was found. Ignored."); + ended = false; + } + + if (!ended) { + final long beforeEndProperty = System.currentTimeMillis(); + mInterpreter.endProperty(); + mTimeEndProperty += System.currentTimeMillis() - beforeEndProperty; + } + } + } + + /* + * item = [groups "."] name [params] ":" value CRLF / [groups "."] "ADR" + * [params] ":" addressparts CRLF / [groups "."] "ORG" [params] ":" orgparts + * CRLF / [groups "."] "N" [params] ":" nameparts CRLF / [groups "."] + * "AGENT" [params] ":" vcard CRLF + */ + protected boolean parseItem() throws IOException, VCardException { + mCurrentEncoding = DEFAULT_ENCODING; + + final String line = getNonEmptyLine(); + long start = System.currentTimeMillis(); + + String[] propertyNameAndValue = separateLineAndHandleGroup(line); + if (propertyNameAndValue == null) { + return true; + } + if (propertyNameAndValue.length != 2) { + throw new VCardInvalidLineException("Invalid line \"" + line + "\""); + } + String propertyName = propertyNameAndValue[0].toUpperCase(); + String propertyValue = propertyNameAndValue[1]; + + mTimeParseLineAndHandleGroup += System.currentTimeMillis() - start; + + if (propertyName.equals("ADR") || propertyName.equals("ORG") || propertyName.equals("N")) { + start = System.currentTimeMillis(); + handleMultiplePropertyValue(propertyName, propertyValue); + mTimeParseAdrOrgN += System.currentTimeMillis() - start; + return false; + } else if (propertyName.equals("AGENT")) { + handleAgent(propertyValue); + return false; + } else if (isValidPropertyName(propertyName)) { + if (propertyName.equals("BEGIN")) { + if (propertyValue.equals("VCARD")) { + throw new VCardNestedException("This vCard has nested vCard data in it."); + } else { + throw new VCardException("Unknown BEGIN type: " + propertyValue); + } + } else if (propertyName.equals("VERSION") && !propertyValue.equals(getVersionString())) { + throw new VCardVersionException("Incompatible version: " + propertyValue + " != " + + getVersionString()); + } + start = System.currentTimeMillis(); + handlePropertyValue(propertyName, propertyValue); + mTimeParsePropertyValues += System.currentTimeMillis() - start; + return false; + } + + throw new VCardException("Unknown property name: \"" + propertyName + "\""); + } + + // For performance reason, the states for group and property name are merged into one. + static private final int STATE_GROUP_OR_PROPERTY_NAME = 0; + static private final int STATE_PARAMS = 1; + // vCard 3.0 specification allows double-quoted parameters, while vCard 2.1 does not. + static private final int STATE_PARAMS_IN_DQUOTE = 2; + + protected String[] separateLineAndHandleGroup(String line) throws VCardException { + final String[] propertyNameAndValue = new String[2]; + final int length = line.length(); + if (length > 0 && line.charAt(0) == '#') { + throw new VCardInvalidCommentLineException(); + } + + int state = STATE_GROUP_OR_PROPERTY_NAME; + int nameIndex = 0; + + // This loop is developed so that we don't have to take care of bottle neck here. + // Refactor carefully when you need to do so. + for (int i = 0; i < length; i++) { + final char ch = line.charAt(i); + switch (state) { + case STATE_GROUP_OR_PROPERTY_NAME: { + if (ch == ':') { // End of a property name. + final String propertyName = line.substring(nameIndex, i); + if (propertyName.equalsIgnoreCase("END")) { + mPreviousLine = line; + return null; + } + mInterpreter.propertyName(propertyName); + propertyNameAndValue[0] = propertyName; + if (i < length - 1) { + propertyNameAndValue[1] = line.substring(i + 1); + } else { + propertyNameAndValue[1] = ""; + } + return propertyNameAndValue; + } else if (ch == '.') { // Each group is followed by the dot. + final String groupName = line.substring(nameIndex, i); + if (groupName.length() == 0) { + Log.w(LOG_TAG, "Empty group found. Ignoring."); + } else { + mInterpreter.propertyGroup(groupName); + } + nameIndex = i + 1; // Next should be another group or a property name. + } else if (ch == ';') { // End of property name and beginneng of parameters. + final String propertyName = line.substring(nameIndex, i); + if (propertyName.equalsIgnoreCase("END")) { + mPreviousLine = line; + return null; + } + mInterpreter.propertyName(propertyName); + propertyNameAndValue[0] = propertyName; + nameIndex = i + 1; + state = STATE_PARAMS; // Start parameter parsing. + } + // TODO: comma support (in vCard 3.0 and 4.0). + break; + } + case STATE_PARAMS: { + if (ch == '"') { + if (VCardConstants.VERSION_V21.equalsIgnoreCase(getVersionString())) { + Log.w(LOG_TAG, "Double-quoted params found in vCard 2.1. " + + "Silently allow it"); + } + state = STATE_PARAMS_IN_DQUOTE; + } else if (ch == ';') { // Starts another param. + handleParams(line.substring(nameIndex, i)); + nameIndex = i + 1; + } else if (ch == ':') { // End of param and beginenning of values. + handleParams(line.substring(nameIndex, i)); + if (i < length - 1) { + propertyNameAndValue[1] = line.substring(i + 1); + } else { + propertyNameAndValue[1] = ""; + } + return propertyNameAndValue; + } + break; + } + case STATE_PARAMS_IN_DQUOTE: { + if (ch == '"') { + if (VCardConstants.VERSION_V21.equalsIgnoreCase(getVersionString())) { + Log.w(LOG_TAG, "Double-quoted params found in vCard 2.1. " + + "Silently allow it"); + } + state = STATE_PARAMS; + } + break; + } + } + } + + throw new VCardInvalidLineException("Invalid line: \"" + line + "\""); + } + + /* + * params = ";" [ws] paramlist paramlist = paramlist [ws] ";" [ws] param / + * param param = "TYPE" [ws] "=" [ws] ptypeval / "VALUE" [ws] "=" [ws] + * pvalueval / "ENCODING" [ws] "=" [ws] pencodingval / "CHARSET" [ws] "=" + * [ws] charsetval / "LANGUAGE" [ws] "=" [ws] langval / "X-" word [ws] "=" + * [ws] word / knowntype + */ + protected void handleParams(String params) throws VCardException { + final String[] strArray = params.split("=", 2); + if (strArray.length == 2) { + final String paramName = strArray[0].trim().toUpperCase(); + String paramValue = strArray[1].trim(); + if (paramName.equals("TYPE")) { + handleType(paramValue); + } else if (paramName.equals("VALUE")) { + handleValue(paramValue); + } else if (paramName.equals("ENCODING")) { + handleEncoding(paramValue); + } else if (paramName.equals("CHARSET")) { + handleCharset(paramValue); + } else if (paramName.equals("LANGUAGE")) { + handleLanguage(paramValue); + } else if (paramName.startsWith("X-")) { + handleAnyParam(paramName, paramValue); + } else { + throw new VCardException("Unknown type \"" + paramName + "\""); + } + } else { + handleParamWithoutName(strArray[0]); + } + } + + /** + * vCard 3.0 parser implementation may throw VCardException. + */ + @SuppressWarnings("unused") + protected void handleParamWithoutName(final String paramValue) throws VCardException { + handleType(paramValue); + } + + /* + * ptypeval = knowntype / "X-" word + */ + protected void handleType(final String ptypeval) { + if (!(getKnownTypeSet().contains(ptypeval.toUpperCase()) + || ptypeval.startsWith("X-")) + && !mUnknownTypeSet.contains(ptypeval)) { + mUnknownTypeSet.add(ptypeval); + Log.w(LOG_TAG, String.format("TYPE unsupported by %s: ", getVersion(), ptypeval)); + } + mInterpreter.propertyParamType("TYPE"); + mInterpreter.propertyParamValue(ptypeval); + } + + /* + * pvalueval = "INLINE" / "URL" / "CONTENT-ID" / "CID" / "X-" word + */ + protected void handleValue(final String pvalueval) { + if (!(getKnownValueSet().contains(pvalueval.toUpperCase()) + || pvalueval.startsWith("X-") + || mUnknownValueSet.contains(pvalueval))) { + mUnknownValueSet.add(pvalueval); + Log.w(LOG_TAG, String.format( + "The value unsupported by TYPE of %s: ", getVersion(), pvalueval)); + } + mInterpreter.propertyParamType("VALUE"); + mInterpreter.propertyParamValue(pvalueval); + } + + /* + * pencodingval = "7BIT" / "8BIT" / "QUOTED-PRINTABLE" / "BASE64" / "X-" word + */ + protected void handleEncoding(String pencodingval) throws VCardException { + if (getAvailableEncodingSet().contains(pencodingval) || + pencodingval.startsWith("X-")) { + mInterpreter.propertyParamType("ENCODING"); + mInterpreter.propertyParamValue(pencodingval); + mCurrentEncoding = pencodingval; + } else { + throw new VCardException("Unknown encoding \"" + pencodingval + "\""); + } + } + + /** + * <p> + * vCard 2.1 specification only allows us-ascii and iso-8859-xxx (See RFC 1521), + * but recent vCard files often contain other charset like UTF-8, SHIFT_JIS, etc. + * We allow any charset. + * </p> + */ + protected void handleCharset(String charsetval) { + mInterpreter.propertyParamType("CHARSET"); + mInterpreter.propertyParamValue(charsetval); + } + + /** + * See also Section 7.1 of RFC 1521 + */ + protected void handleLanguage(String langval) throws VCardException { + String[] strArray = langval.split("-"); + if (strArray.length != 2) { + throw new VCardException("Invalid Language: \"" + langval + "\""); + } + String tmp = strArray[0]; + int length = tmp.length(); + for (int i = 0; i < length; i++) { + if (!isAsciiLetter(tmp.charAt(i))) { + throw new VCardException("Invalid Language: \"" + langval + "\""); + } + } + tmp = strArray[1]; + length = tmp.length(); + for (int i = 0; i < length; i++) { + if (!isAsciiLetter(tmp.charAt(i))) { + throw new VCardException("Invalid Language: \"" + langval + "\""); + } + } + mInterpreter.propertyParamType(VCardConstants.PARAM_LANGUAGE); + mInterpreter.propertyParamValue(langval); + } + + private boolean isAsciiLetter(char ch) { + if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) { + return true; + } + return false; + } + + /** + * Mainly for "X-" type. This accepts any kind of type without check. + */ + protected void handleAnyParam(String paramName, String paramValue) { + mInterpreter.propertyParamType(paramName); + mInterpreter.propertyParamValue(paramValue); + } + + protected void handlePropertyValue(String propertyName, String propertyValue) + throws IOException, VCardException { + final String upperEncoding = mCurrentEncoding.toUpperCase(); + if (upperEncoding.equals(VCardConstants.PARAM_ENCODING_QP)) { + final long start = System.currentTimeMillis(); + final String result = getQuotedPrintable(propertyValue); + final ArrayList<String> v = new ArrayList<String>(); + v.add(result); + mInterpreter.propertyValues(v); + mTimeHandleQuotedPrintable += System.currentTimeMillis() - start; + } else if (upperEncoding.equals(VCardConstants.PARAM_ENCODING_BASE64) + || upperEncoding.equals(VCardConstants.PARAM_ENCODING_B)) { + final long start = System.currentTimeMillis(); + // It is very rare, but some BASE64 data may be so big that + // OutOfMemoryError occurs. To ignore such cases, use try-catch. + try { + final ArrayList<String> arrayList = new ArrayList<String>(); + arrayList.add(getBase64(propertyValue)); + mInterpreter.propertyValues(arrayList); + } catch (OutOfMemoryError error) { + Log.e(LOG_TAG, "OutOfMemoryError happened during parsing BASE64 data!"); + mInterpreter.propertyValues(null); + } + mTimeHandleBase64 += System.currentTimeMillis() - start; + } else { + if (!(upperEncoding.equals("7BIT") || upperEncoding.equals("8BIT") || + upperEncoding.startsWith("X-"))) { + Log.w(LOG_TAG, + String.format("The encoding \"%s\" is unsupported by vCard %s", + mCurrentEncoding, getVersionString())); + } + + // Some device uses line folding defined in RFC 2425, which is not allowed + // in vCard 2.1 (while needed in vCard 3.0). + // + // e.g. + // BEGIN:VCARD + // VERSION:2.1 + // N:;Omega;;; + // EMAIL;INTERNET:"Omega" + // <omega@example.com> + // FN:Omega + // END:VCARD + // + // The vCard above assumes that email address should become: + // "Omega" <omega@example.com> + // + // But vCard 2.1 requires Quote-Printable when a line contains line break(s). + // + // For more information about line folding, + // see "5.8.1. Line delimiting and folding" in RFC 2425. + // + // We take care of this case more formally in vCard 3.0, so we only need to + // do this in vCard 2.1. + if (getVersion() == VCardConfig.VERSION_21) { + StringBuilder builder = null; + while (true) { + final String nextLine = peekLine(); + // We don't need to care too much about this exceptional case, + // but we should not wrongly eat up "END:VCARD", since it critically + // breaks this parser's state machine. + // Thus we roughly look over the next line and confirm it is at least not + // "END:VCARD". This extra fee is worth paying. This is exceptional + // anyway. + if (!TextUtils.isEmpty(nextLine) && + nextLine.charAt(0) == ' ' && + !"END:VCARD".contains(nextLine.toUpperCase())) { + getLine(); // Drop the next line. + + if (builder == null) { + builder = new StringBuilder(); + builder.append(propertyValue); + } + builder.append(nextLine.substring(1)); + } else { + break; + } + } + if (builder != null) { + propertyValue = builder.toString(); + } + } + + final long start = System.currentTimeMillis(); + ArrayList<String> v = new ArrayList<String>(); + v.add(maybeUnescapeText(propertyValue)); + mInterpreter.propertyValues(v); + mTimeHandleMiscPropertyValue += System.currentTimeMillis() - start; + } + } + + /** + * <p> + * Parses and returns Quoted-Printable. + * </p> + * + * @param firstString The string following a parameter name and attributes. + * Example: "string" in + * "ADR:ENCODING=QUOTED-PRINTABLE:string\n\r". + * @return whole Quoted-Printable string, including a given argument and + * following lines. Excludes the last empty line following to Quoted + * Printable lines. + * @throws IOException + * @throws VCardException + */ + private String getQuotedPrintable(String firstString) throws IOException, VCardException { + // Specifically, there may be some padding between = and CRLF. + // See the following: + // + // qp-line := *(qp-segment transport-padding CRLF) + // qp-part transport-padding + // qp-segment := qp-section *(SPACE / TAB) "=" + // ; Maximum length of 76 characters + // + // e.g. (from RFC 2045) + // Now's the time = + // for all folk to come= + // to the aid of their country. + if (firstString.trim().endsWith("=")) { + // remove "transport-padding" + int pos = firstString.length() - 1; + while (firstString.charAt(pos) != '=') { + } + StringBuilder builder = new StringBuilder(); + builder.append(firstString.substring(0, pos + 1)); + builder.append("\r\n"); + String line; + while (true) { + line = getLine(); + if (line == null) { + throw new VCardException("File ended during parsing a Quoted-Printable String"); + } + if (line.trim().endsWith("=")) { + // remove "transport-padding" + pos = line.length() - 1; + while (line.charAt(pos) != '=') { + } + builder.append(line.substring(0, pos + 1)); + builder.append("\r\n"); + } else { + builder.append(line); + break; + } + } + return builder.toString(); + } else { + return firstString; + } + } + + protected String getBase64(String firstString) throws IOException, VCardException { + StringBuilder builder = new StringBuilder(); + builder.append(firstString); + + while (true) { + String line = getLine(); + if (line == null) { + throw new VCardException("File ended during parsing BASE64 binary"); + } + if (line.length() == 0) { + break; + } + builder.append(line); + } + + return builder.toString(); + } + + /** + * <p> + * Mainly for "ADR", "ORG", and "N" + * </p> + */ + /* + * addressparts = 0*6(strnosemi ";") strnosemi ; PO Box, Extended Addr, + * Street, Locality, Region, Postal Code, Country Name orgparts = + * *(strnosemi ";") strnosemi ; First is Organization Name, remainder are + * Organization Units. nameparts = 0*4(strnosemi ";") strnosemi ; Family, + * Given, Middle, Prefix, Suffix. ; Example:Public;John;Q.;Reverend Dr.;III, + * Esq. strnosemi = *(*nonsemi ("\;" / "\" CRLF)) *nonsemi ; To include a + * semicolon in this string, it must be escaped ; with a "\" character. We + * do not care the number of "strnosemi" here. We are not sure whether we + * should add "\" CRLF to each value. We exclude them for now. + */ + protected void handleMultiplePropertyValue(String propertyName, String propertyValue) + throws IOException, VCardException { + // vCard 2.1 does not allow QUOTED-PRINTABLE here, but some + // softwares/devices + // emit such data. + if (mCurrentEncoding.equalsIgnoreCase("QUOTED-PRINTABLE")) { + propertyValue = getQuotedPrintable(propertyValue); + } + + mInterpreter.propertyValues(VCardUtils.constructListFromValue(propertyValue, + getVersion())); + } + + /* + * vCard 2.1 specifies AGENT allows one vcard entry. Currently we emit an + * error toward the AGENT property. + * // TODO: Support AGENT property. + * item = + * ... / [groups "."] "AGENT" [params] ":" vcard CRLF vcard = "BEGIN" [ws] + * ":" [ws] "VCARD" [ws] 1*CRLF items *CRLF "END" [ws] ":" [ws] "VCARD" + */ + protected void handleAgent(final String propertyValue) throws VCardException { + if (!propertyValue.toUpperCase().contains("BEGIN:VCARD")) { + // Apparently invalid line seen in Windows Mobile 6.5. Ignore them. + return; + } else { + throw new VCardAgentNotSupportedException("AGENT Property is not supported now."); + } + } + + /** + * For vCard 3.0. + */ + protected String maybeUnescapeText(final String text) { + return text; + } + + /** + * Returns unescaped String if the character should be unescaped. Return + * null otherwise. e.g. In vCard 2.1, "\;" should be unescaped into ";" + * while "\x" should not be. + */ + protected String maybeUnescapeCharacter(final char ch) { + return unescapeCharacter(ch); + } + + /* package */ static String unescapeCharacter(final char ch) { + // Original vCard 2.1 specification does not allow transformation + // "\:" -> ":", "\," -> ",", and "\\" -> "\", but previous + // implementation of + // this class allowed them, so keep it as is. + if (ch == '\\' || ch == ';' || ch == ':' || ch == ',') { + return String.valueOf(ch); + } else { + return null; + } + } + + private void showPerformanceInfo() { + Log.d(LOG_TAG, "Total parsing time: " + mTimeTotal + " ms"); + Log.d(LOG_TAG, "Total readLine time: " + mReader.getTotalmillisecond() + " ms"); + Log.d(LOG_TAG, "Time for handling the beggining of the record: " + mTimeReadStartRecord + + " ms"); + Log.d(LOG_TAG, "Time for handling the end of the record: " + mTimeReadEndRecord + " ms"); + Log.d(LOG_TAG, "Time for parsing line, and handling group: " + mTimeParseLineAndHandleGroup + + " ms"); + Log.d(LOG_TAG, "Time for parsing ADR, ORG, and N fields:" + mTimeParseAdrOrgN + " ms"); + Log.d(LOG_TAG, "Time for parsing property values: " + mTimeParsePropertyValues + " ms"); + Log.d(LOG_TAG, "Time for handling normal property values: " + mTimeHandleMiscPropertyValue + + " ms"); + Log.d(LOG_TAG, "Time for handling Quoted-Printable: " + mTimeHandleQuotedPrintable + " ms"); + Log.d(LOG_TAG, "Time for handling Base64: " + mTimeHandleBase64 + " ms"); + } + + /** + * @return {@link VCardConfig#VERSION_21} + */ + protected int getVersion() { + return VCardConfig.VERSION_21; + } + + /** + * @return {@link VCardConfig#VERSION_30} + */ + protected String getVersionString() { + return VCardConstants.VERSION_V21; + } + + protected Set<String> getKnownPropertyNameSet() { + return VCardParser_V21.sKnownPropertyNameSet; + } + + protected Set<String> getKnownTypeSet() { + return VCardParser_V21.sKnownTypeSet; + } + + protected Set<String> getKnownValueSet() { + return VCardParser_V21.sKnownValueSet; + } + + protected Set<String> getAvailableEncodingSet() { + return VCardParser_V21.sAvailableEncoding; + } + + protected String getDefaultEncoding() { + return DEFAULT_ENCODING; + } + + + public void parse(InputStream is, VCardInterpreter interpreter) + throws IOException, VCardException { + if (is == null) { + throw new NullPointerException("InputStream must not be null."); + } + + final InputStreamReader tmpReader = new InputStreamReader(is, mIntermediateCharset); + mReader = new CustomBufferedReader(tmpReader); + + mInterpreter = (interpreter != null ? interpreter : new EmptyInterpreter()); + + final long start = System.currentTimeMillis(); + if (mInterpreter != null) { + mInterpreter.start(); + } + parseVCardFile(); + if (mInterpreter != null) { + mInterpreter.end(); + } + mTimeTotal += System.currentTimeMillis() - start; + + if (VCardConfig.showPerformanceLog()) { + showPerformanceInfo(); + } + } + + public final void cancel() { + mCanceled = true; + } +} diff --git a/core/java/android/pim/vcard/VCardParserImpl_V30.java b/core/java/android/pim/vcard/VCardParserImpl_V30.java new file mode 100644 index 000000000000..3fad9a08cc15 --- /dev/null +++ b/core/java/android/pim/vcard/VCardParserImpl_V30.java @@ -0,0 +1,388 @@ +/* + * 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 android.pim.vcard; + +import android.pim.vcard.exception.VCardException; +import android.util.Log; + +import java.io.IOException; +import java.util.Set; + +/** + * <p> + * Basic implementation achieving vCard 3.0 parsing. + * </p> + * <p> + * This class inherits vCard 2.1 implementation since technically they are similar, + * while specifically there's logical no relevance between them. + * So that developers are not confused with the inheritance, + * {@link VCardParser_V30} does not inherit {@link VCardParser_V21}, while + * {@link VCardParserImpl_V30} inherits {@link VCardParserImpl_V21}. + * </p> + * @hide + */ +/* package */ class VCardParserImpl_V30 extends VCardParserImpl_V21 { + private static final String LOG_TAG = "VCardParserImpl_V30"; + + private String mPreviousLine; + private boolean mEmittedAgentWarning = false; + + public VCardParserImpl_V30() { + super(); + } + + public VCardParserImpl_V30(int vcardType) { + super(vcardType); + } + + @Override + protected int getVersion() { + return VCardConfig.VERSION_30; + } + + @Override + protected String getVersionString() { + return VCardConstants.VERSION_V30; + } + + @Override + protected String getLine() throws IOException { + if (mPreviousLine != null) { + String ret = mPreviousLine; + mPreviousLine = null; + return ret; + } else { + return mReader.readLine(); + } + } + + /** + * vCard 3.0 requires that the line with space at the beginning of the line + * must be combined with previous line. + */ + @Override + protected String getNonEmptyLine() throws IOException, VCardException { + String line; + StringBuilder builder = null; + while (true) { + line = mReader.readLine(); + if (line == null) { + if (builder != null) { + return builder.toString(); + } else if (mPreviousLine != null) { + String ret = mPreviousLine; + mPreviousLine = null; + return ret; + } + throw new VCardException("Reached end of buffer."); + } else if (line.length() == 0) { + if (builder != null) { + return builder.toString(); + } else if (mPreviousLine != null) { + String ret = mPreviousLine; + mPreviousLine = null; + return ret; + } + } else if (line.charAt(0) == ' ' || line.charAt(0) == '\t') { + if (builder != null) { + // See Section 5.8.1 of RFC 2425 (MIME-DIR document). + // Following is the excerpts from it. + // + // DESCRIPTION:This is a long description that exists on a long line. + // + // Can be represented as: + // + // DESCRIPTION:This is a long description + // that exists on a long line. + // + // It could also be represented as: + // + // DESCRIPTION:This is a long descrip + // tion that exists o + // n a long line. + builder.append(line.substring(1)); + } else if (mPreviousLine != null) { + builder = new StringBuilder(); + builder.append(mPreviousLine); + mPreviousLine = null; + builder.append(line.substring(1)); + } else { + throw new VCardException("Space exists at the beginning of the line"); + } + } else { + if (mPreviousLine == null) { + mPreviousLine = line; + if (builder != null) { + return builder.toString(); + } + } else { + String ret = mPreviousLine; + mPreviousLine = line; + return ret; + } + } + } + } + + /* + * vcard = [group "."] "BEGIN" ":" "VCARD" 1 * CRLF + * 1 * (contentline) + * ;A vCard object MUST include the VERSION, FN and N types. + * [group "."] "END" ":" "VCARD" 1 * CRLF + */ + @Override + protected boolean readBeginVCard(boolean allowGarbage) throws IOException, VCardException { + // TODO: vCard 3.0 supports group. + return super.readBeginVCard(allowGarbage); + } + + @Override + protected void readEndVCard(boolean useCache, boolean allowGarbage) + throws IOException, VCardException { + // TODO: vCard 3.0 supports group. + super.readEndVCard(useCache, allowGarbage); + } + + /** + * vCard 3.0 allows iana-token as paramType, while vCard 2.1 does not. + */ + @Override + protected void handleParams(final String params) throws VCardException { + try { + super.handleParams(params); + } catch (VCardException e) { + // maybe IANA type + String[] strArray = params.split("=", 2); + if (strArray.length == 2) { + handleAnyParam(strArray[0], strArray[1]); + } else { + // Must not come here in the current implementation. + throw new VCardException( + "Unknown params value: " + params); + } + } + } + + @Override + protected void handleAnyParam(final String paramName, final String paramValue) { + mInterpreter.propertyParamType(paramName); + splitAndPutParamValue(paramValue); + } + + @Override + protected void handleParamWithoutName(final String paramValue) { + handleType(paramValue); + } + + /* + * vCard 3.0 defines + * + * param = param-name "=" param-value *("," param-value) + * param-name = iana-token / x-name + * param-value = ptext / quoted-string + * quoted-string = DQUOTE QSAFE-CHAR DQUOTE + * QSAFE-CHAR = WSP / %x21 / %x23-7E / NON-ASCII + * ; Any character except CTLs, DQUOTE + * + * QSAFE-CHAR must not contain DQUOTE, including escaped one (\"). + */ + @Override + protected void handleType(final String paramValue) { + mInterpreter.propertyParamType("TYPE"); + splitAndPutParamValue(paramValue); + } + + /** + * Splits parameter values into pieces in accordance with vCard 3.0 specification and + * puts pieces into mInterpreter. + */ + /* + * param-value = ptext / quoted-string + * quoted-string = DQUOTE QSAFE-CHAR DQUOTE + * QSAFE-CHAR = WSP / %x21 / %x23-7E / NON-ASCII + * ; Any character except CTLs, DQUOTE + * + * QSAFE-CHAR must not contain DQUOTE, including escaped one (\") + */ + private void splitAndPutParamValue(String paramValue) { + // "comma,separated:inside.dquote",pref + // --> + // - comma,separated:inside.dquote + // - pref + // + // Note: Though there's a code, we don't need to take much care of + // wrongly-added quotes like the example above, as they induce + // parse errors at the top level (when splitting a line into parts). + StringBuilder builder = null; // Delay initialization. + boolean insideDquote = false; + final int length = paramValue.length(); + for (int i = 0; i < length; i++) { + final char ch = paramValue.charAt(i); + if (ch == '"') { + if (insideDquote) { + // End of Dquote. + mInterpreter.propertyParamValue(builder.toString()); + builder = null; + insideDquote = false; + } else { + if (builder != null) { + if (builder.length() > 0) { + // e.g. + // pref"quoted" + Log.w(LOG_TAG, "Unexpected Dquote inside property."); + } else { + // e.g. + // pref,"quoted" + // "quoted",pref + mInterpreter.propertyParamValue(builder.toString()); + } + } + insideDquote = true; + } + } else if (ch == ',' && !insideDquote) { + if (builder == null) { + Log.w(LOG_TAG, "Comma is used before actual string comes. (" + + paramValue + ")"); + } else { + mInterpreter.propertyParamValue(builder.toString()); + builder = null; + } + } else { + // To stop creating empty StringBuffer at the end of parameter, + // we delay creating this object until this point. + if (builder == null) { + builder = new StringBuilder(); + } + builder.append(ch); + } + } + if (insideDquote) { + // e.g. + // "non-quote-at-end + Log.d(LOG_TAG, "Dangling Dquote."); + } + if (builder != null) { + if (builder.length() == 0) { + Log.w(LOG_TAG, "Unintended behavior. We must not see empty StringBuilder " + + "at the end of parameter value parsing."); + } else { + mInterpreter.propertyParamValue(builder.toString()); + } + } + } + + @Override + protected void handleAgent(final String propertyValue) { + // The way how vCard 3.0 supports "AGENT" is completely different from vCard 2.1. + // + // e.g. + // AGENT:BEGIN:VCARD\nFN:Joe Friday\nTEL:+1-919-555-7878\n + // TITLE:Area Administrator\, Assistant\n EMAIL\;TYPE=INTERN\n + // ET:jfriday@host.com\nEND:VCARD\n + // + // TODO: fix this. + // + // issue: + // vCard 3.0 also allows this as an example. + // + // AGENT;VALUE=uri: + // CID:JQPUBLIC.part3.960129T083020.xyzMail@host3.com + // + // This is not vCard. Should we support this? + // + // Just ignore the line for now, since we cannot know how to handle it... + if (!mEmittedAgentWarning) { + Log.w(LOG_TAG, "AGENT in vCard 3.0 is not supported yet. Ignore it"); + mEmittedAgentWarning = true; + } + } + + /** + * vCard 3.0 does not require two CRLF at the last of BASE64 data. + * It only requires that data should be MIME-encoded. + */ + @Override + protected String getBase64(final String firstString) + throws IOException, VCardException { + final StringBuilder builder = new StringBuilder(); + builder.append(firstString); + + while (true) { + final String line = getLine(); + if (line == null) { + throw new VCardException("File ended during parsing BASE64 binary"); + } + if (line.length() == 0) { + break; + } else if (!line.startsWith(" ") && !line.startsWith("\t")) { + mPreviousLine = line; + break; + } + builder.append(line); + } + + return builder.toString(); + } + + /** + * ESCAPED-CHAR = "\\" / "\;" / "\," / "\n" / "\N") + * ; \\ encodes \, \n or \N encodes newline + * ; \; encodes ;, \, encodes , + * + * Note: Apple escapes ':' into '\:' while does not escape '\' + */ + @Override + protected String maybeUnescapeText(final String text) { + return unescapeText(text); + } + + public static String unescapeText(final String text) { + StringBuilder builder = new StringBuilder(); + final int length = text.length(); + for (int i = 0; i < length; i++) { + char ch = text.charAt(i); + if (ch == '\\' && i < length - 1) { + final char next_ch = text.charAt(++i); + if (next_ch == 'n' || next_ch == 'N') { + builder.append("\n"); + } else { + builder.append(next_ch); + } + } else { + builder.append(ch); + } + } + return builder.toString(); + } + + @Override + protected String maybeUnescapeCharacter(final char ch) { + return unescapeCharacter(ch); + } + + public static String unescapeCharacter(final char ch) { + if (ch == 'n' || ch == 'N') { + return "\n"; + } else { + return String.valueOf(ch); + } + } + + @Override + protected Set<String> getKnownPropertyNameSet() { + return VCardParser_V30.sKnownPropertyNameSet; + } +} diff --git a/core/java/android/pim/vcard/VCardParserImpl_V40.java b/core/java/android/pim/vcard/VCardParserImpl_V40.java new file mode 100644 index 000000000000..0fe76bbd39fc --- /dev/null +++ b/core/java/android/pim/vcard/VCardParserImpl_V40.java @@ -0,0 +1,92 @@ +/* + * 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 android.pim.vcard; + +import java.util.Set; + + +/** + * <p> + * Basic implementation parsing vCard 4.0. + * </p> + * <p> + * vCard 4.0 is not published yet. Also this implementation is premature. + * </p> + * @hide + */ +/* package */ class VCardParserImpl_V40 extends VCardParserImpl_V30 { + // private static final String LOG_TAG = "VCardParserImpl_V40"; + + public VCardParserImpl_V40() { + super(); + } + + public VCardParserImpl_V40(final int vcardType) { + super(vcardType); + } + + @Override + protected int getVersion() { + return VCardConfig.VERSION_40; + } + + @Override + protected String getVersionString() { + return VCardConstants.VERSION_V40; + } + + /** + * We escape "\N" into new line for safety. + */ + @Override + protected String maybeUnescapeText(final String text) { + return unescapeText(text); + } + + public static String unescapeText(final String text) { + // TODO: more strictly, vCard 4.0 requires different type of unescaping rule + // toward each property. + final StringBuilder builder = new StringBuilder(); + final int length = text.length(); + for (int i = 0; i < length; i++) { + char ch = text.charAt(i); + if (ch == '\\' && i < length - 1) { + final char next_ch = text.charAt(++i); + if (next_ch == 'n' || next_ch == 'N') { + builder.append("\n"); + } else { + builder.append(next_ch); + } + } else { + builder.append(ch); + } + } + return builder.toString(); + } + + public static String unescapeCharacter(final char ch) { + if (ch == 'n' || ch == 'N') { + return "\n"; + } else { + return String.valueOf(ch); + } + } + + @Override + protected Set<String> getKnownPropertyNameSet() { + return VCardParser_V40.sKnownPropertyNameSet; + } +}
\ No newline at end of file diff --git a/core/java/android/pim/vcard/VCardParser_V21.java b/core/java/android/pim/vcard/VCardParser_V21.java index fe8cfb07776c..507a1763f377 100644 --- a/core/java/android/pim/vcard/VCardParser_V21.java +++ b/core/java/android/pim/vcard/VCardParser_V21.java @@ -15,922 +15,95 @@ */ package android.pim.vcard; -import android.pim.vcard.exception.VCardAgentNotSupportedException; import android.pim.vcard.exception.VCardException; -import android.pim.vcard.exception.VCardInvalidCommentLineException; -import android.pim.vcard.exception.VCardInvalidLineException; -import android.pim.vcard.exception.VCardNestedException; -import android.pim.vcard.exception.VCardVersionException; -import android.util.Log; -import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.Reader; -import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashSet; import java.util.Set; /** - * This class is used to parse vCard. Please refer to vCard Specification 2.1 for more detail. + * </p> + * vCard parser for vCard 2.1. See the specification for more detail about the spec itself. + * </p> + * <p> + * The spec is written in 1996, and currently various types of "vCard 2.1" exist. + * To handle real the world vCard formats appropriately and effectively, this class does not + * obey with strict vCard 2.1. + * In stead, not only vCard spec but also real world vCard is considered. + * </p> + * e.g. A lot of devices and softwares let vCard importer/exporter to use + * the PNG format to determine the type of image, while it is not allowed in + * the original specification. As of 2010, we can see even the FLV format + * (possible in Japanese mobile phones). + * </p> */ -public class VCardParser_V21 extends VCardParser { - private static final String LOG_TAG = "VCardParser_V21"; - - /** Store the known-type */ - private static final HashSet<String> sKnownTypeSet = new HashSet<String>( - Arrays.asList("DOM", "INTL", "POSTAL", "PARCEL", "HOME", "WORK", - "PREF", "VOICE", "FAX", "MSG", "CELL", "PAGER", "BBS", - "MODEM", "CAR", "ISDN", "VIDEO", "AOL", "APPLELINK", - "ATTMAIL", "CIS", "EWORLD", "INTERNET", "IBMMAIL", - "MCIMAIL", "POWERSHARE", "PRODIGY", "TLX", "X400", "GIF", - "CGM", "WMF", "BMP", "MET", "PMB", "DIB", "PICT", "TIFF", - "PDF", "PS", "JPEG", "QTIME", "MPEG", "MPEG2", "AVI", - "WAVE", "AIFF", "PCM", "X509", "PGP")); - - /** Store the known-value */ - private static final HashSet<String> sKnownValueSet = new HashSet<String>( - Arrays.asList("INLINE", "URL", "CONTENT-ID", "CID")); - - /** Store the property names available in vCard 2.1 */ - private static final HashSet<String> sAvailablePropertyNameSetV21 = - new HashSet<String>(Arrays.asList( - "BEGIN", "LOGO", "PHOTO", "LABEL", "FN", "TITLE", "SOUND", - "VERSION", "TEL", "EMAIL", "TZ", "GEO", "NOTE", "URL", - "BDAY", "ROLE", "REV", "UID", "KEY", "MAILER")); - +public final class VCardParser_V21 implements VCardParser { /** - * Though vCard 2.1 specification does not allow "B" encoding, some data may have it. - * We allow it for safety... - */ - private static final HashSet<String> sAvailableEncodingV21 = - new HashSet<String>(Arrays.asList( - "7BIT", "8BIT", "QUOTED-PRINTABLE", "BASE64", "B")); - - // Used only for parsing END:VCARD. - private String mPreviousLine; - - /** The builder to build parsed data */ - protected VCardInterpreter mBuilder = null; - - /** - * The encoding type. "Encoding" in vCard is different from "Charset". - * e.g. 7BIT, 8BIT, QUOTED-PRINTABLE. + * A unmodifiable Set storing the property names available in the vCard 2.1 specification. */ - protected String mEncoding = null; - - protected final String sDefaultEncoding = "8BIT"; - - // Should not directly read a line from this object. Use getLine() instead. - protected BufferedReader mReader; - - // In some cases, vCard is nested. Currently, we only consider the most interior vCard data. - // See v21_foma_1.vcf in test directory for more information. - private int mNestCount; - - // In order to reduce warning message as much as possible, we hold the value which made Logger - // emit a warning message. - protected Set<String> mUnknownTypeMap = new HashSet<String>(); - protected Set<String> mUnknownValueMap = new HashSet<String>(); - - // For measuring performance. - private long mTimeTotal; - private long mTimeReadStartRecord; - private long mTimeReadEndRecord; - private long mTimeStartProperty; - private long mTimeEndProperty; - private long mTimeParseItems; - private long mTimeParseLineAndHandleGroup; - private long mTimeParsePropertyValues; - private long mTimeParseAdrOrgN; - private long mTimeHandleMiscPropertyValue; - private long mTimeHandleQuotedPrintable; - private long mTimeHandleBase64; - - public VCardParser_V21() { - this(null); - } - - public VCardParser_V21(VCardSourceDetector detector) { - this(detector != null ? detector.getEstimatedType() : VCardConfig.PARSE_TYPE_UNKNOWN); - } - - public VCardParser_V21(int parseType) { - super(parseType); - if (parseType == VCardConfig.PARSE_TYPE_FOMA) { - mNestCount = 1; - } - } + /* package */ static final Set<String> sKnownPropertyNameSet = + Collections.unmodifiableSet(new HashSet<String>( + Arrays.asList("BEGIN", "LOGO", "PHOTO", "LABEL", "FN", "TITLE", "SOUND", + "VERSION", "TEL", "EMAIL", "TZ", "GEO", "NOTE", "URL", + "BDAY", "ROLE", "REV", "UID", "KEY", "MAILER"))); /** - * Parses the file at the given position. - * - * vcard_file = [wsls] vcard [wsls] + * A unmodifiable Set storing the types known in vCard 2.1. */ - protected void parseVCardFile() throws IOException, VCardException { - boolean firstReading = true; - while (true) { - if (mCanceled) { - break; - } - if (!parseOneVCard(firstReading)) { - break; - } - firstReading = false; - } - - if (mNestCount > 0) { - boolean useCache = true; - for (int i = 0; i < mNestCount; i++) { - readEndVCard(useCache, true); - useCache = false; - } - } - } - - protected int getVersion() { - return VCardConfig.FLAG_V21; - } - - protected String getVersionString() { - return VCardConstants.VERSION_V21; - } + /* package */ static final Set<String> sKnownTypeSet = + Collections.unmodifiableSet(new HashSet<String>( + Arrays.asList("DOM", "INTL", "POSTAL", "PARCEL", "HOME", "WORK", + "PREF", "VOICE", "FAX", "MSG", "CELL", "PAGER", "BBS", + "MODEM", "CAR", "ISDN", "VIDEO", "AOL", "APPLELINK", + "ATTMAIL", "CIS", "EWORLD", "INTERNET", "IBMMAIL", + "MCIMAIL", "POWERSHARE", "PRODIGY", "TLX", "X400", "GIF", + "CGM", "WMF", "BMP", "MET", "PMB", "DIB", "PICT", "TIFF", + "PDF", "PS", "JPEG", "QTIME", "MPEG", "MPEG2", "AVI", + "WAVE", "AIFF", "PCM", "X509", "PGP"))); /** - * @return true when the propertyName is a valid property name. + * A unmodifiable Set storing the values for the type "VALUE", available in the vCard 2.1. */ - protected boolean isValidPropertyName(String propertyName) { - if (!(sAvailablePropertyNameSetV21.contains(propertyName.toUpperCase()) || - propertyName.startsWith("X-")) && - !mUnknownTypeMap.contains(propertyName)) { - mUnknownTypeMap.add(propertyName); - Log.w(LOG_TAG, "Property name unsupported by vCard 2.1: " + propertyName); - } - return true; - } + /* package */ static final Set<String> sKnownValueSet = + Collections.unmodifiableSet(new HashSet<String>( + Arrays.asList("INLINE", "URL", "CONTENT-ID", "CID"))); /** - * @return true when the encoding is a valid encoding. - */ - protected boolean isValidEncoding(String encoding) { - return sAvailableEncodingV21.contains(encoding.toUpperCase()); - } - - /** - * @return String. It may be null, or its length may be 0 - * @throws IOException - */ - protected String getLine() throws IOException { - return mReader.readLine(); - } - - /** - * @return String with it's length > 0 - * @throws IOException - * @throws VCardException when the stream reached end of line - */ - protected String getNonEmptyLine() throws IOException, VCardException { - String line; - while (true) { - line = getLine(); - if (line == null) { - throw new VCardException("Reached end of buffer."); - } else if (line.trim().length() > 0) { - return line; - } - } - } - - /** - * vcard = "BEGIN" [ws] ":" [ws] "VCARD" [ws] 1*CRLF - * items *CRLF - * "END" [ws] ":" [ws] "VCARD" - */ - private boolean parseOneVCard(boolean firstReading) throws IOException, VCardException { - boolean allowGarbage = false; - if (firstReading) { - if (mNestCount > 0) { - for (int i = 0; i < mNestCount; i++) { - if (!readBeginVCard(allowGarbage)) { - return false; - } - allowGarbage = true; - } - } - } - - if (!readBeginVCard(allowGarbage)) { - return false; - } - long start; - if (mBuilder != null) { - start = System.currentTimeMillis(); - mBuilder.startEntry(); - mTimeReadStartRecord += System.currentTimeMillis() - start; - } - start = System.currentTimeMillis(); - parseItems(); - mTimeParseItems += System.currentTimeMillis() - start; - readEndVCard(true, false); - if (mBuilder != null) { - start = System.currentTimeMillis(); - mBuilder.endEntry(); - mTimeReadEndRecord += System.currentTimeMillis() - start; - } - return true; - } - - /** - * @return True when successful. False when reaching the end of line - * @throws IOException - * @throws VCardException - */ - protected boolean readBeginVCard(boolean allowGarbage) throws IOException, VCardException { - String line; - do { - while (true) { - line = getLine(); - if (line == null) { - return false; - } else if (line.trim().length() > 0) { - break; - } - } - String[] strArray = line.split(":", 2); - int length = strArray.length; - - // Though vCard 2.1/3.0 specification does not allow lower cases, - // vCard file emitted by some external vCard expoter have such invalid Strings. - // So we allow it. - // e.g. BEGIN:vCard - if (length == 2 && - strArray[0].trim().equalsIgnoreCase("BEGIN") && - strArray[1].trim().equalsIgnoreCase("VCARD")) { - return true; - } else if (!allowGarbage) { - if (mNestCount > 0) { - mPreviousLine = line; - return false; - } else { - throw new VCardException( - "Expected String \"BEGIN:VCARD\" did not come " - + "(Instead, \"" + line + "\" came)"); - } - } - } while(allowGarbage); - - throw new VCardException("Reached where must not be reached."); - } - - /** - * The arguments useCache and allowGarbase are usually true and false accordingly when - * this function is called outside this function itself. - * - * @param useCache When true, line is obtained from mPreviousline. Otherwise, getLine() - * is used. - * @param allowGarbage When true, ignore non "END:VCARD" line. - * @throws IOException - * @throws VCardException - */ - protected void readEndVCard(boolean useCache, boolean allowGarbage) - throws IOException, VCardException { - String line; - do { - if (useCache) { - // Though vCard specification does not allow lower cases, - // some data may have them, so we allow it. - line = mPreviousLine; - } else { - while (true) { - line = getLine(); - if (line == null) { - throw new VCardException("Expected END:VCARD was not found."); - } else if (line.trim().length() > 0) { - break; - } - } - } - - String[] strArray = line.split(":", 2); - if (strArray.length == 2 && - strArray[0].trim().equalsIgnoreCase("END") && - strArray[1].trim().equalsIgnoreCase("VCARD")) { - return; - } else if (!allowGarbage) { - throw new VCardException("END:VCARD != \"" + mPreviousLine + "\""); - } - useCache = false; - } while (allowGarbage); - } - - /** - * items = *CRLF item - * / item - */ - protected void parseItems() throws IOException, VCardException { - boolean ended = false; - - if (mBuilder != null) { - long start = System.currentTimeMillis(); - mBuilder.startProperty(); - mTimeStartProperty += System.currentTimeMillis() - start; - } - ended = parseItem(); - if (mBuilder != null && !ended) { - long start = System.currentTimeMillis(); - mBuilder.endProperty(); - mTimeEndProperty += System.currentTimeMillis() - start; - } - - while (!ended) { - // follow VCARD ,it wont reach endProperty - if (mBuilder != null) { - long start = System.currentTimeMillis(); - mBuilder.startProperty(); - mTimeStartProperty += System.currentTimeMillis() - start; - } - try { - ended = parseItem(); - } catch (VCardInvalidCommentLineException e) { - Log.e(LOG_TAG, "Invalid line which looks like some comment was found. Ignored."); - ended = false; - } - if (mBuilder != null && !ended) { - long start = System.currentTimeMillis(); - mBuilder.endProperty(); - mTimeEndProperty += System.currentTimeMillis() - start; - } - } - } - - /** - * item = [groups "."] name [params] ":" value CRLF - * / [groups "."] "ADR" [params] ":" addressparts CRLF - * / [groups "."] "ORG" [params] ":" orgparts CRLF - * / [groups "."] "N" [params] ":" nameparts CRLF - * / [groups "."] "AGENT" [params] ":" vcard CRLF - */ - protected boolean parseItem() throws IOException, VCardException { - mEncoding = sDefaultEncoding; - - final String line = getNonEmptyLine(); - long start = System.currentTimeMillis(); - - String[] propertyNameAndValue = separateLineAndHandleGroup(line); - if (propertyNameAndValue == null) { - return true; - } - if (propertyNameAndValue.length != 2) { - throw new VCardInvalidLineException("Invalid line \"" + line + "\""); - } - String propertyName = propertyNameAndValue[0].toUpperCase(); - String propertyValue = propertyNameAndValue[1]; - - mTimeParseLineAndHandleGroup += System.currentTimeMillis() - start; - - if (propertyName.equals("ADR") || propertyName.equals("ORG") || - propertyName.equals("N")) { - start = System.currentTimeMillis(); - handleMultiplePropertyValue(propertyName, propertyValue); - mTimeParseAdrOrgN += System.currentTimeMillis() - start; - return false; - } else if (propertyName.equals("AGENT")) { - handleAgent(propertyValue); - return false; - } else if (isValidPropertyName(propertyName)) { - if (propertyName.equals("BEGIN")) { - if (propertyValue.equals("VCARD")) { - throw new VCardNestedException("This vCard has nested vCard data in it."); - } else { - throw new VCardException("Unknown BEGIN type: " + propertyValue); - } - } else if (propertyName.equals("VERSION") && - !propertyValue.equals(getVersionString())) { - throw new VCardVersionException("Incompatible version: " + - propertyValue + " != " + getVersionString()); - } - start = System.currentTimeMillis(); - handlePropertyValue(propertyName, propertyValue); - mTimeParsePropertyValues += System.currentTimeMillis() - start; - return false; - } - - throw new VCardException("Unknown property name: \"" + propertyName + "\""); - } - - static private final int STATE_GROUP_OR_PROPNAME = 0; - static private final int STATE_PARAMS = 1; - // vCard 3.0 specification allows double-quoted param-value, while vCard 2.1 does not. - // This is just for safety. - static private final int STATE_PARAMS_IN_DQUOTE = 2; - - protected String[] separateLineAndHandleGroup(String line) throws VCardException { - int state = STATE_GROUP_OR_PROPNAME; - int nameIndex = 0; - - final String[] propertyNameAndValue = new String[2]; - - final int length = line.length(); - if (length > 0 && line.charAt(0) == '#') { - throw new VCardInvalidCommentLineException(); - } - - for (int i = 0; i < length; i++) { - char ch = line.charAt(i); - switch (state) { - case STATE_GROUP_OR_PROPNAME: { - if (ch == ':') { - final String propertyName = line.substring(nameIndex, i); - if (propertyName.equalsIgnoreCase("END")) { - mPreviousLine = line; - return null; - } - if (mBuilder != null) { - mBuilder.propertyName(propertyName); - } - propertyNameAndValue[0] = propertyName; - if (i < length - 1) { - propertyNameAndValue[1] = line.substring(i + 1); - } else { - propertyNameAndValue[1] = ""; - } - return propertyNameAndValue; - } else if (ch == '.') { - String groupName = line.substring(nameIndex, i); - if (mBuilder != null) { - mBuilder.propertyGroup(groupName); - } - nameIndex = i + 1; - } else if (ch == ';') { - String propertyName = line.substring(nameIndex, i); - if (propertyName.equalsIgnoreCase("END")) { - mPreviousLine = line; - return null; - } - if (mBuilder != null) { - mBuilder.propertyName(propertyName); - } - propertyNameAndValue[0] = propertyName; - nameIndex = i + 1; - state = STATE_PARAMS; - } - break; - } - case STATE_PARAMS: { - if (ch == '"') { - state = STATE_PARAMS_IN_DQUOTE; - } else if (ch == ';') { - handleParams(line.substring(nameIndex, i)); - nameIndex = i + 1; - } else if (ch == ':') { - handleParams(line.substring(nameIndex, i)); - if (i < length - 1) { - propertyNameAndValue[1] = line.substring(i + 1); - } else { - propertyNameAndValue[1] = ""; - } - return propertyNameAndValue; - } - break; - } - case STATE_PARAMS_IN_DQUOTE: { - if (ch == '"') { - state = STATE_PARAMS; - } - break; - } - } - } - - throw new VCardInvalidLineException("Invalid line: \"" + line + "\""); - } - - /** - * params = ";" [ws] paramlist - * paramlist = paramlist [ws] ";" [ws] param - * / param - * param = "TYPE" [ws] "=" [ws] ptypeval - * / "VALUE" [ws] "=" [ws] pvalueval - * / "ENCODING" [ws] "=" [ws] pencodingval - * / "CHARSET" [ws] "=" [ws] charsetval - * / "LANGUAGE" [ws] "=" [ws] langval - * / "X-" word [ws] "=" [ws] word - * / knowntype - */ - protected void handleParams(String params) throws VCardException { - String[] strArray = params.split("=", 2); - if (strArray.length == 2) { - final String paramName = strArray[0].trim().toUpperCase(); - String paramValue = strArray[1].trim(); - if (paramName.equals("TYPE")) { - handleType(paramValue); - } else if (paramName.equals("VALUE")) { - handleValue(paramValue); - } else if (paramName.equals("ENCODING")) { - handleEncoding(paramValue); - } else if (paramName.equals("CHARSET")) { - handleCharset(paramValue); - } else if (paramName.equals("LANGUAGE")) { - handleLanguage(paramValue); - } else if (paramName.startsWith("X-")) { - handleAnyParam(paramName, paramValue); - } else { - throw new VCardException("Unknown type \"" + paramName + "\""); - } - } else { - handleParamWithoutName(strArray[0]); - } - } - - /** - * vCard 3.0 parser may throw VCardException. - */ - @SuppressWarnings("unused") - protected void handleParamWithoutName(final String paramValue) throws VCardException { - handleType(paramValue); - } - - /** - * ptypeval = knowntype / "X-" word - */ - protected void handleType(final String ptypeval) { - String upperTypeValue = ptypeval; - if (!(sKnownTypeSet.contains(upperTypeValue) || upperTypeValue.startsWith("X-")) && - !mUnknownTypeMap.contains(ptypeval)) { - mUnknownTypeMap.add(ptypeval); - Log.w(LOG_TAG, "TYPE unsupported by vCard 2.1: " + ptypeval); - } - if (mBuilder != null) { - mBuilder.propertyParamType("TYPE"); - mBuilder.propertyParamValue(upperTypeValue); - } - } - - /** - * pvalueval = "INLINE" / "URL" / "CONTENT-ID" / "CID" / "X-" word - */ - protected void handleValue(final String pvalueval) { - if (!sKnownValueSet.contains(pvalueval.toUpperCase()) && - pvalueval.startsWith("X-") && - !mUnknownValueMap.contains(pvalueval)) { - mUnknownValueMap.add(pvalueval); - Log.w(LOG_TAG, "VALUE unsupported by vCard 2.1: " + pvalueval); - } - if (mBuilder != null) { - mBuilder.propertyParamType("VALUE"); - mBuilder.propertyParamValue(pvalueval); - } - } - - /** - * pencodingval = "7BIT" / "8BIT" / "QUOTED-PRINTABLE" / "BASE64" / "X-" word - */ - protected void handleEncoding(String pencodingval) throws VCardException { - if (isValidEncoding(pencodingval) || - pencodingval.startsWith("X-")) { - if (mBuilder != null) { - mBuilder.propertyParamType("ENCODING"); - mBuilder.propertyParamValue(pencodingval); - } - mEncoding = pencodingval; - } else { - throw new VCardException("Unknown encoding \"" + pencodingval + "\""); - } - } - - /** - * vCard 2.1 specification only allows us-ascii and iso-8859-xxx (See RFC 1521), - * but today's vCard often contains other charset, so we allow them. - */ - protected void handleCharset(String charsetval) { - if (mBuilder != null) { - mBuilder.propertyParamType("CHARSET"); - mBuilder.propertyParamValue(charsetval); - } - } - - /** - * See also Section 7.1 of RFC 1521 - */ - protected void handleLanguage(String langval) throws VCardException { - String[] strArray = langval.split("-"); - if (strArray.length != 2) { - throw new VCardException("Invalid Language: \"" + langval + "\""); - } - String tmp = strArray[0]; - int length = tmp.length(); - for (int i = 0; i < length; i++) { - if (!isLetter(tmp.charAt(i))) { - throw new VCardException("Invalid Language: \"" + langval + "\""); - } - } - tmp = strArray[1]; - length = tmp.length(); - for (int i = 0; i < length; i++) { - if (!isLetter(tmp.charAt(i))) { - throw new VCardException("Invalid Language: \"" + langval + "\""); - } - } - if (mBuilder != null) { - mBuilder.propertyParamType("LANGUAGE"); - mBuilder.propertyParamValue(langval); - } - } - - /** - * Mainly for "X-" type. This accepts any kind of type without check. - */ - protected void handleAnyParam(String paramName, String paramValue) { - if (mBuilder != null) { - mBuilder.propertyParamType(paramName); - mBuilder.propertyParamValue(paramValue); - } - } - - protected void handlePropertyValue(String propertyName, String propertyValue) - throws IOException, VCardException { - if (mEncoding.equalsIgnoreCase("QUOTED-PRINTABLE")) { - final long start = System.currentTimeMillis(); - final String result = getQuotedPrintable(propertyValue); - if (mBuilder != null) { - ArrayList<String> v = new ArrayList<String>(); - v.add(result); - mBuilder.propertyValues(v); - } - mTimeHandleQuotedPrintable += System.currentTimeMillis() - start; - } else if (mEncoding.equalsIgnoreCase("BASE64") || - mEncoding.equalsIgnoreCase("B")) { - final long start = System.currentTimeMillis(); - // It is very rare, but some BASE64 data may be so big that - // OutOfMemoryError occurs. To ignore such cases, use try-catch. - try { - final String result = getBase64(propertyValue); - if (mBuilder != null) { - ArrayList<String> v = new ArrayList<String>(); - v.add(result); - mBuilder.propertyValues(v); - } - } catch (OutOfMemoryError error) { - Log.e(LOG_TAG, "OutOfMemoryError happened during parsing BASE64 data!"); - if (mBuilder != null) { - mBuilder.propertyValues(null); - } - } - mTimeHandleBase64 += System.currentTimeMillis() - start; - } else { - if (!(mEncoding == null || mEncoding.equalsIgnoreCase("7BIT") - || mEncoding.equalsIgnoreCase("8BIT") - || mEncoding.toUpperCase().startsWith("X-"))) { - Log.w(LOG_TAG, "The encoding unsupported by vCard spec: \"" + mEncoding + "\"."); - } - - final long start = System.currentTimeMillis(); - if (mBuilder != null) { - ArrayList<String> v = new ArrayList<String>(); - v.add(maybeUnescapeText(propertyValue)); - mBuilder.propertyValues(v); - } - mTimeHandleMiscPropertyValue += System.currentTimeMillis() - start; - } - } - - protected String getQuotedPrintable(String firstString) throws IOException, VCardException { - // Specifically, there may be some padding between = and CRLF. - // See the following: - // - // qp-line := *(qp-segment transport-padding CRLF) - // qp-part transport-padding - // qp-segment := qp-section *(SPACE / TAB) "=" - // ; Maximum length of 76 characters - // - // e.g. (from RFC 2045) - // Now's the time = - // for all folk to come= - // to the aid of their country. - if (firstString.trim().endsWith("=")) { - // remove "transport-padding" - int pos = firstString.length() - 1; - while(firstString.charAt(pos) != '=') { - } - StringBuilder builder = new StringBuilder(); - builder.append(firstString.substring(0, pos + 1)); - builder.append("\r\n"); - String line; - while (true) { - line = getLine(); - if (line == null) { - throw new VCardException( - "File ended during parsing quoted-printable String"); - } - if (line.trim().endsWith("=")) { - // remove "transport-padding" - pos = line.length() - 1; - while(line.charAt(pos) != '=') { - } - builder.append(line.substring(0, pos + 1)); - builder.append("\r\n"); - } else { - builder.append(line); - break; - } - } - return builder.toString(); - } else { - return firstString; - } - } - - protected String getBase64(String firstString) throws IOException, VCardException { - StringBuilder builder = new StringBuilder(); - builder.append(firstString); - - while (true) { - String line = getLine(); - if (line == null) { - throw new VCardException( - "File ended during parsing BASE64 binary"); - } - if (line.length() == 0) { - break; - } - builder.append(line); - } - - return builder.toString(); - } - - /** - * Mainly for "ADR", "ORG", and "N" - * We do not care the number of strnosemi here. - * - * addressparts = 0*6(strnosemi ";") strnosemi - * ; PO Box, Extended Addr, Street, Locality, Region, - * Postal Code, Country Name - * orgparts = *(strnosemi ";") strnosemi - * ; First is Organization Name, - * remainder are Organization Units. - * nameparts = 0*4(strnosemi ";") strnosemi - * ; Family, Given, Middle, Prefix, Suffix. - * ; Example:Public;John;Q.;Reverend Dr.;III, Esq. - * strnosemi = *(*nonsemi ("\;" / "\" CRLF)) *nonsemi - * ; To include a semicolon in this string, it must be escaped - * ; with a "\" character. - * - * We are not sure whether we should add "\" CRLF to each value. - * For now, we exclude them. + * <p> + * A unmodifiable Set storing the values for the type "ENCODING", available in the vCard 2.1. + * </p> + * <p> + * Though vCard 2.1 specification does not allow "B" encoding, some data may have it. + * We allow it for safety. + * </p> */ - protected void handleMultiplePropertyValue(String propertyName, String propertyValue) - throws IOException, VCardException { - // vCard 2.1 does not allow QUOTED-PRINTABLE here, - // but some softwares/devices emit such data. - if (mEncoding.equalsIgnoreCase("QUOTED-PRINTABLE")) { - propertyValue = getQuotedPrintable(propertyValue); - } - - if (mBuilder != null) { - mBuilder.propertyValues(VCardUtils.constructListFromValue( - propertyValue, (getVersion() == VCardConfig.FLAG_V30))); - } - } + /* package */ static final Set<String> sAvailableEncoding = + Collections.unmodifiableSet(new HashSet<String>( + Arrays.asList(VCardConstants.PARAM_ENCODING_7BIT, + VCardConstants.PARAM_ENCODING_8BIT, + VCardConstants.PARAM_ENCODING_QP, + VCardConstants.PARAM_ENCODING_BASE64, + VCardConstants.PARAM_ENCODING_B))); - /** - * vCard 2.1 specifies AGENT allows one vcard entry. It is not encoded at all. - * - * item = ... - * / [groups "."] "AGENT" - * [params] ":" vcard CRLF - * vcard = "BEGIN" [ws] ":" [ws] "VCARD" [ws] 1*CRLF - * items *CRLF "END" [ws] ":" [ws] "VCARD" - */ - protected void handleAgent(final String propertyValue) throws VCardException { - if (!propertyValue.toUpperCase().contains("BEGIN:VCARD")) { - // Apparently invalid line seen in Windows Mobile 6.5. Ignore them. - return; - } else { - throw new VCardAgentNotSupportedException("AGENT Property is not supported now."); - } - // TODO: Support AGENT property. - } - - /** - * For vCard 3.0. - */ - protected String maybeUnescapeText(final String text) { - return text; - } + private final VCardParserImpl_V21 mVCardParserImpl; - /** - * Returns unescaped String if the character should be unescaped. Return null otherwise. - * e.g. In vCard 2.1, "\;" should be unescaped into ";" while "\x" should not be. - */ - protected String maybeUnescapeCharacter(final char ch) { - return unescapeCharacter(ch); + public VCardParser_V21() { + mVCardParserImpl = new VCardParserImpl_V21(); } - public static String unescapeCharacter(final char ch) { - // Original vCard 2.1 specification does not allow transformation - // "\:" -> ":", "\," -> ",", and "\\" -> "\", but previous implementation of - // this class allowed them, so keep it as is. - if (ch == '\\' || ch == ';' || ch == ':' || ch == ',') { - return String.valueOf(ch); - } else { - return null; - } - } - - @Override - public boolean parse(final InputStream is, final VCardInterpreter builder) - throws IOException, VCardException { - return parse(is, VCardConfig.DEFAULT_CHARSET, builder); + public VCardParser_V21(int vcardType) { + mVCardParserImpl = new VCardParserImpl_V21(vcardType); } - - @Override - public boolean parse(InputStream is, String charset, VCardInterpreter builder) - throws IOException, VCardException { - if (charset == null) { - charset = VCardConfig.DEFAULT_CHARSET; - } - final InputStreamReader tmpReader = new InputStreamReader(is, charset); - if (VCardConfig.showPerformanceLog()) { - mReader = new CustomBufferedReader(tmpReader); - } else { - mReader = new BufferedReader(tmpReader); - } - - mBuilder = builder; - long start = System.currentTimeMillis(); - if (mBuilder != null) { - mBuilder.start(); - } - parseVCardFile(); - if (mBuilder != null) { - mBuilder.end(); - } - mTimeTotal += System.currentTimeMillis() - start; - - if (VCardConfig.showPerformanceLog()) { - showPerformanceInfo(); - } - - return true; - } - - @Override - public void parse(InputStream is, String charset, VCardInterpreter builder, boolean canceled) + public void parse(InputStream is, VCardInterpreter interepreter) throws IOException, VCardException { - mCanceled = canceled; - parse(is, charset, builder); - } - - private void showPerformanceInfo() { - Log.d(LOG_TAG, "Total parsing time: " + mTimeTotal + " ms"); - if (mReader instanceof CustomBufferedReader) { - Log.d(LOG_TAG, "Total readLine time: " + - ((CustomBufferedReader)mReader).getTotalmillisecond() + " ms"); - } - Log.d(LOG_TAG, "Time for handling the beggining of the record: " + - mTimeReadStartRecord + " ms"); - Log.d(LOG_TAG, "Time for handling the end of the record: " + - mTimeReadEndRecord + " ms"); - Log.d(LOG_TAG, "Time for parsing line, and handling group: " + - mTimeParseLineAndHandleGroup + " ms"); - Log.d(LOG_TAG, "Time for parsing ADR, ORG, and N fields:" + mTimeParseAdrOrgN + " ms"); - Log.d(LOG_TAG, "Time for parsing property values: " + mTimeParsePropertyValues + " ms"); - Log.d(LOG_TAG, "Time for handling normal property values: " + - mTimeHandleMiscPropertyValue + " ms"); - Log.d(LOG_TAG, "Time for handling Quoted-Printable: " + - mTimeHandleQuotedPrintable + " ms"); - Log.d(LOG_TAG, "Time for handling Base64: " + mTimeHandleBase64 + " ms"); + mVCardParserImpl.parse(is, interepreter); } - private boolean isLetter(char ch) { - if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) { - return true; - } - return false; - } -} - -class CustomBufferedReader extends BufferedReader { - private long mTime; - - public CustomBufferedReader(Reader in) { - super(in); - } - - @Override - public String readLine() throws IOException { - long start = System.currentTimeMillis(); - String ret = super.readLine(); - long end = System.currentTimeMillis(); - mTime += end - start; - return ret; - } - - public long getTotalmillisecond() { - return mTime; + public void cancel() { + mVCardParserImpl.cancel(); } } diff --git a/core/java/android/pim/vcard/VCardParser_V30.java b/core/java/android/pim/vcard/VCardParser_V30.java index 4ecfe9750c66..238d3a8a51de 100644 --- a/core/java/android/pim/vcard/VCardParser_V30.java +++ b/core/java/android/pim/vcard/VCardParser_V30.java @@ -16,343 +16,72 @@ package android.pim.vcard; import android.pim.vcard.exception.VCardException; -import android.util.Log; import java.io.IOException; +import java.io.InputStream; import java.util.Arrays; +import java.util.Collections; import java.util.HashSet; +import java.util.Set; /** - * The class used to parse vCard 3.0. - * Please refer to vCard Specification 3.0 (http://tools.ietf.org/html/rfc2426). + * <p> + * vCard parser for vCard 3.0. See RFC 2426 for more detail. + * </p> + * <p> + * This parser allows vCard format which is not allowed in the RFC, since + * we have seen several vCard 3.0 files which don't comply with it. + * </p> + * <p> + * e.g. vCard 3.0 does not allow "CHARSET" attribute, but some actual files + * have it and they uses non UTF-8 charsets. UTF-8 is recommended in RFC 2426, + * but it is not a must. We silently allow "CHARSET". + * </p> */ -public class VCardParser_V30 extends VCardParser_V21 { - private static final String LOG_TAG = "VCardParser_V30"; - - private static final HashSet<String> sAcceptablePropsWithParam = new HashSet<String>( - Arrays.asList( +public class VCardParser_V30 implements VCardParser { + /* package */ static final Set<String> sKnownPropertyNameSet = + Collections.unmodifiableSet(new HashSet<String>(Arrays.asList( "BEGIN", "LOGO", "PHOTO", "LABEL", "FN", "TITLE", "SOUND", "VERSION", "TEL", "EMAIL", "TZ", "GEO", "NOTE", "URL", "BDAY", "ROLE", "REV", "UID", "KEY", "MAILER", // 2.1 "NAME", "PROFILE", "SOURCE", "NICKNAME", "CLASS", - "SORT-STRING", "CATEGORIES", "PRODID")); // 3.0 - - // Although "7bit" and "BASE64" is not allowed in vCard 3.0, we allow it for safety. - private static final HashSet<String> sAcceptableEncodingV30 = new HashSet<String>( - Arrays.asList("7BIT", "8BIT", "BASE64", "B")); - - // Although RFC 2426 specifies some property must not have parameters, we allow it, - // since there may be some careers which violates the RFC... - private static final HashSet<String> acceptablePropsWithoutParam = new HashSet<String>(); - - private String mPreviousLine; - - private boolean mEmittedAgentWarning = false; + "SORT-STRING", "CATEGORIES", "PRODID"))); // 3.0 /** - * True when the caller wants the parser to be strict about the input. - * Currently this is only for testing. + * <p> + * A unmodifiable Set storing the values for the type "ENCODING", available in the vCard 3.0. + * </p> + * <p> + * Though vCard 2.1 specification does not allow "7BIT" or "BASE64", we allow them for safety. + * </p> + * <p> + * "QUOTED-PRINTABLE" is not allowed in vCard 3.0 and not in this parser either, + * because the encoding ambiguates how the vCard file to be parsed. + * </p> */ - private final boolean mStrictParsing; + /* package */ static final Set<String> sAcceptableEncoding = + Collections.unmodifiableSet(new HashSet<String>(Arrays.asList( + VCardConstants.PARAM_ENCODING_7BIT, + VCardConstants.PARAM_ENCODING_8BIT, + VCardConstants.PARAM_ENCODING_BASE64, + VCardConstants.PARAM_ENCODING_B))); - public VCardParser_V30() { - super(); - mStrictParsing = false; - } - - /** - * @param strictParsing when true, this object throws VCardException when the vcard is not - * valid from the view of vCard 3.0 specification (defined in RFC 2426). Note that this class - * is not fully yet for being used with this flag and may not notice invalid line(s). - * - * @hide currently only for testing! - */ - public VCardParser_V30(boolean strictParsing) { - super(); - mStrictParsing = strictParsing; - } - - public VCardParser_V30(int parseMode) { - super(parseMode); - mStrictParsing = false; - } - - @Override - protected int getVersion() { - return VCardConfig.FLAG_V30; - } + private final VCardParserImpl_V30 mVCardParserImpl; - @Override - protected String getVersionString() { - return VCardConstants.VERSION_V30; - } - - @Override - protected boolean isValidPropertyName(String propertyName) { - if (!(sAcceptablePropsWithParam.contains(propertyName) || - acceptablePropsWithoutParam.contains(propertyName) || - propertyName.startsWith("X-")) && - !mUnknownTypeMap.contains(propertyName)) { - mUnknownTypeMap.add(propertyName); - Log.w(LOG_TAG, "Property name unsupported by vCard 3.0: " + propertyName); - } - return true; - } - - @Override - protected boolean isValidEncoding(String encoding) { - return sAcceptableEncodingV30.contains(encoding.toUpperCase()); + public VCardParser_V30() { + mVCardParserImpl = new VCardParserImpl_V30(); } - @Override - protected String getLine() throws IOException { - if (mPreviousLine != null) { - String ret = mPreviousLine; - mPreviousLine = null; - return ret; - } else { - return mReader.readLine(); - } - } - - /** - * vCard 3.0 requires that the line with space at the beginning of the line - * must be combined with previous line. - */ - @Override - protected String getNonEmptyLine() throws IOException, VCardException { - String line; - StringBuilder builder = null; - while (true) { - line = mReader.readLine(); - if (line == null) { - if (builder != null) { - return builder.toString(); - } else if (mPreviousLine != null) { - String ret = mPreviousLine; - mPreviousLine = null; - return ret; - } - throw new VCardException("Reached end of buffer."); - } else if (line.length() == 0) { - if (builder != null) { - return builder.toString(); - } else if (mPreviousLine != null) { - String ret = mPreviousLine; - mPreviousLine = null; - return ret; - } - } else if (line.charAt(0) == ' ' || line.charAt(0) == '\t') { - if (builder != null) { - // See Section 5.8.1 of RFC 2425 (MIME-DIR document). - // Following is the excerpts from it. - // - // DESCRIPTION:This is a long description that exists on a long line. - // - // Can be represented as: - // - // DESCRIPTION:This is a long description - // that exists on a long line. - // - // It could also be represented as: - // - // DESCRIPTION:This is a long descrip - // tion that exists o - // n a long line. - builder.append(line.substring(1)); - } else if (mPreviousLine != null) { - builder = new StringBuilder(); - builder.append(mPreviousLine); - mPreviousLine = null; - builder.append(line.substring(1)); - } else { - throw new VCardException("Space exists at the beginning of the line"); - } - } else { - if (mPreviousLine == null) { - mPreviousLine = line; - if (builder != null) { - return builder.toString(); - } - } else { - String ret = mPreviousLine; - mPreviousLine = line; - return ret; - } - } - } - } - - - /** - * vcard = [group "."] "BEGIN" ":" "VCARD" 1 * CRLF - * 1 * (contentline) - * ;A vCard object MUST include the VERSION, FN and N types. - * [group "."] "END" ":" "VCARD" 1 * CRLF - */ - @Override - protected boolean readBeginVCard(boolean allowGarbage) throws IOException, VCardException { - // TODO: vCard 3.0 supports group. - return super.readBeginVCard(allowGarbage); + public VCardParser_V30(int vcardType) { + mVCardParserImpl = new VCardParserImpl_V30(vcardType); } - @Override - protected void readEndVCard(boolean useCache, boolean allowGarbage) + public void parse(InputStream is, VCardInterpreter interepreter) throws IOException, VCardException { - // TODO: vCard 3.0 supports group. - super.readEndVCard(useCache, allowGarbage); - } - - /** - * vCard 3.0 allows iana-token as paramType, while vCard 2.1 does not. - */ - @Override - protected void handleParams(String params) throws VCardException { - try { - super.handleParams(params); - } catch (VCardException e) { - // maybe IANA type - String[] strArray = params.split("=", 2); - if (strArray.length == 2) { - handleAnyParam(strArray[0], strArray[1]); - } else { - // Must not come here in the current implementation. - throw new VCardException( - "Unknown params value: " + params); - } - } - } - - @Override - protected void handleAnyParam(String paramName, String paramValue) { - super.handleAnyParam(paramName, paramValue); - } - - @Override - protected void handleParamWithoutName(final String paramValue) throws VCardException { - if (mStrictParsing) { - throw new VCardException("Parameter without name is not acceptable in vCard 3.0"); - } else { - super.handleParamWithoutName(paramValue); - } - } - - /** - * vCard 3.0 defines - * - * param = param-name "=" param-value *("," param-value) - * param-name = iana-token / x-name - * param-value = ptext / quoted-string - * quoted-string = DQUOTE QSAFE-CHAR DQUOTE - */ - @Override - protected void handleType(String ptypevalues) { - String[] ptypeArray = ptypevalues.split(","); - mBuilder.propertyParamType("TYPE"); - for (String value : ptypeArray) { - int length = value.length(); - if (length >= 2 && value.startsWith("\"") && value.endsWith("\"")) { - mBuilder.propertyParamValue(value.substring(1, value.length() - 1)); - } else { - mBuilder.propertyParamValue(value); - } - } - } - - @Override - protected void handleAgent(String propertyValue) { - // The way how vCard 3.0 supports "AGENT" is completely different from vCard 2.1. - // - // e.g. - // AGENT:BEGIN:VCARD\nFN:Joe Friday\nTEL:+1-919-555-7878\n - // TITLE:Area Administrator\, Assistant\n EMAIL\;TYPE=INTERN\n - // ET:jfriday@host.com\nEND:VCARD\n - // - // TODO: fix this. - // - // issue: - // vCard 3.0 also allows this as an example. - // - // AGENT;VALUE=uri: - // CID:JQPUBLIC.part3.960129T083020.xyzMail@host3.com - // - // This is not vCard. Should we support this? - // - // Just ignore the line for now, since we cannot know how to handle it... - if (!mEmittedAgentWarning) { - Log.w(LOG_TAG, "AGENT in vCard 3.0 is not supported yet. Ignore it"); - mEmittedAgentWarning = true; - } - } - - /** - * vCard 3.0 does not require two CRLF at the last of BASE64 data. - * It only requires that data should be MIME-encoded. - */ - @Override - protected String getBase64(String firstString) throws IOException, VCardException { - StringBuilder builder = new StringBuilder(); - builder.append(firstString); - - while (true) { - String line = getLine(); - if (line == null) { - throw new VCardException( - "File ended during parsing BASE64 binary"); - } - if (line.length() == 0) { - break; - } else if (!line.startsWith(" ") && !line.startsWith("\t")) { - mPreviousLine = line; - break; - } - builder.append(line); - } - - return builder.toString(); - } - - /** - * ESCAPED-CHAR = "\\" / "\;" / "\," / "\n" / "\N") - * ; \\ encodes \, \n or \N encodes newline - * ; \; encodes ;, \, encodes , - * - * Note: Apple escapes ':' into '\:' while does not escape '\' - */ - @Override - protected String maybeUnescapeText(String text) { - return unescapeText(text); - } - - public static String unescapeText(String text) { - StringBuilder builder = new StringBuilder(); - int length = text.length(); - for (int i = 0; i < length; i++) { - char ch = text.charAt(i); - if (ch == '\\' && i < length - 1) { - char next_ch = text.charAt(++i); - if (next_ch == 'n' || next_ch == 'N') { - builder.append("\n"); - } else { - builder.append(next_ch); - } - } else { - builder.append(ch); - } - } - return builder.toString(); - } - - @Override - protected String maybeUnescapeCharacter(char ch) { - return unescapeCharacter(ch); + mVCardParserImpl.parse(is, interepreter); } - public static String unescapeCharacter(char ch) { - if (ch == 'n' || ch == 'N') { - return "\n"; - } else { - return String.valueOf(ch); - } + public void cancel() { + mVCardParserImpl.cancel(); } } diff --git a/core/java/android/pim/vcard/VCardParser_V40.java b/core/java/android/pim/vcard/VCardParser_V40.java new file mode 100644 index 000000000000..65a2f6863e6f --- /dev/null +++ b/core/java/android/pim/vcard/VCardParser_V40.java @@ -0,0 +1,78 @@ +/* + * 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 android.pim.vcard; + +import android.pim.vcard.exception.VCardException; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +/** + * <p> + * vCard parser for vCard 4.0. + * </p> + * <p> + * Currently this parser is based on vCard 4.0 specification rev 11. + * </p> + */ +public class VCardParser_V40 implements VCardParser { + /* package */ static final Set<String> sKnownPropertyNameSet = + Collections.unmodifiableSet(new HashSet<String>(Arrays.asList( + "BEGIN", "END", "SOURCE", "NAME", "KIND", "XML", + "FN", "N", "NICKNAME", "PHOTO", "BDAY", "DDAY", + "BIRTH", "DEATH", "ANNIVERSARY", "SEX", "ADR", + "LABEL", "TEL", "EMAIL", "IMPP", "LANG", "TZ", + "GEO", "TITLE", "ROLE", "LOGO", "ORG", "MEMBER", + "RELATED", "CATEGORIES", "NOTE", "PRODID", + "REV", "SOUND", "UID", "CLIENTPIDMAP", + "URL", "VERSION", "CLASS", "KEY", "FBURL", "CALENDRURI", + "CALURI"))); + + /** + * <p> + * A unmodifiable Set storing the values for the type "ENCODING", available in vCard 4.0. + * </p> + */ + /* package */ static final Set<String> sAcceptableEncoding = + Collections.unmodifiableSet(new HashSet<String>(Arrays.asList( + VCardConstants.PARAM_ENCODING_8BIT, + VCardConstants.PARAM_ENCODING_B))); + + private final VCardParserImpl_V30 mVCardParserImpl; + + public VCardParser_V40() { + mVCardParserImpl = new VCardParserImpl_V40(); + } + + public VCardParser_V40(int vcardType) { + mVCardParserImpl = new VCardParserImpl_V40(vcardType); + } + + @Override + public void parse(InputStream is, VCardInterpreter interepreter) + throws IOException, VCardException { + mVCardParserImpl.parse(is, interepreter); + } + + @Override + public void cancel() { + mVCardParserImpl.cancel(); + } +}
\ No newline at end of file diff --git a/core/java/android/pim/vcard/VCardSourceDetector.java b/core/java/android/pim/vcard/VCardSourceDetector.java index 7297c500690b..4c6461e98ea9 100644 --- a/core/java/android/pim/vcard/VCardSourceDetector.java +++ b/core/java/android/pim/vcard/VCardSourceDetector.java @@ -15,17 +15,33 @@ */ package android.pim.vcard; +import android.text.TextUtils; +import android.util.Log; + import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; /** - * Class which tries to detects the source of the vCard from its properties. - * Currently this implementation is very premature. - * @hide + * <p> + * The class which tries to detects the source of a vCard file from its contents. + * </p> + * <p> + * The specification of vCard (including both 2.1 and 3.0) is not so strict as to + * guess its format just by reading beginning few lines (usually we can, but in + * some most pessimistic case, we cannot until at almost the end of the file). + * Also we cannot store all vCard entries in memory, while there's no specification + * how big the vCard entry would become after the parse. + * </p> + * <p> + * This class is usually used for the "first scan", in which we can understand which vCard + * version is used (and how many entries exist in a file). + * </p> */ public class VCardSourceDetector implements VCardInterpreter { + private static final String LOG_TAG = "VCardSourceDetector"; + private static Set<String> APPLE_SIGNS = new HashSet<String>(Arrays.asList( "X-PHONETIC-FIRST-NAME", "X-PHONETIC-MIDDLE-NAME", "X-PHONETIC-LAST-NAME", "X-ABADR", "X-ABUID")); @@ -42,10 +58,30 @@ public class VCardSourceDetector implements VCardInterpreter { "X-SD-VERN", "X-SD-FORMAT_VER", "X-SD-CATEGORIES", "X-SD-CLASS", "X-SD-DCREATED", "X-SD-DESCRIPTION")); private static String TYPE_FOMA_CHARSET_SIGN = "X-SD-CHAR_CODE"; - - private int mType = VCardConfig.PARSE_TYPE_UNKNOWN; + + /** + * Represents that no estimation is available. Users of this class is able to this + * constant when you don't want to let a vCard parser rely on estimation for parse type. + */ + public static final int PARSE_TYPE_UNKNOWN = 0; + + // For Apple's software, which does not mean this type is effective for all its products. + // We confirmed they usually use UTF-8, but not sure about vCard type. + private static final int PARSE_TYPE_APPLE = 1; + // For Japanese mobile phones, which are usually using Shift_JIS as a charset. + private static final int PARSE_TYPE_MOBILE_PHONE_JP = 2; + // For some of mobile phones released from DoCoMo, which use nested vCard. + private static final int PARSE_TYPE_DOCOMO_TORELATE_NEST = 3; + // For Japanese Windows Mobel phones. It's version is supposed to be 6.5. + private static final int PARSE_TYPE_WINDOWS_MOBILE_V65_JP = 4; + + private int mParseType = 0; // Not sure. + + private boolean mNeedToParseVersion = false; + private int mVersion = -1; // -1 == unknown + // Some mobile phones (like FOMA) tells us the charset of the data. - private boolean mNeedParseSpecifiedCharset; + private boolean mNeedToParseCharset; private String mSpecifiedCharset; public void start() { @@ -58,7 +94,8 @@ public class VCardSourceDetector implements VCardInterpreter { } public void startProperty() { - mNeedParseSpecifiedCharset = false; + mNeedToParseCharset = false; + mNeedToParseVersion = false; } public void endProperty() { @@ -71,22 +108,26 @@ public class VCardSourceDetector implements VCardInterpreter { } public void propertyName(String name) { - if (name.equalsIgnoreCase(TYPE_FOMA_CHARSET_SIGN)) { - mType = VCardConfig.PARSE_TYPE_FOMA; - mNeedParseSpecifiedCharset = true; + if (name.equalsIgnoreCase(VCardConstants.PROPERTY_VERSION)) { + mNeedToParseVersion = true; + return; + } else if (name.equalsIgnoreCase(TYPE_FOMA_CHARSET_SIGN)) { + mParseType = PARSE_TYPE_DOCOMO_TORELATE_NEST; + // Probably Shift_JIS is used, but we should double confirm. + mNeedToParseCharset = true; return; } - if (mType != VCardConfig.PARSE_TYPE_UNKNOWN) { + if (mParseType != PARSE_TYPE_UNKNOWN) { return; } if (WINDOWS_MOBILE_PHONE_SIGNS.contains(name)) { - mType = VCardConfig.PARSE_TYPE_WINDOWS_MOBILE_JP; + mParseType = PARSE_TYPE_WINDOWS_MOBILE_V65_JP; } else if (FOMA_SIGNS.contains(name)) { - mType = VCardConfig.PARSE_TYPE_FOMA; + mParseType = PARSE_TYPE_DOCOMO_TORELATE_NEST; } else if (JAPANESE_MOBILE_PHONE_SIGNS.contains(name)) { - mType = VCardConfig.PARSE_TYPE_MOBILE_PHONE_JP; + mParseType = PARSE_TYPE_MOBILE_PHONE_JP; } else if (APPLE_SIGNS.contains(name)) { - mType = VCardConfig.PARSE_TYPE_APPLE; + mParseType = PARSE_TYPE_APPLE; } } @@ -97,30 +138,65 @@ public class VCardSourceDetector implements VCardInterpreter { } public void propertyValues(List<String> values) { - if (mNeedParseSpecifiedCharset && values.size() > 0) { + if (mNeedToParseVersion && values.size() > 0) { + final String versionString = values.get(0); + if (versionString.equals(VCardConstants.VERSION_V21)) { + mVersion = VCardConfig.VERSION_21; + } else if (versionString.equals(VCardConstants.VERSION_V30)) { + mVersion = VCardConfig.VERSION_30; + } else if (versionString.equals(VCardConstants.VERSION_V40)) { + mVersion = VCardConfig.VERSION_40; + } else { + Log.w(LOG_TAG, "Invalid version string: " + versionString); + } + } else if (mNeedToParseCharset && values.size() > 0) { mSpecifiedCharset = values.get(0); } } - /* package */ int getEstimatedType() { - return mType; + /** + * @return The available type can be used with vCard parser. You probably need to + * use {{@link #getEstimatedCharset()} to understand the charset to be used. + */ + public int getEstimatedType() { + switch (mParseType) { + case PARSE_TYPE_DOCOMO_TORELATE_NEST: + return VCardConfig.VCARD_TYPE_DOCOMO | VCardConfig.FLAG_TORELATE_NEST; + case PARSE_TYPE_MOBILE_PHONE_JP: + return VCardConfig.VCARD_TYPE_V21_JAPANESE_MOBILE; + case PARSE_TYPE_APPLE: + case PARSE_TYPE_WINDOWS_MOBILE_V65_JP: + default: { + if (mVersion == VCardConfig.VERSION_21) { + return VCardConfig.VCARD_TYPE_V21_GENERIC; + } else if (mVersion == VCardConfig.VERSION_30) { + return VCardConfig.VCARD_TYPE_V30_GENERIC; + } else if (mVersion == VCardConfig.VERSION_40) { + return VCardConfig.VCARD_TYPE_V40_GENERIC; + } else { + return VCardConfig.VCARD_TYPE_UNKNOWN; + } + } + } } - + /** - * Return charset String guessed from the source's properties. + * <p> + * Returns charset String guessed from the source's properties. * This method must be called after parsing target file(s). + * </p> * @return Charset String. Null is returned if guessing the source fails. */ public String getEstimatedCharset() { - if (mSpecifiedCharset != null) { + if (TextUtils.isEmpty(mSpecifiedCharset)) { return mSpecifiedCharset; } - switch (mType) { - case VCardConfig.PARSE_TYPE_WINDOWS_MOBILE_JP: - case VCardConfig.PARSE_TYPE_FOMA: - case VCardConfig.PARSE_TYPE_MOBILE_PHONE_JP: + switch (mParseType) { + case PARSE_TYPE_WINDOWS_MOBILE_V65_JP: + case PARSE_TYPE_DOCOMO_TORELATE_NEST: + case PARSE_TYPE_MOBILE_PHONE_JP: return "SHIFT_JIS"; - case VCardConfig.PARSE_TYPE_APPLE: + case PARSE_TYPE_APPLE: return "UTF-8"; default: return null; diff --git a/core/java/android/pim/vcard/VCardUtils.java b/core/java/android/pim/vcard/VCardUtils.java index 11b112b76dbb..abceca0cb476 100644 --- a/core/java/android/pim/vcard/VCardUtils.java +++ b/core/java/android/pim/vcard/VCardUtils.java @@ -16,13 +16,21 @@ package android.pim.vcard; import android.content.ContentProviderOperation; -import android.provider.ContactsContract.Data; +import android.pim.vcard.exception.VCardException; import android.provider.ContactsContract.CommonDataKinds.Im; import android.provider.ContactsContract.CommonDataKinds.Phone; import android.provider.ContactsContract.CommonDataKinds.StructuredPostal; +import android.provider.ContactsContract.Data; import android.telephony.PhoneNumberUtils; import android.text.TextUtils; +import android.util.Log; +import org.apache.commons.codec.DecoderException; +import org.apache.commons.codec.net.QuotedPrintableCodec; + +import java.io.UnsupportedEncodingException; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -36,6 +44,8 @@ import java.util.Set; * Utilities for VCard handling codes. */ public class VCardUtils { + private static final String LOG_TAG = "VCardUtils"; + // Note that not all types are included in this map/set, since, for example, TYPE_HOME_FAX is // converted to two parameter Strings. These only contain some minor fields valid in both // vCard and current (as of 2009-08-07) Contacts structure. @@ -185,8 +195,7 @@ public class VCardUtils { // For backward compatibility. // Detail: Until Donut, there isn't TYPE_MOBILE for email while there is now. // To support mobile type at that time, this custom label had been used. - return (android.provider.Contacts.ContactMethodsColumns.MOBILE_EMAIL_TYPE_NAME.equals(label) - || sMobilePhoneLabelSet.contains(label)); + return ("_AUTO_CELL".equals(label) || sMobilePhoneLabelSet.contains(label)); } public static boolean isValidInV21ButUnknownToContactsPhoteType(final String label) { @@ -240,10 +249,13 @@ public class VCardUtils { } /** + * <p> * Inserts postal data into the builder object. - * + * </p> + * <p> * Note that the data structure of ContactsContract is different from that defined in vCard. * So some conversion may be performed in this method. + * </p> */ public static void insertStructuredPostalDataUsingContactsStruct(int vcardType, final ContentProviderOperation.Builder builder, @@ -319,18 +331,33 @@ public class VCardUtils { return builder.toString(); } + /** + * Splits the given value into pieces using the delimiter ';' inside it. + * + * Escaped characters in those values are automatically unescaped into original form. + */ public static List<String> constructListFromValue(final String value, - final boolean isV30) { + final int vcardType) { final List<String> list = new ArrayList<String>(); StringBuilder builder = new StringBuilder(); - int length = value.length(); + final int length = value.length(); for (int i = 0; i < length; i++) { char ch = value.charAt(i); if (ch == '\\' && i < length - 1) { char nextCh = value.charAt(i + 1); - final String unescapedString = - (isV30 ? VCardParser_V30.unescapeCharacter(nextCh) : - VCardParser_V21.unescapeCharacter(nextCh)); + final String unescapedString; + if (VCardConfig.isVersion40(vcardType)) { + unescapedString = VCardParserImpl_V40.unescapeCharacter(nextCh); + } else if (VCardConfig.isVersion30(vcardType)) { + unescapedString = VCardParserImpl_V30.unescapeCharacter(nextCh); + } else { + if (!VCardConfig.isVersion21(vcardType)) { + // Unknown vCard type + Log.w(LOG_TAG, "Unknown vCard type"); + } + unescapedString = VCardParserImpl_V21.unescapeCharacter(nextCh); + } + if (unescapedString != null) { builder.append(unescapedString); i++; @@ -371,9 +398,13 @@ public class VCardUtils { } /** + * <p> * This is useful when checking the string should be encoded into quoted-printable * or not, which is required by vCard 2.1. + * </p> + * <p> * See the definition of "7bit" in vCard 2.1 spec for more information. + * </p> */ public static boolean containsOnlyNonCrLfPrintableAscii(final String...values) { if (values == null) { @@ -407,13 +438,16 @@ public class VCardUtils { new HashSet<Character>(Arrays.asList('[', ']', '=', ':', '.', ',', ' ')); /** + * <p> * This is useful since vCard 3.0 often requires the ("X-") properties and groups * should contain only alphabets, digits, and hyphen. - * + * </p> + * <p> * Note: It is already known some devices (wrongly) outputs properties with characters * which should not be in the field. One example is "X-GOOGLE TALK". We accept * such kind of input but must never output it unless the target is very specific - * to the device which is able to parse the malformed input. + * to the device which is able to parse the malformed input. + * </p> */ public static boolean containsOnlyAlphaDigitHyphen(final String...values) { if (values == null) { @@ -451,14 +485,39 @@ public class VCardUtils { return true; } + public static boolean containsOnlyWhiteSpaces(final String...values) { + if (values == null) { + return true; + } + return containsOnlyWhiteSpaces(Arrays.asList(values)); + } + + public static boolean containsOnlyWhiteSpaces(final Collection<String> values) { + if (values == null) { + return true; + } + for (final String str : values) { + if (TextUtils.isEmpty(str)) { + continue; + } + final int length = str.length(); + for (int i = 0; i < length; i = str.offsetByCodePoints(i, 1)) { + if (!Character.isWhitespace(str.codePointAt(i))) { + return false; + } + } + } + return true; + } + /** - * <P> + * <p> * Returns true when the given String is categorized as "word" specified in vCard spec 2.1. - * </P> - * <P> - * vCard 2.1 specifies:<BR /> + * </p> + * <p> + * vCard 2.1 specifies:<br /> * word = <any printable 7bit us-ascii except []=:., > - * </P> + * </p> */ public static boolean isV21Word(final String value) { if (TextUtils.isEmpty(value)) { @@ -477,6 +536,66 @@ public class VCardUtils { return true; } + private static final int[] sEscapeIndicatorsV30 = new int[]{ + ':', ';', ',', ' ' + }; + + private static final int[] sEscapeIndicatorsV40 = new int[]{ + ';', ':' + }; + + /** + * <P> + * Returns String available as parameter value in vCard 3.0. + * </P> + * <P> + * RFC 2426 requires vCard composer to quote parameter values when it contains + * semi-colon, for example (See RFC 2426 for more information). + * This method checks whether the given String can be used without quotes. + * </P> + * <P> + * Note: We remove DQUOTE inside the given value silently for now. + * </P> + */ + public static String toStringAsV30ParamValue(String value) { + return toStringAsParamValue(value, sEscapeIndicatorsV30); + } + + public static String toStringAsV40ParamValue(String value) { + return toStringAsParamValue(value, sEscapeIndicatorsV40); + } + + private static String toStringAsParamValue(String value, final int[] escapeIndicators) { + if (TextUtils.isEmpty(value)) { + value = ""; + } + final int asciiFirst = 0x20; + final int asciiLast = 0x7E; // included + final StringBuilder builder = new StringBuilder(); + final int length = value.length(); + boolean needQuote = false; + for (int i = 0; i < length; i = value.offsetByCodePoints(i, 1)) { + final int codePoint = value.codePointAt(i); + if (codePoint < asciiFirst || codePoint == '"') { + // CTL characters and DQUOTE are never accepted. Remove them. + continue; + } + builder.appendCodePoint(codePoint); + for (int indicator : escapeIndicators) { + if (codePoint == indicator) { + needQuote = true; + break; + } + } + } + + final String result = builder.toString(); + return ((result.isEmpty() || VCardUtils.containsOnlyWhiteSpaces(result)) + ? "" + : (needQuote ? ('"' + result + '"') + : result)); + } + public static String toHalfWidthString(final String orgString) { if (TextUtils.isEmpty(orgString)) { return null; @@ -540,6 +659,138 @@ public class VCardUtils { return true; } + //// The methods bellow may be used by unit test. + + /** + * Unquotes given Quoted-Printable value. value must not be null. + */ + public static String parseQuotedPrintable( + final String value, boolean strictLineBreaking, + String sourceCharset, String targetCharset) { + // "= " -> " ", "=\t" -> "\t". + // Previous code had done this replacement. Keep on the safe side. + final String quotedPrintable; + { + final StringBuilder builder = new StringBuilder(); + final int length = value.length(); + for (int i = 0; i < length; i++) { + char ch = value.charAt(i); + if (ch == '=' && i < length - 1) { + char nextCh = value.charAt(i + 1); + if (nextCh == ' ' || nextCh == '\t') { + builder.append(nextCh); + i++; + continue; + } + } + builder.append(ch); + } + quotedPrintable = builder.toString(); + } + + String[] lines; + if (strictLineBreaking) { + lines = quotedPrintable.split("\r\n"); + } else { + StringBuilder builder = new StringBuilder(); + final int length = quotedPrintable.length(); + ArrayList<String> list = new ArrayList<String>(); + for (int i = 0; i < length; i++) { + char ch = quotedPrintable.charAt(i); + if (ch == '\n') { + list.add(builder.toString()); + builder = new StringBuilder(); + } else if (ch == '\r') { + list.add(builder.toString()); + builder = new StringBuilder(); + if (i < length - 1) { + char nextCh = quotedPrintable.charAt(i + 1); + if (nextCh == '\n') { + i++; + } + } + } else { + builder.append(ch); + } + } + final String lastLine = builder.toString(); + if (lastLine.length() > 0) { + list.add(lastLine); + } + lines = list.toArray(new String[0]); + } + + final StringBuilder builder = new StringBuilder(); + for (String line : lines) { + if (line.endsWith("=")) { + line = line.substring(0, line.length() - 1); + } + builder.append(line); + } + + final String rawString = builder.toString(); + if (TextUtils.isEmpty(rawString)) { + Log.w(LOG_TAG, "Given raw string is empty."); + } + + byte[] rawBytes = null; + try { + rawBytes = rawString.getBytes(sourceCharset); + } catch (UnsupportedEncodingException e) { + Log.w(LOG_TAG, "Failed to decode: " + sourceCharset); + rawBytes = rawString.getBytes(); + } + + byte[] decodedBytes = null; + try { + decodedBytes = QuotedPrintableCodec.decodeQuotedPrintable(rawBytes); + } catch (DecoderException e) { + Log.e(LOG_TAG, "DecoderException is thrown."); + decodedBytes = rawBytes; + } + + try { + return new String(decodedBytes, targetCharset); + } catch (UnsupportedEncodingException e) { + Log.e(LOG_TAG, "Failed to encode: charset=" + targetCharset); + return new String(decodedBytes); + } + } + + public static final VCardParser getAppropriateParser(int vcardType) + throws VCardException { + if (VCardConfig.isVersion21(vcardType)) { + return new VCardParser_V21(); + } else if (VCardConfig.isVersion30(vcardType)) { + return new VCardParser_V30(); + } else if (VCardConfig.isVersion40(vcardType)) { + return new VCardParser_V40(); + } else { + throw new VCardException("Version is not specified"); + } + } + + public static final String convertStringCharset( + String originalString, String sourceCharset, String targetCharset) { + if (sourceCharset.equalsIgnoreCase(targetCharset)) { + return originalString; + } + final Charset charset = Charset.forName(sourceCharset); + final ByteBuffer byteBuffer = charset.encode(originalString); + // byteBuffer.array() "may" return byte array which is larger than + // byteBuffer.remaining(). Here, we keep on the safe side. + final byte[] bytes = new byte[byteBuffer.remaining()]; + byteBuffer.get(bytes); + try { + return new String(bytes, targetCharset); + } catch (UnsupportedEncodingException e) { + Log.e(LOG_TAG, "Failed to encode: charset=" + targetCharset); + return null; + } + } + + // TODO: utilities for vCard 4.0: datetime, timestamp, integer, float, and boolean + private VCardUtils() { } } diff --git a/core/java/android/pim/vcard/exception/VCardInvalidLineException.java b/core/java/android/pim/vcard/exception/VCardInvalidLineException.java index 330153ec2638..b80584beca7d 100644 --- a/core/java/android/pim/vcard/exception/VCardInvalidLineException.java +++ b/core/java/android/pim/vcard/exception/VCardInvalidLineException.java @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package android.pim.vcard.exception; /** diff --git a/core/java/android/pim/vcard/exception/VCardVersionException.java b/core/java/android/pim/vcard/exception/VCardVersionException.java index 9fe8b7f92af9..0709fe4113ee 100644 --- a/core/java/android/pim/vcard/exception/VCardVersionException.java +++ b/core/java/android/pim/vcard/exception/VCardVersionException.java @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package android.pim.vcard.exception; /** diff --git a/core/java/android/preference/DialogPreference.java b/core/java/android/preference/DialogPreference.java index cc48aeb70844..bbad2b6d432c 100644 --- a/core/java/android/preference/DialogPreference.java +++ b/core/java/android/preference/DialogPreference.java @@ -33,7 +33,6 @@ import android.view.LayoutInflater; import android.view.View; import android.view.Window; import android.view.WindowManager; -import android.view.inputmethod.InputMethodManager; import android.widget.TextView; /** @@ -275,7 +274,7 @@ public abstract class DialogPreference extends Preference implements protected void showDialog(Bundle state) { Context context = getContext(); - mWhichButtonClicked = DialogInterface.BUTTON2; + mWhichButtonClicked = DialogInterface.BUTTON_NEGATIVE; mBuilder = new AlertDialog.Builder(context) .setTitle(mDialogTitle) diff --git a/core/java/android/preference/Preference.java b/core/java/android/preference/Preference.java index 197d976777c6..dde6493fb331 100644 --- a/core/java/android/preference/Preference.java +++ b/core/java/android/preference/Preference.java @@ -1195,7 +1195,7 @@ public class Preference implements Comparable<Preference>, OnDependencyChangeLis private void tryCommit(SharedPreferences.Editor editor) { if (mPreferenceManager.shouldCommit()) { - editor.commit(); + editor.apply(); } } diff --git a/core/java/android/preference/PreferenceManager.java b/core/java/android/preference/PreferenceManager.java index 2b2094693f78..fa838976fed9 100644 --- a/core/java/android/preference/PreferenceManager.java +++ b/core/java/android/preference/PreferenceManager.java @@ -443,7 +443,7 @@ public class PreferenceManager { pm.setSharedPreferencesMode(sharedPreferencesMode); pm.inflateFromResource(context, resId, null); - defaultValueSp.edit().putBoolean(KEY_HAS_SET_DEFAULT_VALUES, true).commit(); + defaultValueSp.edit().putBoolean(KEY_HAS_SET_DEFAULT_VALUES, true).apply(); } } @@ -481,7 +481,7 @@ public class PreferenceManager { private void setNoCommit(boolean noCommit) { if (!noCommit && mEditor != null) { - mEditor.commit(); + mEditor.apply(); } mNoCommit = noCommit; diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java index 40a408a12ee8..13cbda8df636 100644 --- a/core/java/android/provider/ContactsContract.java +++ b/core/java/android/provider/ContactsContract.java @@ -409,6 +409,13 @@ public final class ContactsContract { public static final String CONTACT_PRESENCE = "contact_presence"; /** + * Contact Chat Capabilities. See {@link StatusUpdates} for individual + * definitions. + * <p>Type: NUMBER</p> + */ + public static final String CONTACT_CHAT_CAPABILITY = "contact_chat_capability"; + + /** * Contact's latest status update. * <p>Type: TEXT</p> */ @@ -1950,6 +1957,29 @@ public final class ContactsContract { * <p>Type: NUMBER</p> */ public static final String STATUS_ICON = "status_icon"; + + /** + * Contact's audio/video chat capability level. + * <P>Type: INTEGER (one of the values below)</P> + */ + public static final String CHAT_CAPABILITY = "chat_capability"; + + /** + * An allowed value of {@link #CHAT_CAPABILITY}. Indicates that the contact's device can + * display a video feed. + */ + public static final int CAPABILITY_HAS_VIDEO_PLAYBACK_ONLY = 1; + + /** + * An allowed value of {@link #CHAT_CAPABILITY}. Indicates audio-chat capability. + */ + public static final int CAPABILITY_HAS_VOICE = 2; + + /** + * An allowed value of {@link #CHAT_CAPABILITY}. Indicates that the contact's device has a + * camera that can be used for video chat (e.g. a front-facing camera on a phone). + */ + public static final int CAPABILITY_HAS_CAMERA = 4; } /** @@ -2275,6 +2305,7 @@ public final class ContactsContract { * <li>{@link CommonDataKinds.Website Website.CONTENT_ITEM_TYPE}</li> * <li>{@link CommonDataKinds.Event Event.CONTENT_ITEM_TYPE}</li> * <li>{@link CommonDataKinds.Relation Relation.CONTENT_ITEM_TYPE}</li> + * <li>{@link CommonDataKinds.SipAddress SipAddress.CONTENT_ITEM_TYPE}</li> * </ul> * </p> * </td> @@ -3095,6 +3126,25 @@ public final class ContactsContract { * </td> * </tr> * <tr> + * <td>int</td> + * <td>{@link #CHAT_CAPABILITY}</td> + * <td>read/write</td> + * <td>Contact IM chat compatibility value. The allowed values are: + * <p> + * <ul> + * <li>{@link #CAPABILITY_HAS_VIDEO_PLAYBACK_ONLY}</li> + * <li>{@link #CAPABILITY_HAS_VOICE}</li> + * <li>{@link #CAPABILITY_HAS_CAMERA}</li> + * </ul> + * </p> + * <p> + * Since chat compatibility is inherently volatile as the contact's availability moves from + * one device to another, the content provider may choose not to store this field in long-term + * storage. + * </p> + * </td> + * </tr> + * <tr> * <td>String</td> * <td>{@link #STATUS}</td> * <td>read/write</td> @@ -4811,6 +4861,52 @@ public final class ContactsContract { */ public static final String URL = DATA; } + + /** + * <p> + * A data kind representing a SIP address for the contact. + * </p> + * <p> + * You can use all columns defined for {@link ContactsContract.Data} as + * well as the following aliases. + * </p> + * <h2>Column aliases</h2> + * <table class="jd-sumtable"> + * <tr> + * <th>Type</th> + * <th>Alias</th><th colspan='2'>Data column</th> + * </tr> + * <tr> + * <td>String</td> + * <td>{@link #SIP_ADDRESS}</td> + * <td>{@link #DATA1}</td> + * <td></td> + * </tr> + * </table> + */ + public static final class SipAddress implements DataColumnsWithJoins { + // TODO: Ultimately this class will probably implement + // CommonColumns too (in addition to DataColumnsWithJoins) + // since it may make sense to have multiple SIP addresses with + // different types+labels, just like with phone numbers. + // + // But that can be extended in the future without breaking any + // public API, so let's keep this class ultra-simple for now. + + /** + * This utility class cannot be instantiated + */ + private SipAddress() {} + + /** MIME type used when storing this in data table. */ + public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/sip_address"; + + /** + * The SIP address. + * <P>Type: TEXT</P> + */ + public static final String SIP_ADDRESS = DATA1; + } } /** diff --git a/core/java/android/provider/Downloads.java b/core/java/android/provider/Downloads.java index 348e9e8a63aa..603e5983871e 100644 --- a/core/java/android/provider/Downloads.java +++ b/core/java/android/provider/Downloads.java @@ -17,11 +17,6 @@ package android.provider; import android.net.Uri; -import android.content.ContentUris; -import android.content.ContentValues; -import android.content.Context; - -import java.io.File; /** * The Download Manager @@ -629,12 +624,17 @@ public final class Downloads { "android.permission.SEND_DOWNLOAD_COMPLETED_INTENTS"; /** - * The permission to access downloads to {@link DESTINATION_EXTERNAL} - * which were downloaded by other applications. - * @hide + * The permission to download files to the cache partition that won't be automatically + * purged when space is needed. */ - public static final String PERMISSION_SEE_ALL_EXTERNAL = - "android.permission.SEE_ALL_EXTERNAL"; + public static final String PERMISSION_CACHE_NON_PURGEABLE = + "android.permission.DOWNLOAD_CACHE_NON_PURGEABLE"; + + /** + * The permission to download files without any system notification being shown. + */ + public static final String PERMISSION_NO_NOTIFICATION = + "android.permission.DOWNLOAD_WITHOUT_NOTIFICATION"; /** * The content:// URI for the data table in the provider @@ -856,6 +856,38 @@ public final class Downloads { */ public static final String COLUMN_DESCRIPTION = "description"; + /** + * The name of the column indicating whether the download was requesting through the public + * API. This controls some differences in behavior. + * <P>Type: BOOLEAN</P> + * <P>Owner can Init/Read</P> + */ + public static final String COLUMN_IS_PUBLIC_API = "is_public_api"; + + /** + * The name of the column indicating whether roaming connections can be used. This is only + * used for public API downloads. + * <P>Type: BOOLEAN</P> + * <P>Owner can Init/Read</P> + */ + public static final String COLUMN_ALLOW_ROAMING = "allow_roaming"; + + /** + * The name of the column holding a bitmask of allowed network types. This is only used for + * public API downloads. + * <P>Type: INTEGER</P> + * <P>Owner can Init/Read</P> + */ + public static final String COLUMN_ALLOWED_NETWORK_TYPES = "allowed_network_types"; + + /** + * Whether or not this download should be displayed in the system's Downloads UI. Defaults + * to true. + * <P>Type: INTEGER</P> + * <P>Owner can Init/Read</P> + */ + public static final String COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI = "is_visible_in_downloads_ui"; + /* * Lists the destinations that an application can specify for a download. */ @@ -899,6 +931,12 @@ public final class Downloads { public static final int DESTINATION_CACHE_PARTITION_NOROAMING = 3; /** + * This download will be saved to the location given by the file URI in + * {@link #COLUMN_FILE_NAME_HINT}. + */ + public static final int DESTINATION_FILE_URI = 4; + + /** * This download is allowed to run. */ public static final int CONTROL_RUN = 0; @@ -1030,6 +1068,16 @@ public final class Downloads { public static final int STATUS_PRECONDITION_FAILED = 412; /** + * The lowest-valued error status that is not an actual HTTP status code. + */ + public static final int MIN_ARTIFICIAL_ERROR_STATUS = 489; + + /** + * Some possibly transient error occurred, but we can't resume the download. + */ + public static final int STATUS_CANNOT_RESUME = 489; + + /** * This download was canceled */ public static final int STATUS_CANCELED = 490; @@ -1109,5 +1157,26 @@ public final class Downloads { * This download doesn't show in the UI or in the notifications. */ public static final int VISIBILITY_HIDDEN = 2; + + /** + * Constants related to HTTP request headers associated with each download. + */ + public static class RequestHeaders { + public static final String HEADERS_DB_TABLE = "request_headers"; + public static final String COLUMN_DOWNLOAD_ID = "download_id"; + public static final String COLUMN_HEADER = "header"; + public static final String COLUMN_VALUE = "value"; + + /** + * Path segment to add to a download URI to retrieve request headers + */ + public static final String URI_SEGMENT = "headers"; + + /** + * Prefix for ContentValues keys that contain HTTP header lines, to be passed to + * DownloadProvider.insert(). + */ + public static final String INSERT_KEY_PREFIX = "http_header_"; + } } } diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java index c9d125b9159f..075da338cb39 100644 --- a/core/java/android/provider/MediaStore.java +++ b/core/java/android/provider/MediaStore.java @@ -75,6 +75,22 @@ public final class MediaStore { public static final String INTENT_ACTION_MEDIA_SEARCH = "android.intent.action.MEDIA_SEARCH"; /** + * An intent to perform a search for music media and automatically play content from the + * result when possible. This can be fired, for example, by the result of a voice recognition + * command to listen to music. + * <p> + * Contains the {@link android.app.SearchManager#QUERY} extra, which is a string + * that can contain any type of unstructured music search, like the name of an artist, + * an album, a song, a genre, or any combination of these. + * <p> + * Because this intent includes an open-ended unstructured search string, it makes the most + * sense for apps that can support large-scale search of music, such as services connected + * to an online database of music which can be streamed and played on the device. + */ + public static final String INTENT_ACTION_MEDIA_PLAY_FROM_SEARCH = + "android.media.action.MEDIA_PLAY_FROM_SEARCH"; + + /** * The name of the Intent-extra used to define the artist */ public static final String EXTRA_MEDIA_ARTIST = "android.intent.extra.artist"; @@ -249,6 +265,8 @@ public final class MediaStore { private static final int MICRO_KIND = 3; private static final String[] PROJECTION = new String[] {_ID, MediaColumns.DATA}; static final int DEFAULT_GROUP_ID = 0; + private static final Object sThumbBufLock = new Object(); + private static byte[] sThumbBuf; private static Bitmap getMiniThumbFromFile(Cursor c, Uri baseUri, ContentResolver cr, BitmapFactory.Options options) { Bitmap bitmap = null; @@ -321,11 +339,15 @@ public final class MediaStore { long magic = thumbFile.getMagic(origId); if (magic != 0) { if (kind == MICRO_KIND) { - byte[] data = new byte[MiniThumbFile.BYTES_PER_MINTHUMB]; - if (thumbFile.getMiniThumbFromFile(origId, data) != null) { - bitmap = BitmapFactory.decodeByteArray(data, 0, data.length); - if (bitmap == null) { - Log.w(TAG, "couldn't decode byte array."); + synchronized (sThumbBufLock) { + if (sThumbBuf == null) { + sThumbBuf = new byte[MiniThumbFile.BYTES_PER_MINTHUMB]; + } + if (thumbFile.getMiniThumbFromFile(origId, sThumbBuf) != null) { + bitmap = BitmapFactory.decodeByteArray(sThumbBuf, 0, sThumbBuf.length); + if (bitmap == null) { + Log.w(TAG, "couldn't decode byte array."); + } } } return bitmap; @@ -357,11 +379,15 @@ public final class MediaStore { // Assuming thumbnail has been generated, at least original image exists. if (kind == MICRO_KIND) { - byte[] data = new byte[MiniThumbFile.BYTES_PER_MINTHUMB]; - if (thumbFile.getMiniThumbFromFile(origId, data) != null) { - bitmap = BitmapFactory.decodeByteArray(data, 0, data.length); - if (bitmap == null) { - Log.w(TAG, "couldn't decode byte array."); + synchronized (sThumbBufLock) { + if (sThumbBuf == null) { + sThumbBuf = new byte[MiniThumbFile.BYTES_PER_MINTHUMB]; + } + if (thumbFile.getMiniThumbFromFile(origId, sThumbBuf) != null) { + bitmap = BitmapFactory.decodeByteArray(sThumbBuf, 0, sThumbBuf.length); + if (bitmap == null) { + Log.w(TAG, "couldn't decode byte array."); + } } } } else if (kind == MINI_KIND) { @@ -1819,4 +1845,12 @@ public final class MediaStore { * Name of current volume being scanned by the media scanner. */ public static final String MEDIA_SCANNER_VOLUME = "volume"; + + /** + * Name of the file signaling the media scanner to ignore media in the containing directory + * and its subdirectories. Developers should use this to avoid application graphics showing + * up in the Gallery and likewise prevent application sounds and music from showing up in + * the Music app. + */ + public static final String MEDIA_IGNORE_FILENAME = ".nomedia"; } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index e12dfb06087e..fd601159981d 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -355,6 +355,21 @@ public final class Settings { "android.settings.MANAGE_APPLICATIONS_SETTINGS"; /** + * Activity Action: Show screen of details about a particular application. + * <p> + * In some cases, a matching Activity may not exist, so ensure you + * safeguard against this. + * <p> + * Input: The Intent's data URI specifies the application package name + * to be shown, with the "package" scheme. That is "package:com.my.app". + * <p> + * Output: Nothing. + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_APPLICATION_DETAILS_SETTINGS = + "android.settings.APPLICATION_DETAILS_SETTINGS"; + + /** * Activity Action: Show settings for system update functionality. * <p> * In some cases, a matching Activity may not exist, so ensure you @@ -1679,6 +1694,41 @@ public final class Settings { public static final String UNLOCK_SOUND = "unlock_sound"; /** + * Receive incoming SIP calls? + * 0 = no + * 1 = yes + * @hide + */ + public static final String SIP_RECEIVE_CALLS = "sip_receive_calls"; + + /** + * Call Preference String. + * "SIP_ALWAYS" : Always use SIP with network access + * "SIP_ADDRESS_ONLY" : Only if destination is a SIP address + * "SIP_ASK_ME_EACH_TIME" : Always ask me each time + * @hide + */ + public static final String SIP_CALL_OPTIONS = "sip_call_options"; + + /** + * One of the sip call options: Always use SIP with network access. + * @hide + */ + public static final String SIP_ALWAYS = "SIP_ALWAYS"; + + /** + * One of the sip call options: Only if destination is a SIP address. + * @hide + */ + public static final String SIP_ADDRESS_ONLY = "SIP_ADDRESS_ONLY"; + + /** + * One of the sip call options: Always ask me each time. + * @hide + */ + public static final String SIP_ASK_ME_EACH_TIME = "SIP_ASK_ME_EACH_TIME"; + + /** * Settings to backup. This is here so that it's in the same place as the settings * keys and easy to update. * @hide @@ -1738,7 +1788,9 @@ public final class Settings { DOCK_SOUNDS_ENABLED, LOCKSCREEN_SOUNDS_ENABLED, SHOW_WEB_SUGGESTIONS, - NOTIFICATION_LIGHT_PULSE + NOTIFICATION_LIGHT_PULSE, + SIP_CALL_OPTIONS, + SIP_RECEIVE_CALLS }; // Settings moved to Settings.Secure @@ -2909,31 +2961,31 @@ public final class Settings { public static final String WTF_IS_FATAL = "wtf_is_fatal"; /** - * Maximum age of entries kept by {@link android.os.IDropBox}. + * Maximum age of entries kept by {@link com.android.internal.os.IDropBoxManagerService}. * @hide */ public static final String DROPBOX_AGE_SECONDS = "dropbox_age_seconds"; /** - * Maximum number of entry files which {@link android.os.IDropBox} will keep around. + * Maximum number of entry files which {@link com.android.internal.os.IDropBoxManagerService} will keep around. * @hide */ public static final String DROPBOX_MAX_FILES = "dropbox_max_files"; /** - * Maximum amount of disk space used by {@link android.os.IDropBox} no matter what. + * Maximum amount of disk space used by {@link com.android.internal.os.IDropBoxManagerService} no matter what. * @hide */ public static final String DROPBOX_QUOTA_KB = "dropbox_quota_kb"; /** - * Percent of free disk (excluding reserve) which {@link android.os.IDropBox} will use. + * Percent of free disk (excluding reserve) which {@link com.android.internal.os.IDropBoxManagerService} will use. * @hide */ public static final String DROPBOX_QUOTA_PERCENT = "dropbox_quota_percent"; /** - * Percent of total disk which {@link android.os.IDropBox} will never dip into. + * Percent of total disk which {@link com.android.internal.os.IDropBoxManagerService} will never dip into. * @hide */ public static final String DROPBOX_RESERVE_PERCENT = @@ -2993,6 +3045,15 @@ public final class Settings { "sys_storage_threshold_percentage"; /** + * Minimum bytes of free storage on the device before the data + * partition is considered full. By default, 1 MB is reserved + * to avoid system-wide SQLite disk full exceptions. + * @hide + */ + public static final String SYS_STORAGE_FULL_THRESHOLD_BYTES = + "sys_storage_full_threshold_bytes"; + + /** * The interval in milliseconds after which Wi-Fi is considered idle. * When idle, it is possible for the device to be switched from Wi-Fi to * the mobile data network. @@ -3358,6 +3419,29 @@ public final class Settings { public static final String THROTTLE_MAX_NTP_CACHE_AGE_SEC = "throttle_max_ntp_cache_age_sec"; + /** + * The maximum size, in bytes, of a download that the download manager will transfer over + * a non-wifi connection. + * @hide + */ + public static final String DOWNLOAD_MAX_BYTES_OVER_MOBILE = + "download_manager_max_bytes_over_mobile"; + + /** + * ms during which to consume extra events related to Inet connection condition + * after a transtion to fully-connected + * @hide + */ + public static final String INET_CONDITION_DEBOUNCE_UP_DELAY = + "inet_condition_debounce_up_delay"; + + /** + * ms during which to consume extra events related to Inet connection condtion + * after a transtion to partly-connected + * @hide + */ + public static final String INET_CONDITION_DEBOUNCE_DOWN_DELAY = + "inet_condition_debounce_down_delay"; /** * @hide @@ -3397,13 +3481,7 @@ public final class Settings { */ public static final boolean isLocationProviderEnabled(ContentResolver cr, String provider) { String allowedProviders = Settings.Secure.getString(cr, LOCATION_PROVIDERS_ALLOWED); - if (allowedProviders != null) { - return (allowedProviders.equals(provider) || - allowedProviders.contains("," + provider + ",") || - allowedProviders.startsWith(provider + ",") || - allowedProviders.endsWith("," + provider)); - } - return false; + return TextUtils.delimitedStringContains(allowedProviders, ',', provider); } /** diff --git a/core/java/android/server/BluetoothA2dpService.java b/core/java/android/server/BluetoothA2dpService.java index 893db2e748c6..a52a221e01aa 100644 --- a/core/java/android/server/BluetoothA2dpService.java +++ b/core/java/android/server/BluetoothA2dpService.java @@ -27,7 +27,6 @@ import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothUuid; import android.bluetooth.IBluetoothA2dp; -import android.os.ParcelUuid; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -35,6 +34,7 @@ import android.content.IntentFilter; import android.media.AudioManager; import android.os.Handler; import android.os.Message; +import android.os.ParcelUuid; import android.provider.Settings; import android.util.Log; @@ -55,8 +55,6 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { private static final String BLUETOOTH_ENABLED = "bluetooth_enabled"; - private static final int MESSAGE_CONNECT_TO = 1; - private static final String PROPERTY_STATE = "State"; private static final String SINK_STATE_DISCONNECTED = "disconnected"; @@ -73,6 +71,7 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { private final BluetoothService mBluetoothService; private final BluetoothAdapter mAdapter; private int mTargetA2dpState; + private boolean mAdjustedPriority = false; private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override @@ -104,16 +103,6 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { setSinkPriority(device, BluetoothA2dp.PRIORITY_UNDEFINED); break; } - } else if (action.equals(BluetoothDevice.ACTION_ACL_CONNECTED)) { - if (getSinkPriority(device) == BluetoothA2dp.PRIORITY_AUTO_CONNECT && - isSinkDevice(device)) { - // This device is a preferred sink. Make an A2DP connection - // after a delay. We delay to avoid connection collisions, - // and to give other profiles such as HFP a chance to - // connect first. - Message msg = Message.obtain(mHandler, MESSAGE_CONNECT_TO, device); - mHandler.sendMessageDelayed(msg, 6000); - } } else if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) { synchronized (this) { if (mAudioDevices.containsKey(device)) { @@ -187,6 +176,7 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { if (mBluetoothService.isEnabled()) onBluetoothEnable(); mTargetA2dpState = -1; + mBluetoothService.setA2dpService(this); } @Override @@ -198,29 +188,6 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { } } - private final Handler mHandler = new Handler() { - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MESSAGE_CONNECT_TO: - BluetoothDevice device = (BluetoothDevice) msg.obj; - // check bluetooth is still on, device is still preferred, and - // nothing is currently connected - if (mBluetoothService.isEnabled() && - getSinkPriority(device) == BluetoothA2dp.PRIORITY_AUTO_CONNECT && - lookupSinksMatchingStates(new int[] { - BluetoothA2dp.STATE_CONNECTING, - BluetoothA2dp.STATE_CONNECTED, - BluetoothA2dp.STATE_PLAYING, - BluetoothA2dp.STATE_DISCONNECTING}).size() == 0) { - log("Auto-connecting A2DP to sink " + device); - connectSink(device); - } - break; - } - } - }; - private int convertBluezSinkStringtoState(String value) { if (value.equalsIgnoreCase("disconnected")) return BluetoothA2dp.STATE_DISCONNECTED; @@ -308,13 +275,37 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { mAudioManager.setParameters(BLUETOOTH_ENABLED + "=false"); } + private synchronized boolean isConnectSinkFeasible(BluetoothDevice device) { + if (!mBluetoothService.isEnabled() || !isSinkDevice(device) || + getSinkPriority(device) == BluetoothA2dp.PRIORITY_OFF) { + return false; + } + + if (mAudioDevices.get(device) == null && !addAudioSink(device)) { + return false; + } + + String path = mBluetoothService.getObjectPathFromAddress(device.getAddress()); + if (path == null) { + return false; + } + return true; + } + public synchronized boolean connectSink(BluetoothDevice device) { mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); if (DBG) log("connectSink(" + device + ")"); + if (!isConnectSinkFeasible(device)) return false; + return mBluetoothService.connectSink(device.getAddress()); + } + + public synchronized boolean connectSinkInternal(BluetoothDevice device) { if (!mBluetoothService.isEnabled()) return false; + int state = mAudioDevices.get(device); + // ignore if there are any active sinks if (lookupSinksMatchingStates(new int[] { BluetoothA2dp.STATE_CONNECTING, @@ -324,11 +315,6 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { return false; } - if (mAudioDevices.get(device) == null && !addAudioSink(device)) - return false; - - int state = mAudioDevices.get(device); - switch (state) { case BluetoothA2dp.STATE_CONNECTED: case BluetoothA2dp.STATE_PLAYING: @@ -339,8 +325,6 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { } String path = mBluetoothService.getObjectPathFromAddress(device.getAddress()); - if (path == null) - return false; // State is DISCONNECTED handleSinkStateChange(device, state, BluetoothA2dp.STATE_CONNECTING); @@ -353,11 +337,7 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { return true; } - public synchronized boolean disconnectSink(BluetoothDevice device) { - mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, - "Need BLUETOOTH_ADMIN permission"); - if (DBG) log("disconnectSink(" + device + ")"); - + private synchronized boolean isDisconnectSinkFeasible(BluetoothDevice device) { String path = mBluetoothService.getObjectPathFromAddress(device.getAddress()); if (path == null) { return false; @@ -370,6 +350,20 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { case BluetoothA2dp.STATE_DISCONNECTING: return true; } + return true; + } + + public synchronized boolean disconnectSink(BluetoothDevice device) { + mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, + "Need BLUETOOTH_ADMIN permission"); + if (DBG) log("disconnectSink(" + device + ")"); + if (!isDisconnectSinkFeasible(device)) return false; + return mBluetoothService.disconnectSink(device.getAddress()); + } + + public synchronized boolean disconnectSinkInternal(BluetoothDevice device) { + int state = getSinkState(device); + String path = mBluetoothService.getObjectPathFromAddress(device.getAddress()); // State is CONNECTING or CONNECTED or PLAYING handleSinkStateChange(device, state, BluetoothA2dp.STATE_DISCONNECTING); @@ -504,6 +498,12 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { setSinkPriority(device, BluetoothA2dp.PRIORITY_AUTO_CONNECT); } + if (state == BluetoothA2dp.STATE_CONNECTED) { + // We will only have 1 device with AUTO_CONNECT priority + // To be backward compatible set everyone else to have PRIORITY_ON + adjustOtherSinkPriorities(device); + } + Intent intent = new Intent(BluetoothA2dp.ACTION_SINK_STATE_CHANGED); intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); intent.putExtra(BluetoothA2dp.EXTRA_PREVIOUS_SINK_STATE, prevState); @@ -514,6 +514,18 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { } } + private void adjustOtherSinkPriorities(BluetoothDevice connectedDevice) { + if (!mAdjustedPriority) { + for (BluetoothDevice device : mAdapter.getBondedDevices()) { + if (getSinkPriority(device) >= BluetoothA2dp.PRIORITY_AUTO_CONNECT && + !device.equals(connectedDevice)) { + setSinkPriority(device, BluetoothA2dp.PRIORITY_ON); + } + } + mAdjustedPriority = true; + } + } + private synchronized Set<BluetoothDevice> lookupSinksMatchingStates(int[] states) { Set<BluetoothDevice> sinks = new HashSet<BluetoothDevice>(); if (mAudioDevices.isEmpty()) { @@ -554,6 +566,7 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { if (!result) { if (deviceObjectPath != null) { String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath); + if (address == null) return; BluetoothDevice device = mAdapter.getRemoteDevice(address); int state = getSinkState(device); handleSinkStateChange(device, state, BluetoothA2dp.STATE_DISCONNECTED); diff --git a/core/java/android/server/BluetoothEventLoop.java b/core/java/android/server/BluetoothEventLoop.java index c0e4600ae24c..e05fe7b8ce9f 100644 --- a/core/java/android/server/BluetoothEventLoop.java +++ b/core/java/android/server/BluetoothEventLoop.java @@ -52,22 +52,14 @@ class BluetoothEventLoop { private final BluetoothAdapter mAdapter; private final Context mContext; - private static final int EVENT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY = 1; - private static final int EVENT_RESTART_BLUETOOTH = 2; - private static final int EVENT_PAIRING_CONSENT_DELAYED_ACCEPT = 3; - private static final int EVENT_AGENT_CANCEL = 4; + private static final int EVENT_RESTART_BLUETOOTH = 1; + private static final int EVENT_PAIRING_CONSENT_DELAYED_ACCEPT = 2; + private static final int EVENT_AGENT_CANCEL = 3; private static final int CREATE_DEVICE_ALREADY_EXISTS = 1; private static final int CREATE_DEVICE_SUCCESS = 0; private static final int CREATE_DEVICE_FAILED = -1; - // The time (in millisecs) to delay the pairing attempt after the first - // auto pairing attempt fails. We use an exponential delay with - // INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY as the initial value and - // MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY as the max value. - private static final long INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY = 3000; - private static final long MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY = 12000; - private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN; private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH; @@ -76,13 +68,6 @@ class BluetoothEventLoop { public void handleMessage(Message msg) { String address = null; switch (msg.what) { - case EVENT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY: - address = (String)msg.obj; - if (address != null) { - mBluetoothService.createBond(address); - return; - } - break; case EVENT_RESTART_BLUETOOTH: mBluetoothService.restart(); break; @@ -95,8 +80,7 @@ class BluetoothEventLoop { case EVENT_AGENT_CANCEL: // Set the Bond State to BOND_NONE. // We always have only 1 device in BONDING state. - String[] devices = - mBluetoothService.getBondState().listInState(BluetoothDevice.BOND_BONDING); + String[] devices = mBluetoothService.listInState(BluetoothDevice.BOND_BONDING); if (devices.length == 0) { break; } else if (devices.length > 1) { @@ -104,7 +88,7 @@ class BluetoothEventLoop { break; } address = devices[0]; - mBluetoothService.getBondState().setBondState(address, + mBluetoothService.setBondState(address, BluetoothDevice.BOND_NONE, BluetoothDevice.UNBOND_REASON_REMOTE_AUTH_CANCELED); break; @@ -115,7 +99,7 @@ class BluetoothEventLoop { static { classInitNative(); } private static native void classInitNative(); - /* pacakge */ BluetoothEventLoop(Context context, BluetoothAdapter adapter, + /* package */ BluetoothEventLoop(Context context, BluetoothAdapter adapter, BluetoothService bluetoothService) { mBluetoothService = bluetoothService; mContext = context; @@ -209,55 +193,7 @@ class BluetoothEventLoop { private void onCreatePairedDeviceResult(String address, int result) { address = address.toUpperCase(); - if (result == BluetoothDevice.BOND_SUCCESS) { - mBluetoothService.getBondState().setBondState(address, BluetoothDevice.BOND_BONDED); - if (mBluetoothService.getBondState().isAutoPairingAttemptsInProgress(address)) { - mBluetoothService.getBondState().clearPinAttempts(address); - } - } else if (result == BluetoothDevice.UNBOND_REASON_AUTH_FAILED && - mBluetoothService.getBondState().getAttempt(address) == 1) { - mBluetoothService.getBondState().addAutoPairingFailure(address); - pairingAttempt(address, result); - } else if (result == BluetoothDevice.UNBOND_REASON_REMOTE_DEVICE_DOWN && - mBluetoothService.getBondState().isAutoPairingAttemptsInProgress(address)) { - pairingAttempt(address, result); - } else { - mBluetoothService.getBondState().setBondState(address, - BluetoothDevice.BOND_NONE, result); - if (mBluetoothService.getBondState().isAutoPairingAttemptsInProgress(address)) { - mBluetoothService.getBondState().clearPinAttempts(address); - } - } - } - - private void pairingAttempt(String address, int result) { - // This happens when our initial guess of "0000" as the pass key - // fails. Try to create the bond again and display the pin dialog - // to the user. Use back-off while posting the delayed - // message. The initial value is - // INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY and the max value is - // MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY. If the max value is - // reached, display an error to the user. - int attempt = mBluetoothService.getBondState().getAttempt(address); - if (attempt * INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY > - MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY) { - mBluetoothService.getBondState().clearPinAttempts(address); - mBluetoothService.getBondState().setBondState(address, - BluetoothDevice.BOND_NONE, result); - return; - } - - Message message = mHandler.obtainMessage(EVENT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY); - message.obj = address; - boolean postResult = mHandler.sendMessageDelayed(message, - attempt * INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY); - if (!postResult) { - mBluetoothService.getBondState().clearPinAttempts(address); - mBluetoothService.getBondState().setBondState(address, - BluetoothDevice.BOND_NONE, result); - return; - } - mBluetoothService.getBondState().attempt(address); + mBluetoothService.onCreatePairedDeviceResult(address, result); } private void onDeviceCreated(String deviceObjectPath) { @@ -275,8 +211,8 @@ class BluetoothEventLoop { private void onDeviceRemoved(String deviceObjectPath) { String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath); if (address != null) { - mBluetoothService.getBondState().setBondState(address.toUpperCase(), - BluetoothDevice.BOND_NONE, BluetoothDevice.UNBOND_REASON_REMOVED); + mBluetoothService.setBondState(address.toUpperCase(), BluetoothDevice.BOND_NONE, + BluetoothDevice.UNBOND_REASON_REMOVED); mBluetoothService.setRemoteDeviceProperty(address, "UUIDs", null); } } @@ -404,10 +340,14 @@ class BluetoothEventLoop { mBluetoothService.sendUuidIntent(address); } else if (name.equals("Paired")) { if (propValues[1].equals("true")) { - mBluetoothService.getBondState().setBondState(address, BluetoothDevice.BOND_BONDED); + // If locally initiated pairing, we will + // not go to BOND_BONDED state until we have received a + // successful return value in onCreatePairedDeviceResult + if (null == mBluetoothService.getPendingOutgoingBonding()) { + mBluetoothService.setBondState(address, BluetoothDevice.BOND_BONDED); + } } else { - mBluetoothService.getBondState().setBondState(address, - BluetoothDevice.BOND_NONE); + mBluetoothService.setBondState(address, BluetoothDevice.BOND_NONE); mBluetoothService.setRemoteDeviceProperty(address, "Trusted", "false"); } } else if (name.equals("Trusted")) { @@ -437,8 +377,8 @@ class BluetoothEventLoop { // Also set it only when the state is not already Bonded, we can sometimes // get an authorization request from the remote end if it doesn't have the link key // while we still have it. - if (mBluetoothService.getBondState().getBondState(address) != BluetoothDevice.BOND_BONDED) - mBluetoothService.getBondState().setBondState(address, BluetoothDevice.BOND_BONDING); + if (mBluetoothService.getBondState(address) != BluetoothDevice.BOND_BONDED) + mBluetoothService.setBondState(address, BluetoothDevice.BOND_BONDING); return address; } @@ -452,7 +392,7 @@ class BluetoothEventLoop { * so we may get this request many times. Also if we respond immediately, * the other end is unable to handle it. Delay sending the message. */ - if (mBluetoothService.getBondState().getBondState(address) == BluetoothDevice.BOND_BONDED) { + if (mBluetoothService.getBondState(address) == BluetoothDevice.BOND_BONDED) { Message message = mHandler.obtainMessage(EVENT_PAIRING_CONSENT_DELAYED_ACCEPT); message.obj = address; mHandler.sendMessageDelayed(message, 1500); @@ -497,7 +437,7 @@ class BluetoothEventLoop { if (address == null) return; String pendingOutgoingAddress = - mBluetoothService.getBondState().getPendingOutgoingBonding(); + mBluetoothService.getPendingOutgoingBonding(); if (address.equals(pendingOutgoingAddress)) { // we initiated the bonding @@ -518,12 +458,7 @@ class BluetoothEventLoop { case BluetoothClass.Device.AUDIO_VIDEO_PORTABLE_AUDIO: case BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO: case BluetoothClass.Device.AUDIO_VIDEO_HIFI_AUDIO: - if (!mBluetoothService.getBondState().hasAutoPairingFailed(address) && - !mBluetoothService.getBondState().isAutoPairingBlacklisted(address)) { - mBluetoothService.getBondState().attempt(address); - mBluetoothService.setPin(address, BluetoothDevice.convertPinToBytes("0000")); - return; - } + if (mBluetoothService.attemptAutoPair(address)) return; } } Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST); @@ -545,6 +480,17 @@ class BluetoothEventLoop { mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM); } + private void onRequestOobData(String objectPath , int nativeData) { + String address = checkPairingRequestAndGetAddress(objectPath, nativeData); + if (address == null) return; + + Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST); + intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address)); + intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, + BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT); + mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM); + } + private boolean onAgentAuthorize(String objectPath, String deviceUuid) { String address = mBluetoothService.getAddressFromObjectPath(objectPath); if (address == null) { @@ -566,6 +512,7 @@ class BluetoothEventLoop { authorized = a2dp.getSinkPriority(device) > BluetoothA2dp.PRIORITY_OFF; if (authorized) { Log.i(TAG, "Allowing incoming A2DP / AVRCP connection from " + address); + mBluetoothService.notifyIncomingA2dpConnection(address); } else { Log.i(TAG, "Rejecting incoming A2DP / AVRCP connection from " + address); } @@ -576,7 +523,21 @@ class BluetoothEventLoop { return authorized; } - boolean isOtherSinkInNonDisconnectingState(String address) { + private boolean onAgentOutOfBandDataAvailable(String objectPath) { + if (!mBluetoothService.isEnabled()) return false; + + String address = mBluetoothService.getAddressFromObjectPath(objectPath); + if (address == null) return false; + + if (mBluetoothService.getDeviceOutOfBandData( + mAdapter.getRemoteDevice(address)) != null) { + return true; + } + return false; + + } + + private boolean isOtherSinkInNonDisconnectingState(String address) { BluetoothA2dp a2dp = new BluetoothA2dp(mContext); Set<BluetoothDevice> devices = a2dp.getNonDisconnectedSinks(); if (devices.size() == 0) return false; diff --git a/core/java/android/server/BluetoothService.java b/core/java/android/server/BluetoothService.java index c0affd3755d6..dfe3a25fbc15 100644 --- a/core/java/android/server/BluetoothService.java +++ b/core/java/android/server/BluetoothService.java @@ -28,6 +28,8 @@ import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothHeadset; +import android.bluetooth.BluetoothDeviceProfileState; +import android.bluetooth.BluetoothProfileState; import android.bluetooth.BluetoothSocket; import android.bluetooth.BluetoothUuid; import android.bluetooth.IBluetooth; @@ -48,6 +50,7 @@ import android.os.ServiceManager; import android.os.SystemService; import android.provider.Settings; import android.util.Log; +import android.util.Pair; import com.android.internal.app.IBatteryStats; @@ -101,6 +104,14 @@ public class BluetoothService extends IBluetooth.Stub { private static final int MESSAGE_FINISH_DISABLE = 2; private static final int MESSAGE_UUID_INTENT = 3; private static final int MESSAGE_DISCOVERABLE_TIMEOUT = 4; + private static final int MESSAGE_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY = 5; + + // The time (in millisecs) to delay the pairing attempt after the first + // auto pairing attempt fails. We use an exponential delay with + // INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY as the initial value and + // MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY as the max value. + private static final long INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY = 3000; + private static final long MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY = 12000; // The timeout used to sent the UUIDs Intent // This timeout should be greater than the page timeout @@ -112,7 +123,7 @@ public class BluetoothService extends IBluetooth.Stub { BluetoothUuid.HSP, BluetoothUuid.ObexObjectPush }; - + // TODO(): Optimize all these string handling private final Map<String, String> mAdapterProperties; private final HashMap<String, Map<String, String>> mDeviceProperties; @@ -122,6 +133,13 @@ public class BluetoothService extends IBluetooth.Stub { private final HashMap<Integer, Integer> mServiceRecordToPid; + private final HashMap<String, BluetoothDeviceProfileState> mDeviceProfileState; + private final BluetoothProfileState mA2dpProfileState; + private final BluetoothProfileState mHfpProfileState; + + private BluetoothA2dpService mA2dpService; + private final HashMap<String, Pair<byte[], byte[]>> mDeviceOobData; + private static String mDockAddress; private String mDockPin; @@ -176,9 +194,16 @@ public class BluetoothService extends IBluetooth.Stub { mDeviceProperties = new HashMap<String, Map<String,String>>(); mDeviceServiceChannelCache = new HashMap<String, Map<ParcelUuid, Integer>>(); + mDeviceOobData = new HashMap<String, Pair<byte[], byte[]>>(); mUuidIntentTracker = new ArrayList<String>(); mUuidCallbackTracker = new HashMap<RemoteService, IBluetoothCallback>(); mServiceRecordToPid = new HashMap<Integer, Integer>(); + mDeviceProfileState = new HashMap<String, BluetoothDeviceProfileState>(); + mA2dpProfileState = new BluetoothProfileState(mContext, BluetoothProfileState.A2DP); + mHfpProfileState = new BluetoothProfileState(mContext, BluetoothProfileState.HFP); + + mHfpProfileState.start(); + mA2dpProfileState.start(); IntentFilter filter = new IntentFilter(); registerForAirplaneMode(filter); @@ -187,7 +212,7 @@ public class BluetoothService extends IBluetooth.Stub { mContext.registerReceiver(mReceiver, filter); } - public static synchronized String readDockBluetoothAddress() { + public static synchronized String readDockBluetoothAddress() { if (mDockAddress != null) return mDockAddress; BufferedInputStream file = null; @@ -485,6 +510,13 @@ public class BluetoothService extends IBluetooth.Stub { setScanMode(BluetoothAdapter.SCAN_MODE_CONNECTABLE, -1); } break; + case MESSAGE_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY: + address = (String)msg.obj; + if (address != null) { + createBond(address); + return; + } + break; } } }; @@ -534,6 +566,7 @@ public class BluetoothService extends IBluetooth.Stub { mIsDiscovering = false; mBondState.readAutoPairingData(); mBondState.loadBondState(); + initProfileState(); mHandler.sendMessageDelayed( mHandler.obtainMessage(MESSAGE_REGISTER_SDP_RECORDS, 1, -1), 3000); @@ -573,8 +606,68 @@ public class BluetoothService extends IBluetooth.Stub { Binder.restoreCallingIdentity(origCallerIdentityToken); } - /* package */ BondState getBondState() { - return mBondState; + /*package*/ synchronized boolean attemptAutoPair(String address) { + if (!mBondState.hasAutoPairingFailed(address) && + !mBondState.isAutoPairingBlacklisted(address)) { + mBondState.attempt(address); + setPin(address, BluetoothDevice.convertPinToBytes("0000")); + return true; + } + return false; + } + + /*package*/ synchronized void onCreatePairedDeviceResult(String address, int result) { + if (result == BluetoothDevice.BOND_SUCCESS) { + setBondState(address, BluetoothDevice.BOND_BONDED); + if (mBondState.isAutoPairingAttemptsInProgress(address)) { + mBondState.clearPinAttempts(address); + } + } else if (result == BluetoothDevice.UNBOND_REASON_AUTH_FAILED && + mBondState.getAttempt(address) == 1) { + mBondState.addAutoPairingFailure(address); + pairingAttempt(address, result); + } else if (result == BluetoothDevice.UNBOND_REASON_REMOTE_DEVICE_DOWN && + mBondState.isAutoPairingAttemptsInProgress(address)) { + pairingAttempt(address, result); + } else { + setBondState(address, BluetoothDevice.BOND_NONE, result); + if (mBondState.isAutoPairingAttemptsInProgress(address)) { + mBondState.clearPinAttempts(address); + } + } + } + + /*package*/ synchronized String getPendingOutgoingBonding() { + return mBondState.getPendingOutgoingBonding(); + } + + private void pairingAttempt(String address, int result) { + // This happens when our initial guess of "0000" as the pass key + // fails. Try to create the bond again and display the pin dialog + // to the user. Use back-off while posting the delayed + // message. The initial value is + // INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY and the max value is + // MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY. If the max value is + // reached, display an error to the user. + int attempt = mBondState.getAttempt(address); + if (attempt * INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY > + MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY) { + mBondState.clearPinAttempts(address); + setBondState(address, BluetoothDevice.BOND_NONE, result); + return; + } + + Message message = mHandler.obtainMessage(MESSAGE_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY); + message.obj = address; + boolean postResult = mHandler.sendMessageDelayed(message, + attempt * INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY); + if (!postResult) { + mBondState.clearPinAttempts(address); + setBondState(address, + BluetoothDevice.BOND_NONE, result); + return; + } + mBondState.attempt(address); } /** local cache of bonding state. @@ -648,6 +741,12 @@ public class BluetoothService extends IBluetooth.Stub { } } + if (state == BluetoothDevice.BOND_BONDED) { + addProfileState(address); + } else if (state == BluetoothDevice.BOND_NONE) { + removeProfileState(address); + } + if (DBG) log(address + " bond state " + oldState + " -> " + state + " (" + reason + ")"); Intent intent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED); @@ -899,7 +998,7 @@ public class BluetoothService extends IBluetooth.Stub { Log.e(TAG, "Error:Adapter Property at index" + i + "is null"); continue; } - if (name.equals("Devices")) { + if (name.equals("Devices") || name.equals("UUIDs")) { StringBuilder str = new StringBuilder(); len = Integer.valueOf(properties[++i]); for (int j = 0; j < len; j++) { @@ -1099,7 +1198,7 @@ public class BluetoothService extends IBluetooth.Stub { mIsDiscovering = isDiscovering; } - public synchronized boolean createBond(String address) { + private boolean isBondingFeasible(String address) { mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); if (!isEnabledInternal()) return false; @@ -1129,17 +1228,67 @@ public class BluetoothService extends IBluetooth.Stub { return false; } } + return true; + } + + public synchronized boolean createBond(String address) { + if (!isBondingFeasible(address)) return false; + + if (!createPairedDeviceNative(address, 60000 /*1 minute*/ )) { + return false; + } - if (!createPairedDeviceNative(address, 60000 /* 1 minute */)) { + mBondState.setPendingOutgoingBonding(address); + mBondState.setBondState(address, BluetoothDevice.BOND_BONDING); + + return true; + } + + public synchronized boolean createBondOutOfBand(String address, byte[] hash, + byte[] randomizer) { + if (!isBondingFeasible(address)) return false; + + if (!createPairedDeviceOutOfBandNative(address, 60000 /* 1 minute */)) { return false; } + setDeviceOutOfBandData(address, hash, randomizer); mBondState.setPendingOutgoingBonding(address); mBondState.setBondState(address, BluetoothDevice.BOND_BONDING); return true; } + public synchronized boolean setDeviceOutOfBandData(String address, byte[] hash, + byte[] randomizer) { + mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, + "Need BLUETOOTH_ADMIN permission"); + if (!isEnabledInternal()) return false; + + Pair <byte[], byte[]> value = new Pair<byte[], byte[]>(hash, randomizer); + + if (DBG) { + log("Setting out of band data for:" + address + ":" + + Arrays.toString(hash) + ":" + Arrays.toString(randomizer)); + } + + mDeviceOobData.put(address, value); + return true; + } + + Pair<byte[], byte[]> getDeviceOutOfBandData(BluetoothDevice device) { + return mDeviceOobData.get(device.getAddress()); + } + + + public synchronized byte[] readOutOfBandData() { + mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, + "Need BLUETOOTH permission"); + if (!isEnabledInternal()) return null; + + return readAdapterOutOfBandDataNative(); + } + public synchronized boolean cancelBondProcess(String address) { mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); @@ -1167,6 +1316,16 @@ public class BluetoothService extends IBluetooth.Stub { if (!BluetoothAdapter.checkBluetoothAddress(address)) { return false; } + BluetoothDeviceProfileState state = mDeviceProfileState.get(address); + if (state != null) { + state.sendMessage(BluetoothDeviceProfileState.UNPAIR); + return true; + } else { + return false; + } + } + + public synchronized boolean removeBondInternal(String address) { return removeDeviceNative(getObjectPathFromAddress(address)); } @@ -1175,6 +1334,10 @@ public class BluetoothService extends IBluetooth.Stub { return mBondState.listInState(BluetoothDevice.BOND_BONDED); } + /*package*/ synchronized String[] listInState(int state) { + return mBondState.listInState(state); + } + public synchronized int getBondState(String address) { mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); if (!BluetoothAdapter.checkBluetoothAddress(address)) { @@ -1183,6 +1346,15 @@ public class BluetoothService extends IBluetooth.Stub { return mBondState.getBondState(address.toUpperCase()); } + /*package*/ synchronized boolean setBondState(String address, int state) { + return setBondState(address, state, 0); + } + + /*package*/ synchronized boolean setBondState(String address, int state, int reason) { + mBondState.setBondState(address.toUpperCase(), state); + return true; + } + public synchronized boolean isBluetoothDock(String address) { SharedPreferences sp = mContext.getSharedPreferences(SHARED_PREFERENCES_NAME, mContext.MODE_PRIVATE); @@ -1521,6 +1693,32 @@ public class BluetoothService extends IBluetooth.Stub { return setPairingConfirmationNative(address, confirm, data.intValue()); } + public synchronized boolean setRemoteOutOfBandData(String address) { + mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, + "Need BLUETOOTH_ADMIN permission"); + if (!isEnabledInternal()) return false; + address = address.toUpperCase(); + Integer data = mEventLoop.getPasskeyAgentRequestData().remove(address); + if (data == null) { + Log.w(TAG, "setRemoteOobData(" + address + ") called but no native data available, " + + "ignoring. Maybe the PasskeyAgent Request was cancelled by the remote device" + + " or by bluez.\n"); + return false; + } + + Pair<byte[], byte[]> val = mDeviceOobData.get(address); + byte[] hash, randomizer; + if (val == null) { + // TODO: check what should be passed in this case. + hash = new byte[16]; + randomizer = new byte[16]; + } else { + hash = val.first; + randomizer = val.second; + } + return setRemoteOutOfBandDataNative(address, hash, randomizer, data.intValue()); + } + public synchronized boolean cancelPairingUserInput(String address) { mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); @@ -1707,7 +1905,7 @@ public class BluetoothService extends IBluetooth.Stub { mContext.getSharedPreferences(SHARED_PREFERENCES_NAME, mContext.MODE_PRIVATE).edit(); editor.putBoolean(SHARED_PREFERENCE_DOCK_ADDRESS + mDockAddress, true); - editor.commit(); + editor.apply(); } } } @@ -1836,7 +2034,7 @@ public class BluetoothService extends IBluetooth.Stub { // Rather not do this from here, but no-where else and I need this // dump pw.println("\n--Headset Service--"); - switch (headset.getState()) { + switch (headset.getState(headset.getCurrentHeadset())) { case BluetoothHeadset.STATE_DISCONNECTED: pw.println("getState() = STATE_DISCONNECTED"); break; @@ -1919,6 +2117,116 @@ public class BluetoothService extends IBluetooth.Stub { if (!result) log("Set Link Timeout to:" + num_slots + " slots failed"); } + public boolean connectHeadset(String address) { + BluetoothDeviceProfileState state = mDeviceProfileState.get(address); + if (state != null) { + Message msg = new Message(); + msg.arg1 = BluetoothDeviceProfileState.CONNECT_HFP_OUTGOING; + msg.obj = state; + mHfpProfileState.sendMessage(msg); + return true; + } + return false; + } + + public boolean disconnectHeadset(String address) { + BluetoothDeviceProfileState state = mDeviceProfileState.get(address); + if (state != null) { + Message msg = new Message(); + msg.arg1 = BluetoothDeviceProfileState.DISCONNECT_HFP_OUTGOING; + msg.obj = state; + mHfpProfileState.sendMessage(msg); + return true; + } + return false; + } + + public boolean connectSink(String address) { + BluetoothDeviceProfileState state = mDeviceProfileState.get(address); + if (state != null) { + Message msg = new Message(); + msg.arg1 = BluetoothDeviceProfileState.CONNECT_A2DP_OUTGOING; + msg.obj = state; + mA2dpProfileState.sendMessage(msg); + return true; + } + return false; + } + + public boolean disconnectSink(String address) { + BluetoothDeviceProfileState state = mDeviceProfileState.get(address); + if (state != null) { + Message msg = new Message(); + msg.arg1 = BluetoothDeviceProfileState.DISCONNECT_A2DP_OUTGOING; + msg.obj = state; + mA2dpProfileState.sendMessage(msg); + return true; + } + return false; + } + + private BluetoothDeviceProfileState addProfileState(String address) { + BluetoothDeviceProfileState state = mDeviceProfileState.get(address); + if (state != null) return state; + + state = new BluetoothDeviceProfileState(mContext, address, this, mA2dpService); + mDeviceProfileState.put(address, state); + state.start(); + return state; + } + + private void removeProfileState(String address) { + mDeviceProfileState.remove(address); + } + + private void initProfileState() { + String []bonds = null; + String val = getPropertyInternal("Devices"); + if (val != null) { + bonds = val.split(","); + } + if (bonds == null) { + return; + } + + for (String path : bonds) { + String address = getAddressFromObjectPath(path); + BluetoothDeviceProfileState state = addProfileState(address); + // Allow 8 secs for SDP records to get registered. + Message msg = new Message(); + msg.what = BluetoothDeviceProfileState.AUTO_CONNECT_PROFILES; + state.sendMessageDelayed(msg, 8000); + } + } + + public boolean notifyIncomingConnection(String address) { + BluetoothDeviceProfileState state = + mDeviceProfileState.get(address); + if (state != null) { + Message msg = new Message(); + msg.what = BluetoothDeviceProfileState.CONNECT_HFP_INCOMING; + state.sendMessage(msg); + return true; + } + return false; + } + + /*package*/ boolean notifyIncomingA2dpConnection(String address) { + BluetoothDeviceProfileState state = + mDeviceProfileState.get(address); + if (state != null) { + Message msg = new Message(); + msg.what = BluetoothDeviceProfileState.CONNECT_A2DP_INCOMING; + state.sendMessage(msg); + return true; + } + return false; + } + + /*package*/ void setA2dpService(BluetoothA2dpService a2dpService) { + mA2dpService = a2dpService; + } + private static void log(String msg) { Log.d(TAG, msg); } @@ -1944,6 +2252,9 @@ public class BluetoothService extends IBluetooth.Stub { private native boolean stopDiscoveryNative(); private native boolean createPairedDeviceNative(String address, int timeout_ms); + private native boolean createPairedDeviceOutOfBandNative(String address, int timeout_ms); + private native byte[] readAdapterOutOfBandDataNative(); + private native boolean cancelDeviceCreationNative(String address); private native boolean removeDeviceNative(String objectPath); private native int getDeviceServiceChannelNative(String objectPath, String uuid, @@ -1954,6 +2265,9 @@ public class BluetoothService extends IBluetooth.Stub { private native boolean setPasskeyNative(String address, int passkey, int nativeData); private native boolean setPairingConfirmationNative(String address, boolean confirm, int nativeData); + private native boolean setRemoteOutOfBandDataNative(String address, byte[] hash, + byte[] randomizer, int nativeData); + private native boolean setDevicePropertyBooleanNative(String objectPath, String key, int value); private native boolean createDeviceNative(String address); diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index 3d1d7d63b3ed..2b083dca2ca6 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -18,6 +18,7 @@ package android.service.wallpaper; import com.android.internal.os.HandlerCaller; import com.android.internal.view.BaseIWindow; +import com.android.internal.view.BaseInputHandler; import com.android.internal.view.BaseSurfaceHolder; import android.annotation.SdkConstant; @@ -29,6 +30,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.res.Configuration; +import android.graphics.PixelFormat; import android.graphics.Rect; import android.os.Bundle; import android.os.IBinder; @@ -39,6 +41,11 @@ import android.util.Log; import android.util.LogPrinter; import android.view.Gravity; import android.view.IWindowSession; +import android.view.InputChannel; +import android.view.InputDevice; +import android.view.InputHandler; +import android.view.InputQueue; +import android.view.KeyEvent; import android.view.MotionEvent; import android.view.SurfaceHolder; import android.view.View; @@ -46,6 +53,7 @@ import android.view.ViewGroup; import android.view.ViewRoot; import android.view.WindowManager; import android.view.WindowManagerImpl; +import android.view.WindowManagerPolicy; import java.util.ArrayList; @@ -146,6 +154,7 @@ public abstract class WallpaperService extends Service { final WindowManager.LayoutParams mLayout = new WindowManager.LayoutParams(); IWindowSession mSession; + InputChannel mInputChannel; final Object mLock = new Object(); boolean mOffsetMessageEnqueued; @@ -170,6 +179,9 @@ public abstract class WallpaperService extends Service { }; final BaseSurfaceHolder mSurfaceHolder = new BaseSurfaceHolder() { + { + mRequestedFormat = PixelFormat.RGB_565; + } @Override public boolean onAllowLockCanvas() { @@ -205,27 +217,21 @@ public abstract class WallpaperService extends Service { }; - final BaseIWindow mWindow = new BaseIWindow() { + final InputHandler mInputHandler = new BaseInputHandler() { @Override - public boolean onDispatchPointer(MotionEvent event, long eventTime, - boolean callWhenDone) { - synchronized (mLock) { - if (event.getAction() == MotionEvent.ACTION_MOVE) { - if (mPendingMove != null) { - mCaller.removeMessages(MSG_TOUCH_EVENT, mPendingMove); - mPendingMove.recycle(); - } - mPendingMove = event; - } else { - mPendingMove = null; + public void handleMotion(MotionEvent event, Runnable finishedCallback) { + try { + int source = event.getSource(); + if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) { + dispatchPointer(event); } - Message msg = mCaller.obtainMessageO(MSG_TOUCH_EVENT, - event); - mCaller.sendMessage(msg); + } finally { + finishedCallback.run(); } - return false; } - + }; + + final BaseIWindow mWindow = new BaseIWindow() { @Override public void resized(int w, int h, Rect coveredInsets, Rect visibleInsets, boolean reportDraw, Configuration newConfig) { @@ -338,7 +344,7 @@ public abstract class WallpaperService extends Service { ? (mWindowFlags&~WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) : (mWindowFlags|WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE); if (mCreated) { - updateSurface(false, false); + updateSurface(false, false, false); } } @@ -423,6 +429,13 @@ public abstract class WallpaperService extends Service { } /** + * Convenience for {@link SurfaceHolder.Callback2#surfaceRedrawNeeded + * SurfaceHolder.Callback.surfaceRedrawNeeded()}. + */ + public void onSurfaceRedrawNeeded(SurfaceHolder holder) { + } + + /** * Convenience for {@link SurfaceHolder.Callback#surfaceCreated * SurfaceHolder.Callback.surfaceCreated()}. */ @@ -435,8 +448,24 @@ public abstract class WallpaperService extends Service { */ public void onSurfaceDestroyed(SurfaceHolder holder) { } + + private void dispatchPointer(MotionEvent event) { + synchronized (mLock) { + if (event.getAction() == MotionEvent.ACTION_MOVE) { + if (mPendingMove != null) { + mCaller.removeMessages(MSG_TOUCH_EVENT, mPendingMove); + mPendingMove.recycle(); + } + mPendingMove = event; + } else { + mPendingMove = null; + } + Message msg = mCaller.obtainMessageO(MSG_TOUCH_EVENT, event); + mCaller.sendMessage(msg); + } + } - void updateSurface(boolean forceRelayout, boolean forceReport) { + void updateSurface(boolean forceRelayout, boolean forceReport, boolean redrawNeeded) { if (mDestroyed) { Log.w(TAG, "Ignoring updateSurface: destroyed"); } @@ -453,7 +482,7 @@ public abstract class WallpaperService extends Service { final boolean typeChanged = mType != mSurfaceHolder.getRequestedType(); final boolean flagsChanged = mCurWindowFlags != mWindowFlags; if (forceRelayout || creating || surfaceCreating || formatChanged || sizeChanged - || typeChanged || flagsChanged) { + || typeChanged || flagsChanged || redrawNeeded) { if (DEBUG) Log.v(TAG, "Changes: creating=" + creating + " format=" + formatChanged + " size=" + sizeChanged); @@ -487,8 +516,13 @@ public abstract class WallpaperService extends Service { mLayout.setTitle(WallpaperService.this.getClass().getName()); mLayout.windowAnimations = com.android.internal.R.style.Animation_Wallpaper; - mSession.add(mWindow, mLayout, View.VISIBLE, mContentInsets); + mInputChannel = new InputChannel(); + mSession.add(mWindow, mLayout, View.VISIBLE, mContentInsets, + mInputChannel); mCreated = true; + + InputQueue.registerInputChannel(mInputChannel, mInputHandler, + Looper.myQueue()); } mSurfaceHolder.mSurfaceLock.lock(); @@ -522,26 +556,24 @@ public abstract class WallpaperService extends Service { } try { - SurfaceHolder.Callback callbacks[] = null; - synchronized (mSurfaceHolder.mCallbacks) { - final int N = mSurfaceHolder.mCallbacks.size(); - if (N > 0) { - callbacks = new SurfaceHolder.Callback[N]; - mSurfaceHolder.mCallbacks.toArray(callbacks); - } - } + mSurfaceHolder.ungetCallbacks(); if (surfaceCreating) { mIsCreating = true; if (DEBUG) Log.v(TAG, "onSurfaceCreated(" + mSurfaceHolder + "): " + this); onSurfaceCreated(mSurfaceHolder); + SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks(); if (callbacks != null) { for (SurfaceHolder.Callback c : callbacks) { c.surfaceCreated(mSurfaceHolder); } } } + + redrawNeeded |= creating + || (relayoutResult&WindowManagerImpl.RELAYOUT_FIRST_TIME) != 0; + if (forceReport || creating || surfaceCreating || formatChanged || sizeChanged) { if (DEBUG) { @@ -557,6 +589,7 @@ public abstract class WallpaperService extends Service { + "): " + this); onSurfaceChanged(mSurfaceHolder, mFormat, mCurWidth, mCurHeight); + SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks(); if (callbacks != null) { for (SurfaceHolder.Callback c : callbacks) { c.surfaceChanged(mSurfaceHolder, mFormat, @@ -564,10 +597,24 @@ public abstract class WallpaperService extends Service { } } } + + if (redrawNeeded) { + onSurfaceRedrawNeeded(mSurfaceHolder); + SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks(); + if (callbacks != null) { + for (SurfaceHolder.Callback c : callbacks) { + if (c instanceof SurfaceHolder.Callback2) { + ((SurfaceHolder.Callback2)c).surfaceRedrawNeeded( + mSurfaceHolder); + } + } + } + } + } finally { mIsCreating = false; mSurfaceCreated = true; - if (creating || (relayoutResult&WindowManagerImpl.RELAYOUT_FIRST_TIME) != 0) { + if (redrawNeeded) { mSession.finishDrawing(mWindow); } } @@ -592,6 +639,7 @@ public abstract class WallpaperService extends Service { mSurfaceHolder.setSizeFromLayout(); mInitializing = true; mSession = ViewRoot.getWindowSession(getMainLooper()); + mWindow.setSession(mSession); IntentFilter filter = new IntentFilter(); @@ -603,7 +651,7 @@ public abstract class WallpaperService extends Service { onCreate(mSurfaceHolder); mInitializing = false; - updateSurface(false, false); + updateSurface(false, false, false); } void doDesiredSizeChanged(int desiredWidth, int desiredHeight) { @@ -632,7 +680,7 @@ public abstract class WallpaperService extends Service { // If becoming visible, in preview mode the surface // may have been destroyed so now we need to make // sure it is re-created. - updateSurface(false, false); + updateSurface(false, false, false); } onVisibilityChanged(visible); } @@ -698,14 +746,12 @@ public abstract class WallpaperService extends Service { void reportSurfaceDestroyed() { if (mSurfaceCreated) { mSurfaceCreated = false; - SurfaceHolder.Callback callbacks[]; - synchronized (mSurfaceHolder.mCallbacks) { - callbacks = new SurfaceHolder.Callback[ - mSurfaceHolder.mCallbacks.size()]; - mSurfaceHolder.mCallbacks.toArray(callbacks); - } - for (SurfaceHolder.Callback c : callbacks) { - c.surfaceDestroyed(mSurfaceHolder); + mSurfaceHolder.ungetCallbacks(); + SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks(); + if (callbacks != null) { + for (SurfaceHolder.Callback c : callbacks) { + c.surfaceDestroyed(mSurfaceHolder); + } } if (DEBUG) Log.v(TAG, "onSurfaceDestroyed(" + mSurfaceHolder + "): " + this); @@ -737,11 +783,23 @@ public abstract class WallpaperService extends Service { try { if (DEBUG) Log.v(TAG, "Removing window and destroying surface " + mSurfaceHolder.getSurface() + " of: " + this); + + if (mInputChannel != null) { + InputQueue.unregisterInputChannel(mInputChannel); + } + mSession.remove(mWindow); } catch (RemoteException e) { } mSurfaceHolder.mSurface.release(); mCreated = false; + + // Dispose the input channel after removing the window so the Window Manager + // doesn't interpret the input channel being closed as an abnormal termination. + if (mInputChannel != null) { + mInputChannel.dispose(); + mInputChannel = null; + } } } } @@ -793,7 +851,7 @@ public abstract class WallpaperService extends Service { public void dispatchPointer(MotionEvent event) { if (mEngine != null) { - mEngine.mWindow.onDispatchPointer(event, event.getEventTime(), false); + mEngine.dispatchPointer(event); } } @@ -827,7 +885,7 @@ public abstract class WallpaperService extends Service { return; } case MSG_UPDATE_SURFACE: - mEngine.updateSurface(true, false); + mEngine.updateSurface(true, false, false); break; case MSG_VISIBILITY_CHANGED: if (DEBUG) Log.v(TAG, "Visibility change in " + mEngine @@ -843,14 +901,8 @@ public abstract class WallpaperService extends Service { } break; case MSG_WINDOW_RESIZED: { final boolean reportDraw = message.arg1 != 0; - mEngine.updateSurface(true, false); + mEngine.updateSurface(true, false, reportDraw); mEngine.doOffsetsChanged(); - if (reportDraw) { - try { - mEngine.mSession.finishDrawing(mEngine.mWindow); - } catch (RemoteException e) { - } - } } break; case MSG_TOUCH_EVENT: { MotionEvent ev = (MotionEvent)message.obj; diff --git a/core/java/android/speech/tts/TextToSpeech.java b/core/java/android/speech/tts/TextToSpeech.java index 26c167e1f013..841257f36770 100755 --- a/core/java/android/speech/tts/TextToSpeech.java +++ b/core/java/android/speech/tts/TextToSpeech.java @@ -1065,6 +1065,9 @@ public class TextToSpeech { if (!mStarted) { return result; } + if (loc == null) { + return result; + } try { String language = loc.getISO3Language(); String country = loc.getISO3Country(); diff --git a/core/java/android/text/Selection.java b/core/java/android/text/Selection.java index bb98bce67cee..13cb5e6e305c 100644 --- a/core/java/android/text/Selection.java +++ b/core/java/android/text/Selection.java @@ -417,8 +417,8 @@ public class Selection { } } - private static final class START implements NoCopySpan { }; - private static final class END implements NoCopySpan { }; + private static final class START implements NoCopySpan { } + private static final class END implements NoCopySpan { } /* * Public constants diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java index 9589bf369e6e..8675d05f3116 100644 --- a/core/java/android/text/TextUtils.java +++ b/core/java/android/text/TextUtils.java @@ -1651,7 +1651,36 @@ public class TextUtils { return mode; } - + + /** + * Does a comma-delimited list 'delimitedString' contain a certain item? + * (without allocating memory) + * + * @hide + */ + public static boolean delimitedStringContains( + String delimitedString, char delimiter, String item) { + if (isEmpty(delimitedString) || isEmpty(item)) { + return false; + } + int pos = -1; + int length = delimitedString.length(); + while ((pos = delimitedString.indexOf(item, pos + 1)) != -1) { + if (pos > 0 && delimitedString.charAt(pos - 1) != delimiter) { + continue; + } + int expectedDelimiterPos = pos + item.length(); + if (expectedDelimiterPos == length) { + // Match at end of string. + return true; + } + if (delimitedString.charAt(expectedDelimiterPos) == delimiter) { + return true; + } + } + return false; + } + private static Object sLock = new Object(); private static char[] sTemp = null; } diff --git a/core/java/android/text/format/DateUtils.java b/core/java/android/text/format/DateUtils.java index dde0889a5e8b..4e2c3c395ce7 100644 --- a/core/java/android/text/format/DateUtils.java +++ b/core/java/android/text/format/DateUtils.java @@ -168,6 +168,12 @@ public class DateUtils public static final int FORMAT_CAP_NOON = 0x00400; public static final int FORMAT_NO_MIDNIGHT = 0x00800; public static final int FORMAT_CAP_MIDNIGHT = 0x01000; + /** + * @deprecated Use + * {@link #formatDateRange(Context, Formatter, long, long, int, String) formatDateRange} + * and pass in {@link Time#TIMEZONE_UTC Time.TIMEZONE_UTC} for the timeZone instead. + */ + @Deprecated public static final int FORMAT_UTC = 0x02000; public static final int FORMAT_ABBREV_TIME = 0x04000; public static final int FORMAT_ABBREV_WEEKDAY = 0x08000; @@ -946,12 +952,12 @@ public class DateUtils * {@link java.util.Formatter} instance and use the version of * {@link #formatDateRange(Context, long, long, int) formatDateRange} * that takes a {@link java.util.Formatter}. - * + * * @param context the context is required only if the time is shown * @param startMillis the start time in UTC milliseconds * @param endMillis the end time in UTC milliseconds * @param flags a bit mask of options See - * {@link #formatDateRange(Context, long, long, int) formatDateRange} + * {@link #formatDateRange(Context, Formatter, long, long, int, String) formatDateRange} * @return a string containing the formatted date/time range. */ public static String formatDateRange(Context context, long startMillis, @@ -962,6 +968,29 @@ public class DateUtils /** * Formats a date or a time range according to the local conventions. + * <p> + * Note that this is a convenience method for formatting the date or + * time range in the local time zone. If you want to specify the time + * zone please use + * {@link #formatDateRange(Context, Formatter, long, long, int, String) formatDateRange}. + * + * @param context the context is required only if the time is shown + * @param formatter the Formatter used for formatting the date range. + * Note: be sure to call setLength(0) on StringBuilder passed to + * the Formatter constructor unless you want the results to accumulate. + * @param startMillis the start time in UTC milliseconds + * @param endMillis the end time in UTC milliseconds + * @param flags a bit mask of options See + * {@link #formatDateRange(Context, Formatter, long, long, int, String) formatDateRange} + * @return a string containing the formatted date/time range. + */ + public static Formatter formatDateRange(Context context, Formatter formatter, long startMillis, + long endMillis, int flags) { + return formatDateRange(context, formatter, startMillis, endMillis, flags, null); + } + + /** + * Formats a date or a time range according to the local conventions. * * <p> * Example output strings (date formats in these examples are shown using @@ -1076,8 +1105,9 @@ public class DateUtils * FORMAT_24HOUR takes precedence. * * <p> - * If FORMAT_UTC is set, then the UTC timezone is used for the start - * and end milliseconds. + * If FORMAT_UTC is set, then the UTC time zone is used for the start + * and end milliseconds unless a time zone is specified. If a time zone + * is specified it will be used regardless of the FORMAT_UTC flag. * * <p> * If FORMAT_ABBREV_TIME is set and 12-hour time format is used, then the @@ -1109,11 +1139,13 @@ public class DateUtils * @param startMillis the start time in UTC milliseconds * @param endMillis the end time in UTC milliseconds * @param flags a bit mask of options - * + * @param timeZone the time zone to compute the string in. Use null for local + * or if the FORMAT_UTC flag is being used. + * * @return the formatter with the formatted date/time range appended to the string buffer. */ public static Formatter formatDateRange(Context context, Formatter formatter, long startMillis, - long endMillis, int flags) { + long endMillis, int flags, String timeZone) { Resources res = Resources.getSystem(); boolean showTime = (flags & FORMAT_SHOW_TIME) != 0; boolean showWeekDay = (flags & FORMAT_SHOW_WEEKDAY) != 0; @@ -1130,7 +1162,14 @@ public class DateUtils // computation below that'd otherwise be thrown out. boolean isInstant = (startMillis == endMillis); - Time startDate = useUTC ? new Time(Time.TIMEZONE_UTC) : new Time(); + Time startDate; + if (timeZone != null) { + startDate = new Time(timeZone); + } else if (useUTC) { + startDate = new Time(Time.TIMEZONE_UTC); + } else { + startDate = new Time(); + } startDate.set(startMillis); Time endDate; @@ -1139,7 +1178,13 @@ public class DateUtils endDate = startDate; dayDistance = 0; } else { - endDate = useUTC ? new Time(Time.TIMEZONE_UTC) : new Time(); + if (timeZone != null) { + endDate = new Time(timeZone); + } else if (useUTC) { + endDate = new Time(Time.TIMEZONE_UTC); + } else { + endDate = new Time(); + } endDate.set(endMillis); int startJulianDay = Time.getJulianDay(startMillis, startDate.gmtoff); int endJulianDay = Time.getJulianDay(endMillis, endDate.gmtoff); diff --git a/core/java/android/text/method/ArrowKeyMovementMethod.java b/core/java/android/text/method/ArrowKeyMovementMethod.java index 9af42cc60822..04086737523c 100644 --- a/core/java/android/text/method/ArrowKeyMovementMethod.java +++ b/core/java/android/text/method/ArrowKeyMovementMethod.java @@ -16,30 +16,38 @@ package android.text.method; -import android.util.Log; +import android.text.Layout; +import android.text.Selection; +import android.text.Spannable; import android.view.KeyEvent; -import android.graphics.Rect; -import android.text.*; -import android.widget.TextView; -import android.view.View; -import android.view.ViewConfiguration; import android.view.MotionEvent; +import android.view.View; +import android.widget.TextView; +import android.widget.TextView.CursorController; // XXX this doesn't extend MetaKeyKeyListener because the signatures // don't match. Need to figure that out. Meanwhile the meta keys // won't work in fields that don't take input. -public class -ArrowKeyMovementMethod -implements MovementMethod -{ +public class ArrowKeyMovementMethod implements MovementMethod { + /** + * An optional controller for the cursor. + * Use {@link #setCursorController(CursorController)} to set this field. + */ + private CursorController mCursorController; + + private boolean isCap(Spannable buffer) { + return ((MetaKeyKeyListener.getMetaState(buffer, KeyEvent.META_SHIFT_ON) == 1) || + (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0)); + } + + private boolean isAlt(Spannable buffer) { + return MetaKeyKeyListener.getMetaState(buffer, KeyEvent.META_ALT_ON) == 1; + } + private boolean up(TextView widget, Spannable buffer) { - boolean cap = (MetaKeyKeyListener.getMetaState(buffer, - KeyEvent.META_SHIFT_ON) == 1) || - (MetaKeyKeyListener.getMetaState(buffer, - MetaKeyKeyListener.META_SELECTING) != 0); - boolean alt = MetaKeyKeyListener.getMetaState(buffer, - KeyEvent.META_ALT_ON) == 1; + boolean cap = isCap(buffer); + boolean alt = isAlt(buffer); Layout layout = widget.getLayout(); if (cap) { @@ -60,12 +68,8 @@ implements MovementMethod } private boolean down(TextView widget, Spannable buffer) { - boolean cap = (MetaKeyKeyListener.getMetaState(buffer, - KeyEvent.META_SHIFT_ON) == 1) || - (MetaKeyKeyListener.getMetaState(buffer, - MetaKeyKeyListener.META_SELECTING) != 0); - boolean alt = MetaKeyKeyListener.getMetaState(buffer, - KeyEvent.META_ALT_ON) == 1; + boolean cap = isCap(buffer); + boolean alt = isAlt(buffer); Layout layout = widget.getLayout(); if (cap) { @@ -86,12 +90,8 @@ implements MovementMethod } private boolean left(TextView widget, Spannable buffer) { - boolean cap = (MetaKeyKeyListener.getMetaState(buffer, - KeyEvent.META_SHIFT_ON) == 1) || - (MetaKeyKeyListener.getMetaState(buffer, - MetaKeyKeyListener.META_SELECTING) != 0); - boolean alt = MetaKeyKeyListener.getMetaState(buffer, - KeyEvent.META_ALT_ON) == 1; + boolean cap = isCap(buffer); + boolean alt = isAlt(buffer); Layout layout = widget.getLayout(); if (cap) { @@ -110,12 +110,8 @@ implements MovementMethod } private boolean right(TextView widget, Spannable buffer) { - boolean cap = (MetaKeyKeyListener.getMetaState(buffer, - KeyEvent.META_SHIFT_ON) == 1) || - (MetaKeyKeyListener.getMetaState(buffer, - MetaKeyKeyListener.META_SELECTING) != 0); - boolean alt = MetaKeyKeyListener.getMetaState(buffer, - KeyEvent.META_ALT_ON) == 1; + boolean cap = isCap(buffer); + boolean alt = isAlt(buffer); Layout layout = widget.getLayout(); if (cap) { @@ -133,35 +129,6 @@ implements MovementMethod } } - private int getOffset(int x, int y, TextView widget){ - // Converts the absolute X,Y coordinates to the character offset for the - // character whose position is closest to the specified - // horizontal position. - x -= widget.getTotalPaddingLeft(); - y -= widget.getTotalPaddingTop(); - - // Clamp the position to inside of the view. - if (x < 0) { - x = 0; - } else if (x >= (widget.getWidth()-widget.getTotalPaddingRight())) { - x = widget.getWidth()-widget.getTotalPaddingRight() - 1; - } - if (y < 0) { - y = 0; - } else if (y >= (widget.getHeight()-widget.getTotalPaddingBottom())) { - y = widget.getHeight()-widget.getTotalPaddingBottom() - 1; - } - - x += widget.getScrollX(); - y += widget.getScrollY(); - - Layout layout = widget.getLayout(); - int line = layout.getLineForVertical(y); - - int offset = layout.getOffsetForHorizontal(line, x); - return offset; - } - public boolean onKeyDown(TextView widget, Spannable buffer, int keyCode, KeyEvent event) { if (executeDown(widget, buffer, keyCode)) { MetaKeyKeyListener.adjustMetaAfterKeypress(buffer); @@ -193,10 +160,9 @@ implements MovementMethod break; case KeyEvent.KEYCODE_DPAD_CENTER: - if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) { - if (widget.showContextMenu()) { + if ((MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) && + (widget.showContextMenu())) { handled = true; - } } } @@ -214,8 +180,7 @@ implements MovementMethod public boolean onKeyOther(TextView view, Spannable text, KeyEvent event) { int code = event.getKeyCode(); - if (code != KeyEvent.KEYCODE_UNKNOWN - && event.getAction() == KeyEvent.ACTION_MULTIPLE) { + if (code != KeyEvent.KEYCODE_UNKNOWN && event.getAction() == KeyEvent.ACTION_MULTIPLE) { int repeat = event.getRepeatCount(); boolean handled = false; while ((--repeat) > 0) { @@ -226,13 +191,22 @@ implements MovementMethod return false; } - public boolean onTrackballEvent(TextView widget, Spannable text, - MotionEvent event) { + public boolean onTrackballEvent(TextView widget, Spannable text, MotionEvent event) { + if (mCursorController != null) { + mCursorController.hide(); + } return false; } - public boolean onTouchEvent(TextView widget, Spannable buffer, - MotionEvent event) { + public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event) { + if (mCursorController != null) { + return onTouchEventCursor(widget, buffer, event); + } else { + return onTouchEventStandard(widget, buffer, event); + } + } + + private boolean onTouchEventStandard(TextView widget, Spannable buffer, MotionEvent event) { int initialScrollX = -1, initialScrollY = -1; if (event.getAction() == MotionEvent.ACTION_UP) { initialScrollX = Touch.getInitialScrollX(widget, buffer); @@ -243,53 +217,20 @@ implements MovementMethod if (widget.isFocused() && !widget.didTouchFocusSelect()) { if (event.getAction() == MotionEvent.ACTION_DOWN) { - boolean cap = (MetaKeyKeyListener.getMetaState(buffer, - KeyEvent.META_SHIFT_ON) == 1) || - (MetaKeyKeyListener.getMetaState(buffer, - MetaKeyKeyListener.META_SELECTING) != 0); - int x = (int) event.getX(); - int y = (int) event.getY(); - int offset = getOffset(x, y, widget); - + boolean cap = isCap(buffer); if (cap) { - buffer.setSpan(LAST_TAP_DOWN, offset, offset, - Spannable.SPAN_POINT_POINT); + int offset = widget.getOffset((int) event.getX(), (int) event.getY()); + + buffer.setSpan(LAST_TAP_DOWN, offset, offset, Spannable.SPAN_POINT_POINT); // Disallow intercepting of the touch events, so that // users can scroll and select at the same time. // without this, users would get booted out of select // mode once the view detected it needed to scroll. widget.getParent().requestDisallowInterceptTouchEvent(true); - } else { - OnePointFiveTapState[] tap = buffer.getSpans(0, buffer.length(), - OnePointFiveTapState.class); - - if (tap.length > 0) { - if (event.getEventTime() - tap[0].mWhen <= - ViewConfiguration.getDoubleTapTimeout() && - sameWord(buffer, offset, Selection.getSelectionEnd(buffer))) { - - tap[0].active = true; - MetaKeyKeyListener.startSelecting(widget, buffer); - widget.getParent().requestDisallowInterceptTouchEvent(true); - buffer.setSpan(LAST_TAP_DOWN, offset, offset, - Spannable.SPAN_POINT_POINT); - } - - tap[0].mWhen = event.getEventTime(); - } else { - OnePointFiveTapState newtap = new OnePointFiveTapState(); - newtap.mWhen = event.getEventTime(); - newtap.active = false; - buffer.setSpan(newtap, 0, buffer.length(), - Spannable.SPAN_INCLUSIVE_INCLUSIVE); - } } } else if (event.getAction() == MotionEvent.ACTION_MOVE) { - boolean cap = (MetaKeyKeyListener.getMetaState(buffer, - KeyEvent.META_SHIFT_ON) == 1) || - (MetaKeyKeyListener.getMetaState(buffer, - MetaKeyKeyListener.META_SELECTING) != 0); + boolean cap = isCap(buffer); if (cap && handled) { // Before selecting, make sure we've moved out of the "slop". @@ -297,45 +238,15 @@ implements MovementMethod // OUT of the slop // Turn long press off while we're selecting. User needs to - // re-tap on the selection to enable longpress + // re-tap on the selection to enable long press widget.cancelLongPress(); // Update selection as we're moving the selection area. // Get the current touch position - int x = (int) event.getX(); - int y = (int) event.getY(); - int offset = getOffset(x, y, widget); - - final OnePointFiveTapState[] tap = buffer.getSpans(0, buffer.length(), - OnePointFiveTapState.class); - - if (tap.length > 0 && tap[0].active) { - // Get the last down touch position (the position at which the - // user started the selection) - int lastDownOffset = buffer.getSpanStart(LAST_TAP_DOWN); - - // Compute the selection boundaries - int spanstart; - int spanend; - if (offset >= lastDownOffset) { - // Expand from word start of the original tap to new word - // end, since we are selecting "forwards" - spanstart = findWordStart(buffer, lastDownOffset); - spanend = findWordEnd(buffer, offset); - } else { - // Expand to from new word start to word end of the original - // tap since we are selecting "backwards". - // The spanend will always need to be associated with the touch - // up position, so that refining the selection with the - // trackball will work as expected. - spanstart = findWordEnd(buffer, lastDownOffset); - spanend = findWordStart(buffer, offset); - } - Selection.setSelection(buffer, spanstart, spanend); - } else { - Selection.extendSelection(buffer, offset); - } + int offset = widget.getOffset((int) event.getX(), (int) event.getY()); + + Selection.extendSelection(buffer, offset); return true; } } else if (event.getAction() == MotionEvent.ACTION_UP) { @@ -344,70 +255,17 @@ implements MovementMethod // the current scroll offset to avoid the scroll jumping later // to show it. if ((initialScrollY >= 0 && initialScrollY != widget.getScrollY()) || - (initialScrollX >= 0 && initialScrollX != widget.getScrollX())) { + (initialScrollX >= 0 && initialScrollX != widget.getScrollX())) { widget.moveCursorToVisibleOffset(); return true; } - int x = (int) event.getX(); - int y = (int) event.getY(); - int off = getOffset(x, y, widget); - - // XXX should do the same adjust for x as we do for the line. - - OnePointFiveTapState[] onepointfivetap = buffer.getSpans(0, buffer.length(), - OnePointFiveTapState.class); - if (onepointfivetap.length > 0 && onepointfivetap[0].active && - Selection.getSelectionStart(buffer) == Selection.getSelectionEnd(buffer)) { - // If we've set select mode, because there was a onepointfivetap, - // but there was no ensuing swipe gesture, undo the select mode - // and remove reference to the last onepointfivetap. - MetaKeyKeyListener.stopSelecting(widget, buffer); - for (int i=0; i < onepointfivetap.length; i++) { - buffer.removeSpan(onepointfivetap[i]); - } + int offset = widget.getOffset((int) event.getX(), (int) event.getY()); + if (isCap(buffer)) { buffer.removeSpan(LAST_TAP_DOWN); - } - boolean cap = (MetaKeyKeyListener.getMetaState(buffer, - KeyEvent.META_SHIFT_ON) == 1) || - (MetaKeyKeyListener.getMetaState(buffer, - MetaKeyKeyListener.META_SELECTING) != 0); - - DoubleTapState[] tap = buffer.getSpans(0, buffer.length(), - DoubleTapState.class); - boolean doubletap = false; - - if (tap.length > 0) { - if (event.getEventTime() - tap[0].mWhen <= - ViewConfiguration.getDoubleTapTimeout() && - sameWord(buffer, off, Selection.getSelectionEnd(buffer))) { - - doubletap = true; - } - - tap[0].mWhen = event.getEventTime(); + Selection.extendSelection(buffer, offset); } else { - DoubleTapState newtap = new DoubleTapState(); - newtap.mWhen = event.getEventTime(); - buffer.setSpan(newtap, 0, buffer.length(), - Spannable.SPAN_INCLUSIVE_INCLUSIVE); - } - - if (cap) { - buffer.removeSpan(LAST_TAP_DOWN); - if (onepointfivetap.length > 0 && onepointfivetap[0].active) { - // If we selecting something with the onepointfivetap-and - // swipe gesture, stop it on finger up. - MetaKeyKeyListener.stopSelecting(widget, buffer); - } else { - Selection.extendSelection(buffer, off); - } - } else if (doubletap) { - Selection.setSelection(buffer, - findWordStart(buffer, off), - findWordEnd(buffer, off)); - } else { - Selection.setSelection(buffer, off); + Selection.setSelection(buffer, offset); } MetaKeyKeyListener.adjustMetaAfterKeypress(buffer); @@ -420,73 +278,45 @@ implements MovementMethod return handled; } - private static class DoubleTapState implements NoCopySpan { - long mWhen; - } - - /* We check for a onepointfive tap. This is similar to - * doubletap gesture (where a finger goes down, up, down, up, in a short - * time period), except in the onepointfive tap, a users finger only needs - * to go down, up, down in a short time period. We detect this type of tap - * to implement the onepointfivetap-and-swipe selection gesture. - * This gesture allows users to select a segment of text without going - * through the "select text" option in the context menu. - */ - private static class OnePointFiveTapState implements NoCopySpan { - long mWhen; - boolean active; - } - - private static boolean sameWord(CharSequence text, int one, int two) { - int start = findWordStart(text, one); - int end = findWordEnd(text, one); - - if (end == start) { - return false; - } + private boolean onTouchEventCursor(TextView widget, Spannable buffer, MotionEvent event) { + if (widget.isFocused() && !widget.didTouchFocusSelect()) { + switch (event.getActionMasked()) { + case MotionEvent.ACTION_MOVE: + widget.cancelLongPress(); - return start == findWordStart(text, two) && - end == findWordEnd(text, two); - } + // Offset the current touch position (from controller to cursor) + final float x = event.getX() + mCursorController.getOffsetX(); + final float y = event.getY() + mCursorController.getOffsetY(); + mCursorController.updatePosition((int) x, (int) y); + return true; - // TODO: Unify with TextView.getWordForDictionary() - private static int findWordStart(CharSequence text, int start) { - for (; start > 0; start--) { - char c = text.charAt(start - 1); - int type = Character.getType(c); - - if (c != '\'' && - type != Character.UPPERCASE_LETTER && - type != Character.LOWERCASE_LETTER && - type != Character.TITLECASE_LETTER && - type != Character.MODIFIER_LETTER && - type != Character.DECIMAL_DIGIT_NUMBER) { - break; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + mCursorController = null; + return true; } } - - return start; + return false; } - // TODO: Unify with TextView.getWordForDictionary() - private static int findWordEnd(CharSequence text, int end) { - int len = text.length(); - - for (; end < len; end++) { - char c = text.charAt(end); - int type = Character.getType(c); - - if (c != '\'' && - type != Character.UPPERCASE_LETTER && - type != Character.LOWERCASE_LETTER && - type != Character.TITLECASE_LETTER && - type != Character.MODIFIER_LETTER && - type != Character.DECIMAL_DIGIT_NUMBER) { - break; - } - } - - return end; + /** + * Defines the cursor controller. + * + * When set, this object can be used to handle touch events, that can be translated into cursor + * updates. + * + * {@link MotionEvent#ACTION_MOVE} events will call back the + * {@link CursorController#updatePosition(int, int)} controller's method, passing the current + * finger coordinates (offset by {@link CursorController#getOffsetX()} and + * {@link CursorController#getOffsetY()}) as parameters. + * + * When the gesture is finished (on a {@link MotionEvent#ACTION_UP} or + * {@link MotionEvent#ACTION_CANCEL} event), the controller is reset to null. + * + * @param cursorController A cursor controller implementation + */ + public void setCursorController(CursorController cursorController) { + mCursorController = cursorController; } public boolean canSelectArbitrarily() { @@ -499,25 +329,9 @@ implements MovementMethod public void onTakeFocus(TextView view, Spannable text, int dir) { if ((dir & (View.FOCUS_FORWARD | View.FOCUS_DOWN)) != 0) { - Layout layout = view.getLayout(); - - if (layout == null) { - /* - * This shouldn't be null, but do something sensible if it is. - */ + if (view.getLayout() == null) { + // This shouldn't be null, but do something sensible if it is. Selection.setSelection(text, text.length()); - } else { - /* - * Put the cursor at the end of the first line, which is - * either the last offset if there is only one line, or the - * offset before the first character of the second line - * if there is more than one line. - */ - if (layout.getLineCount() == 1) { - Selection.setSelection(text, text.length()); - } else { - Selection.setSelection(text, layout.getLineStart(1) - 1); - } } } else { Selection.setSelection(text, text.length()); @@ -525,8 +339,9 @@ implements MovementMethod } public static MovementMethod getInstance() { - if (sInstance == null) + if (sInstance == null) { sInstance = new ArrowKeyMovementMethod(); + } return sInstance; } diff --git a/core/java/android/text/method/TextKeyListener.java b/core/java/android/text/method/TextKeyListener.java index 5be2a4866c4f..09cbbb8069da 100644 --- a/core/java/android/text/method/TextKeyListener.java +++ b/core/java/android/text/method/TextKeyListener.java @@ -246,8 +246,10 @@ public class TextKeyListener extends BaseKeyListener implements SpanWatcher { private void initPrefs(Context context) { final ContentResolver contentResolver = context.getContentResolver(); mResolver = new WeakReference<ContentResolver>(contentResolver); - mObserver = new SettingsObserver(); - contentResolver.registerContentObserver(Settings.System.CONTENT_URI, true, mObserver); + if (mObserver == null) { + mObserver = new SettingsObserver(); + contentResolver.registerContentObserver(Settings.System.CONTENT_URI, true, mObserver); + } updatePrefs(contentResolver); mPrefsInited = true; diff --git a/core/java/android/text/method/Touch.java b/core/java/android/text/method/Touch.java index 42ad10e706ef..a19a78e46988 100644 --- a/core/java/android/text/method/Touch.java +++ b/core/java/android/text/method/Touch.java @@ -17,14 +17,13 @@ package android.text.method; import android.text.Layout; -import android.text.NoCopySpan; import android.text.Layout.Alignment; +import android.text.NoCopySpan; import android.text.Spannable; -import android.util.Log; +import android.view.KeyEvent; import android.view.MotionEvent; import android.view.ViewConfiguration; import android.widget.TextView; -import android.view.KeyEvent; public class Touch { private Touch() { } @@ -99,7 +98,7 @@ public class Touch { MotionEvent event) { DragState[] ds; - switch (event.getAction()) { + switch (event.getActionMasked()) { case MotionEvent.ACTION_DOWN: ds = buffer.getSpans(0, buffer.length(), DragState.class); diff --git a/core/java/android/text/util/Rfc822Tokenizer.java b/core/java/android/text/util/Rfc822Tokenizer.java index 952d8339a29e..69cf93cf35fc 100644 --- a/core/java/android/text/util/Rfc822Tokenizer.java +++ b/core/java/android/text/util/Rfc822Tokenizer.java @@ -84,8 +84,10 @@ public class Rfc822Tokenizer implements MultiAutoCompleteTextView.Tokenizer { if (c == '"') { i++; break; - } else if (c == '\\' && i + 1 < cursor) { - name.append(text.charAt(i + 1)); + } else if (c == '\\') { + if (i + 1 < cursor) { + name.append(text.charAt(i + 1)); + } i += 2; } else { name.append(c); @@ -110,8 +112,10 @@ public class Rfc822Tokenizer implements MultiAutoCompleteTextView.Tokenizer { comment.append(c); level++; i++; - } else if (c == '\\' && i + 1 < cursor) { - comment.append(text.charAt(i + 1)); + } else if (c == '\\') { + if (i + 1 < cursor) { + comment.append(text.charAt(i + 1)); + } i += 2; } else { comment.append(c); diff --git a/core/java/android/util/DisplayMetrics.java b/core/java/android/util/DisplayMetrics.java index 2628eb4026d5..76d81065f2e5 100644 --- a/core/java/android/util/DisplayMetrics.java +++ b/core/java/android/util/DisplayMetrics.java @@ -135,6 +135,7 @@ public class DisplayMetrics { int screenLayout) { boolean expandable = compatibilityInfo.isConfiguredExpandable(); boolean largeScreens = compatibilityInfo.isConfiguredLargeScreens(); + boolean xlargeScreens = compatibilityInfo.isConfiguredXLargeScreens(); // Note: this assume that configuration is updated before calling // updateMetrics method. @@ -157,8 +158,18 @@ public class DisplayMetrics { compatibilityInfo.setLargeScreens(false); } } + if (!xlargeScreens) { + if ((screenLayout&Configuration.SCREENLAYOUT_SIZE_MASK) + != Configuration.SCREENLAYOUT_SIZE_XLARGE) { + xlargeScreens = true; + // the current screen size is not large. + compatibilityInfo.setXLargeScreens(true); + } else { + compatibilityInfo.setXLargeScreens(false); + } + } - if (!expandable || !largeScreens) { + if (!expandable || (!largeScreens && !xlargeScreens)) { // This is a larger screen device and the app is not // compatible with large screens, so diddle it. diff --git a/core/java/android/util/Log.java b/core/java/android/util/Log.java index e1116694e4a6..d577b74d5d2b 100644 --- a/core/java/android/util/Log.java +++ b/core/java/android/util/Log.java @@ -88,6 +88,21 @@ public final class Log { TerribleFailure(String msg, Throwable cause) { super(msg, cause); } } + /** + * Interface to handle terrible failures from {@link #wtf()}. + * + * @hide + */ + public interface TerribleFailureHandler { + void onTerribleFailure(String tag, TerribleFailure what); + } + + private static TerribleFailureHandler sWtfHandler = new TerribleFailureHandler() { + public void onTerribleFailure(String tag, TerribleFailure what) { + RuntimeInit.wtf(tag, what); + } + }; + private Log() { } @@ -257,13 +272,29 @@ public final class Log { * @param tr An exception to log. May be null. */ public static int wtf(String tag, String msg, Throwable tr) { - tr = new TerribleFailure(msg, tr); + TerribleFailure what = new TerribleFailure(msg, tr); int bytes = println_native(LOG_ID_MAIN, ASSERT, tag, getStackTraceString(tr)); - RuntimeInit.wtf(tag, tr); + sWtfHandler.onTerribleFailure(tag, what); return bytes; } /** + * Sets the terrible failure handler, for testing. + * + * @return the old handler + * + * @hide + */ + public static TerribleFailureHandler setWtfHandler(TerribleFailureHandler handler) { + if (handler == null) { + throw new NullPointerException("handler == null"); + } + TerribleFailureHandler oldHandler = sWtfHandler; + sWtfHandler = handler; + return oldHandler; + } + + /** * Handy function to get a loggable stack trace from a Throwable * @param tr An exception to log */ diff --git a/core/java/android/util/TimeUtils.java b/core/java/android/util/TimeUtils.java index 0fc70d52b878..60ca3849d6f9 100644 --- a/core/java/android/util/TimeUtils.java +++ b/core/java/android/util/TimeUtils.java @@ -24,6 +24,7 @@ import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; +import java.io.PrintWriter; import java.util.TimeZone; import java.util.Date; @@ -130,4 +131,153 @@ public class TimeUtils { public static String getTimeZoneDatabaseVersion() { return ZoneInfoDB.getVersion(); } + + /** @hide Field length that can hold 999 days of time */ + public static final int HUNDRED_DAY_FIELD_LEN = 19; + + private static final int SECONDS_PER_MINUTE = 60; + private static final int SECONDS_PER_HOUR = 60 * 60; + private static final int SECONDS_PER_DAY = 24 * 60 * 60; + + private static final Object sFormatSync = new Object(); + private static char[] sFormatStr = new char[HUNDRED_DAY_FIELD_LEN+5]; + + static private int accumField(int amt, int suffix, boolean always, int zeropad) { + if (amt > 99 || (always && zeropad >= 3)) { + return 3+suffix; + } + if (amt > 9 || (always && zeropad >= 2)) { + return 2+suffix; + } + if (always || amt > 0) { + return 1+suffix; + } + return 0; + } + + static private int printField(char[] formatStr, int amt, char suffix, int pos, + boolean always, int zeropad) { + if (always || amt > 0) { + if ((always && zeropad >= 3) || amt > 99) { + int dig = amt/100; + formatStr[pos] = (char)(dig + '0'); + pos++; + always = true; + amt -= (dig*100); + } + if ((always && zeropad >= 2) || amt > 9) { + int dig = amt/10; + formatStr[pos] = (char)(dig + '0'); + pos++; + always = true; + amt -= (dig*10); + } + formatStr[pos] = (char)(amt + '0'); + pos++; + formatStr[pos] = suffix; + pos++; + } + return pos; + } + + private static int formatDurationLocked(long duration, int fieldLen) { + if (sFormatStr.length < fieldLen) { + sFormatStr = new char[fieldLen]; + } + + char[] formatStr = sFormatStr; + + if (duration == 0) { + int pos = 0; + fieldLen -= 1; + while (pos < fieldLen) { + formatStr[pos] = ' '; + } + formatStr[pos] = '0'; + return pos+1; + } + + char prefix; + if (duration > 0) { + prefix = '+'; + } else { + prefix = '-'; + duration = -duration; + } + + int millis = (int)(duration%1000); + int seconds = (int) Math.floor(duration / 1000); + int days = 0, hours = 0, minutes = 0; + + if (seconds > SECONDS_PER_DAY) { + days = seconds / SECONDS_PER_DAY; + seconds -= days * SECONDS_PER_DAY; + } + if (seconds > SECONDS_PER_HOUR) { + hours = seconds / SECONDS_PER_HOUR; + seconds -= hours * SECONDS_PER_HOUR; + } + if (seconds > SECONDS_PER_MINUTE) { + minutes = seconds / SECONDS_PER_MINUTE; + seconds -= minutes * SECONDS_PER_MINUTE; + } + + int pos = 0; + + if (fieldLen != 0) { + int myLen = accumField(days, 1, false, 0); + myLen += accumField(hours, 1, myLen > 0, 2); + myLen += accumField(minutes, 1, myLen > 0, 2); + myLen += accumField(seconds, 1, myLen > 0, 2); + myLen += accumField(millis, 2, true, myLen > 0 ? 3 : 0) + 1; + while (myLen < fieldLen) { + formatStr[pos] = ' '; + pos++; + myLen++; + } + } + + formatStr[pos] = prefix; + pos++; + + int start = pos; + boolean zeropad = fieldLen != 0; + pos = printField(formatStr, days, 'd', pos, false, 0); + pos = printField(formatStr, hours, 'h', pos, pos != start, zeropad ? 2 : 0); + pos = printField(formatStr, minutes, 'm', pos, pos != start, zeropad ? 2 : 0); + pos = printField(formatStr, seconds, 's', pos, pos != start, zeropad ? 2 : 0); + pos = printField(formatStr, millis, 'm', pos, true, (zeropad && pos != start) ? 3 : 0); + formatStr[pos] = 's'; + return pos + 1; + } + + /** @hide Just for debugging; not internationalized. */ + public static void formatDuration(long duration, StringBuilder builder) { + synchronized (sFormatSync) { + int len = formatDurationLocked(duration, 0); + builder.append(sFormatStr, 0, len); + } + } + + /** @hide Just for debugging; not internationalized. */ + public static void formatDuration(long duration, PrintWriter pw, int fieldLen) { + synchronized (sFormatSync) { + int len = formatDurationLocked(duration, fieldLen); + pw.print(new String(sFormatStr, 0, len)); + } + } + + /** @hide Just for debugging; not internationalized. */ + public static void formatDuration(long duration, PrintWriter pw) { + formatDuration(duration, pw, 0); + } + + /** @hide Just for debugging; not internationalized. */ + public static void formatDuration(long time, long now, PrintWriter pw) { + if (time == 0) { + pw.print("--"); + return; + } + formatDuration(time-now, pw, 0); + } } diff --git a/core/java/android/view/AbsSavedState.java b/core/java/android/view/AbsSavedState.java index 840d7c170bd8..6ad33dd25d45 100644 --- a/core/java/android/view/AbsSavedState.java +++ b/core/java/android/view/AbsSavedState.java @@ -54,7 +54,7 @@ public abstract class AbsSavedState implements Parcelable { */ protected AbsSavedState(Parcel source) { // FIXME need class loader - Parcelable superState = (Parcelable) source.readParcelable(null); + Parcelable superState = source.readParcelable(null); mSuperState = superState != null ? superState : EMPTY_STATE; } @@ -75,7 +75,7 @@ public abstract class AbsSavedState implements Parcelable { = new Parcelable.Creator<AbsSavedState>() { public AbsSavedState createFromParcel(Parcel in) { - Parcelable superState = (Parcelable) in.readParcelable(null); + Parcelable superState = in.readParcelable(null); if (superState != null) { throw new IllegalStateException("superState must be null"); } diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java index fabe5c893335..34d7935dd2f9 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -27,7 +27,8 @@ public class Display /** - * Use the WindowManager interface to create a Display object. + * Use {@link android.view.WindowManager#getDefaultDisplay() + * WindowManager.getDefaultDisplay()} to create a Display object. * Display gives you access to some information about a particular display * connected to the device. */ diff --git a/core/java/android/view/IWindow.aidl b/core/java/android/view/IWindow.aidl index 3b0980875e42..921018a899ad 100644 --- a/core/java/android/view/IWindow.aidl +++ b/core/java/android/view/IWindow.aidl @@ -46,9 +46,6 @@ oneway interface IWindow { void resized(int w, int h, in Rect coveredInsets, in Rect visibleInsets, boolean reportDraw, in Configuration newConfig); - void dispatchKey(in KeyEvent event); - void dispatchPointer(in MotionEvent event, long eventTime, boolean callWhenDone); - void dispatchTrackball(in MotionEvent event, long eventTime, boolean callWhenDone); void dispatchAppVisibility(boolean visible); void dispatchGetNewSurface(); diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index 9b7b2f40fd3c..d4dd05c19adc 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -26,7 +26,10 @@ import android.view.IOnKeyguardExitResult; import android.view.IRotationWatcher; import android.view.IWindowSession; import android.view.KeyEvent; +import android.view.InputEvent; import android.view.MotionEvent; +import android.view.InputChannel; +import android.view.InputDevice; /** * System private interface to the window manager. @@ -50,10 +53,13 @@ interface IWindowManager boolean inputMethodClientHasFocus(IInputMethodClient client); // These can only be called when injecting events to your own window, - // or by holding the INJECT_EVENTS permission. + // or by holding the INJECT_EVENTS permission. These methods may block + // until pending input events are finished being dispatched even when 'sync' is false. + // Avoid calling these methods on your UI thread or use the 'NoWait' version instead. boolean injectKeyEvent(in KeyEvent ev, boolean sync); boolean injectPointerEvent(in MotionEvent ev, boolean sync); boolean injectTrackballEvent(in MotionEvent ev, boolean sync); + boolean injectInputEventNoWait(in InputEvent ev); // These can only be called when holding the MANAGE_APP_TOKENS permission. void pauseKeyDispatching(IBinder token); @@ -115,10 +121,15 @@ interface IWindowManager int getKeycodeStateForDevice(int devid, int sw); int getTrackballKeycodeState(int sw); int getDPadKeycodeState(int sw); + InputChannel monitorInput(String inputChannelName); // Report whether the hardware supports the given keys; returns true if successful boolean hasKeys(in int[] keycodes, inout boolean[] keyExists); + // Get input device information. + InputDevice getInputDevice(int deviceId); + int[] getInputDeviceIds(); + // For testing void setInTouchMode(boolean showFocus); diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl index 01f07d694eb1..7f10b7686dd7 100644 --- a/core/java/android/view/IWindowSession.aidl +++ b/core/java/android/view/IWindowSession.aidl @@ -21,6 +21,7 @@ import android.content.res.Configuration; import android.graphics.Rect; import android.graphics.Region; import android.os.Bundle; +import android.view.InputChannel; import android.view.IWindow; import android.view.MotionEvent; import android.view.WindowManager; @@ -33,6 +34,9 @@ import android.view.Surface; */ interface IWindowSession { int add(IWindow window, in WindowManager.LayoutParams attrs, + in int viewVisibility, out Rect outContentInsets, + out InputChannel outInputChannel); + int addWithoutInputChannel(IWindow window, in WindowManager.LayoutParams attrs, in int viewVisibility, out Rect outContentInsets); void remove(IWindow window); @@ -105,10 +109,6 @@ interface IWindowSession { void getDisplayFrame(IWindow window, out Rect outDisplayFrame); void finishDrawing(IWindow window); - - void finishKey(IWindow window); - MotionEvent getPendingPointerMove(IWindow window); - MotionEvent getPendingTrackballMove(IWindow window); void setInTouchMode(boolean showFocus); boolean getInTouchMode(); diff --git a/core/java/android/hardware/ISensorService.aidl b/core/java/android/view/InputChannel.aidl index 67180bd90064..74c0aa463239 100644 --- a/core/java/android/hardware/ISensorService.aidl +++ b/core/java/android/view/InputChannel.aidl @@ -1,6 +1,6 @@ -/* //device/java/android/android/hardware/ISensorService.aidl +/* //device/java/android/android/view/InputChannel.aidl ** -** Copyright 2008, The Android Open Source Project +** Copyright 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. @@ -15,15 +15,6 @@ ** limitations under the License. */ -package android.hardware; +package android.view; -import android.os.Bundle; - -/** - * {@hide} - */ -interface ISensorService -{ - Bundle getDataChannel(); - boolean enableSensor(IBinder listener, String name, int sensor, int enable); -} +parcelable InputChannel; diff --git a/core/java/android/view/InputChannel.java b/core/java/android/view/InputChannel.java new file mode 100644 index 000000000000..f2cad2f94d1d --- /dev/null +++ b/core/java/android/view/InputChannel.java @@ -0,0 +1,157 @@ +/* + * 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 android.view; + +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Slog; + +/** + * An input channel specifies the file descriptors used to send input events to + * a window in another process. It is Parcelable so that it can be sent + * to the process that is to receive events. Only one thread should be reading + * from an InputChannel at a time. + * @hide + */ +public final class InputChannel implements Parcelable { + private static final String TAG = "InputChannel"; + + private static final boolean DEBUG = false; + + public static final Parcelable.Creator<InputChannel> CREATOR + = new Parcelable.Creator<InputChannel>() { + public InputChannel createFromParcel(Parcel source) { + InputChannel result = new InputChannel(); + result.readFromParcel(source); + return result; + } + + public InputChannel[] newArray(int size) { + return new InputChannel[size]; + } + }; + + @SuppressWarnings("unused") + private int mPtr; // used by native code + + private boolean mDisposeAfterWriteToParcel; + + private static native InputChannel[] nativeOpenInputChannelPair(String name); + + private native void nativeDispose(boolean finalized); + private native void nativeTransferTo(InputChannel other); + private native void nativeReadFromParcel(Parcel parcel); + private native void nativeWriteToParcel(Parcel parcel); + + private native String nativeGetName(); + + /** + * Creates an uninitialized input channel. + * It can be initialized by reading from a Parcel or by transferring the state of + * another input channel into this one. + */ + public InputChannel() { + } + + @Override + protected void finalize() throws Throwable { + try { + nativeDispose(true); + } finally { + super.finalize(); + } + } + + /** + * Creates a new input channel pair. One channel should be provided to the input + * dispatcher and the other to the application's input queue. + * @param name The descriptive (non-unique) name of the channel pair. + * @return A pair of input channels. They are symmetric and indistinguishable. + */ + public static InputChannel[] openInputChannelPair(String name) { + if (name == null) { + throw new IllegalArgumentException("name must not be null"); + } + + if (DEBUG) { + Slog.d(TAG, "Opening input channel pair '" + name + "'"); + } + return nativeOpenInputChannelPair(name); + } + + /** + * Gets the name of the input channel. + * @return The input channel name. + */ + public String getName() { + String name = nativeGetName(); + return name != null ? name : "uninitialized"; + } + + /** + * Disposes the input channel. + * Explicitly releases the reference this object is holding on the input channel. + * When all references are released, the input channel will be closed. + */ + public void dispose() { + nativeDispose(false); + } + + /** + * Transfers ownership of the internal state of the input channel to another + * instance and invalidates this instance. This is used to pass an input channel + * as an out parameter in a binder call. + * @param other The other input channel instance. + */ + public void transferToBinderOutParameter(InputChannel outParameter) { + if (outParameter == null) { + throw new IllegalArgumentException("outParameter must not be null"); + } + + nativeTransferTo(outParameter); + outParameter.mDisposeAfterWriteToParcel = true; + } + + public int describeContents() { + return Parcelable.CONTENTS_FILE_DESCRIPTOR; + } + + public void readFromParcel(Parcel in) { + if (in == null) { + throw new IllegalArgumentException("in must not be null"); + } + + nativeReadFromParcel(in); + } + + public void writeToParcel(Parcel out, int flags) { + if (out == null) { + throw new IllegalArgumentException("out must not be null"); + } + + nativeWriteToParcel(out); + + if (mDisposeAfterWriteToParcel) { + dispose(); + } + } + + @Override + public String toString() { + return getName(); + } +} diff --git a/core/res/res/drawable/stat_sys_gps_acquiring_anim.xml b/core/java/android/view/InputDevice.aidl index 954c19ce10eb..dbc40c131642 100644 --- a/core/res/res/drawable/stat_sys_gps_acquiring_anim.xml +++ b/core/java/android/view/InputDevice.aidl @@ -1,8 +1,6 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -/* +/* //device/java/android/android.view.InputDevice.aidl ** -** Copyright 2008, The Android Open Source Project +** Copyright 2007, 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. @@ -16,10 +14,7 @@ ** See the License for the specific language governing permissions and ** limitations under the License. */ ---> -<animation-list - xmlns:android="http://schemas.android.com/apk/res/android" - android:oneshot="false"> - <item android:drawable="@drawable/stat_sys_gps_acquiring" android:duration="500" /> - <item android:drawable="@drawable/stat_sys_gps_on" android:duration="500" /> -</animation-list> + +package android.view; + +parcelable InputDevice; diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java new file mode 100755 index 000000000000..7468579cb9ae --- /dev/null +++ b/core/java/android/view/InputDevice.java @@ -0,0 +1,551 @@ +/* + * 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 android.view; + +import android.os.Parcel; +import android.os.Parcelable; +import android.os.RemoteException; +import android.os.ServiceManager; + +/** + * Describes the capabilities of a particular input device. + * <p> + * Each input device may support multiple classes of input. For example, a multifunction + * keyboard may compose the capabilities of a standard keyboard together with a track pad mouse + * or other pointing device. + * </p><p> + * Some input devices present multiple distinguishable sources of input. For example, a + * game pad may have two analog joysticks, a directional pad and a full complement of buttons. + * Applications can query the framework about the characteristics of each distinct source. + * </p><p> + * As a further wrinkle, different kinds of input sources uses different coordinate systems + * to describe motion events. Refer to the comments on the input source constants for + * the appropriate interpretation. + * </p> + */ +public final class InputDevice implements Parcelable { + private int mId; + private String mName; + private int mSources; + private int mKeyboardType; + + private MotionRange[] mMotionRanges; + + /** + * A mask for input source classes. + * + * Each distinct input source constant has one or more input source class bits set to + * specify the desired interpretation for its input events. + */ + public static final int SOURCE_CLASS_MASK = 0x000000ff; + + /** + * The input source has buttons or keys. + * Examples: {@link #SOURCE_KEYBOARD}, {@link #SOURCE_GAMEPAD}, {@link #SOURCE_DPAD}. + * + * A {@link KeyEvent} should be interpreted as a button or key press. + * + * Use {@link #getKeyCharacterMap} to query the device's button and key mappings. + */ + public static final int SOURCE_CLASS_BUTTON = 0x00000001; + + /** + * The input source is a pointing device associated with a display. + * Examples: {@link #SOURCE_TOUCHSCREEN}, {@link #SOURCE_MOUSE}. + * + * A {@link MotionEvent} should be interpreted as absolute coordinates in + * display units according to the {@link View} hierarchy. Pointer down/up indicated when + * the finger touches the display or when the selection button is pressed/released. + * + * Use {@link #getMotionRange} to query the range of the pointing device. Some devices permit + * touches outside the display area so the effective range may be somewhat smaller or larger + * than the actual display size. + */ + public static final int SOURCE_CLASS_POINTER = 0x00000002; + + /** + * The input source is a trackball navigation device. + * Examples: {@link #SOURCE_TRACKBALL}. + * + * A {@link MotionEvent} should be interpreted as relative movements in device-specific + * units used for navigation purposes. Pointer down/up indicates when the selection button + * is pressed/released. + * + * Use {@link #getMotionRange} to query the range of motion. + */ + public static final int SOURCE_CLASS_TRACKBALL = 0x00000004; + + /** + * The input source is an absolute positioning device not associated with a display + * (unlike {@link #SOURCE_CLASS_POINTER}). + * + * A {@link MotionEvent} should be interpreted as absolute coordinates in + * device-specific surface units. + * + * Use {@link #getMotionRange} to query the range of positions. + */ + public static final int SOURCE_CLASS_POSITION = 0x00000008; + + /** + * The input source is a joystick. + * + * A {@link KeyEvent} should be interpreted as a joystick button press. + * + * A {@link MotionEvent} should be interpreted in absolute coordinates as a joystick + * position in normalized device-specific units nominally between -1.0 and 1.0. + * + * Use {@link #getMotionRange} to query the range and precision of motion. + */ + public static final int SOURCE_CLASS_JOYSTICK = 0x00000010; + + /** + * The input source is unknown. + */ + public static final int SOURCE_UNKNOWN = 0x00000000; + + /** + * The input source is a keyboard. + * + * @see #SOURCE_CLASS_BUTTON + */ + public static final int SOURCE_KEYBOARD = 0x00000100 | SOURCE_CLASS_BUTTON; + + /** + * The input source is a DPad. + * + * @see #SOURCE_CLASS_BUTTON + */ + public static final int SOURCE_DPAD = 0x00000200 | SOURCE_CLASS_BUTTON; + + /** + * The input source is a gamepad. + * + * @see #SOURCE_CLASS_BUTTON + */ + public static final int SOURCE_GAMEPAD = 0x00000400 | SOURCE_CLASS_BUTTON; + + /** + * The input source is a touch screen pointing device. + * + * @see #SOURCE_CLASS_POINTER + */ + public static final int SOURCE_TOUCHSCREEN = 0x00001000 | SOURCE_CLASS_POINTER; + + /** + * The input source is a mouse pointing device. + * This code is also used for other mouse-like pointing devices such as trackpads + * and trackpoints. + * + * @see #SOURCE_CLASS_POINTER + */ + public static final int SOURCE_MOUSE = 0x00002000 | SOURCE_CLASS_POINTER; + + /** + * The input source is a trackball. + * + * @see #SOURCE_CLASS_TRACKBALL + */ + public static final int SOURCE_TRACKBALL = 0x00010000 | SOURCE_CLASS_TRACKBALL; + + /** + * The input source is a touch pad or digitizer tablet that is not + * associated with a display (unlike {@link #SOURCE_TOUCHSCREEN}). + * + * @see #SOURCE_CLASS_POSITION + */ + public static final int SOURCE_TOUCHPAD = 0x00100000 | SOURCE_CLASS_POSITION; + + /** + * The input source is a joystick mounted on the left or is a standalone joystick. + * + * @see #SOURCE_CLASS_JOYSTICK + */ + public static final int SOURCE_JOYSTICK_LEFT = 0x01000000 | SOURCE_CLASS_JOYSTICK; + + /** + * The input source is a joystick mounted on the right. + * + * @see #SOURCE_CLASS_JOYSTICK + */ + public static final int SOURCE_JOYSTICK_RIGHT = 0x02000000 | SOURCE_CLASS_JOYSTICK; + + /** + * A special input source constant that is used when filtering input devices + * to match devices that provide any type of input source. + */ + public static final int SOURCE_ANY = 0xffffff00; + + /** + * Constant for retrieving the range of values for {@link MotionEvent.PointerCoords#x}. + * + * @see #getMotionRange + */ + public static final int MOTION_RANGE_X = 0; + + /** + * Constant for retrieving the range of values for {@link MotionEvent.PointerCoords#y}. + * + * @see #getMotionRange + */ + public static final int MOTION_RANGE_Y = 1; + + /** + * Constant for retrieving the range of values for {@link MotionEvent.PointerCoords#pressure}. + * + * @see #getMotionRange + */ + public static final int MOTION_RANGE_PRESSURE = 2; + + /** + * Constant for retrieving the range of values for {@link MotionEvent.PointerCoords#size}. + * + * @see #getMotionRange + */ + public static final int MOTION_RANGE_SIZE = 3; + + /** + * Constant for retrieving the range of values for {@link MotionEvent.PointerCoords#touchMajor}. + * + * @see #getMotionRange + */ + public static final int MOTION_RANGE_TOUCH_MAJOR = 4; + + /** + * Constant for retrieving the range of values for {@link MotionEvent.PointerCoords#touchMinor}. + * + * @see #getMotionRange + */ + public static final int MOTION_RANGE_TOUCH_MINOR = 5; + + /** + * Constant for retrieving the range of values for {@link MotionEvent.PointerCoords#toolMajor}. + * + * @see #getMotionRange + */ + public static final int MOTION_RANGE_TOOL_MAJOR = 6; + + /** + * Constant for retrieving the range of values for {@link MotionEvent.PointerCoords#toolMinor}. + * + * @see #getMotionRange + */ + public static final int MOTION_RANGE_TOOL_MINOR = 7; + + /** + * Constant for retrieving the range of values for + * {@link MotionEvent.PointerCoords#orientation}. + * + * @see #getMotionRange + */ + public static final int MOTION_RANGE_ORIENTATION = 8; + + private static final int MOTION_RANGE_LAST = MOTION_RANGE_ORIENTATION; + + /** + * There is no keyboard. + */ + public static final int KEYBOARD_TYPE_NONE = 0; + + /** + * The keyboard is not fully alphabetic. It may be a numeric keypad or an assortment + * of buttons that are not mapped as alphabetic keys suitable for text input. + */ + public static final int KEYBOARD_TYPE_NON_ALPHABETIC = 1; + + /** + * The keyboard supports a complement of alphabetic keys. + */ + public static final int KEYBOARD_TYPE_ALPHABETIC = 2; + + // Called by native code. + private InputDevice() { + mMotionRanges = new MotionRange[MOTION_RANGE_LAST + 1]; + } + + /** + * Gets information about the input device with the specified id. + * @param id The device id. + * @return The input device or null if not found. + */ + public static InputDevice getDevice(int id) { + IWindowManager wm = IWindowManager.Stub.asInterface(ServiceManager.getService("window")); + try { + return wm.getInputDevice(id); + } catch (RemoteException ex) { + throw new RuntimeException( + "Could not get input device information from Window Manager.", ex); + } + } + + /** + * Gets the ids of all input devices in the system. + * @return The input device ids. + */ + public static int[] getDeviceIds() { + IWindowManager wm = IWindowManager.Stub.asInterface(ServiceManager.getService("window")); + try { + return wm.getInputDeviceIds(); + } catch (RemoteException ex) { + throw new RuntimeException( + "Could not get input device ids from Window Manager.", ex); + } + } + + /** + * Gets the input device id. + * @return The input device id. + */ + public int getId() { + return mId; + } + + /** + * Gets the name of this input device. + * @return The input device name. + */ + public String getName() { + return mName; + } + + /** + * Gets the input sources supported by this input device as a combined bitfield. + * @return The supported input sources. + */ + public int getSources() { + return mSources; + } + + /** + * Gets the keyboard type. + * @return The keyboard type. + */ + public int getKeyboardType() { + return mKeyboardType; + } + + /** + * Gets the key character map associated with this input device. + * @return The key character map. + */ + public KeyCharacterMap getKeyCharacterMap() { + return KeyCharacterMap.load(mId); + } + + /** + * Gets information about the range of values for a particular {@link MotionEvent} + * coordinate. + * @param rangeType The motion range constant. + * @return The range of values, or null if the requested coordinate is not + * supported by the device. + */ + public MotionRange getMotionRange(int rangeType) { + if (rangeType < 0 || rangeType > MOTION_RANGE_LAST) { + throw new IllegalArgumentException("Requested range is out of bounds."); + } + + return mMotionRanges[rangeType]; + } + + private void addMotionRange(int rangeType, float min, float max, float flat, float fuzz) { + if (rangeType >= 0 && rangeType <= MOTION_RANGE_LAST) { + MotionRange range = new MotionRange(min, max, flat, fuzz); + mMotionRanges[rangeType] = range; + } + } + + /** + * Provides information about the range of values for a particular {@link MotionEvent} + * coordinate. + */ + public static final class MotionRange { + private float mMin; + private float mMax; + private float mFlat; + private float mFuzz; + + private MotionRange(float min, float max, float flat, float fuzz) { + mMin = min; + mMax = max; + mFlat = flat; + mFuzz = fuzz; + } + + /** + * Gets the minimum value for the coordinate. + * @return The minimum value. + */ + public float getMin() { + return mMin; + } + + /** + * Gets the maximum value for the coordinate. + * @return The minimum value. + */ + public float getMax() { + return mMax; + } + + /** + * Gets the range of the coordinate (difference between maximum and minimum). + * @return The range of values. + */ + public float getRange() { + return mMax - mMin; + } + + /** + * Gets the extent of the center flat position with respect to this coordinate. + * For example, a flat value of 8 means that the center position is between -8 and +8. + * This value is mainly useful for calibrating joysticks. + * @return The extent of the center flat position. + */ + public float getFlat() { + return mFlat; + } + + /** + * Gets the error tolerance for input device measurements with respect to this coordinate. + * For example, a value of 2 indicates that the measured value may be up to +/- 2 units + * away from the actual value due to noise and device sensitivity limitations. + * @return The error tolerance. + */ + public float getFuzz() { + return mFuzz; + } + } + + public static final Parcelable.Creator<InputDevice> CREATOR + = new Parcelable.Creator<InputDevice>() { + public InputDevice createFromParcel(Parcel in) { + InputDevice result = new InputDevice(); + result.readFromParcel(in); + return result; + } + + public InputDevice[] newArray(int size) { + return new InputDevice[size]; + } + }; + + private void readFromParcel(Parcel in) { + mId = in.readInt(); + mName = in.readString(); + mSources = in.readInt(); + mKeyboardType = in.readInt(); + + for (;;) { + int rangeType = in.readInt(); + if (rangeType < 0) { + break; + } + + addMotionRange(rangeType, + in.readFloat(), in.readFloat(), in.readFloat(), in.readFloat()); + } + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(mId); + out.writeString(mName); + out.writeInt(mSources); + out.writeInt(mKeyboardType); + + for (int i = 0; i <= MOTION_RANGE_LAST; i++) { + MotionRange range = mMotionRanges[i]; + if (range != null) { + out.writeInt(i); + out.writeFloat(range.mMin); + out.writeFloat(range.mMax); + out.writeFloat(range.mFlat); + out.writeFloat(range.mFuzz); + } + } + out.writeInt(-1); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public String toString() { + StringBuilder description = new StringBuilder(); + description.append("Input Device ").append(mId).append(": ").append(mName).append("\n"); + + description.append(" Keyboard Type: "); + switch (mKeyboardType) { + case KEYBOARD_TYPE_NONE: + description.append("none"); + break; + case KEYBOARD_TYPE_NON_ALPHABETIC: + description.append("non-alphabetic"); + break; + case KEYBOARD_TYPE_ALPHABETIC: + description.append("alphabetic"); + break; + } + description.append("\n"); + + description.append(" Sources:"); + appendSourceDescriptionIfApplicable(description, SOURCE_KEYBOARD, "keyboard"); + appendSourceDescriptionIfApplicable(description, SOURCE_DPAD, "dpad"); + appendSourceDescriptionIfApplicable(description, SOURCE_GAMEPAD, "gamepad"); + appendSourceDescriptionIfApplicable(description, SOURCE_TOUCHSCREEN, "touchscreen"); + appendSourceDescriptionIfApplicable(description, SOURCE_MOUSE, "mouse"); + appendSourceDescriptionIfApplicable(description, SOURCE_TRACKBALL, "trackball"); + appendSourceDescriptionIfApplicable(description, SOURCE_TOUCHPAD, "touchpad"); + appendSourceDescriptionIfApplicable(description, SOURCE_JOYSTICK_LEFT, "joystick_left"); + appendSourceDescriptionIfApplicable(description, SOURCE_JOYSTICK_RIGHT, "joystick_right"); + description.append("\n"); + + appendRangeDescriptionIfApplicable(description, MOTION_RANGE_X, "x"); + appendRangeDescriptionIfApplicable(description, MOTION_RANGE_Y, "y"); + appendRangeDescriptionIfApplicable(description, MOTION_RANGE_PRESSURE, "pressure"); + appendRangeDescriptionIfApplicable(description, MOTION_RANGE_SIZE, "size"); + appendRangeDescriptionIfApplicable(description, MOTION_RANGE_TOUCH_MAJOR, "touchMajor"); + appendRangeDescriptionIfApplicable(description, MOTION_RANGE_TOUCH_MINOR, "touchMinor"); + appendRangeDescriptionIfApplicable(description, MOTION_RANGE_TOOL_MAJOR, "toolMajor"); + appendRangeDescriptionIfApplicable(description, MOTION_RANGE_TOOL_MINOR, "toolMinor"); + appendRangeDescriptionIfApplicable(description, MOTION_RANGE_ORIENTATION, "orientation"); + + return description.toString(); + } + + private void appendSourceDescriptionIfApplicable(StringBuilder description, int source, + String sourceName) { + if ((mSources & source) == source) { + description.append(" "); + description.append(sourceName); + } + } + + private void appendRangeDescriptionIfApplicable(StringBuilder description, + int rangeType, String rangeName) { + MotionRange range = mMotionRanges[rangeType]; + if (range != null) { + description.append(" Range[").append(rangeName); + description.append("]: min=").append(range.mMin); + description.append(" max=").append(range.mMax); + description.append(" flat=").append(range.mFlat); + description.append(" fuzz=").append(range.mFuzz); + description.append("\n"); + } + } +} diff --git a/core/res/res/drawable/stat_sys_roaming_cdma_flash.xml b/core/java/android/view/InputEvent.aidl index 07dc4465ee32..61acaaf1fa8d 100644 --- a/core/res/res/drawable/stat_sys_roaming_cdma_flash.xml +++ b/core/java/android/view/InputEvent.aidl @@ -1,8 +1,6 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -/* //device/apps/common/res/drawable/stat_sys_battery.xml +/* //device/java/android/android.view.InputEvent.aidl ** -** Copyright 2009, The Android Open Source Project +** Copyright 2007, 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. @@ -16,10 +14,7 @@ ** See the License for the specific language governing permissions and ** limitations under the License. */ ---> -<animation-list - xmlns:android="http://schemas.android.com/apk/res/android" - android:oneshot="false"> - <item android:drawable="@drawable/stat_sys_roaming_cdma_flash_anim0" android:duration="800" /> - <item android:drawable="@drawable/stat_sys_roaming_cdma_flash_anim1" android:duration="1200" /> -</animation-list> + +package android.view; + +parcelable InputEvent; diff --git a/core/java/android/view/InputEvent.java b/core/java/android/view/InputEvent.java new file mode 100755 index 000000000000..184e0fc8daba --- /dev/null +++ b/core/java/android/view/InputEvent.java @@ -0,0 +1,114 @@ +/* + * 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 android.view; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Common base class for input events. + */ +public abstract class InputEvent implements Parcelable { + /** @hide */ + protected int mDeviceId; + /** @hide */ + protected int mSource; + + /** @hide */ + protected static final int PARCEL_TOKEN_MOTION_EVENT = 1; + /** @hide */ + protected static final int PARCEL_TOKEN_KEY_EVENT = 2; + + /*package*/ InputEvent() { + } + + /** + * Gets the id for the device that this event came from. An id of + * zero indicates that the event didn't come from a physical device + * and maps to the default keymap. The other numbers are arbitrary and + * you shouldn't depend on the values. + * + * @return The device id. + * @see InputDevice#getDevice + */ + public final int getDeviceId() { + return mDeviceId; + } + + /** + * Gets the device that this event came from. + * + * @return The device, or null if unknown. + */ + public final InputDevice getDevice() { + return InputDevice.getDevice(mDeviceId); + } + + /** + * Gets the source of the event. + * + * @return The event source or {@link InputDevice#SOURCE_UNKNOWN} if unknown. + * @see InputDevice#getSourceInfo + */ + public final int getSource() { + return mSource; + } + + /** + * Modifies the source of the event. + * @param source The source. + * + * @hide + */ + public final void setSource(int source) { + mSource = source; + } + + public int describeContents() { + return 0; + } + + /** @hide */ + protected final void readBaseFromParcel(Parcel in) { + mDeviceId = in.readInt(); + mSource = in.readInt(); + } + + /** @hide */ + protected final void writeBaseToParcel(Parcel out) { + out.writeInt(mDeviceId); + out.writeInt(mSource); + } + + public static final Parcelable.Creator<InputEvent> CREATOR + = new Parcelable.Creator<InputEvent>() { + public InputEvent createFromParcel(Parcel in) { + int token = in.readInt(); + if (token == PARCEL_TOKEN_KEY_EVENT) { + return KeyEvent.createFromParcelBody(in); + } else if (token == PARCEL_TOKEN_MOTION_EVENT) { + return MotionEvent.createFromParcelBody(in); + } else { + throw new IllegalStateException("Unexpected input event type token in parcel."); + } + } + + public InputEvent[] newArray(int size) { + return new InputEvent[size]; + } + }; +} diff --git a/core/java/android/view/InputHandler.java b/core/java/android/view/InputHandler.java new file mode 100644 index 000000000000..41a152dd9577 --- /dev/null +++ b/core/java/android/view/InputHandler.java @@ -0,0 +1,43 @@ +/* + * 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 android.view; + +/** + * Handles input messages that arrive on an input channel. + * @hide + */ +public interface InputHandler { + /** + * Handle a key event. + * It is the responsibility of the callee to ensure that the finished callback is + * eventually invoked when the event processing is finished and the input system + * can send the next event. + * @param event The key event data. + * @param finishedCallback The callback to invoke when event processing is finished. + */ + public void handleKey(KeyEvent event, Runnable finishedCallback); + + /** + * Handle a motion event. + * It is the responsibility of the callee to ensure that the finished callback is + * eventually invoked when the event processing is finished and the input system + * can send the next event. + * @param event The motion event data. + * @param finishedCallback The callback to invoke when event processing is finished. + */ + public void handleMotion(MotionEvent event, Runnable finishedCallback); +} diff --git a/core/java/android/view/InputQueue.java b/core/java/android/view/InputQueue.java new file mode 100644 index 000000000000..43c957adebb6 --- /dev/null +++ b/core/java/android/view/InputQueue.java @@ -0,0 +1,167 @@ +/* + * 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 android.view; + +import android.os.MessageQueue; +import android.util.Slog; + +/** + * An input queue provides a mechanism for an application to receive incoming + * input events. Currently only usable from native code. + */ +public final class InputQueue { + private static final String TAG = "InputQueue"; + + private static final boolean DEBUG = false; + + public static interface Callback { + void onInputQueueCreated(InputQueue queue); + void onInputQueueDestroyed(InputQueue queue); + } + + final InputChannel mChannel; + + private static final Object sLock = new Object(); + + private static native void nativeRegisterInputChannel(InputChannel inputChannel, + InputHandler inputHandler, MessageQueue messageQueue); + private static native void nativeUnregisterInputChannel(InputChannel inputChannel); + private static native void nativeFinished(long finishedToken); + + /** @hide */ + public InputQueue(InputChannel channel) { + mChannel = channel; + } + + /** @hide */ + public InputChannel getInputChannel() { + return mChannel; + } + + /** + * Registers an input channel and handler. + * @param inputChannel The input channel to register. + * @param inputHandler The input handler to input events send to the target. + * @param messageQueue The message queue on whose thread the handler should be invoked. + * @hide + */ + public static void registerInputChannel(InputChannel inputChannel, InputHandler inputHandler, + MessageQueue messageQueue) { + if (inputChannel == null) { + throw new IllegalArgumentException("inputChannel must not be null"); + } + if (inputHandler == null) { + throw new IllegalArgumentException("inputHandler must not be null"); + } + if (messageQueue == null) { + throw new IllegalArgumentException("messageQueue must not be null"); + } + + synchronized (sLock) { + if (DEBUG) { + Slog.d(TAG, "Registering input channel '" + inputChannel + "'"); + } + + nativeRegisterInputChannel(inputChannel, inputHandler, messageQueue); + } + } + + /** + * Unregisters an input channel. + * Does nothing if the channel is not currently registered. + * @param inputChannel The input channel to unregister. + * @hide + */ + public static void unregisterInputChannel(InputChannel inputChannel) { + if (inputChannel == null) { + throw new IllegalArgumentException("inputChannel must not be null"); + } + + synchronized (sLock) { + if (DEBUG) { + Slog.d(TAG, "Unregistering input channel '" + inputChannel + "'"); + } + + nativeUnregisterInputChannel(inputChannel); + } + } + + @SuppressWarnings("unused") + private static void dispatchKeyEvent(InputHandler inputHandler, + KeyEvent event, long finishedToken) { + Runnable finishedCallback = FinishedCallback.obtain(finishedToken); + inputHandler.handleKey(event, finishedCallback); + } + + @SuppressWarnings("unused") + private static void dispatchMotionEvent(InputHandler inputHandler, + MotionEvent event, long finishedToken) { + Runnable finishedCallback = FinishedCallback.obtain(finishedToken); + inputHandler.handleMotion(event, finishedCallback); + } + + private static class FinishedCallback implements Runnable { + private static final boolean DEBUG_RECYCLING = false; + + private static final int RECYCLE_MAX_COUNT = 4; + + private static FinishedCallback sRecycleHead; + private static int sRecycleCount; + + private FinishedCallback mRecycleNext; + private long mFinishedToken; + + private FinishedCallback() { + } + + public static FinishedCallback obtain(long finishedToken) { + synchronized (sLock) { + FinishedCallback callback = sRecycleHead; + if (callback != null) { + sRecycleHead = callback.mRecycleNext; + sRecycleCount -= 1; + callback.mRecycleNext = null; + } else { + callback = new FinishedCallback(); + } + callback.mFinishedToken = finishedToken; + return callback; + } + } + + public void run() { + synchronized (sLock) { + if (mFinishedToken == -1) { + throw new IllegalStateException("Event finished callback already invoked."); + } + + nativeFinished(mFinishedToken); + mFinishedToken = -1; + + if (sRecycleCount < RECYCLE_MAX_COUNT) { + mRecycleNext = sRecycleHead; + sRecycleHead = this; + sRecycleCount += 1; + + if (DEBUG_RECYCLING) { + Slog.d(TAG, "Recycled finished callbacks: " + sRecycleCount); + } + } + } + } + } +} diff --git a/core/java/android/view/KeyCharacterMap.java b/core/java/android/view/KeyCharacterMap.java index 25958aa46314..9981d877ce02 100644 --- a/core/java/android/view/KeyCharacterMap.java +++ b/core/java/android/view/KeyCharacterMap.java @@ -26,6 +26,9 @@ import android.util.SparseArray; import java.lang.Character; import java.lang.ref.WeakReference; +/** + * Describes the keys provided by a device and their associated labels. + */ public class KeyCharacterMap { /** @@ -59,6 +62,11 @@ public class KeyCharacterMap private static SparseArray<WeakReference<KeyCharacterMap>> sInstances = new SparseArray<WeakReference<KeyCharacterMap>>(); + /** + * Loads the key character maps for the keyboard with the specified device id. + * @param keyboard The device id of the keyboard. + * @return The associated key character map. + */ public static KeyCharacterMap load(int keyboard) { synchronized (sLock) { diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java index d4f978756f4d..ed10e412ac51 100644..100755 --- a/core/java/android/view/KeyEvent.java +++ b/core/java/android/view/KeyEvent.java @@ -26,7 +26,7 @@ import android.view.KeyCharacterMap.KeyData; /** * Contains constants for key events. */ -public class KeyEvent implements Parcelable { +public class KeyEvent extends InputEvent implements Parcelable { // key codes public static final int KEYCODE_UNKNOWN = 0; public static final int KEYCODE_SOFT_LEFT = 1; @@ -120,10 +120,31 @@ public class KeyEvent implements Parcelable { public static final int KEYCODE_MEDIA_REWIND = 89; public static final int KEYCODE_MEDIA_FAST_FORWARD = 90; public static final int KEYCODE_MUTE = 91; + public static final int KEYCODE_PAGE_UP = 92; + public static final int KEYCODE_PAGE_DOWN = 93; + public static final int KEYCODE_PICTSYMBOLS = 94; // switch symbol-sets (Emoji,Kao-moji) + public static final int KEYCODE_SWITCH_CHARSET = 95; // switch char-sets (Kanji,Katakana) + public static final int KEYCODE_BUTTON_A = 96; + public static final int KEYCODE_BUTTON_B = 97; + public static final int KEYCODE_BUTTON_C = 98; + public static final int KEYCODE_BUTTON_X = 99; + public static final int KEYCODE_BUTTON_Y = 100; + public static final int KEYCODE_BUTTON_Z = 101; + public static final int KEYCODE_BUTTON_L1 = 102; + public static final int KEYCODE_BUTTON_R1 = 103; + public static final int KEYCODE_BUTTON_L2 = 104; + public static final int KEYCODE_BUTTON_R2 = 105; + public static final int KEYCODE_BUTTON_THUMBL = 106; + public static final int KEYCODE_BUTTON_THUMBR = 107; + public static final int KEYCODE_BUTTON_START = 108; + public static final int KEYCODE_BUTTON_SELECT = 109; + public static final int KEYCODE_BUTTON_MODE = 110; // NOTE: If you add a new keycode here you must also add it to: // isSystem() + // native/include/android/keycodes.h // frameworks/base/include/ui/KeycodeLabels.h + // external/webkit/WebKit/android/plugins/ANPKeyCodes.h // tools/puppet_master/PuppetMaster/nav_keys.py // frameworks/base/core/res/res/values/attrs.xml // commands/monkey/Monkey.java @@ -135,7 +156,7 @@ public class KeyEvent implements Parcelable { // those new codes. This is intended to maintain a consistent // set of key code definitions across all Android devices. - private static final int LAST_KEYCODE = KEYCODE_MUTE; + private static final int LAST_KEYCODE = KEYCODE_BUTTON_MODE; /** * @deprecated There are now more than MAX_KEYCODE keycodes. @@ -158,7 +179,7 @@ public class KeyEvent implements Parcelable { * key code is not {#link {@link #KEYCODE_UNKNOWN} then the * {#link {@link #getRepeatCount()} method returns the number of times * the given key code should be executed. - * Otherwise, if the key code {@link #KEYCODE_UNKNOWN}, then + * Otherwise, if the key code is {@link #KEYCODE_UNKNOWN}, then * this is a sequence of characters as returned by {@link #getCharacters}. */ public static final int ACTION_MULTIPLE = 2; @@ -326,9 +347,8 @@ public class KeyEvent implements Parcelable { private int mMetaState; private int mAction; private int mKeyCode; - private int mScancode; + private int mScanCode; private int mRepeatCount; - private int mDeviceId; private int mFlags; private long mDownTime; private long mEventTime; @@ -463,20 +483,20 @@ public class KeyEvent implements Parcelable { * @param repeat A repeat count for down events (> 0 if this is after the * initial down) or event count for multiple events. * @param metaState Flags indicating which meta keys are currently pressed. - * @param device The device ID that generated the key event. + * @param deviceId The device ID that generated the key event. * @param scancode Raw device scan code of the event. */ public KeyEvent(long downTime, long eventTime, int action, int code, int repeat, int metaState, - int device, int scancode) { + int deviceId, int scancode) { mDownTime = downTime; mEventTime = eventTime; mAction = action; mKeyCode = code; mRepeatCount = repeat; mMetaState = metaState; - mDeviceId = device; - mScancode = scancode; + mDeviceId = deviceId; + mScanCode = scancode; } /** @@ -492,44 +512,79 @@ public class KeyEvent implements Parcelable { * @param repeat A repeat count for down events (> 0 if this is after the * initial down) or event count for multiple events. * @param metaState Flags indicating which meta keys are currently pressed. - * @param device The device ID that generated the key event. + * @param deviceId The device ID that generated the key event. * @param scancode Raw device scan code of the event. * @param flags The flags for this key event */ public KeyEvent(long downTime, long eventTime, int action, int code, int repeat, int metaState, - int device, int scancode, int flags) { + int deviceId, int scancode, int flags) { mDownTime = downTime; mEventTime = eventTime; mAction = action; mKeyCode = code; mRepeatCount = repeat; mMetaState = metaState; - mDeviceId = device; - mScancode = scancode; + mDeviceId = deviceId; + mScanCode = scancode; mFlags = flags; } /** + * Create a new key event. + * + * @param downTime The time (in {@link android.os.SystemClock#uptimeMillis}) + * at which this key code originally went down. + * @param eventTime The time (in {@link android.os.SystemClock#uptimeMillis}) + * at which this event happened. + * @param action Action code: either {@link #ACTION_DOWN}, + * {@link #ACTION_UP}, or {@link #ACTION_MULTIPLE}. + * @param code The key code. + * @param repeat A repeat count for down events (> 0 if this is after the + * initial down) or event count for multiple events. + * @param metaState Flags indicating which meta keys are currently pressed. + * @param deviceId The device ID that generated the key event. + * @param scancode Raw device scan code of the event. + * @param flags The flags for this key event + * @param source The input source such as {@link InputDevice#SOURCE_KEYBOARD}. + */ + public KeyEvent(long downTime, long eventTime, int action, + int code, int repeat, int metaState, + int deviceId, int scancode, int flags, int source) { + mDownTime = downTime; + mEventTime = eventTime; + mAction = action; + mKeyCode = code; + mRepeatCount = repeat; + mMetaState = metaState; + mDeviceId = deviceId; + mScanCode = scancode; + mFlags = flags; + mSource = source; + } + + /** * Create a new key event for a string of characters. The key code, - * action, and repeat could will automatically be set to - * {@link #KEYCODE_UNKNOWN}, {@link #ACTION_MULTIPLE}, and 0 for you. + * action, repeat count and source will automatically be set to + * {@link #KEYCODE_UNKNOWN}, {@link #ACTION_MULTIPLE}, 0, and + * {@link InputDevice#SOURCE_KEYBOARD} for you. * * @param time The time (in {@link android.os.SystemClock#uptimeMillis}) * at which this event occured. * @param characters The string of characters. - * @param device The device ID that generated the key event. + * @param deviceId The device ID that generated the key event. * @param flags The flags for this key event */ - public KeyEvent(long time, String characters, int device, int flags) { + public KeyEvent(long time, String characters, int deviceId, int flags) { mDownTime = time; mEventTime = time; mCharacters = characters; mAction = ACTION_MULTIPLE; mKeyCode = KEYCODE_UNKNOWN; mRepeatCount = 0; - mDeviceId = device; + mDeviceId = deviceId; mFlags = flags; + mSource = InputDevice.SOURCE_KEYBOARD; } /** @@ -543,7 +598,8 @@ public class KeyEvent implements Parcelable { mRepeatCount = origEvent.mRepeatCount; mMetaState = origEvent.mMetaState; mDeviceId = origEvent.mDeviceId; - mScancode = origEvent.mScancode; + mSource = origEvent.mSource; + mScanCode = origEvent.mScanCode; mFlags = origEvent.mFlags; mCharacters = origEvent.mCharacters; } @@ -568,7 +624,8 @@ public class KeyEvent implements Parcelable { mRepeatCount = newRepeat; mMetaState = origEvent.mMetaState; mDeviceId = origEvent.mDeviceId; - mScancode = origEvent.mScancode; + mSource = origEvent.mSource; + mScanCode = origEvent.mScanCode; mFlags = origEvent.mFlags; mCharacters = origEvent.mCharacters; } @@ -621,7 +678,8 @@ public class KeyEvent implements Parcelable { mRepeatCount = origEvent.mRepeatCount; mMetaState = origEvent.mMetaState; mDeviceId = origEvent.mDeviceId; - mScancode = origEvent.mScancode; + mSource = origEvent.mSource; + mScanCode = origEvent.mScanCode; mFlags = origEvent.mFlags; // Don't copy mCharacters, since one way or the other we'll lose it // when changing the action. @@ -671,31 +729,12 @@ public class KeyEvent implements Parcelable { * TODO: should the dpad keys be here? arguably, because they also shouldn't be menu shortcuts */ public final boolean isSystem() { - switch (mKeyCode) { - case KEYCODE_MENU: - case KEYCODE_SOFT_RIGHT: - case KEYCODE_HOME: - case KEYCODE_BACK: - case KEYCODE_CALL: - case KEYCODE_ENDCALL: - case KEYCODE_VOLUME_UP: - case KEYCODE_VOLUME_DOWN: - case KEYCODE_MUTE: - case KEYCODE_POWER: - case KEYCODE_HEADSETHOOK: - case KEYCODE_MEDIA_PLAY_PAUSE: - case KEYCODE_MEDIA_STOP: - case KEYCODE_MEDIA_NEXT: - case KEYCODE_MEDIA_PREVIOUS: - case KEYCODE_MEDIA_REWIND: - case KEYCODE_MEDIA_FAST_FORWARD: - case KEYCODE_CAMERA: - case KEYCODE_FOCUS: - case KEYCODE_SEARCH: - return true; - default: - return false; - } + return native_isSystemKey(mKeyCode); + } + + /** @hide */ + public final boolean hasDefaultAction() { + return native_hasDefaultAction(mKeyCode); } @@ -853,7 +892,7 @@ public class KeyEvent implements Parcelable { * Mostly this is here for debugging purposes. */ public final int getScanCode() { - return mScancode; + return mScanCode; } /** @@ -895,18 +934,6 @@ public class KeyEvent implements Parcelable { } /** - * Return the id for the keyboard that this event came from. A device - * id of 0 indicates the event didn't come from a physical device and - * maps to the default keymap. The other numbers are arbitrary and - * you shouldn't depend on the values. - * - * @see KeyCharacterMap#load - */ - public final int getDeviceId() { - return mDeviceId; - } - - /** * Renamed to {@link #getDeviceId}. * * @hide @@ -1177,46 +1204,55 @@ public class KeyEvent implements Parcelable { public String toString() { return "KeyEvent{action=" + mAction + " code=" + mKeyCode + " repeat=" + mRepeatCount - + " meta=" + mMetaState + " scancode=" + mScancode + + " meta=" + mMetaState + " scancode=" + mScanCode + " mFlags=" + mFlags + "}"; } public static final Parcelable.Creator<KeyEvent> CREATOR = new Parcelable.Creator<KeyEvent>() { public KeyEvent createFromParcel(Parcel in) { - return new KeyEvent(in); + in.readInt(); // skip token, we already know this is a KeyEvent + return KeyEvent.createFromParcelBody(in); } public KeyEvent[] newArray(int size) { return new KeyEvent[size]; } }; - - public int describeContents() { - return 0; + + /** @hide */ + public static KeyEvent createFromParcelBody(Parcel in) { + return new KeyEvent(in); + } + + private KeyEvent(Parcel in) { + readBaseFromParcel(in); + + mAction = in.readInt(); + mKeyCode = in.readInt(); + mRepeatCount = in.readInt(); + mMetaState = in.readInt(); + mScanCode = in.readInt(); + mFlags = in.readInt(); + mDownTime = in.readLong(); + mEventTime = in.readLong(); } public void writeToParcel(Parcel out, int flags) { + out.writeInt(PARCEL_TOKEN_KEY_EVENT); + + writeBaseToParcel(out); + out.writeInt(mAction); out.writeInt(mKeyCode); out.writeInt(mRepeatCount); out.writeInt(mMetaState); - out.writeInt(mDeviceId); - out.writeInt(mScancode); + out.writeInt(mScanCode); out.writeInt(mFlags); out.writeLong(mDownTime); out.writeLong(mEventTime); } - private KeyEvent(Parcel in) { - mAction = in.readInt(); - mKeyCode = in.readInt(); - mRepeatCount = in.readInt(); - mMetaState = in.readInt(); - mDeviceId = in.readInt(); - mScancode = in.readInt(); - mFlags = in.readInt(); - mDownTime = in.readLong(); - mEventTime = in.readLong(); - } + private native boolean native_isSystemKey(int keyCode); + private native boolean native_hasDefaultAction(int keyCode); } diff --git a/core/java/android/view/LayoutInflater.java b/core/java/android/view/LayoutInflater.java index e5985c179e9d..194c01357613 100644 --- a/core/java/android/view/LayoutInflater.java +++ b/core/java/android/view/LayoutInflater.java @@ -348,6 +348,7 @@ public abstract class LayoutInflater { public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) { synchronized (mConstructorArgs) { final AttributeSet attrs = Xml.asAttributeSet(parser); + Context lastContext = (Context)mConstructorArgs[0]; mConstructorArgs[0] = mContext; View result = root; @@ -432,6 +433,10 @@ public abstract class LayoutInflater { + ": " + e.getMessage()); ex.initCause(e); throw ex; + } finally { + // Don't retain static reference on context. + mConstructorArgs[0] = lastContext; + mConstructorArgs[1] = null; } return result; diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java index d648e96693c7..78b9b5d8a84a 100644 --- a/core/java/android/view/MotionEvent.java +++ b/core/java/android/view/MotionEvent.java @@ -19,15 +19,18 @@ package android.view; import android.os.Parcel; import android.os.Parcelable; import android.os.SystemClock; -import android.util.Log; /** * Object used to report movement (mouse, pen, finger, trackball) events. This * class may hold either absolute or relative movements, depending on what * it is being used for. + * + * Refer to {@link InputDevice} for information about how different kinds of + * input devices and sources represent pointer coordinates. */ -public final class MotionEvent implements Parcelable { - static final boolean DEBUG_POINTERS = false; +public final class MotionEvent extends InputEvent implements Parcelable { + private static final long MS_PER_NS = 1000000; + private static final boolean TRACK_RECYCLED_LOCATION = false; /** * Bit mask of the parts of the action code that are the action itself. @@ -153,7 +156,17 @@ public final class MotionEvent implements Parcelable { @Deprecated public static final int ACTION_POINTER_ID_SHIFT = 8; - private static final boolean TRACK_RECYCLED_LOCATION = false; + /** + * This flag indicates that the window that received this motion event is partly + * or wholly obscured by another visible window above it. This flag is set to true + * even if the event did not directly pass through the obscured area. + * A security sensitive application can check this flag to identify situations in which + * a malicious application may have covered up part of its content for the purpose + * of misleading the user or hijacking touches. An appropriate response might be + * to drop the suspect touches or to take additional precautions to confirm the user's + * actual intent. + */ + public static final int FLAG_WINDOW_IS_OBSCURED = 0x1; /** * Flag indicating the motion event intersected the top edge of the screen. @@ -175,42 +188,65 @@ public final class MotionEvent implements Parcelable { */ public static final int EDGE_RIGHT = 0x00000008; - /** + /* * Offset for the sample's X coordinate. - * @hide */ - static public final int SAMPLE_X = 0; + static private final int SAMPLE_X = 0; - /** + /* * Offset for the sample's Y coordinate. - * @hide */ - static public final int SAMPLE_Y = 1; + static private final int SAMPLE_Y = 1; - /** - * Offset for the sample's X coordinate. - * @hide + /* + * Offset for the sample's pressure. */ - static public final int SAMPLE_PRESSURE = 2; + static private final int SAMPLE_PRESSURE = 2; - /** - * Offset for the sample's X coordinate. - * @hide + /* + * Offset for the sample's size */ - static public final int SAMPLE_SIZE = 3; + static private final int SAMPLE_SIZE = 3; - /** + /* + * Offset for the sample's touch major axis length. + */ + static private final int SAMPLE_TOUCH_MAJOR = 4; + + /* + * Offset for the sample's touch minor axis length. + */ + static private final int SAMPLE_TOUCH_MINOR = 5; + + /* + * Offset for the sample's tool major axis length. + */ + static private final int SAMPLE_TOOL_MAJOR = 6; + + /* + * Offset for the sample's tool minor axis length. + */ + static private final int SAMPLE_TOOL_MINOR = 7; + + /* + * Offset for the sample's orientation. + */ + static private final int SAMPLE_ORIENTATION = 8; + + /* * Number of data items for each sample. - * @hide */ - static public final int NUM_SAMPLE_DATA = 4; + static private final int NUM_SAMPLE_DATA = 9; - /** - * Number of possible pointers. - * @hide + /* + * Minimum number of pointers for which to reserve space when allocating new + * motion events. This is explicitly not a bound on the maximum number of pointers. */ - static public final int BASE_AVAIL_POINTERS = 5; + static private final int BASE_AVAIL_POINTERS = 5; + /* + * Minimum number of samples for which to reserve space when allocating new motion events. + */ static private final int BASE_AVAIL_SAMPLES = 8; static private final int MAX_RECYCLED = 10; @@ -218,74 +254,95 @@ public final class MotionEvent implements Parcelable { static private int gRecyclerUsed = 0; static private MotionEvent gRecyclerTop = null; - private long mDownTime; - private long mEventTimeNano; + private long mDownTimeNano; private int mAction; - private float mRawX; - private float mRawY; + private float mXOffset; + private float mYOffset; private float mXPrecision; private float mYPrecision; - private int mDeviceId; private int mEdgeFlags; private int mMetaState; - - // Here is the actual event data. Note that the order of the array - // is a little odd: the first entry is the most recent, and the ones - // following it are the historical data from oldest to newest. This - // allows us to easily retrieve the most recent data, without having - // to copy the arrays every time a new sample is added. + private int mFlags; private int mNumPointers; private int mNumSamples; + + private int mLastDataSampleIndex; + private int mLastEventTimeNanoSampleIndex; + // Array of mNumPointers size of identifiers for each pointer of data. private int[] mPointerIdentifiers; + // Array of (mNumSamples * mNumPointers * NUM_SAMPLE_DATA) size of event data. + // Samples are ordered from oldest to newest. private float[] mDataSamples; - // Array of mNumSamples size of time stamps. - private long[] mTimeSamples; + + // Array of mNumSamples size of event time stamps in nanoseconds. + // Samples are ordered from oldest to newest. + private long[] mEventTimeNanoSamples; private MotionEvent mNext; private RuntimeException mRecycledLocation; private boolean mRecycled; - private MotionEvent() { - mPointerIdentifiers = new int[BASE_AVAIL_POINTERS]; - mDataSamples = new float[BASE_AVAIL_POINTERS*BASE_AVAIL_SAMPLES*NUM_SAMPLE_DATA]; - mTimeSamples = new long[BASE_AVAIL_SAMPLES]; + private MotionEvent(int pointerCount, int sampleCount) { + mPointerIdentifiers = new int[pointerCount]; + mDataSamples = new float[pointerCount * sampleCount * NUM_SAMPLE_DATA]; + mEventTimeNanoSamples = new long[sampleCount]; } - static private MotionEvent obtain() { + static private MotionEvent obtain(int pointerCount, int sampleCount) { + final MotionEvent ev; synchronized (gRecyclerLock) { if (gRecyclerTop == null) { - return new MotionEvent(); + if (pointerCount < BASE_AVAIL_POINTERS) { + pointerCount = BASE_AVAIL_POINTERS; + } + if (sampleCount < BASE_AVAIL_SAMPLES) { + sampleCount = BASE_AVAIL_SAMPLES; + } + return new MotionEvent(pointerCount, sampleCount); } - MotionEvent ev = gRecyclerTop; + ev = gRecyclerTop; gRecyclerTop = ev.mNext; - gRecyclerUsed--; - ev.mRecycledLocation = null; - ev.mRecycled = false; - return ev; + gRecyclerUsed -= 1; + } + ev.mRecycledLocation = null; + ev.mRecycled = false; + ev.mNext = null; + + if (ev.mPointerIdentifiers.length < pointerCount) { + ev.mPointerIdentifiers = new int[pointerCount]; + } + + if (ev.mEventTimeNanoSamples.length < sampleCount) { + ev.mEventTimeNanoSamples = new long[sampleCount]; + } + + final int neededDataSamplesLength = pointerCount * sampleCount * NUM_SAMPLE_DATA; + if (ev.mDataSamples.length < neededDataSamplesLength) { + ev.mDataSamples = new float[neededDataSamplesLength]; } + + return ev; } - + /** * Create a new MotionEvent, filling in all of the basic values that * define the motion. * * @param downTime The time (in ms) when the user originally pressed down to start * a stream of position events. This must be obtained from {@link SystemClock#uptimeMillis()}. - * @param eventTime The the time (in ms) when this specific event was generated. This + * @param eventTime The the time (in ms) when this specific event was generated. This * must be obtained from {@link SystemClock#uptimeMillis()}. - * @param eventTimeNano The the time (in ns) when this specific event was generated. This - * must be obtained from {@link System#nanoTime()}. * @param action The kind of action being performed -- one of either * {@link #ACTION_DOWN}, {@link #ACTION_MOVE}, {@link #ACTION_UP}, or * {@link #ACTION_CANCEL}. * @param pointers The number of points that will be in this event. - * @param inPointerIds An array of <em>pointers</em> values providing + * @param pointerIds An array of <em>pointers</em> values providing * an identifier for each pointer. - * @param inData An array of <em>pointers*NUM_SAMPLE_DATA</em> of initial - * data samples for the event. + * @param pointerCoords An array of <em>pointers</em> values providing + * a {@link PointerCoords} coordinate object for each pointer. * @param metaState The state of any meta / modifier keys that were in effect when * the event was generated. * @param xPrecision The precision of the X coordinate being reported. @@ -293,57 +350,39 @@ public final class MotionEvent implements Parcelable { * @param deviceId The id for the device that this event came from. An id of * zero indicates that the event didn't come from a physical device; other * numbers are arbitrary and you shouldn't depend on the values. - * @param edgeFlags A bitfield indicating which edges, if any, where touched by this + * @param edgeFlags A bitfield indicating which edges, if any, were touched by this * MotionEvent. - * - * @hide - */ - static public MotionEvent obtainNano(long downTime, long eventTime, long eventTimeNano, - int action, int pointers, int[] inPointerIds, float[] inData, int metaState, - float xPrecision, float yPrecision, int deviceId, int edgeFlags) { - MotionEvent ev = obtain(); + * @param source The source of this event. + * @param flags The motion event flags. + */ + static public MotionEvent obtain(long downTime, long eventTime, + int action, int pointers, int[] pointerIds, PointerCoords[] pointerCoords, + int metaState, float xPrecision, float yPrecision, int deviceId, + int edgeFlags, int source, int flags) { + MotionEvent ev = obtain(pointers, 1); ev.mDeviceId = deviceId; + ev.mSource = source; ev.mEdgeFlags = edgeFlags; - ev.mDownTime = downTime; - ev.mEventTimeNano = eventTimeNano; + ev.mDownTimeNano = downTime * MS_PER_NS; ev.mAction = action; + ev.mFlags = flags; ev.mMetaState = metaState; - ev.mRawX = inData[SAMPLE_X]; - ev.mRawY = inData[SAMPLE_Y]; + ev.mXOffset = 0; + ev.mYOffset = 0; ev.mXPrecision = xPrecision; ev.mYPrecision = yPrecision; + ev.mNumPointers = pointers; ev.mNumSamples = 1; - int[] pointerIdentifiers = ev.mPointerIdentifiers; - if (pointerIdentifiers.length < pointers) { - ev.mPointerIdentifiers = pointerIdentifiers = new int[pointers]; - } - System.arraycopy(inPointerIds, 0, pointerIdentifiers, 0, pointers); + ev.mLastDataSampleIndex = 0; + ev.mLastEventTimeNanoSampleIndex = 0; - final int ND = pointers * NUM_SAMPLE_DATA; - float[] dataSamples = ev.mDataSamples; - if (dataSamples.length < ND) { - ev.mDataSamples = dataSamples = new float[ND]; - } - System.arraycopy(inData, 0, dataSamples, 0, ND); + System.arraycopy(pointerIds, 0, ev.mPointerIdentifiers, 0, pointers); - ev.mTimeSamples[0] = eventTime; - - if (DEBUG_POINTERS) { - StringBuilder sb = new StringBuilder(128); - sb.append("New:"); - for (int i=0; i<pointers; i++) { - sb.append(" #"); - sb.append(ev.mPointerIdentifiers[i]); - sb.append("("); - sb.append(ev.mDataSamples[(i*NUM_SAMPLE_DATA) + SAMPLE_X]); - sb.append(","); - sb.append(ev.mDataSamples[(i*NUM_SAMPLE_DATA) + SAMPLE_Y]); - sb.append(")"); - } - Log.v("MotionEvent", sb.toString()); - } + ev.mEventTimeNanoSamples[0] = eventTime * MS_PER_NS; + + ev.setPointerCoordsAtSampleIndex(0, pointerCoords); return ev; } @@ -376,33 +415,36 @@ public final class MotionEvent implements Parcelable { * @param deviceId The id for the device that this event came from. An id of * zero indicates that the event didn't come from a physical device; other * numbers are arbitrary and you shouldn't depend on the values. - * @param edgeFlags A bitfield indicating which edges, if any, where touched by this + * @param edgeFlags A bitfield indicating which edges, if any, were touched by this * MotionEvent. */ static public MotionEvent obtain(long downTime, long eventTime, int action, float x, float y, float pressure, float size, int metaState, float xPrecision, float yPrecision, int deviceId, int edgeFlags) { - MotionEvent ev = obtain(); + MotionEvent ev = obtain(1, 1); ev.mDeviceId = deviceId; + ev.mSource = InputDevice.SOURCE_UNKNOWN; ev.mEdgeFlags = edgeFlags; - ev.mDownTime = downTime; - ev.mEventTimeNano = eventTime * 1000000; + ev.mDownTimeNano = downTime * MS_PER_NS; ev.mAction = action; + ev.mFlags = 0; ev.mMetaState = metaState; + ev.mXOffset = 0; + ev.mYOffset = 0; ev.mXPrecision = xPrecision; ev.mYPrecision = yPrecision; - + ev.mNumPointers = 1; ev.mNumSamples = 1; - int[] pointerIds = ev.mPointerIdentifiers; - pointerIds[0] = 0; - float[] data = ev.mDataSamples; - data[SAMPLE_X] = ev.mRawX = x; - data[SAMPLE_Y] = ev.mRawY = y; - data[SAMPLE_PRESSURE] = pressure; - data[SAMPLE_SIZE] = size; - ev.mTimeSamples[0] = eventTime; - + + ev.mLastDataSampleIndex = 0; + ev.mLastEventTimeNanoSampleIndex = 0; + + ev.mPointerIdentifiers[0] = 0; + + ev.mEventTimeNanoSamples[0] = eventTime * MS_PER_NS; + + ev.setPointerCoordsAtSampleIndex(0, x, y, pressure, size); return ev; } @@ -435,35 +477,18 @@ public final class MotionEvent implements Parcelable { * @param deviceId The id for the device that this event came from. An id of * zero indicates that the event didn't come from a physical device; other * numbers are arbitrary and you shouldn't depend on the values. - * @param edgeFlags A bitfield indicating which edges, if any, where touched by this + * @param edgeFlags A bitfield indicating which edges, if any, were touched by this * MotionEvent. + * + * @deprecated Use {@link #obtain(long, long, int, float, float, float, float, int, float, float, int, int)} + * instead. */ + @Deprecated static public MotionEvent obtain(long downTime, long eventTime, int action, int pointers, float x, float y, float pressure, float size, int metaState, float xPrecision, float yPrecision, int deviceId, int edgeFlags) { - MotionEvent ev = obtain(); - ev.mDeviceId = deviceId; - ev.mEdgeFlags = edgeFlags; - ev.mDownTime = downTime; - ev.mEventTimeNano = eventTime * 1000000; - ev.mAction = action; - ev.mNumPointers = pointers; - ev.mMetaState = metaState; - ev.mXPrecision = xPrecision; - ev.mYPrecision = yPrecision; - - ev.mNumPointers = 1; - ev.mNumSamples = 1; - int[] pointerIds = ev.mPointerIdentifiers; - pointerIds[0] = 0; - float[] data = ev.mDataSamples; - data[SAMPLE_X] = ev.mRawX = x; - data[SAMPLE_Y] = ev.mRawY = y; - data[SAMPLE_PRESSURE] = pressure; - data[SAMPLE_SIZE] = size; - ev.mTimeSamples[0] = eventTime; - - return ev; + return obtain(downTime, eventTime, action, x, y, pressure, size, + metaState, xPrecision, yPrecision, deviceId, edgeFlags); } /** @@ -485,89 +510,38 @@ public final class MotionEvent implements Parcelable { */ static public MotionEvent obtain(long downTime, long eventTime, int action, float x, float y, int metaState) { - MotionEvent ev = obtain(); - ev.mDeviceId = 0; - ev.mEdgeFlags = 0; - ev.mDownTime = downTime; - ev.mEventTimeNano = eventTime * 1000000; - ev.mAction = action; - ev.mNumPointers = 1; - ev.mMetaState = metaState; - ev.mXPrecision = 1.0f; - ev.mYPrecision = 1.0f; - - ev.mNumPointers = 1; - ev.mNumSamples = 1; - int[] pointerIds = ev.mPointerIdentifiers; - pointerIds[0] = 0; - float[] data = ev.mDataSamples; - data[SAMPLE_X] = ev.mRawX = x; - data[SAMPLE_Y] = ev.mRawY = y; - data[SAMPLE_PRESSURE] = 1.0f; - data[SAMPLE_SIZE] = 1.0f; - ev.mTimeSamples[0] = eventTime; - - return ev; - } - - /** - * Scales down the coordination of this event by the given scale. - * - * @hide - */ - public void scale(float scale) { - mRawX *= scale; - mRawY *= scale; - mXPrecision *= scale; - mYPrecision *= scale; - float[] history = mDataSamples; - final int length = mNumPointers * mNumSamples * NUM_SAMPLE_DATA; - for (int i = 0; i < length; i += NUM_SAMPLE_DATA) { - history[i + SAMPLE_X] *= scale; - history[i + SAMPLE_Y] *= scale; - // no need to scale pressure - history[i + SAMPLE_SIZE] *= scale; // TODO: square this? - } + return obtain(downTime, eventTime, action, x, y, 1.0f, 1.0f, + metaState, 1.0f, 1.0f, 0, 0); } /** * Create a new MotionEvent, copying from an existing one. */ static public MotionEvent obtain(MotionEvent o) { - MotionEvent ev = obtain(); + MotionEvent ev = obtain(o.mNumPointers, o.mNumSamples); ev.mDeviceId = o.mDeviceId; + ev.mSource = o.mSource; ev.mEdgeFlags = o.mEdgeFlags; - ev.mDownTime = o.mDownTime; - ev.mEventTimeNano = o.mEventTimeNano; + ev.mDownTimeNano = o.mDownTimeNano; ev.mAction = o.mAction; - ev.mNumPointers = o.mNumPointers; - ev.mRawX = o.mRawX; - ev.mRawY = o.mRawY; + ev.mFlags = o.mFlags; ev.mMetaState = o.mMetaState; + ev.mXOffset = o.mXOffset; + ev.mYOffset = o.mYOffset; ev.mXPrecision = o.mXPrecision; ev.mYPrecision = o.mYPrecision; + int numPointers = ev.mNumPointers = o.mNumPointers; + int numSamples = ev.mNumSamples = o.mNumSamples; - final int NS = ev.mNumSamples = o.mNumSamples; - if (ev.mTimeSamples.length >= NS) { - System.arraycopy(o.mTimeSamples, 0, ev.mTimeSamples, 0, NS); - } else { - ev.mTimeSamples = (long[])o.mTimeSamples.clone(); - } + ev.mLastDataSampleIndex = o.mLastDataSampleIndex; + ev.mLastEventTimeNanoSampleIndex = o.mLastEventTimeNanoSampleIndex; - final int NP = (ev.mNumPointers=o.mNumPointers); - if (ev.mPointerIdentifiers.length >= NP) { - System.arraycopy(o.mPointerIdentifiers, 0, ev.mPointerIdentifiers, 0, NP); - } else { - ev.mPointerIdentifiers = (int[])o.mPointerIdentifiers.clone(); - } + System.arraycopy(o.mPointerIdentifiers, 0, ev.mPointerIdentifiers, 0, numPointers); - final int ND = NP * NS * NUM_SAMPLE_DATA; - if (ev.mDataSamples.length >= ND) { - System.arraycopy(o.mDataSamples, 0, ev.mDataSamples, 0, ND); - } else { - ev.mDataSamples = (float[])o.mDataSamples.clone(); - } + System.arraycopy(o.mEventTimeNanoSamples, 0, ev.mEventTimeNanoSamples, 0, numSamples); + System.arraycopy(o.mDataSamples, 0, ev.mDataSamples, 0, + numPointers * numSamples * NUM_SAMPLE_DATA); return ev; } @@ -576,36 +550,31 @@ public final class MotionEvent implements Parcelable { * any historical point information. */ static public MotionEvent obtainNoHistory(MotionEvent o) { - MotionEvent ev = obtain(); + MotionEvent ev = obtain(o.mNumPointers, 1); ev.mDeviceId = o.mDeviceId; + ev.mSource = o.mSource; ev.mEdgeFlags = o.mEdgeFlags; - ev.mDownTime = o.mDownTime; - ev.mEventTimeNano = o.mEventTimeNano; + ev.mDownTimeNano = o.mDownTimeNano; ev.mAction = o.mAction; - ev.mNumPointers = o.mNumPointers; - ev.mRawX = o.mRawX; - ev.mRawY = o.mRawY; + o.mFlags = o.mFlags; ev.mMetaState = o.mMetaState; + ev.mXOffset = o.mXOffset; + ev.mYOffset = o.mYOffset; ev.mXPrecision = o.mXPrecision; ev.mYPrecision = o.mYPrecision; + int numPointers = ev.mNumPointers = o.mNumPointers; ev.mNumSamples = 1; - ev.mTimeSamples[0] = o.mTimeSamples[0]; - final int NP = (ev.mNumPointers=o.mNumPointers); - if (ev.mPointerIdentifiers.length >= NP) { - System.arraycopy(o.mPointerIdentifiers, 0, ev.mPointerIdentifiers, 0, NP); - } else { - ev.mPointerIdentifiers = (int[])o.mPointerIdentifiers.clone(); - } + ev.mLastDataSampleIndex = 0; + ev.mLastEventTimeNanoSampleIndex = 0; - final int ND = NP * NUM_SAMPLE_DATA; - if (ev.mDataSamples.length >= ND) { - System.arraycopy(o.mDataSamples, 0, ev.mDataSamples, 0, ND); - } else { - ev.mDataSamples = (float[])o.mDataSamples.clone(); - } + System.arraycopy(o.mPointerIdentifiers, 0, ev.mPointerIdentifiers, 0, numPointers); + ev.mEventTimeNanoSamples[0] = o.mEventTimeNanoSamples[o.mLastEventTimeNanoSampleIndex]; + + System.arraycopy(o.mDataSamples, o.mLastDataSampleIndex, ev.mDataSamples, 0, + numPointers * NUM_SAMPLE_DATA); return ev; } @@ -613,18 +582,21 @@ public final class MotionEvent implements Parcelable { * Recycle the MotionEvent, to be re-used by a later caller. After calling * this function you must not ever touch the event again. */ - public void recycle() { + public final void recycle() { // Ensure recycle is only called once! if (TRACK_RECYCLED_LOCATION) { if (mRecycledLocation != null) { throw new RuntimeException(toString() + " recycled twice!", mRecycledLocation); } mRecycledLocation = new RuntimeException("Last recycled here"); - } else if (mRecycled) { - throw new RuntimeException(toString() + " recycled twice!"); + //Log.w("MotionEvent", "Recycling event " + this, mRecycledLocation); + } else { + if (mRecycled) { + throw new RuntimeException(toString() + " recycled twice!"); + } + mRecycled = true; } - //Log.w("MotionEvent", "Recycling event " + this, mRecycledLocation); synchronized (gRecyclerLock) { if (gRecyclerUsed < MAX_RECYCLED) { gRecyclerUsed++; @@ -634,6 +606,31 @@ public final class MotionEvent implements Parcelable { } } } + + /** + * Scales down the coordination of this event by the given scale. + * + * @hide + */ + public final void scale(float scale) { + mXOffset *= scale; + mYOffset *= scale; + mXPrecision *= scale; + mYPrecision *= scale; + + float[] history = mDataSamples; + final int length = mNumPointers * mNumSamples * NUM_SAMPLE_DATA; + for (int i = 0; i < length; i += NUM_SAMPLE_DATA) { + history[i + SAMPLE_X] *= scale; + history[i + SAMPLE_Y] *= scale; + // no need to scale pressure + history[i + SAMPLE_SIZE] *= scale; // TODO: square this? + history[i + SAMPLE_TOUCH_MAJOR] *= scale; + history[i + SAMPLE_TOUCH_MINOR] *= scale; + history[i + SAMPLE_TOOL_MAJOR] *= scale; + history[i + SAMPLE_TOOL_MINOR] *= scale; + } + } /** * Return the kind of action being performed -- one of either @@ -671,18 +668,27 @@ public final class MotionEvent implements Parcelable { } /** + * Gets the motion event flags. + * + * @see #FLAG_WINDOW_IS_OBSCURED + */ + public final int getFlags() { + return mFlags; + } + + /** * Returns the time (in ms) when the user originally pressed down to start * a stream of position events. */ public final long getDownTime() { - return mDownTime; + return mDownTimeNano / MS_PER_NS; } /** * Returns the time (in ms) when this specific event was generated. */ public final long getEventTime() { - return mTimeSamples[0]; + return mEventTimeNanoSamples[mLastEventTimeNanoSampleIndex] / MS_PER_NS; } /** @@ -692,7 +698,7 @@ public final class MotionEvent implements Parcelable { * @hide */ public final long getEventTimeNano() { - return mEventTimeNano; + return mEventTimeNanoSamples[mLastEventTimeNanoSampleIndex]; } /** @@ -700,7 +706,7 @@ public final class MotionEvent implements Parcelable { * arbitrary pointer identifier). */ public final float getX() { - return mDataSamples[SAMPLE_X]; + return mDataSamples[mLastDataSampleIndex + SAMPLE_X] + mXOffset; } /** @@ -708,7 +714,7 @@ public final class MotionEvent implements Parcelable { * arbitrary pointer identifier). */ public final float getY() { - return mDataSamples[SAMPLE_Y]; + return mDataSamples[mLastDataSampleIndex + SAMPLE_Y] + mYOffset; } /** @@ -716,7 +722,7 @@ public final class MotionEvent implements Parcelable { * arbitrary pointer identifier). */ public final float getPressure() { - return mDataSamples[SAMPLE_PRESSURE]; + return mDataSamples[mLastDataSampleIndex + SAMPLE_PRESSURE]; } /** @@ -724,7 +730,47 @@ public final class MotionEvent implements Parcelable { * arbitrary pointer identifier). */ public final float getSize() { - return mDataSamples[SAMPLE_SIZE]; + return mDataSamples[mLastDataSampleIndex + SAMPLE_SIZE]; + } + + /** + * {@link #getTouchMajor(int)} for the first pointer index (may be an + * arbitrary pointer identifier). + */ + public final float getTouchMajor() { + return mDataSamples[mLastDataSampleIndex + SAMPLE_TOUCH_MAJOR]; + } + + /** + * {@link #getTouchMinor(int)} for the first pointer index (may be an + * arbitrary pointer identifier). + */ + public final float getTouchMinor() { + return mDataSamples[mLastDataSampleIndex + SAMPLE_TOUCH_MINOR]; + } + + /** + * {@link #getToolMajor(int)} for the first pointer index (may be an + * arbitrary pointer identifier). + */ + public final float getToolMajor() { + return mDataSamples[mLastDataSampleIndex + SAMPLE_TOOL_MAJOR]; + } + + /** + * {@link #getToolMinor(int)} for the first pointer index (may be an + * arbitrary pointer identifier). + */ + public final float getToolMinor() { + return mDataSamples[mLastDataSampleIndex + SAMPLE_TOOL_MINOR]; + } + + /** + * {@link #getOrientation(int)} for the first pointer index (may be an + * arbitrary pointer identifier). + */ + public final float getOrientation() { + return mDataSamples[mLastDataSampleIndex + SAMPLE_ORIENTATION]; } /** @@ -752,7 +798,7 @@ public final class MotionEvent implements Parcelable { * * @param pointerId The identifier of the pointer to be found. * @return Returns either the index of the pointer (for use with - * {@link #getX(int) et al.), or -1 if there is no data available for + * {@link #getX(int)} et al.), or -1 if there is no data available for * that pointer identifier. */ public final int findPointerIndex(int pointerId) { @@ -776,7 +822,8 @@ public final class MotionEvent implements Parcelable { * (the first pointer that is down) to {@link #getPointerCount()}-1. */ public final float getX(int pointerIndex) { - return mDataSamples[(pointerIndex*NUM_SAMPLE_DATA) + SAMPLE_X]; + return mDataSamples[mLastDataSampleIndex + + pointerIndex * NUM_SAMPLE_DATA + SAMPLE_X] + mXOffset; } /** @@ -789,7 +836,8 @@ public final class MotionEvent implements Parcelable { * (the first pointer that is down) to {@link #getPointerCount()}-1. */ public final float getY(int pointerIndex) { - return mDataSamples[(pointerIndex*NUM_SAMPLE_DATA) + SAMPLE_Y]; + return mDataSamples[mLastDataSampleIndex + + pointerIndex * NUM_SAMPLE_DATA + SAMPLE_Y] + mYOffset; } /** @@ -804,7 +852,8 @@ public final class MotionEvent implements Parcelable { * (the first pointer that is down) to {@link #getPointerCount()}-1. */ public final float getPressure(int pointerIndex) { - return mDataSamples[(pointerIndex*NUM_SAMPLE_DATA) + SAMPLE_PRESSURE]; + return mDataSamples[mLastDataSampleIndex + + pointerIndex * NUM_SAMPLE_DATA + SAMPLE_PRESSURE]; } /** @@ -820,7 +869,95 @@ public final class MotionEvent implements Parcelable { * (the first pointer that is down) to {@link #getPointerCount()}-1. */ public final float getSize(int pointerIndex) { - return mDataSamples[(pointerIndex*NUM_SAMPLE_DATA) + SAMPLE_SIZE]; + return mDataSamples[mLastDataSampleIndex + + pointerIndex * NUM_SAMPLE_DATA + SAMPLE_SIZE]; + } + + /** + * Returns the length of the major axis of an ellipse that describes the touch + * area at the point of contact for the given pointer + * <em>index</em> (use {@link #getPointerId(int)} to find the pointer + * identifier for this index). + * @param pointerIndex Raw index of pointer to retrieve. Value may be from 0 + * (the first pointer that is down) to {@link #getPointerCount()}-1. + */ + public final float getTouchMajor(int pointerIndex) { + return mDataSamples[mLastDataSampleIndex + + pointerIndex * NUM_SAMPLE_DATA + SAMPLE_TOUCH_MAJOR]; + } + + /** + * Returns the length of the minor axis of an ellipse that describes the touch + * area at the point of contact for the given pointer + * <em>index</em> (use {@link #getPointerId(int)} to find the pointer + * identifier for this index). + * @param pointerIndex Raw index of pointer to retrieve. Value may be from 0 + * (the first pointer that is down) to {@link #getPointerCount()}-1. + */ + public final float getTouchMinor(int pointerIndex) { + return mDataSamples[mLastDataSampleIndex + + pointerIndex * NUM_SAMPLE_DATA + SAMPLE_TOUCH_MINOR]; + } + + /** + * Returns the length of the major axis of an ellipse that describes the size of + * the approaching tool for the given pointer + * <em>index</em> (use {@link #getPointerId(int)} to find the pointer + * identifier for this index). + * The tool area represents the estimated size of the finger or pen that is + * touching the device independent of its actual touch area at the point of contact. + * @param pointerIndex Raw index of pointer to retrieve. Value may be from 0 + * (the first pointer that is down) to {@link #getPointerCount()}-1. + */ + public final float getToolMajor(int pointerIndex) { + return mDataSamples[mLastDataSampleIndex + + pointerIndex * NUM_SAMPLE_DATA + SAMPLE_TOOL_MAJOR]; + } + + /** + * Returns the length of the minor axis of an ellipse that describes the size of + * the approaching tool for the given pointer + * <em>index</em> (use {@link #getPointerId(int)} to find the pointer + * identifier for this index). + * The tool area represents the estimated size of the finger or pen that is + * touching the device independent of its actual touch area at the point of contact. + * @param pointerIndex Raw index of pointer to retrieve. Value may be from 0 + * (the first pointer that is down) to {@link #getPointerCount()}-1. + */ + public final float getToolMinor(int pointerIndex) { + return mDataSamples[mLastDataSampleIndex + + pointerIndex * NUM_SAMPLE_DATA + SAMPLE_TOOL_MINOR]; + } + + /** + * Returns the orientation of the touch area and tool area in radians clockwise from vertical + * for the given pointer <em>index</em> (use {@link #getPointerId(int)} to find the pointer + * identifier for this index). + * An angle of 0 degrees indicates that the major axis of contact is oriented + * upwards, is perfectly circular or is of unknown orientation. A positive angle + * indicates that the major axis of contact is oriented to the right. A negative angle + * indicates that the major axis of contact is oriented to the left. + * The full range is from -PI/2 radians (finger pointing fully left) to PI/2 radians + * (finger pointing fully right). + * @param pointerIndex Raw index of pointer to retrieve. Value may be from 0 + * (the first pointer that is down) to {@link #getPointerCount()}-1. + */ + public final float getOrientation(int pointerIndex) { + return mDataSamples[mLastDataSampleIndex + + pointerIndex * NUM_SAMPLE_DATA + SAMPLE_ORIENTATION]; + } + + /** + * Populates a {@link PointerCoords} object with pointer coordinate data for + * the specified pointer index. + * + * @param pointerIndex Raw index of pointer to retrieve. Value may be from 0 + * (the first pointer that is down) to {@link #getPointerCount()}-1. + * @param outPointerCoords The pointer coordinate object to populate. + */ + public final void getPointerCoords(int pointerIndex, PointerCoords outPointerCoords) { + final int sampleIndex = mLastDataSampleIndex + pointerIndex * NUM_SAMPLE_DATA; + getPointerCoordsAtSampleIndex(sampleIndex, outPointerCoords); } /** @@ -844,9 +981,9 @@ public final class MotionEvent implements Parcelable { * and views. */ public final float getRawX() { - return mRawX; + return mDataSamples[mLastDataSampleIndex + SAMPLE_X]; } - + /** * Returns the original raw Y coordinate of this event. For touch * events on the screen, this is the original location of the event @@ -854,7 +991,7 @@ public final class MotionEvent implements Parcelable { * and views. */ public final float getRawY() { - return mRawY; + return mDataSamples[mLastDataSampleIndex + SAMPLE_Y]; } /** @@ -886,7 +1023,7 @@ public final class MotionEvent implements Parcelable { * @return Returns the number of historical points in the event. */ public final int getHistorySize() { - return mNumSamples - 1; + return mLastEventTimeNanoSampleIndex; } /** @@ -900,7 +1037,7 @@ public final class MotionEvent implements Parcelable { * @see #getEventTime */ public final long getHistoricalEventTime(int pos) { - return mTimeSamples[pos + 1]; + return mEventTimeNanoSamples[pos] / MS_PER_NS; } /** @@ -908,7 +1045,7 @@ public final class MotionEvent implements Parcelable { * arbitrary pointer identifier). */ public final float getHistoricalX(int pos) { - return mDataSamples[((pos + 1) * NUM_SAMPLE_DATA * mNumPointers) + SAMPLE_X]; + return mDataSamples[pos * mNumPointers * NUM_SAMPLE_DATA + SAMPLE_X] + mXOffset; } /** @@ -916,7 +1053,7 @@ public final class MotionEvent implements Parcelable { * arbitrary pointer identifier). */ public final float getHistoricalY(int pos) { - return mDataSamples[((pos + 1) * NUM_SAMPLE_DATA * mNumPointers) + SAMPLE_Y]; + return mDataSamples[pos * mNumPointers * NUM_SAMPLE_DATA + SAMPLE_Y] + mYOffset; } /** @@ -924,7 +1061,7 @@ public final class MotionEvent implements Parcelable { * arbitrary pointer identifier). */ public final float getHistoricalPressure(int pos) { - return mDataSamples[((pos + 1) * NUM_SAMPLE_DATA * mNumPointers) + SAMPLE_PRESSURE]; + return mDataSamples[pos * mNumPointers * NUM_SAMPLE_DATA + SAMPLE_PRESSURE]; } /** @@ -932,10 +1069,50 @@ public final class MotionEvent implements Parcelable { * arbitrary pointer identifier). */ public final float getHistoricalSize(int pos) { - return mDataSamples[((pos + 1) * NUM_SAMPLE_DATA * mNumPointers) + SAMPLE_SIZE]; + return mDataSamples[pos * mNumPointers * NUM_SAMPLE_DATA + SAMPLE_SIZE]; + } + + /** + * {@link #getHistoricalTouchMajor(int)} for the first pointer index (may be an + * arbitrary pointer identifier). + */ + public final float getHistoricalTouchMajor(int pos) { + return mDataSamples[pos * mNumPointers * NUM_SAMPLE_DATA + SAMPLE_TOUCH_MAJOR]; + } + + /** + * {@link #getHistoricalTouchMinor(int)} for the first pointer index (may be an + * arbitrary pointer identifier). + */ + public final float getHistoricalTouchMinor(int pos) { + return mDataSamples[pos * mNumPointers * NUM_SAMPLE_DATA + SAMPLE_TOUCH_MINOR]; + } + + /** + * {@link #getHistoricalToolMajor(int)} for the first pointer index (may be an + * arbitrary pointer identifier). + */ + public final float getHistoricalToolMajor(int pos) { + return mDataSamples[pos * mNumPointers * NUM_SAMPLE_DATA + SAMPLE_TOOL_MAJOR]; } /** + * {@link #getHistoricalToolMinor(int)} for the first pointer index (may be an + * arbitrary pointer identifier). + */ + public final float getHistoricalToolMinor(int pos) { + return mDataSamples[pos * mNumPointers * NUM_SAMPLE_DATA + SAMPLE_TOOL_MINOR]; + } + + /** + * {@link #getHistoricalOrientation(int)} for the first pointer index (may be an + * arbitrary pointer identifier). + */ + public final float getHistoricalOrientation(int pos) { + return mDataSamples[pos * mNumPointers * NUM_SAMPLE_DATA + SAMPLE_ORIENTATION]; + } + + /** * Returns a historical X coordinate, as per {@link #getX(int)}, that * occurred between this event and the previous event for the given pointer. * Only applies to ACTION_MOVE events. @@ -949,8 +1126,8 @@ public final class MotionEvent implements Parcelable { * @see #getX */ public final float getHistoricalX(int pointerIndex, int pos) { - return mDataSamples[((pos + 1) * NUM_SAMPLE_DATA * mNumPointers) - + (pointerIndex * NUM_SAMPLE_DATA) + SAMPLE_X]; + return mDataSamples[(pos * mNumPointers + pointerIndex) + * NUM_SAMPLE_DATA + SAMPLE_X] + mXOffset; } /** @@ -967,8 +1144,8 @@ public final class MotionEvent implements Parcelable { * @see #getY */ public final float getHistoricalY(int pointerIndex, int pos) { - return mDataSamples[((pos + 1) * NUM_SAMPLE_DATA * mNumPointers) - + (pointerIndex * NUM_SAMPLE_DATA) + SAMPLE_Y]; + return mDataSamples[(pos * mNumPointers + pointerIndex) + * NUM_SAMPLE_DATA + SAMPLE_Y] + mYOffset; } /** @@ -985,8 +1162,8 @@ public final class MotionEvent implements Parcelable { * @see #getPressure */ public final float getHistoricalPressure(int pointerIndex, int pos) { - return mDataSamples[((pos + 1) * NUM_SAMPLE_DATA * mNumPointers) - + (pointerIndex * NUM_SAMPLE_DATA) + SAMPLE_PRESSURE]; + return mDataSamples[(pos * mNumPointers + pointerIndex) + * NUM_SAMPLE_DATA + SAMPLE_PRESSURE]; } /** @@ -1003,21 +1180,123 @@ public final class MotionEvent implements Parcelable { * @see #getSize */ public final float getHistoricalSize(int pointerIndex, int pos) { - return mDataSamples[((pos + 1) * NUM_SAMPLE_DATA * mNumPointers) - + (pointerIndex * NUM_SAMPLE_DATA) + SAMPLE_SIZE]; + return mDataSamples[(pos * mNumPointers + pointerIndex) + * NUM_SAMPLE_DATA + SAMPLE_SIZE]; + } + + /** + * Returns a historical touch major axis coordinate, as per {@link #getTouchMajor(int)}, that + * occurred between this event and the previous event for the given pointer. + * Only applies to ACTION_MOVE events. + * + * @param pointerIndex Raw index of pointer to retrieve. Value may be from 0 + * (the first pointer that is down) to {@link #getPointerCount()}-1. + * @param pos Which historical value to return; must be less than + * {@link #getHistorySize} + * + * @see #getHistorySize + * @see #getTouchMajor + */ + public final float getHistoricalTouchMajor(int pointerIndex, int pos) { + return mDataSamples[(pos * mNumPointers + pointerIndex) + * NUM_SAMPLE_DATA + SAMPLE_TOUCH_MAJOR]; } /** - * Return the id for the device that this event came from. An id of - * zero indicates that the event didn't come from a physical device; other - * numbers are arbitrary and you shouldn't depend on the values. + * Returns a historical touch minor axis coordinate, as per {@link #getTouchMinor(int)}, that + * occurred between this event and the previous event for the given pointer. + * Only applies to ACTION_MOVE events. + * + * @param pointerIndex Raw index of pointer to retrieve. Value may be from 0 + * (the first pointer that is down) to {@link #getPointerCount()}-1. + * @param pos Which historical value to return; must be less than + * {@link #getHistorySize} + * + * @see #getHistorySize + * @see #getTouchMinor + */ + public final float getHistoricalTouchMinor(int pointerIndex, int pos) { + return mDataSamples[(pos * mNumPointers + pointerIndex) + * NUM_SAMPLE_DATA + SAMPLE_TOUCH_MINOR]; + } + + /** + * Returns a historical tool major axis coordinate, as per {@link #getToolMajor(int)}, that + * occurred between this event and the previous event for the given pointer. + * Only applies to ACTION_MOVE events. + * + * @param pointerIndex Raw index of pointer to retrieve. Value may be from 0 + * (the first pointer that is down) to {@link #getPointerCount()}-1. + * @param pos Which historical value to return; must be less than + * {@link #getHistorySize} + * + * @see #getHistorySize + * @see #getToolMajor */ - public final int getDeviceId() { - return mDeviceId; + public final float getHistoricalToolMajor(int pointerIndex, int pos) { + return mDataSamples[(pos * mNumPointers + pointerIndex) + * NUM_SAMPLE_DATA + SAMPLE_TOOL_MAJOR]; } /** - * Returns a bitfield indicating which edges, if any, where touched by this + * Returns a historical tool minor axis coordinate, as per {@link #getToolMinor(int)}, that + * occurred between this event and the previous event for the given pointer. + * Only applies to ACTION_MOVE events. + * + * @param pointerIndex Raw index of pointer to retrieve. Value may be from 0 + * (the first pointer that is down) to {@link #getPointerCount()}-1. + * @param pos Which historical value to return; must be less than + * {@link #getHistorySize} + * + * @see #getHistorySize + * @see #getToolMinor + */ + public final float getHistoricalToolMinor(int pointerIndex, int pos) { + return mDataSamples[(pos * mNumPointers + pointerIndex) + * NUM_SAMPLE_DATA + SAMPLE_TOOL_MINOR]; + } + + /** + * Returns a historical orientation coordinate, as per {@link #getOrientation(int)}, that + * occurred between this event and the previous event for the given pointer. + * Only applies to ACTION_MOVE events. + * + * @param pointerIndex Raw index of pointer to retrieve. Value may be from 0 + * (the first pointer that is down) to {@link #getPointerCount()}-1. + * @param pos Which historical value to return; must be less than + * {@link #getHistorySize} + * + * @see #getHistorySize + * @see #getOrientation + */ + public final float getHistoricalOrientation(int pointerIndex, int pos) { + return mDataSamples[(pos * mNumPointers + pointerIndex) + * NUM_SAMPLE_DATA + SAMPLE_ORIENTATION]; + } + + /** + * Populates a {@link PointerCoords} object with historical pointer coordinate data, + * as per {@link #getPointerCoords}, that occurred between this event and the previous + * event for the given pointer. + * Only applies to ACTION_MOVE events. + * + * @param pointerIndex Raw index of pointer to retrieve. Value may be from 0 + * (the first pointer that is down) to {@link #getPointerCount()}-1. + * @param pos Which historical value to return; must be less than + * {@link #getHistorySize} + * @param outPointerCoords The pointer coordinate object to populate. + * + * @see #getHistorySize + * @see #getPointerCoords + */ + public final void getHistoricalPointerCoords(int pointerIndex, int pos, + PointerCoords outPointerCoords) { + final int sampleIndex = (pos * mNumPointers + pointerIndex) * NUM_SAMPLE_DATA; + getPointerCoordsAtSampleIndex(sampleIndex, outPointerCoords); + } + + /** + * Returns a bitfield indicating which edges, if any, were touched by this * MotionEvent. For touch events, clients can use this to determine if the * user's finger was touching the edge of the display. * @@ -1032,7 +1311,7 @@ public final class MotionEvent implements Parcelable { /** - * Sets the bitfield indicating which edges, if any, where touched by this + * Sets the bitfield indicating which edges, if any, were touched by this * MotionEvent. * * @see #getEdgeFlags() @@ -1054,12 +1333,8 @@ public final class MotionEvent implements Parcelable { * @param deltaY Amount to add to the current Y coordinate of the event. */ public final void offsetLocation(float deltaX, float deltaY) { - final int N = mNumPointers*mNumSamples*4; - final float[] pos = mDataSamples; - for (int i=0; i<N; i+=NUM_SAMPLE_DATA) { - pos[i+SAMPLE_X] += deltaX; - pos[i+SAMPLE_Y] += deltaY; - } + mXOffset += deltaX; + mYOffset += deltaY; } /** @@ -1070,20 +1345,91 @@ public final class MotionEvent implements Parcelable { * @param y New absolute Y location. */ public final void setLocation(float x, float y) { - float deltaX = x-mDataSamples[SAMPLE_X]; - float deltaY = y-mDataSamples[SAMPLE_Y]; - if (deltaX != 0 || deltaY != 0) { - offsetLocation(deltaX, deltaY); + final float[] dataSamples = mDataSamples; + final int lastDataSampleIndex = mLastDataSampleIndex; + mXOffset = x - dataSamples[lastDataSampleIndex + SAMPLE_X]; + mYOffset = y - dataSamples[lastDataSampleIndex + SAMPLE_Y]; + } + + private final void getPointerCoordsAtSampleIndex(int sampleIndex, + PointerCoords outPointerCoords) { + final float[] dataSamples = mDataSamples; + outPointerCoords.x = dataSamples[sampleIndex + SAMPLE_X] + mXOffset; + outPointerCoords.y = dataSamples[sampleIndex + SAMPLE_Y] + mYOffset; + outPointerCoords.pressure = dataSamples[sampleIndex + SAMPLE_PRESSURE]; + outPointerCoords.size = dataSamples[sampleIndex + SAMPLE_SIZE]; + outPointerCoords.touchMajor = dataSamples[sampleIndex + SAMPLE_TOUCH_MAJOR]; + outPointerCoords.touchMinor = dataSamples[sampleIndex + SAMPLE_TOUCH_MINOR]; + outPointerCoords.toolMajor = dataSamples[sampleIndex + SAMPLE_TOOL_MAJOR]; + outPointerCoords.toolMinor = dataSamples[sampleIndex + SAMPLE_TOOL_MINOR]; + outPointerCoords.orientation = dataSamples[sampleIndex + SAMPLE_ORIENTATION]; + } + + private final void setPointerCoordsAtSampleIndex(int sampleIndex, + PointerCoords[] pointerCoords) { + final int numPointers = mNumPointers; + for (int i = 0; i < numPointers; i++) { + setPointerCoordsAtSampleIndex(sampleIndex, pointerCoords[i]); + sampleIndex += NUM_SAMPLE_DATA; + } + } + + private final void setPointerCoordsAtSampleIndex(int sampleIndex, + PointerCoords pointerCoords) { + final float[] dataSamples = mDataSamples; + dataSamples[sampleIndex + SAMPLE_X] = pointerCoords.x - mXOffset; + dataSamples[sampleIndex + SAMPLE_Y] = pointerCoords.y - mYOffset; + dataSamples[sampleIndex + SAMPLE_PRESSURE] = pointerCoords.pressure; + dataSamples[sampleIndex + SAMPLE_SIZE] = pointerCoords.size; + dataSamples[sampleIndex + SAMPLE_TOUCH_MAJOR] = pointerCoords.touchMajor; + dataSamples[sampleIndex + SAMPLE_TOUCH_MINOR] = pointerCoords.touchMinor; + dataSamples[sampleIndex + SAMPLE_TOOL_MAJOR] = pointerCoords.toolMajor; + dataSamples[sampleIndex + SAMPLE_TOOL_MINOR] = pointerCoords.toolMinor; + dataSamples[sampleIndex + SAMPLE_ORIENTATION] = pointerCoords.orientation; + } + + private final void setPointerCoordsAtSampleIndex(int sampleIndex, + float x, float y, float pressure, float size) { + final float[] dataSamples = mDataSamples; + dataSamples[sampleIndex + SAMPLE_X] = x - mXOffset; + dataSamples[sampleIndex + SAMPLE_Y] = y - mYOffset; + dataSamples[sampleIndex + SAMPLE_PRESSURE] = pressure; + dataSamples[sampleIndex + SAMPLE_SIZE] = size; + dataSamples[sampleIndex + SAMPLE_TOUCH_MAJOR] = pressure; + dataSamples[sampleIndex + SAMPLE_TOUCH_MINOR] = pressure; + dataSamples[sampleIndex + SAMPLE_TOOL_MAJOR] = size; + dataSamples[sampleIndex + SAMPLE_TOOL_MINOR] = size; + dataSamples[sampleIndex + SAMPLE_ORIENTATION] = 0; + } + + private final void incrementNumSamplesAndReserveStorage(int dataSampleStride) { + if (mNumSamples == mEventTimeNanoSamples.length) { + long[] newEventTimeNanoSamples = new long[mNumSamples + BASE_AVAIL_SAMPLES]; + System.arraycopy(mEventTimeNanoSamples, 0, newEventTimeNanoSamples, 0, mNumSamples); + mEventTimeNanoSamples = newEventTimeNanoSamples; + } + + int nextDataSampleIndex = mLastDataSampleIndex + dataSampleStride; + if (nextDataSampleIndex + dataSampleStride > mDataSamples.length) { + float[] newDataSamples = new float[nextDataSampleIndex + + BASE_AVAIL_SAMPLES * dataSampleStride]; + System.arraycopy(mDataSamples, 0, newDataSamples, 0, nextDataSampleIndex); + mDataSamples = newDataSamples; } + + mLastEventTimeNanoSampleIndex = mNumSamples; + mLastDataSampleIndex = nextDataSampleIndex; + mNumSamples += 1; } /** * Add a new movement to the batch of movements in this event. The event's - * current location, position and size is updated to the new values. In - * the future, the current values in the event will be added to a list of - * historic values. + * current location, position and size is updated to the new values. + * The current values in the event are added to a list of historical values. + * + * Only applies to {@link #ACTION_MOVE} events. * - * @param eventTime The time stamp for this data. + * @param eventTime The time stamp (in ms) for this data. * @param x The new X position. * @param y The new Y position. * @param pressure The new pressure. @@ -1092,103 +1438,33 @@ public final class MotionEvent implements Parcelable { */ public final void addBatch(long eventTime, float x, float y, float pressure, float size, int metaState) { - float[] data = mDataSamples; - long[] times = mTimeSamples; + incrementNumSamplesAndReserveStorage(NUM_SAMPLE_DATA); - final int NP = mNumPointers; - final int NS = mNumSamples; - final int NI = NP*NS; - final int ND = NI * NUM_SAMPLE_DATA; - if (data.length <= ND) { - final int NEW_ND = ND + (NP * (BASE_AVAIL_SAMPLES * NUM_SAMPLE_DATA)); - float[] newData = new float[NEW_ND]; - System.arraycopy(data, 0, newData, 0, ND); - mDataSamples = data = newData; - } - if (times.length <= NS) { - final int NEW_NS = NS + BASE_AVAIL_SAMPLES; - long[] newHistoryTimes = new long[NEW_NS]; - System.arraycopy(times, 0, newHistoryTimes, 0, NS); - mTimeSamples = times = newHistoryTimes; - } - - times[NS] = times[0]; - times[0] = eventTime; + mEventTimeNanoSamples[mLastEventTimeNanoSampleIndex] = eventTime * MS_PER_NS; + setPointerCoordsAtSampleIndex(mLastDataSampleIndex, x, y, pressure, size); - final int pos = NS*NUM_SAMPLE_DATA; - data[pos+SAMPLE_X] = data[SAMPLE_X]; - data[pos+SAMPLE_Y] = data[SAMPLE_Y]; - data[pos+SAMPLE_PRESSURE] = data[SAMPLE_PRESSURE]; - data[pos+SAMPLE_SIZE] = data[SAMPLE_SIZE]; - data[SAMPLE_X] = x; - data[SAMPLE_Y] = y; - data[SAMPLE_PRESSURE] = pressure; - data[SAMPLE_SIZE] = size; - mNumSamples = NS+1; - - mRawX = x; - mRawY = y; mMetaState |= metaState; } /** - * Add a new movement to the batch of movements in this event. The - * input data must contain (NUM_SAMPLE_DATA * {@link #getPointerCount()}) - * samples of data. + * Add a new movement to the batch of movements in this event. The event's + * current location, position and size is updated to the new values. + * The current values in the event are added to a list of historical values. + * + * Only applies to {@link #ACTION_MOVE} events. * - * @param eventTime The time stamp for this data. - * @param inData The actual data. + * @param eventTime The time stamp (in ms) for this data. + * @param pointerCoords The new pointer coordinates. * @param metaState Meta key state. - * - * @hide */ - public final void addBatch(long eventTime, float[] inData, int metaState) { - float[] data = mDataSamples; - long[] times = mTimeSamples; - - final int NP = mNumPointers; - final int NS = mNumSamples; - final int NI = NP*NS; - final int ND = NI * NUM_SAMPLE_DATA; - if (data.length < (ND+(NP*NUM_SAMPLE_DATA))) { - final int NEW_ND = ND + (NP * (BASE_AVAIL_SAMPLES * NUM_SAMPLE_DATA)); - float[] newData = new float[NEW_ND]; - System.arraycopy(data, 0, newData, 0, ND); - mDataSamples = data = newData; - } - if (times.length < (NS+1)) { - final int NEW_NS = NS + BASE_AVAIL_SAMPLES; - long[] newHistoryTimes = new long[NEW_NS]; - System.arraycopy(times, 0, newHistoryTimes, 0, NS); - mTimeSamples = times = newHistoryTimes; - } - - times[NS] = times[0]; - times[0] = eventTime; + public final void addBatch(long eventTime, PointerCoords[] pointerCoords, int metaState) { + final int dataSampleStride = mNumPointers * NUM_SAMPLE_DATA; + incrementNumSamplesAndReserveStorage(dataSampleStride); - System.arraycopy(data, 0, data, ND, mNumPointers*NUM_SAMPLE_DATA); - System.arraycopy(inData, 0, data, 0, mNumPointers*NUM_SAMPLE_DATA); + mEventTimeNanoSamples[mLastEventTimeNanoSampleIndex] = eventTime * MS_PER_NS; + setPointerCoordsAtSampleIndex(mLastDataSampleIndex, pointerCoords); - mNumSamples = NS+1; - - mRawX = inData[SAMPLE_X]; - mRawY = inData[SAMPLE_Y]; mMetaState |= metaState; - - if (DEBUG_POINTERS) { - StringBuilder sb = new StringBuilder(128); - sb.append("Add:"); - for (int i=0; i<mNumPointers; i++) { - sb.append(" #"); - sb.append(mPointerIdentifiers[i]); - sb.append("("); - sb.append(mDataSamples[(i*NUM_SAMPLE_DATA) + SAMPLE_X]); - sb.append(","); - sb.append(mDataSamples[(i*NUM_SAMPLE_DATA) + SAMPLE_Y]); - sb.append(")"); - } - Log.v("MotionEvent", sb.toString()); - } } @Override @@ -1201,9 +1477,8 @@ public final class MotionEvent implements Parcelable { public static final Parcelable.Creator<MotionEvent> CREATOR = new Parcelable.Creator<MotionEvent>() { public MotionEvent createFromParcel(Parcel in) { - MotionEvent ev = obtain(); - ev.readFromParcel(in); - return ev; + in.readInt(); // skip token, we already know this is a MotionEvent + return MotionEvent.createFromParcelBody(in); } public MotionEvent[] newArray(int size) { @@ -1211,84 +1486,187 @@ public final class MotionEvent implements Parcelable { } }; - public int describeContents() { - return 0; - } + /** @hide */ + public static MotionEvent createFromParcelBody(Parcel in) { + final int NP = in.readInt(); + final int NS = in.readInt(); + final int NI = NP * NS * NUM_SAMPLE_DATA; + + MotionEvent ev = obtain(NP, NS); + ev.mNumPointers = NP; + ev.mNumSamples = NS; + + ev.readBaseFromParcel(in); + + ev.mDownTimeNano = in.readLong(); + ev.mAction = in.readInt(); + ev.mXOffset = in.readFloat(); + ev.mYOffset = in.readFloat(); + ev.mXPrecision = in.readFloat(); + ev.mYPrecision = in.readFloat(); + ev.mEdgeFlags = in.readInt(); + ev.mMetaState = in.readInt(); + ev.mFlags = in.readInt(); + + final int[] pointerIdentifiers = ev.mPointerIdentifiers; + for (int i = 0; i < NP; i++) { + pointerIdentifiers[i] = in.readInt(); + } + + final long[] eventTimeNanoSamples = ev.mEventTimeNanoSamples; + for (int i = 0; i < NS; i++) { + eventTimeNanoSamples[i] = in.readLong(); + } + final float[] dataSamples = ev.mDataSamples; + for (int i = 0; i < NI; i++) { + dataSamples[i] = in.readFloat(); + } + + ev.mLastEventTimeNanoSampleIndex = NS - 1; + ev.mLastDataSampleIndex = (NS - 1) * NP * NUM_SAMPLE_DATA; + return ev; + } + public void writeToParcel(Parcel out, int flags) { - out.writeLong(mDownTime); - out.writeLong(mEventTimeNano); - out.writeInt(mAction); - out.writeInt(mMetaState); - out.writeFloat(mRawX); - out.writeFloat(mRawY); + out.writeInt(PARCEL_TOKEN_MOTION_EVENT); + final int NP = mNumPointers; - out.writeInt(NP); final int NS = mNumSamples; + final int NI = NP * NS * NUM_SAMPLE_DATA; + + out.writeInt(NP); out.writeInt(NS); - final int NI = NP*NS; - if (NI > 0) { - int i; - int[] state = mPointerIdentifiers; - for (i=0; i<NP; i++) { - out.writeInt(state[i]); - } - final int ND = NI*NUM_SAMPLE_DATA; - float[] history = mDataSamples; - for (i=0; i<ND; i++) { - out.writeFloat(history[i]); - } - long[] times = mTimeSamples; - for (i=0; i<NS; i++) { - out.writeLong(times[i]); - } - } + + writeBaseToParcel(out); + + out.writeLong(mDownTimeNano); + out.writeInt(mAction); + out.writeFloat(mXOffset); + out.writeFloat(mYOffset); out.writeFloat(mXPrecision); out.writeFloat(mYPrecision); - out.writeInt(mDeviceId); out.writeInt(mEdgeFlags); - } + out.writeInt(mMetaState); + out.writeInt(mFlags); + + final int[] pointerIdentifiers = mPointerIdentifiers; + for (int i = 0; i < NP; i++) { + out.writeInt(pointerIdentifiers[i]); + } + + final long[] eventTimeNanoSamples = mEventTimeNanoSamples; + for (int i = 0; i < NS; i++) { + out.writeLong(eventTimeNanoSamples[i]); + } - private void readFromParcel(Parcel in) { - mDownTime = in.readLong(); - mEventTimeNano = in.readLong(); - mAction = in.readInt(); - mMetaState = in.readInt(); - mRawX = in.readFloat(); - mRawY = in.readFloat(); - final int NP = in.readInt(); - mNumPointers = NP; - final int NS = in.readInt(); - mNumSamples = NS; - final int NI = NP*NS; - if (NI > 0) { - int[] ids = mPointerIdentifiers; - if (ids.length < NP) { - mPointerIdentifiers = ids = new int[NP]; - } - for (int i=0; i<NP; i++) { - ids[i] = in.readInt(); - } - float[] history = mDataSamples; - final int ND = NI*NUM_SAMPLE_DATA; - if (history.length < ND) { - mDataSamples = history = new float[ND]; - } - for (int i=0; i<ND; i++) { - history[i] = in.readFloat(); - } - long[] times = mTimeSamples; - if (times == null || times.length < NS) { - mTimeSamples = times = new long[NS]; - } - for (int i=0; i<NS; i++) { - times[i] = in.readLong(); - } + final float[] dataSamples = mDataSamples; + for (int i = 0; i < NI; i++) { + out.writeFloat(dataSamples[i]); } - mXPrecision = in.readFloat(); - mYPrecision = in.readFloat(); - mDeviceId = in.readInt(); - mEdgeFlags = in.readInt(); } - + + /** + * Transfer object for pointer coordinates. + * + * Objects of this type can be used to manufacture new {@link MotionEvent} objects + * and to query pointer coordinate information in bulk. + * + * Refer to {@link InputDevice} for information about how different kinds of + * input devices and sources represent pointer coordinates. + */ + public static final class PointerCoords { + /** + * The X coordinate of the pointer movement. + * The interpretation varies by input source and may represent the position of + * the center of the contact area, a relative displacement in device-specific units + * or something else. + */ + public float x; + + /** + * The Y coordinate of the pointer movement. + * The interpretation varies by input source and may represent the position of + * the center of the contact area, a relative displacement in device-specific units + * or something else. + */ + public float y; + + /** + * A scaled value that describes the pressure applied to the pointer. + * The pressure generally ranges from 0 (no pressure at all) to 1 (normal pressure), + * however values higher than 1 may be generated depending on the calibration of + * the input device. + */ + public float pressure; + + /** + * A scaled value of the approximate size of the pointer touch area. + * This represents some approximation of the area of the screen being + * pressed; the actual value in pixels corresponding to the + * touch is normalized with the device specific range of values + * and scaled to a value between 0 and 1. The value of size can be used to + * determine fat touch events. + */ + public float size; + + /** + * The length of the major axis of an ellipse that describes the touch area at + * the point of contact. + */ + public float touchMajor; + + /** + * The length of the minor axis of an ellipse that describes the touch area at + * the point of contact. + */ + public float touchMinor; + + /** + * The length of the major axis of an ellipse that describes the size of + * the approaching tool. + * The tool area represents the estimated size of the finger or pen that is + * touching the device independent of its actual touch area at the point of contact. + */ + public float toolMajor; + + /** + * The length of the minor axis of an ellipse that describes the size of + * the approaching tool. + * The tool area represents the estimated size of the finger or pen that is + * touching the device independent of its actual touch area at the point of contact. + */ + public float toolMinor; + + /** + * The orientation of the touch area and tool area in radians clockwise from vertical. + * An angle of 0 degrees indicates that the major axis of contact is oriented + * upwards, is perfectly circular or is of unknown orientation. A positive angle + * indicates that the major axis of contact is oriented to the right. A negative angle + * indicates that the major axis of contact is oriented to the left. + * The full range is from -PI/2 radians (finger pointing fully left) to PI/2 radians + * (finger pointing fully right). + */ + public float orientation; + + /* + private static final float PI_4 = (float) (Math.PI / 4); + + public float getTouchWidth() { + return Math.abs(orientation) > PI_4 ? touchMajor : touchMinor; + } + + public float getTouchHeight() { + return Math.abs(orientation) > PI_4 ? touchMinor : touchMajor; + } + + public float getToolWidth() { + return Math.abs(orientation) > PI_4 ? toolMajor : toolMinor; + } + + public float getToolHeight() { + return Math.abs(orientation) > PI_4 ? toolMinor : toolMajor; + } + */ + } } diff --git a/core/java/android/view/RawInputEvent.java b/core/java/android/view/RawInputEvent.java deleted file mode 100644 index 3bbfea8a975f..000000000000 --- a/core/java/android/view/RawInputEvent.java +++ /dev/null @@ -1,201 +0,0 @@ -/* - * Copyright (C) 2008 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.view; - -/** - * @hide - * This really belongs in services.jar; WindowManagerPolicy should go there too. - */ -public class RawInputEvent { - // Event class as defined by EventHub. - public static final int CLASS_KEYBOARD = 0x00000001; - public static final int CLASS_ALPHAKEY = 0x00000002; - public static final int CLASS_TOUCHSCREEN = 0x00000004; - public static final int CLASS_TRACKBALL = 0x00000008; - public static final int CLASS_TOUCHSCREEN_MT = 0x00000010; - public static final int CLASS_DPAD = 0x00000020; - - // More special classes for QueuedEvent below. - public static final int CLASS_CONFIGURATION_CHANGED = 0x10000000; - - // Event types. - - public static final int EV_SYN = 0x00; - public static final int EV_KEY = 0x01; - public static final int EV_REL = 0x02; - public static final int EV_ABS = 0x03; - public static final int EV_MSC = 0x04; - public static final int EV_SW = 0x05; - public static final int EV_LED = 0x11; - public static final int EV_SND = 0x12; - public static final int EV_REP = 0x14; - public static final int EV_FF = 0x15; - public static final int EV_PWR = 0x16; - public static final int EV_FF_STATUS = 0x17; - - // Platform-specific event types. - - public static final int EV_DEVICE_ADDED = 0x10000000; - public static final int EV_DEVICE_REMOVED = 0x20000000; - - // Special key (EV_KEY) scan codes for pointer buttons. - - public static final int BTN_FIRST = 0x100; - - public static final int BTN_MISC = 0x100; - public static final int BTN_0 = 0x100; - public static final int BTN_1 = 0x101; - public static final int BTN_2 = 0x102; - public static final int BTN_3 = 0x103; - public static final int BTN_4 = 0x104; - public static final int BTN_5 = 0x105; - public static final int BTN_6 = 0x106; - public static final int BTN_7 = 0x107; - public static final int BTN_8 = 0x108; - public static final int BTN_9 = 0x109; - - public static final int BTN_MOUSE = 0x110; - public static final int BTN_LEFT = 0x110; - public static final int BTN_RIGHT = 0x111; - public static final int BTN_MIDDLE = 0x112; - public static final int BTN_SIDE = 0x113; - public static final int BTN_EXTRA = 0x114; - public static final int BTN_FORWARD = 0x115; - public static final int BTN_BACK = 0x116; - public static final int BTN_TASK = 0x117; - - public static final int BTN_JOYSTICK = 0x120; - public static final int BTN_TRIGGER = 0x120; - public static final int BTN_THUMB = 0x121; - public static final int BTN_THUMB2 = 0x122; - public static final int BTN_TOP = 0x123; - public static final int BTN_TOP2 = 0x124; - public static final int BTN_PINKIE = 0x125; - public static final int BTN_BASE = 0x126; - public static final int BTN_BASE2 = 0x127; - public static final int BTN_BASE3 = 0x128; - public static final int BTN_BASE4 = 0x129; - public static final int BTN_BASE5 = 0x12a; - public static final int BTN_BASE6 = 0x12b; - public static final int BTN_DEAD = 0x12f; - - public static final int BTN_GAMEPAD = 0x130; - public static final int BTN_A = 0x130; - public static final int BTN_B = 0x131; - public static final int BTN_C = 0x132; - public static final int BTN_X = 0x133; - public static final int BTN_Y = 0x134; - public static final int BTN_Z = 0x135; - public static final int BTN_TL = 0x136; - public static final int BTN_TR = 0x137; - public static final int BTN_TL2 = 0x138; - public static final int BTN_TR2 = 0x139; - public static final int BTN_SELECT = 0x13a; - public static final int BTN_START = 0x13b; - public static final int BTN_MODE = 0x13c; - public static final int BTN_THUMBL = 0x13d; - public static final int BTN_THUMBR = 0x13e; - - public static final int BTN_DIGI = 0x140; - public static final int BTN_TOOL_PEN = 0x140; - public static final int BTN_TOOL_RUBBER = 0x141; - public static final int BTN_TOOL_BRUSH = 0x142; - public static final int BTN_TOOL_PENCIL = 0x143; - public static final int BTN_TOOL_AIRBRUSH = 0x144; - public static final int BTN_TOOL_FINGER = 0x145; - public static final int BTN_TOOL_MOUSE = 0x146; - public static final int BTN_TOOL_LENS = 0x147; - public static final int BTN_TOUCH = 0x14a; - public static final int BTN_STYLUS = 0x14b; - public static final int BTN_STYLUS2 = 0x14c; - public static final int BTN_TOOL_DOUBLETAP = 0x14d; - public static final int BTN_TOOL_TRIPLETAP = 0x14e; - - public static final int BTN_WHEEL = 0x150; - public static final int BTN_GEAR_DOWN = 0x150; - public static final int BTN_GEAR_UP = 0x151; - - public static final int BTN_LAST = 0x15f; - - // Relative axes (EV_REL) scan codes. - - public static final int REL_X = 0x00; - public static final int REL_Y = 0x01; - public static final int REL_Z = 0x02; - public static final int REL_RX = 0x03; - public static final int REL_RY = 0x04; - public static final int REL_RZ = 0x05; - public static final int REL_HWHEEL = 0x06; - public static final int REL_DIAL = 0x07; - public static final int REL_WHEEL = 0x08; - public static final int REL_MISC = 0x09; - public static final int REL_MAX = 0x0f; - - // Absolute axes (EV_ABS) scan codes. - - public static final int ABS_X = 0x00; - public static final int ABS_Y = 0x01; - public static final int ABS_Z = 0x02; - public static final int ABS_RX = 0x03; - public static final int ABS_RY = 0x04; - public static final int ABS_RZ = 0x05; - public static final int ABS_THROTTLE = 0x06; - public static final int ABS_RUDDER = 0x07; - public static final int ABS_WHEEL = 0x08; - public static final int ABS_GAS = 0x09; - public static final int ABS_BRAKE = 0x0a; - public static final int ABS_HAT0X = 0x10; - public static final int ABS_HAT0Y = 0x11; - public static final int ABS_HAT1X = 0x12; - public static final int ABS_HAT1Y = 0x13; - public static final int ABS_HAT2X = 0x14; - public static final int ABS_HAT2Y = 0x15; - public static final int ABS_HAT3X = 0x16; - public static final int ABS_HAT3Y = 0x17; - public static final int ABS_PRESSURE = 0x18; - public static final int ABS_DISTANCE = 0x19; - public static final int ABS_TILT_X = 0x1a; - public static final int ABS_TILT_Y = 0x1b; - public static final int ABS_TOOL_WIDTH = 0x1c; - public static final int ABS_VOLUME = 0x20; - public static final int ABS_MISC = 0x28; - public static final int ABS_MT_TOUCH_MAJOR = 0x30; - public static final int ABS_MT_TOUCH_MINOR = 0x31; - public static final int ABS_MT_WIDTH_MAJOR = 0x32; - public static final int ABS_MT_WIDTH_MINOR = 0x33; - public static final int ABS_MT_ORIENTATION = 0x34; - public static final int ABS_MT_POSITION_X = 0x35; - public static final int ABS_MT_POSITION_Y = 0x36; - public static final int ABS_MT_TOOL_TYPE = 0x37; - public static final int ABS_MT_BLOB_ID = 0x38; - public static final int ABS_MAX = 0x3f; - - // Switch events - public static final int SW_LID = 0x00; - - public static final int SYN_REPORT = 0; - public static final int SYN_CONFIG = 1; - public static final int SYN_MT_REPORT = 2; - - public int deviceId; - public int type; - public int scancode; - public int keycode; - public int flags; - public int value; - public long when; -} diff --git a/core/java/android/view/ScaleGestureDetector.java b/core/java/android/view/ScaleGestureDetector.java index ff34f4add39d..09995987e65d 100644 --- a/core/java/android/view/ScaleGestureDetector.java +++ b/core/java/android/view/ScaleGestureDetector.java @@ -312,7 +312,7 @@ public class ScaleGestureDetector { * MotionEvent has no getRawX(int) method; simulate it pending future API approval. */ private static float getRawX(MotionEvent event, int pointerIndex) { - float offset = event.getX() - event.getRawX(); + float offset = event.getRawX() - event.getX(); return event.getX(pointerIndex) + offset; } @@ -320,7 +320,7 @@ public class ScaleGestureDetector { * MotionEvent has no getRawY(int) method; simulate it pending future API approval. */ private static float getRawY(MotionEvent event, int pointerIndex) { - float offset = event.getY() - event.getRawY(); + float offset = event.getRawY() - event.getY(); return event.getY(pointerIndex) + offset; } diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java index 83ef8ba51747..cd0ae3b02fbb 100644 --- a/core/java/android/view/Surface.java +++ b/core/java/android/view/Surface.java @@ -140,13 +140,13 @@ public class Surface implements Parcelable { public static final int FLAGS_ORIENTATION_ANIMATION_DISABLE = 0x000000001; @SuppressWarnings("unused") - private int mSurface; - @SuppressWarnings("unused") private int mSurfaceControl; @SuppressWarnings("unused") private int mSaveCount; @SuppressWarnings("unused") private Canvas mCanvas; + @SuppressWarnings("unused") + private int mNativeSurface; private String mName; // The display metrics used to provide the pseudo canvas size for applications @@ -422,13 +422,13 @@ public class Surface implements Parcelable { /* no user serviceable parts here ... */ @Override protected void finalize() throws Throwable { - if (mSurface != 0 || mSurfaceControl != 0) { + if (mNativeSurface != 0 || mSurfaceControl != 0) { if (DEBUG_RELEASE) { Log.w(LOG_TAG, "Surface.finalize() has work. You should have called release() (" - + mSurface + ", " + mSurfaceControl + ")", mCreationStack); + + mNativeSurface + ", " + mSurfaceControl + ")", mCreationStack); } else { Log.w(LOG_TAG, "Surface.finalize() has work. You should have called release() (" - + mSurface + ", " + mSurfaceControl + ")"); + + mNativeSurface + ", " + mSurfaceControl + ")"); } } release(); diff --git a/core/java/android/view/SurfaceHolder.java b/core/java/android/view/SurfaceHolder.java index 64a10d194f5e..0d38f7bffa26 100644 --- a/core/java/android/view/SurfaceHolder.java +++ b/core/java/android/view/SurfaceHolder.java @@ -119,6 +119,23 @@ public interface SurfaceHolder { } /** + * Additional callbacks that can be received for {@link Callback}. + */ + public interface Callback2 extends Callback { + /** + * Called when the application needs to redraw the content of its + * surface, after it is resized or for some other reason. By not + * returning here until the redraw is complete, you can ensure that + * the user will not see your surface in a bad state (at its new + * size before it has been correctly drawn that way). This will + * typically be preceeded by a call to {@link #surfaceChanged}. + * + * @param holder The SurfaceHolder whose surface has changed. + */ + public void surfaceRedrawNeeded(SurfaceHolder holder); + } + + /** * Add a Callback interface for this holder. There can several Callback * interfaces associated to a holder. * @@ -182,7 +199,6 @@ public interface SurfaceHolder { /** * Enable or disable option to keep the screen turned on while this * surface is displayed. The default is false, allowing it to turn off. - * Enabling the option effectivelty. * This is safe to call from any thread. * * @param screenOn Supply to true to force the screen to stay on, false diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index 53f0c2e5f917..54cb4ca8cbfa 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -123,7 +123,7 @@ public class SurfaceView extends View { handleGetNewSurface(); } break; case UPDATE_WINDOW_MSG: { - updateWindow(false); + updateWindow(false, false); } break; } } @@ -132,7 +132,7 @@ public class SurfaceView extends View { final ViewTreeObserver.OnScrollChangedListener mScrollChangedListener = new ViewTreeObserver.OnScrollChangedListener() { public void onScrollChanged() { - updateWindow(false); + updateWindow(false, false); } }; @@ -141,7 +141,10 @@ public class SurfaceView extends View { boolean mViewVisibility = false; int mRequestedWidth = -1; int mRequestedHeight = -1; - int mRequestedFormat = PixelFormat.OPAQUE; + /* Set SurfaceView's format to 565 by default to maintain backward + * compatibility with applications assuming this format. + */ + int mRequestedFormat = PixelFormat.RGB_565; int mRequestedType = -1; boolean mHaveFrame = false; @@ -164,16 +167,20 @@ public class SurfaceView extends View { public SurfaceView(Context context) { super(context); - setWillNotDraw(true); + init(); } public SurfaceView(Context context, AttributeSet attrs) { super(context, attrs); - setWillNotDraw(true); + init(); } public SurfaceView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); + init(); + } + + private void init() { setWillNotDraw(true); } @@ -203,7 +210,7 @@ public class SurfaceView extends View { super.onWindowVisibilityChanged(visibility); mWindowVisibility = visibility == VISIBLE; mRequestedVisible = mWindowVisibility && mViewVisibility; - updateWindow(false); + updateWindow(false, false); } @Override @@ -211,7 +218,7 @@ public class SurfaceView extends View { super.setVisibility(visibility); mViewVisibility = visibility == VISIBLE; mRequestedVisible = mWindowVisibility && mViewVisibility; - updateWindow(false); + updateWindow(false, false); } /** @@ -225,7 +232,7 @@ public class SurfaceView extends View { */ protected void showSurface() { if (mSession != null) { - updateWindow(true); + updateWindow(true, false); } } @@ -258,7 +265,7 @@ public class SurfaceView extends View { protected void onDetachedFromWindow() { getViewTreeObserver().removeOnScrollChangedListener(mScrollChangedListener); mRequestedVisible = false; - updateWindow(false); + updateWindow(false, false); mHaveFrame = false; if (mWindow != null) { try { @@ -283,7 +290,7 @@ public class SurfaceView extends View { @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); - updateWindow(false); + updateWindow(false, false); } @Override @@ -336,7 +343,7 @@ public class SurfaceView extends View { } // reposition ourselves where the surface is mHaveFrame = true; - updateWindow(false); + updateWindow(false, false); super.dispatchDraw(canvas); } @@ -390,7 +397,7 @@ public class SurfaceView extends View { mWindowType = type; } - private void updateWindow(boolean force) { + private void updateWindow(boolean force, boolean redrawNeeded) { if (!mHaveFrame) { return; } @@ -418,7 +425,7 @@ public class SurfaceView extends View { final boolean typeChanged = mType != mRequestedType; if (force || creating || formatChanged || sizeChanged || visibleChanged || typeChanged || mLeft != mLocation[0] || mTop != mLocation[1] - || mUpdateWindowNeeded || mReportDrawNeeded) { + || mUpdateWindowNeeded || mReportDrawNeeded || redrawNeeded) { if (localLOGV) Log.i(TAG, "Changes: creating=" + creating + " format=" + formatChanged + " size=" + sizeChanged @@ -464,7 +471,7 @@ public class SurfaceView extends View { mWindow = new MyWindow(this); mLayout.type = mWindowType; mLayout.gravity = Gravity.LEFT|Gravity.TOP; - mSession.add(mWindow, mLayout, + mSession.addWithoutInputChannel(mWindow, mLayout, mVisible ? VISIBLE : GONE, mContentInsets); } @@ -517,6 +524,8 @@ public class SurfaceView extends View { } try { + redrawNeeded |= creating | reportDrawNeeded; + if (visible) { mDestroyReportNeeded = true; @@ -538,12 +547,20 @@ public class SurfaceView extends View { c.surfaceChanged(mSurfaceHolder, mFormat, myWidth, myHeight); } } + if (redrawNeeded) { + for (SurfaceHolder.Callback c : callbacks) { + if (c instanceof SurfaceHolder.Callback2) { + ((SurfaceHolder.Callback2)c).surfaceRedrawNeeded( + mSurfaceHolder); + } + } + } } else { mSurface.release(); } } finally { mIsCreating = false; - if (creating || reportDrawNeeded) { + if (redrawNeeded) { mSession.finishDrawing(mWindow); } } @@ -573,7 +590,7 @@ public class SurfaceView extends View { void handleGetNewSurface() { mNewSurfaceNeeded = true; - updateWindow(false); + updateWindow(false, false); } /** @@ -618,41 +635,6 @@ public class SurfaceView extends View { } } - public void dispatchKey(KeyEvent event) { - SurfaceView surfaceView = mSurfaceView.get(); - if (surfaceView != null) { - //Log.w("SurfaceView", "Unexpected key event in surface: " + event); - if (surfaceView.mSession != null && surfaceView.mSurface != null) { - try { - surfaceView.mSession.finishKey(surfaceView.mWindow); - } catch (RemoteException ex) { - } - } - } - } - - public void dispatchPointer(MotionEvent event, long eventTime, - boolean callWhenDone) { - Log.w("SurfaceView", "Unexpected pointer event in surface: " + event); - //if (mSession != null && mSurface != null) { - // try { - // //mSession.finishKey(mWindow); - // } catch (RemoteException ex) { - // } - //} - } - - public void dispatchTrackball(MotionEvent event, long eventTime, - boolean callWhenDone) { - Log.w("SurfaceView", "Unexpected trackball event in surface: " + event); - //if (mSession != null && mSurface != null) { - // try { - // //mSession.finishKey(mWindow); - // } catch (RemoteException ex) { - // } - //} - } - public void dispatchAppVisibility(boolean visible) { // The point of SurfaceView is to let the app control the surface. } @@ -679,7 +661,6 @@ public class SurfaceView extends View { private SurfaceHolder mSurfaceHolder = new SurfaceHolder() { private static final String LOG_TAG = "SurfaceHolder"; - private int mSaveCount; public boolean isCreating() { return mIsCreating; @@ -717,9 +698,15 @@ public class SurfaceView extends View { } public void setFormat(int format) { + + // for backward compatibility reason, OPAQUE always + // means 565 for SurfaceView + if (format == PixelFormat.OPAQUE) + format = PixelFormat.RGB_565; + mRequestedFormat = format; if (mWindow != null) { - updateWindow(false); + updateWindow(false, false); } } @@ -736,7 +723,7 @@ public class SurfaceView extends View { case SURFACE_TYPE_PUSH_BUFFERS: mRequestedType = type; if (mWindow != null) { - updateWindow(false); + updateWindow(false, false); } break; } diff --git a/core/java/android/view/VelocityTracker.java b/core/java/android/view/VelocityTracker.java index aab76c4fdfcf..fb88c7135ba6 100644 --- a/core/java/android/view/VelocityTracker.java +++ b/core/java/android/view/VelocityTracker.java @@ -33,14 +33,15 @@ import android.util.PoolableManager; * and {@link #getXVelocity()}. */ public final class VelocityTracker implements Poolable<VelocityTracker> { - static final String TAG = "VelocityTracker"; - static final boolean DEBUG = false; - static final boolean localLOGV = DEBUG || Config.LOGV; + private static final String TAG = "VelocityTracker"; + private static final boolean DEBUG = false; + private static final boolean localLOGV = DEBUG || Config.LOGV; - static final int NUM_PAST = 10; - static final int LONGEST_PAST_TIME = 200; + private static final int NUM_PAST = 10; + private static final int MAX_AGE_MILLISECONDS = 200; + + private static final int POINTER_POOL_CAPACITY = 20; - static final VelocityTracker[] mPool = new VelocityTracker[1]; private static final Pool<VelocityTracker> sPool = Pools.synchronizedPool( Pools.finitePool(new PoolableManager<VelocityTracker>() { public VelocityTracker newInstance() { @@ -48,20 +49,33 @@ public final class VelocityTracker implements Poolable<VelocityTracker> { } public void onAcquired(VelocityTracker element) { - element.clear(); } public void onReleased(VelocityTracker element) { + element.clear(); } }, 2)); - - final float mPastX[][] = new float[MotionEvent.BASE_AVAIL_POINTERS][NUM_PAST]; - final float mPastY[][] = new float[MotionEvent.BASE_AVAIL_POINTERS][NUM_PAST]; - final long mPastTime[][] = new long[MotionEvent.BASE_AVAIL_POINTERS][NUM_PAST]; - - float mYVelocity[] = new float[MotionEvent.BASE_AVAIL_POINTERS]; - float mXVelocity[] = new float[MotionEvent.BASE_AVAIL_POINTERS]; - int mLastTouch; + + private static Pointer sRecycledPointerListHead; + private static int sRecycledPointerCount; + + private static final class Pointer { + public Pointer next; + + public int id; + public float xVelocity; + public float yVelocity; + + public final float[] pastX = new float[NUM_PAST]; + public final float[] pastY = new float[NUM_PAST]; + public final long[] pastTime = new long[NUM_PAST]; // uses Long.MIN_VALUE as a sentinel + + public int generation; + } + + private Pointer mPointerListHead; // sorted by id in increasing order + private int mLastTouchIndex; + private int mGeneration; private VelocityTracker mNext; @@ -107,12 +121,10 @@ public final class VelocityTracker implements Poolable<VelocityTracker> { * Reset the velocity tracker back to its initial state. */ public void clear() { - final long[][] pastTime = mPastTime; - for (int p = 0; p < MotionEvent.BASE_AVAIL_POINTERS; p++) { - for (int i = 0; i < NUM_PAST; i++) { - pastTime[p][i] = Long.MIN_VALUE; - } - } + releasePointerList(mPointerListHead); + + mPointerListHead = null; + mLastTouchIndex = 0; } /** @@ -125,37 +137,96 @@ public final class VelocityTracker implements Poolable<VelocityTracker> { * @param ev The MotionEvent you received and would like to track. */ public void addMovement(MotionEvent ev) { - final int N = ev.getHistorySize(); + final int historySize = ev.getHistorySize(); final int pointerCount = ev.getPointerCount(); - int touchIndex = (mLastTouch + 1) % NUM_PAST; - for (int i=0; i<N; i++) { - for (int id = 0; id < MotionEvent.BASE_AVAIL_POINTERS; id++) { - mPastTime[id][touchIndex] = Long.MIN_VALUE; + final int lastTouchIndex = mLastTouchIndex; + final int nextTouchIndex = (lastTouchIndex + 1) % NUM_PAST; + final int finalTouchIndex = (nextTouchIndex + historySize) % NUM_PAST; + final int generation = mGeneration++; + + mLastTouchIndex = finalTouchIndex; + + // Update pointer data. + Pointer previousPointer = null; + for (int i = 0; i < pointerCount; i++){ + final int pointerId = ev.getPointerId(i); + + // Find the pointer data for this pointer id. + // This loop is optimized for the common case where pointer ids in the event + // are in sorted order. However, we check for this case explicitly and + // perform a full linear scan from the start if needed. + Pointer nextPointer; + if (previousPointer == null || pointerId < previousPointer.id) { + previousPointer = null; + nextPointer = mPointerListHead; + } else { + nextPointer = previousPointer.next; } - for (int p = 0; p < pointerCount; p++) { - int id = ev.getPointerId(p); - mPastX[id][touchIndex] = ev.getHistoricalX(p, i); - mPastY[id][touchIndex] = ev.getHistoricalY(p, i); - mPastTime[id][touchIndex] = ev.getHistoricalEventTime(i); + + final Pointer pointer; + for (;;) { + if (nextPointer != null) { + final int nextPointerId = nextPointer.id; + if (nextPointerId == pointerId) { + pointer = nextPointer; + break; + } + if (nextPointerId < pointerId) { + nextPointer = nextPointer.next; + continue; + } + } + + // Pointer went down. Add it to the list. + // Write a sentinel at the end of the pastTime trace so we will be able to + // tell when the trace started. + pointer = obtainPointer(); + pointer.id = pointerId; + pointer.pastTime[lastTouchIndex] = Long.MIN_VALUE; + pointer.next = nextPointer; + if (previousPointer == null) { + mPointerListHead = pointer; + } else { + previousPointer.next = pointer; + } + break; } - - touchIndex = (touchIndex + 1) % NUM_PAST; - } - - // During calculation any pointer values with a time of MIN_VALUE are treated - // as a break in input. Initialize all to MIN_VALUE for each new touch index. - for (int id = 0; id < MotionEvent.BASE_AVAIL_POINTERS; id++) { - mPastTime[id][touchIndex] = Long.MIN_VALUE; + + pointer.generation = generation; + previousPointer = pointer; + + final float[] pastX = pointer.pastX; + final float[] pastY = pointer.pastY; + final long[] pastTime = pointer.pastTime; + + for (int j = 0; j < historySize; j++) { + final int touchIndex = (nextTouchIndex + j) % NUM_PAST; + pastX[touchIndex] = ev.getHistoricalX(i, j); + pastY[touchIndex] = ev.getHistoricalY(i, j); + pastTime[touchIndex] = ev.getHistoricalEventTime(j); + } + pastX[finalTouchIndex] = ev.getX(i); + pastY[finalTouchIndex] = ev.getY(i); + pastTime[finalTouchIndex] = ev.getEventTime(); } - final long time = ev.getEventTime(); - for (int p = 0; p < pointerCount; p++) { - int id = ev.getPointerId(p); - mPastX[id][touchIndex] = ev.getX(p); - mPastY[id][touchIndex] = ev.getY(p); - mPastTime[id][touchIndex] = time; + + // Find removed pointers. + previousPointer = null; + for (Pointer pointer = mPointerListHead; pointer != null; ) { + final Pointer nextPointer = pointer.next; + if (pointer.generation != generation) { + // Pointer went up. Remove it from the list. + if (previousPointer == null) { + mPointerListHead = nextPointer; + } else { + previousPointer.next = nextPointer; + } + releasePointer(pointer); + } else { + previousPointer = pointer; + } + pointer = nextPointer; } - - mLastTouch = touchIndex; } /** @@ -182,54 +253,77 @@ public final class VelocityTracker implements Poolable<VelocityTracker> { * must be positive. */ public void computeCurrentVelocity(int units, float maxVelocity) { - for (int pos = 0; pos < MotionEvent.BASE_AVAIL_POINTERS; pos++) { - final float[] pastX = mPastX[pos]; - final float[] pastY = mPastY[pos]; - final long[] pastTime = mPastTime[pos]; - final int lastTouch = mLastTouch; + final int lastTouchIndex = mLastTouchIndex; - // find oldest acceptable time - int oldestTouch = lastTouch; - if (pastTime[lastTouch] != Long.MIN_VALUE) { // cleared ? - final float acceptableTime = pastTime[lastTouch] - LONGEST_PAST_TIME; - int nextOldestTouch = (NUM_PAST + oldestTouch - 1) % NUM_PAST; - while (pastTime[nextOldestTouch] >= acceptableTime && - nextOldestTouch != lastTouch) { - oldestTouch = nextOldestTouch; - nextOldestTouch = (NUM_PAST + oldestTouch - 1) % NUM_PAST; + for (Pointer pointer = mPointerListHead; pointer != null; pointer = pointer.next) { + final long[] pastTime = pointer.pastTime; + + // Search backwards in time for oldest acceptable time. + // Stop at the beginning of the trace as indicated by the sentinel time Long.MIN_VALUE. + int oldestTouchIndex = lastTouchIndex; + int numTouches = 1; + final long minTime = pastTime[lastTouchIndex] - MAX_AGE_MILLISECONDS; + while (numTouches < NUM_PAST) { + final int nextOldestTouchIndex = (oldestTouchIndex + NUM_PAST - 1) % NUM_PAST; + final long nextOldestTime = pastTime[nextOldestTouchIndex]; + if (nextOldestTime < minTime) { // also handles end of trace sentinel + break; } + oldestTouchIndex = nextOldestTouchIndex; + numTouches += 1; } - + + // If we have a lot of samples, skip the last received sample since it is + // probably pretty noisy compared to the sum of all of the traces already acquired. + if (numTouches > 3) { + numTouches -= 1; + } + // Kind-of stupid. - final float oldestX = pastX[oldestTouch]; - final float oldestY = pastY[oldestTouch]; - final long oldestTime = pastTime[oldestTouch]; + final float[] pastX = pointer.pastX; + final float[] pastY = pointer.pastY; + + final float oldestX = pastX[oldestTouchIndex]; + final float oldestY = pastY[oldestTouchIndex]; + final long oldestTime = pastTime[oldestTouchIndex]; + float accumX = 0; float accumY = 0; - float N = (lastTouch - oldestTouch + NUM_PAST) % NUM_PAST + 1; - // Skip the last received event, since it is probably pretty noisy. - if (N > 3) N--; - - for (int i=1; i < N; i++) { - final int j = (oldestTouch + i) % NUM_PAST; - final int dur = (int)(pastTime[j] - oldestTime); - if (dur == 0) continue; - float dist = pastX[j] - oldestX; - float vel = (dist/dur) * units; // pixels/frame. - accumX = (accumX == 0) ? vel : (accumX + vel) * .5f; - dist = pastY[j] - oldestY; - vel = (dist/dur) * units; // pixels/frame. - accumY = (accumY == 0) ? vel : (accumY + vel) * .5f; + for (int i = 1; i < numTouches; i++) { + final int touchIndex = (oldestTouchIndex + i) % NUM_PAST; + final int duration = (int)(pastTime[touchIndex] - oldestTime); + + if (duration == 0) continue; + + float delta = pastX[touchIndex] - oldestX; + float velocity = (delta / duration) * units; // pixels/frame. + accumX = (accumX == 0) ? velocity : (accumX + velocity) * .5f; + + delta = pastY[touchIndex] - oldestY; + velocity = (delta / duration) * units; // pixels/frame. + accumY = (accumY == 0) ? velocity : (accumY + velocity) * .5f; } - mXVelocity[pos] = accumX < 0.0f ? Math.max(accumX, -maxVelocity) - : Math.min(accumX, maxVelocity); - mYVelocity[pos] = accumY < 0.0f ? Math.max(accumY, -maxVelocity) - : Math.min(accumY, maxVelocity); - - if (localLOGV) Log.v(TAG, "Y velocity=" + mYVelocity +" X velocity=" - + mXVelocity + " N=" + N); + if (accumX < -maxVelocity) { + accumX = - maxVelocity; + } else if (accumX > maxVelocity) { + accumX = maxVelocity; + } + + if (accumY < -maxVelocity) { + accumY = - maxVelocity; + } else if (accumY > maxVelocity) { + accumY = maxVelocity; + } + + pointer.xVelocity = accumX; + pointer.yVelocity = accumY; + + if (localLOGV) { + Log.v(TAG, "Pointer " + pointer.id + + ": Y velocity=" + accumX +" X velocity=" + accumY + " N=" + numTouches); + } } } @@ -240,7 +334,8 @@ public final class VelocityTracker implements Poolable<VelocityTracker> { * @return The previously computed X velocity. */ public float getXVelocity() { - return mXVelocity[0]; + Pointer pointer = getPointer(0); + return pointer != null ? pointer.xVelocity : 0; } /** @@ -250,7 +345,8 @@ public final class VelocityTracker implements Poolable<VelocityTracker> { * @return The previously computed Y velocity. */ public float getYVelocity() { - return mYVelocity[0]; + Pointer pointer = getPointer(0); + return pointer != null ? pointer.yVelocity : 0; } /** @@ -261,7 +357,8 @@ public final class VelocityTracker implements Poolable<VelocityTracker> { * @return The previously computed X velocity. */ public float getXVelocity(int id) { - return mXVelocity[id]; + Pointer pointer = getPointer(id); + return pointer != null ? pointer.xVelocity : 0; } /** @@ -272,6 +369,68 @@ public final class VelocityTracker implements Poolable<VelocityTracker> { * @return The previously computed Y velocity. */ public float getYVelocity(int id) { - return mYVelocity[id]; + Pointer pointer = getPointer(id); + return pointer != null ? pointer.yVelocity : 0; + } + + private final Pointer getPointer(int id) { + for (Pointer pointer = mPointerListHead; pointer != null; pointer = pointer.next) { + if (pointer.id == id) { + return pointer; + } + } + return null; + } + + private static final Pointer obtainPointer() { + synchronized (sPool) { + if (sRecycledPointerCount != 0) { + Pointer element = sRecycledPointerListHead; + sRecycledPointerCount -= 1; + sRecycledPointerListHead = element.next; + element.next = null; + return element; + } + } + return new Pointer(); + } + + private static final void releasePointer(Pointer pointer) { + synchronized (sPool) { + if (sRecycledPointerCount < POINTER_POOL_CAPACITY) { + pointer.next = sRecycledPointerListHead; + sRecycledPointerCount += 1; + sRecycledPointerListHead = pointer; + } + } + } + + private static final void releasePointerList(Pointer pointer) { + if (pointer != null) { + synchronized (sPool) { + int count = sRecycledPointerCount; + if (count >= POINTER_POOL_CAPACITY) { + return; + } + + Pointer tail = pointer; + for (;;) { + count += 1; + if (count >= POINTER_POOL_CAPACITY) { + break; + } + + Pointer next = tail.next; + if (next == null) { + break; + } + tail = next; + } + + tail.next = sRecycledPointerListHead; + sRecycledPointerCount = count; + sRecycledPointerListHead = pointer; + } + } } } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index f9abe60b47c6..b794a6a1e406 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -542,6 +542,28 @@ import java.util.WeakHashMap; * take care of redrawing the appropriate views until the animation completes. * </p> * + * <a name="Security"></a> + * <h3>Security</h3> + * <p> + * Sometimes it is essential that an application be able to verify that an action + * is being performed with the full knowledge and consent of the user, such as + * granting a permission request, making a purchase or clicking on an advertisement. + * Unfortunately, a malicious application could try to spoof the user into + * performing these actions, unaware, by concealing the intended purpose of the view. + * As a remedy, the framework offers a touch filtering mechanism that can be used to + * improve the security of views that provide access to sensitive functionality. + * </p><p> + * To enable touch filtering, call {@link #setFilterTouchesWhenObscured} or set the + * andoird:filterTouchesWhenObscured attribute to true. When enabled, the framework + * will discard touches that are received whenever the view's window is obscured by + * another visible window. As a result, the view will not receive touches whenever a + * toast, dialog or other window appears above the view's window. + * </p><p> + * For more fine-grained control over security, consider overriding the + * {@link #onFilterTouchEventForSecurity} method to implement your own security policy. + * See also {@link MotionEvent#FLAG_WINDOW_IS_OBSCURED}. + * </p> + * * @attr ref android.R.styleable#View_background * @attr ref android.R.styleable#View_clickable * @attr ref android.R.styleable#View_contentDescription @@ -550,6 +572,7 @@ import java.util.WeakHashMap; * @attr ref android.R.styleable#View_id * @attr ref android.R.styleable#View_fadingEdge * @attr ref android.R.styleable#View_fadingEdgeLength + * @attr ref android.R.styleable#View_filterTouchesWhenObscured * @attr ref android.R.styleable#View_fitsSystemWindows * @attr ref android.R.styleable#View_isScrollContainer * @attr ref android.R.styleable#View_focusable @@ -711,7 +734,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility */ static final int SCROLLBARS_MASK = 0x00000300; - // note 0x00000400 and 0x00000800 are now available for next flags... + /** + * Indicates that the view should filter touches when its window is obscured. + * Refer to the class comments for more information about this security feature. + * {@hide} + */ + static final int FILTER_TOUCHES_WHEN_OBSCURED = 0x00000400; + + // note flag value 0x00000800 is now available for next flags... /** * <p>This view doesn't show fading edges.</p> @@ -1358,14 +1388,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility * Width as measured during measure pass. * {@hide} */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "measurement") protected int mMeasuredWidth; /** * Height as measured during measure pass. * {@hide} */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "measurement") protected int mMeasuredHeight; /** @@ -1420,8 +1450,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility static final int MEASURED_DIMENSION_SET = 0x00000800; /** {@hide} */ static final int FORCE_LAYOUT = 0x00001000; - - private static final int LAYOUT_REQUIRED = 0x00002000; + /** {@hide} */ + static final int LAYOUT_REQUIRED = 0x00002000; private static final int PRESSED = 0x00004000; @@ -1521,6 +1551,40 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility private static final int AWAKEN_SCROLL_BARS_ON_ATTACH = 0x08000000; /** + * Always allow a user to overscroll this view, provided it is a + * view that can scroll. + * + * @see #getOverscrollMode() + * @see #setOverscrollMode(int) + */ + public static final int OVERSCROLL_ALWAYS = 0; + + /** + * Allow a user to overscroll this view only if the content is large + * enough to meaningfully scroll, provided it is a view that can scroll. + * + * @see #getOverscrollMode() + * @see #setOverscrollMode(int) + */ + public static final int OVERSCROLL_IF_CONTENT_SCROLLS = 1; + + /** + * Never allow a user to overscroll this view. + * + * @see #getOverscrollMode() + * @see #setOverscrollMode(int) + */ + public static final int OVERSCROLL_NEVER = 2; + + /** + * Controls the overscroll mode for this view. + * See {@link #overscrollBy(int, int, int, int, int, int, int, int, boolean)}, + * {@link #OVERSCROLL_ALWAYS}, {@link #OVERSCROLL_IF_CONTENT_SCROLLS}, + * and {@link #OVERSCROLL_NEVER}. + */ + private int mOverscrollMode; + + /** * The parent this view is attached to. * {@hide} * @@ -1575,28 +1639,28 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility * to the left edge of this view. * {@hide} */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "layout") protected int mLeft; /** * The distance in pixels from the left edge of this view's parent * to the right edge of this view. * {@hide} */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "layout") protected int mRight; /** * The distance in pixels from the top edge of this view's parent * to the top edge of this view. * {@hide} */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "layout") protected int mTop; /** * The distance in pixels from the top edge of this view's parent * to the bottom edge of this view. * {@hide} */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "layout") protected int mBottom; /** @@ -1604,14 +1668,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility * horizontally. * {@hide} */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "scrolling") protected int mScrollX; /** * The offset, in pixels, by which the content of this view is scrolled * vertically. * {@hide} */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "scrolling") protected int mScrollY; /** @@ -1619,28 +1683,28 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility * left edge of this view and the left edge of its content. * {@hide} */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "padding") protected int mPaddingLeft; /** * The right padding in pixels, that is the distance in pixels between the * right edge of this view and the right edge of its content. * {@hide} */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "padding") protected int mPaddingRight; /** * The top padding in pixels, that is the distance in pixels between the * top edge of this view and the top edge of its content. * {@hide} */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "padding") protected int mPaddingTop; /** * The bottom padding in pixels, that is the distance in pixels between the * bottom edge of this view and the bottom edge of its content. * {@hide} */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "padding") protected int mPaddingBottom; /** @@ -1651,13 +1715,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility /** * Cache the paddingRight set by the user to append to the scrollbar's size. */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "padding") int mUserPaddingRight; /** * Cache the paddingBottom set by the user to append to the scrollbar's size. */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "padding") int mUserPaddingBottom; /** @@ -1764,14 +1828,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility * The minimum height of the view. We'll try our best to have the height * of this view to at least this amount. */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "measurement") private int mMinHeight; /** * The minimum width of the view. We'll try our best to have the width * of this view to at least this amount. */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "measurement") private int mMinWidth; /** @@ -1812,6 +1876,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility // Used for debug only //++sInstanceCount; mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); + setOverscrollMode(OVERSCROLL_ALWAYS); } /** @@ -1877,6 +1942,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility int scrollbarStyle = SCROLLBARS_INSIDE_OVERLAY; + int overscrollMode = mOverscrollMode; final int N = a.getIndexCount(); for (int i = 0; i < N; i++) { int attr = a.getIndex(i); @@ -2017,6 +2083,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility viewFlagMasks |= KEEP_SCREEN_ON; } break; + case R.styleable.View_filterTouchesWhenObscured: + if (a.getBoolean(attr, false)) { + viewFlagValues |= FILTER_TOUCHES_WHEN_OBSCURED; + viewFlagMasks |= FILTER_TOUCHES_WHEN_OBSCURED; + } + break; case R.styleable.View_nextFocusLeft: mNextFocusLeftId = a.getResourceId(attr, View.NO_ID); break; @@ -2076,9 +2148,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility }); } break; + case R.styleable.View_overscrollMode: + overscrollMode = a.getInt(attr, OVERSCROLL_ALWAYS); + break; } } + setOverscrollMode(overscrollMode); + if (background != null) { setBackgroundDrawable(background); } @@ -2413,11 +2490,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility } /** - * Call this view's OnLongClickListener, if it is defined. Invokes the context menu - * if the OnLongClickListener did not consume the event. + * Call this view's OnLongClickListener, if it is defined. Invokes the context menu if the + * OnLongClickListener did not consume the event. * - * @return True there was an assigned OnLongClickListener that was called, false - * otherwise is returned. + * @return True if one of the above receivers consumed the event, false otherwise. */ public boolean performLongClick() { sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED); @@ -2602,7 +2678,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility * * @return True if this view has or contains focus, false otherwise. */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "focus") public boolean hasFocus() { return (mPrivateFlags & FOCUSED) != 0; } @@ -2780,7 +2856,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility * * @return True if this view has focus, false otherwise. */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "focus") public boolean isFocused() { return (mPrivateFlags & FOCUSED) != 0; } @@ -3005,6 +3081,16 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility } /** + * Determine if this view has the FITS_SYSTEM_WINDOWS flag set. + * @return True if window has FITS_SYSTEM_WINDOWS set + * + * @hide + */ + public boolean isFitsSystemWindowsFlagSet() { + return (mViewFlags & FITS_SYSTEM_WINDOWS) == FITS_SYSTEM_WINDOWS; + } + + /** * Returns the visibility status for this view. * * @return One of {@link #VISIBLE}, {@link #INVISIBLE}, or {@link #GONE}. @@ -3181,7 +3267,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility * * @return true if this view has nothing to draw, false otherwise */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "drawing") public boolean willNotDraw() { return (mViewFlags & DRAW_MASK) == WILL_NOT_DRAW; } @@ -3204,7 +3290,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility * * @return true if this view does not cache its drawing, false otherwise */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "drawing") public boolean willNotCacheDrawing() { return (mViewFlags & WILL_NOT_CACHE_DRAWING) == WILL_NOT_CACHE_DRAWING; } @@ -3340,6 +3426,35 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility setFlags(enabled ? 0 : SAVE_DISABLED, SAVE_DISABLED_MASK); } + /** + * Gets whether the framework should discard touches when the view's + * window is obscured by another visible window. + * Refer to the {@link View} security documentation for more details. + * + * @return True if touch filtering is enabled. + * + * @see #setFilterTouchesWhenObscured(boolean) + * @attr ref android.R.styleable#View_filterTouchesWhenObscured + */ + @ViewDebug.ExportedProperty + public boolean getFilterTouchesWhenObscured() { + return (mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0; + } + + /** + * Sets whether the framework should discard touches when the view's + * window is obscured by another visible window. + * Refer to the {@link View} security documentation for more details. + * + * @param enabled True if touch filtering should be enabled. + * + * @see #getFilterTouchesWhenObscured + * @attr ref android.R.styleable#View_filterTouchesWhenObscured + */ + public void setFilterTouchesWhenObscured(boolean enabled) { + setFlags(enabled ? 0 : FILTER_TOUCHES_WHEN_OBSCURED, + FILTER_TOUCHES_WHEN_OBSCURED); + } /** * Returns whether this View is able to take focus. @@ -3347,7 +3462,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility * @return True if this view can take focus, or false otherwise. * @attr ref android.R.styleable#View_focusable */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "focus") public final boolean isFocusable() { return FOCUSABLE == (mViewFlags & FOCUSABLE_MASK); } @@ -3759,6 +3874,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility * @return True if the event was handled by the view, false otherwise. */ public boolean dispatchTouchEvent(MotionEvent event) { + if (!onFilterTouchEventForSecurity(event)) { + return false; + } + if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && mOnTouchListener.onTouch(this, event)) { return true; @@ -3767,6 +3886,23 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility } /** + * Filter the touch event to apply security policies. + * + * @param event The motion event to be filtered. + * @return True if the event should be dispatched, false if the event should be dropped. + * + * @see #getFilterTouchesWhenObscured + */ + public boolean onFilterTouchEventForSecurity(MotionEvent event) { + if ((mViewFlags & FILTER_TOUCHES_WHEN_OBSCURED) != 0 + && (event.getFlags() & MotionEvent.FLAG_WINDOW_IS_OBSCURED) != 0) { + // Window is obscured, drop this touch. + return false; + } + return true; + } + + /** * Pass a trackball motion event down to the focused view. * * @param event The motion event to be dispatched. @@ -4208,6 +4344,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility * Show the context menu for this view. It is not safe to hold on to the * menu after returning from this method. * + * You should normally not overload this method. Overload + * {@link #onCreateContextMenu(ContextMenu)} or define an + * {@link OnCreateContextMenuListener} to add items to the context menu. + * * @param menu The context menu to populate */ public void createContextMenu(ContextMenu menu) { @@ -4656,7 +4796,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility * * @return The width of your view, in pixels. */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "layout") public final int getWidth() { return mRight - mLeft; } @@ -4666,7 +4806,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility * * @return The height of your view, in pixels. */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "layout") public final int getHeight() { return mBottom - mTop; } @@ -5152,7 +5292,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility * * @return True if this View is guaranteed to be fully opaque, false otherwise. */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "drawing") public boolean isOpaque() { return (mPrivateFlags & OPAQUE_MASK) == OPAQUE_MASK; } @@ -6237,7 +6377,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility * @see #setDrawingCacheEnabled(boolean) * @see #getDrawingCache() */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "drawing") public boolean isDrawingCacheEnabled() { return (mViewFlags & DRAWING_CACHE_ENABLED) == DRAWING_CACHE_ENABLED; } @@ -8106,7 +8246,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility * @return the offset of the baseline within the widget's bounds or -1 * if baseline alignment is not supported */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "layout") public int getBaseline() { return -1; } @@ -8668,6 +8808,128 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility } /** + * Scroll the view with standard behavior for scrolling beyond the normal + * content boundaries. Views that call this method should override + * {@link #onOverscrolled(int, int, boolean, boolean)} to respond to the + * results of an overscroll operation. + * + * Views can use this method to handle any touch or fling-based scrolling. + * + * @param deltaX Change in X in pixels + * @param deltaY Change in Y in pixels + * @param scrollX Current X scroll value in pixels before applying deltaX + * @param scrollY Current Y scroll value in pixels before applying deltaY + * @param scrollRangeX Maximum content scroll range along the X axis + * @param scrollRangeY Maximum content scroll range along the Y axis + * @param maxOverscrollX Number of pixels to overscroll by in either direction + * along the X axis. + * @param maxOverscrollY Number of pixels to overscroll by in either direction + * along the Y axis. + * @param isTouchEvent true if this scroll operation is the result of a touch event. + * @return true if scrolling was clamped to an overscroll boundary along either + * axis, false otherwise. + */ + protected boolean overscrollBy(int deltaX, int deltaY, + int scrollX, int scrollY, + int scrollRangeX, int scrollRangeY, + int maxOverscrollX, int maxOverscrollY, + boolean isTouchEvent) { + final int overscrollMode = mOverscrollMode; + final boolean canScrollHorizontal = + computeHorizontalScrollRange() > computeHorizontalScrollExtent(); + final boolean canScrollVertical = + computeVerticalScrollRange() > computeVerticalScrollExtent(); + final boolean overscrollHorizontal = overscrollMode == OVERSCROLL_ALWAYS || + (overscrollMode == OVERSCROLL_IF_CONTENT_SCROLLS && canScrollHorizontal); + final boolean overscrollVertical = overscrollMode == OVERSCROLL_ALWAYS || + (overscrollMode == OVERSCROLL_IF_CONTENT_SCROLLS && canScrollVertical); + + int newScrollX = scrollX + deltaX; + if (!overscrollHorizontal) { + maxOverscrollX = 0; + } + + int newScrollY = scrollY + deltaY; + if (!overscrollVertical) { + maxOverscrollY = 0; + } + + // Clamp values if at the limits and record + final int left = -maxOverscrollX; + final int right = maxOverscrollX + scrollRangeX; + final int top = -maxOverscrollY; + final int bottom = maxOverscrollY + scrollRangeY; + + boolean clampedX = false; + if (newScrollX > right) { + newScrollX = right; + clampedX = true; + } else if (newScrollX < left) { + newScrollX = left; + clampedX = true; + } + + boolean clampedY = false; + if (newScrollY > bottom) { + newScrollY = bottom; + clampedY = true; + } else if (newScrollY < top) { + newScrollY = top; + clampedY = true; + } + + onOverscrolled(newScrollX, newScrollY, clampedX, clampedY); + + return clampedX || clampedY; + } + + /** + * Called by {@link #overscrollBy(int, int, int, int, int, int, int, int, boolean)} to + * respond to the results of an overscroll operation. + * + * @param scrollX New X scroll value in pixels + * @param scrollY New Y scroll value in pixels + * @param clampedX True if scrollX was clamped to an overscroll boundary + * @param clampedY True if scrollY was clamped to an overscroll boundary + */ + protected void onOverscrolled(int scrollX, int scrollY, + boolean clampedX, boolean clampedY) { + // Intentionally empty. + } + + /** + * Returns the overscroll mode for this view. The result will be + * one of {@link #OVERSCROLL_ALWAYS} (default), {@link #OVERSCROLL_IF_CONTENT_SCROLLS} + * (allow overscrolling only if the view content is larger than the container), + * or {@link #OVERSCROLL_NEVER}. + * + * @return This view's overscroll mode. + */ + public int getOverscrollMode() { + return mOverscrollMode; + } + + /** + * Set the overscroll mode for this view. Valid overscroll modes are + * {@link #OVERSCROLL_ALWAYS} (default), {@link #OVERSCROLL_IF_CONTENT_SCROLLS} + * (allow overscrolling only if the view content is larger than the container), + * or {@link #OVERSCROLL_NEVER}. + * + * Setting the overscroll mode of a view will have an effect only if the + * view is capable of scrolling. + * + * @param overscrollMode The new overscroll mode for this view. + */ + public void setOverscrollMode(int overscrollMode) { + if (overscrollMode != OVERSCROLL_ALWAYS && + overscrollMode != OVERSCROLL_IF_CONTENT_SCROLLS && + overscrollMode != OVERSCROLL_NEVER) { + throw new IllegalArgumentException("Invalid overscroll mode " + overscrollMode); + } + mOverscrollMode = overscrollMode; + } + + /** * A MeasureSpec encapsulates the layout requirements passed from parent to child. * Each MeasureSpec represents a requirement for either the width or the height. * A MeasureSpec is comprised of a size and a mode. There are three possible diff --git a/core/java/android/view/ViewConfiguration.java b/core/java/android/view/ViewConfiguration.java index acdfc2829da9..924c9d48f5d4 100644 --- a/core/java/android/view/ViewConfiguration.java +++ b/core/java/android/view/ViewConfiguration.java @@ -140,6 +140,16 @@ public class ViewConfiguration { */ private static float SCROLL_FRICTION = 0.015f; + /** + * Max distance to overscroll for edge effects + */ + private static final int OVERSCROLL_DISTANCE = 0; + + /** + * Max distance to overfling for edge effects + */ + private static final int OVERFLING_DISTANCE = 4; + private final int mEdgeSlop; private final int mFadingEdgeLength; private final int mMinimumFlingVelocity; @@ -150,6 +160,8 @@ public class ViewConfiguration { private final int mDoubleTapSlop; private final int mWindowTouchSlop; private final int mMaximumDrawingCacheSize; + private final int mOverscrollDistance; + private final int mOverflingDistance; private static final SparseArray<ViewConfiguration> sConfigurations = new SparseArray<ViewConfiguration>(2); @@ -170,6 +182,8 @@ public class ViewConfiguration { mWindowTouchSlop = WINDOW_TOUCH_SLOP; //noinspection deprecation mMaximumDrawingCacheSize = MAXIMUM_DRAWING_CACHE_SIZE; + mOverscrollDistance = OVERSCROLL_DISTANCE; + mOverflingDistance = OVERFLING_DISTANCE; } /** @@ -198,6 +212,9 @@ public class ViewConfiguration { // Size of the screen in bytes, in ARGB_8888 format mMaximumDrawingCacheSize = 4 * metrics.widthPixels * metrics.heightPixels; + + mOverscrollDistance = (int) (density * OVERSCROLL_DISTANCE + 0.5f); + mOverflingDistance = (int) (density * OVERFLING_DISTANCE + 0.5f); } /** @@ -455,6 +472,20 @@ public class ViewConfiguration { } /** + * @return The maximum distance a View should overscroll by when showing edge effects. + */ + public int getScaledOverscrollDistance() { + return mOverscrollDistance; + } + + /** + * @return The maximum distance a View should overfling by when showing edge effects. + */ + public int getScaledOverflingDistance() { + return mOverflingDistance; + } + + /** * The amount of time that the zoom controls should be * displayed on the screen expressed in milliseconds. * diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java index d2563a87713f..7b6991fed051 100644 --- a/core/java/android/view/ViewDebug.java +++ b/core/java/android/view/ViewDebug.java @@ -255,6 +255,14 @@ public class ViewDebug { * @see #deepExport() */ String prefix() default ""; + + /** + * Specifies the category the property falls into, such as measurement, + * layout, drawing, etc. + * + * @return the category as String + */ + String category() default ""; } /** @@ -916,72 +924,13 @@ public class ViewDebug { out = new BufferedWriter(new OutputStreamWriter(clientStream), 32 * 1024); if (view != null) { - final long durationMeasure = profileViewOperation(view, new ViewOperation<Void>() { - public Void[] pre() { - forceLayout(view); - return null; - } - - private void forceLayout(View view) { - view.forceLayout(); - if (view instanceof ViewGroup) { - ViewGroup group = (ViewGroup) view; - final int count = group.getChildCount(); - for (int i = 0; i < count; i++) { - forceLayout(group.getChildAt(i)); - } - } - } - - public void run(Void... data) { - view.measure(view.mOldWidthMeasureSpec, view.mOldHeightMeasureSpec); - } - - public void post(Void... data) { - } - }); - - final long durationLayout = profileViewOperation(view, new ViewOperation<Void>() { - public Void[] pre() { - return null; - } - - public void run(Void... data) { - view.layout(view.mLeft, view.mTop, view.mRight, view.mBottom); - } - - public void post(Void... data) { - } - }); - - final long durationDraw = profileViewOperation(view, new ViewOperation<Object>() { - public Object[] pre() { - final DisplayMetrics metrics = view.getResources().getDisplayMetrics(); - final Bitmap bitmap = Bitmap.createBitmap(metrics.widthPixels, - metrics.heightPixels, Bitmap.Config.RGB_565); - final Canvas canvas = new Canvas(bitmap); - return new Object[] { bitmap, canvas }; - } - - public void run(Object... data) { - view.draw((Canvas) data[1]); - } - - public void post(Object... data) { - ((Bitmap) data[0]).recycle(); - } - }); - - out.write(String.valueOf(durationMeasure)); - out.write(' '); - out.write(String.valueOf(durationLayout)); - out.write(' '); - out.write(String.valueOf(durationDraw)); - out.newLine(); + profileViewAndChildren(view, out); } else { out.write("-1 -1 -1"); out.newLine(); } + out.write("DONE."); + out.newLine(); } catch (Exception e) { android.util.Log.w("View", "Problem profiling the view:", e); } finally { @@ -991,6 +940,94 @@ public class ViewDebug { } } + private static void profileViewAndChildren(final View view, BufferedWriter out) + throws IOException { + profileViewAndChildren(view, out, true); + } + + private static void profileViewAndChildren(final View view, BufferedWriter out, boolean root) + throws IOException { + + long durationMeasure = + (root || (view.mPrivateFlags & View.MEASURED_DIMENSION_SET) != 0) ? profileViewOperation( + view, new ViewOperation<Void>() { + public Void[] pre() { + forceLayout(view); + return null; + } + + private void forceLayout(View view) { + view.forceLayout(); + if (view instanceof ViewGroup) { + ViewGroup group = (ViewGroup) view; + final int count = group.getChildCount(); + for (int i = 0; i < count; i++) { + forceLayout(group.getChildAt(i)); + } + } + } + + public void run(Void... data) { + view.measure(view.mOldWidthMeasureSpec, view.mOldHeightMeasureSpec); + } + + public void post(Void... data) { + } + }) + : 0; + long durationLayout = + (root || (view.mPrivateFlags & View.LAYOUT_REQUIRED) != 0) ? profileViewOperation( + view, new ViewOperation<Void>() { + public Void[] pre() { + return null; + } + + public void run(Void... data) { + view.layout(view.mLeft, view.mTop, view.mRight, view.mBottom); + } + + public void post(Void... data) { + } + }) : 0; + long durationDraw = + (root || !view.willNotDraw() || (view.mPrivateFlags & View.DRAWN) != 0) ? profileViewOperation( + view, + new ViewOperation<Object>() { + public Object[] pre() { + final DisplayMetrics metrics = + view.getResources().getDisplayMetrics(); + final Bitmap bitmap = + Bitmap.createBitmap(metrics.widthPixels, + metrics.heightPixels, Bitmap.Config.RGB_565); + final Canvas canvas = new Canvas(bitmap); + return new Object[] { + bitmap, canvas + }; + } + + public void run(Object... data) { + view.draw((Canvas) data[1]); + } + + public void post(Object... data) { + ((Bitmap) data[0]).recycle(); + } + }) : 0; + out.write(String.valueOf(durationMeasure)); + out.write(' '); + out.write(String.valueOf(durationLayout)); + out.write(' '); + out.write(String.valueOf(durationDraw)); + out.newLine(); + if (view instanceof ViewGroup) { + ViewGroup group = (ViewGroup) view; + final int count = group.getChildCount(); + for (int i = 0; i < count; i++) { + profileViewAndChildren(group.getChildAt(i), out, false); + } + } + } + interface ViewOperation<T> { T[] pre(); void run(T... data); @@ -1016,7 +1053,10 @@ public class ViewDebug { }); try { - latch.await(CAPTURE_TIMEOUT, TimeUnit.MILLISECONDS); + if (!latch.await(CAPTURE_TIMEOUT, TimeUnit.MILLISECONDS)) { + Log.w("View", "Could not complete the profiling of the view " + view); + return -1; + } } catch (InterruptedException e) { Log.w("View", "Could not complete the profiling of the view " + view); Thread.currentThread().interrupt(); @@ -1098,22 +1138,24 @@ public class ViewDebug { final View captureView = findView(root, parameter); Bitmap b = performViewCapture(captureView, false); - - if (b != null) { - BufferedOutputStream out = null; - try { - out = new BufferedOutputStream(clientStream, 32 * 1024); - b.compress(Bitmap.CompressFormat.PNG, 100, out); - out.flush(); - } finally { - if (out != null) { - out.close(); - } - b.recycle(); - } - } else { + + if (b == null) { Log.w("View", "Failed to create capture bitmap!"); - clientStream.close(); + // Send an empty one so that it doesn't get stuck waiting for + // something. + b = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888); + } + + BufferedOutputStream out = null; + try { + out = new BufferedOutputStream(clientStream, 32 * 1024); + b.compress(Bitmap.CompressFormat.PNG, 100, out); + out.flush(); + } finally { + if (out != null) { + out.close(); + } + b.recycle(); } } @@ -1337,9 +1379,12 @@ public class ViewDebug { // TODO: This should happen on the UI thread Object methodValue = method.invoke(view, (Object[]) null); final Class<?> returnType = method.getReturnType(); + final ExportedProperty property = sAnnotations.get(method); + String categoryPrefix = + property.category().length() != 0 ? property.category() + ":" : ""; if (returnType == int.class) { - final ExportedProperty property = sAnnotations.get(method); + if (property.resolveId() && context != null) { final int id = (Integer) methodValue; methodValue = resolveId(context, id); @@ -1347,7 +1392,8 @@ public class ViewDebug { final FlagToString[] flagsMapping = property.flagMapping(); if (flagsMapping.length > 0) { final int intValue = (Integer) methodValue; - final String valuePrefix = prefix + method.getName() + '_'; + final String valuePrefix = + categoryPrefix + prefix + method.getName() + '_'; exportUnrolledFlags(out, flagsMapping, intValue, valuePrefix); } @@ -1371,21 +1417,22 @@ public class ViewDebug { } } } else if (returnType == int[].class) { - final ExportedProperty property = sAnnotations.get(method); final int[] array = (int[]) methodValue; - final String valuePrefix = prefix + method.getName() + '_'; + final String valuePrefix = categoryPrefix + prefix + method.getName() + '_'; final String suffix = "()"; exportUnrolledArray(context, out, property, array, valuePrefix, suffix); + + // Probably want to return here, same as for fields. + return; } else if (!returnType.isPrimitive()) { - final ExportedProperty property = sAnnotations.get(method); if (property.deepExport()) { dumpViewProperties(context, methodValue, out, prefix + property.prefix()); continue; } } - writeEntry(out, prefix, method.getName(), "()", methodValue); + writeEntry(out, categoryPrefix + prefix, method.getName(), "()", methodValue); } catch (IllegalAccessException e) { } catch (InvocationTargetException e) { } @@ -1405,9 +1452,12 @@ public class ViewDebug { try { Object fieldValue = null; final Class<?> type = field.getType(); + final ExportedProperty property = sAnnotations.get(field); + String categoryPrefix = + property.category().length() != 0 ? property.category() + ":" : ""; if (type == int.class) { - final ExportedProperty property = sAnnotations.get(field); + if (property.resolveId() && context != null) { final int id = field.getInt(view); fieldValue = resolveId(context, id); @@ -1415,7 +1465,8 @@ public class ViewDebug { final FlagToString[] flagsMapping = property.flagMapping(); if (flagsMapping.length > 0) { final int intValue = field.getInt(view); - final String valuePrefix = prefix + field.getName() + '_'; + final String valuePrefix = + categoryPrefix + prefix + field.getName() + '_'; exportUnrolledFlags(out, flagsMapping, intValue, valuePrefix); } @@ -1437,9 +1488,8 @@ public class ViewDebug { } } } else if (type == int[].class) { - final ExportedProperty property = sAnnotations.get(field); final int[] array = (int[]) field.get(view); - final String valuePrefix = prefix + field.getName() + '_'; + final String valuePrefix = categoryPrefix + prefix + field.getName() + '_'; final String suffix = ""; exportUnrolledArray(context, out, property, array, valuePrefix, suffix); @@ -1447,10 +1497,9 @@ public class ViewDebug { // We exit here! return; } else if (!type.isPrimitive()) { - final ExportedProperty property = sAnnotations.get(field); if (property.deepExport()) { - dumpViewProperties(context, field.get(view), out, - prefix + property.prefix()); + dumpViewProperties(context, field.get(view), out, prefix + + property.prefix()); continue; } } @@ -1459,7 +1508,7 @@ public class ViewDebug { fieldValue = field.get(view); } - writeEntry(out, prefix, field.getName(), "", fieldValue); + writeEntry(out, categoryPrefix + prefix, field.getName(), "", fieldValue); } catch (IllegalAccessException e) { } } diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index eca583f46b22..28bed3a0d489 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -223,8 +223,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager /** * When set, this ViewGroup should not intercept touch events. + * {@hide} */ - private static final int FLAG_DISALLOW_INTERCEPT = 0x80000; + protected static final int FLAG_DISALLOW_INTERCEPT = 0x80000; /** * Indicates which types of drawing caches are to be kept in memory. @@ -362,7 +363,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * @return one of {@link #FOCUS_BEFORE_DESCENDANTS}, {@link #FOCUS_AFTER_DESCENDANTS}, * {@link #FOCUS_BLOCK_DESCENDANTS}. */ - @ViewDebug.ExportedProperty(mapping = { + @ViewDebug.ExportedProperty(category = "focus", mapping = { @ViewDebug.IntToString(from = FOCUS_BEFORE_DESCENDANTS, to = "FOCUS_BEFORE_DESCENDANTS"), @ViewDebug.IntToString(from = FOCUS_AFTER_DESCENDANTS, to = "FOCUS_AFTER_DESCENDANTS"), @ViewDebug.IntToString(from = FOCUS_BLOCK_DESCENDANTS, to = "FOCUS_BLOCK_DESCENDANTS") @@ -821,6 +822,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager */ @Override public boolean dispatchTouchEvent(MotionEvent ev) { + if (!onFilterTouchEventForSecurity(ev)) { + return false; + } + final int action = ev.getAction(); final float xf = ev.getX(); final float yf = ev.getY(); @@ -2763,7 +2768,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * @see #setChildrenDrawnWithCacheEnabled(boolean) * @see View#setDrawingCacheEnabled(boolean) */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "drawing") public boolean isAlwaysDrawnWithCacheEnabled() { return (mGroupFlags & FLAG_ALWAYS_DRAWN_WITH_CACHE) == FLAG_ALWAYS_DRAWN_WITH_CACHE; } @@ -2798,7 +2803,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * @see #setAlwaysDrawnWithCacheEnabled(boolean) * @see #setChildrenDrawnWithCacheEnabled(boolean) */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "drawing") protected boolean isChildrenDrawnWithCacheEnabled() { return (mGroupFlags & FLAG_CHILDREN_DRAWN_WITH_CACHE) == FLAG_CHILDREN_DRAWN_WITH_CACHE; } @@ -2830,7 +2835,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * @see #setChildrenDrawingOrderEnabled(boolean) * @see #getChildDrawingOrder(int, int) */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "drawing") protected boolean isChildrenDrawingOrderEnabled() { return (mGroupFlags & FLAG_USE_CHILD_DRAWING_ORDER) == FLAG_USE_CHILD_DRAWING_ORDER; } @@ -2867,7 +2872,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * {@link #PERSISTENT_ANIMATION_CACHE}, {@link #PERSISTENT_SCROLLING_CACHE} * and {@link #PERSISTENT_ALL_CACHES} */ - @ViewDebug.ExportedProperty(mapping = { + @ViewDebug.ExportedProperty(category = "drawing", mapping = { @ViewDebug.IntToString(from = PERSISTENT_NO_CACHE, to = "NONE"), @ViewDebug.IntToString(from = PERSISTENT_ALL_CACHES, to = "ANIMATION"), @ViewDebug.IntToString(from = PERSISTENT_SCROLLING_CACHE, to = "SCROLLING"), @@ -3500,7 +3505,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * constants FILL_PARENT (replaced by MATCH_PARENT , * in API Level 8) or WRAP_CONTENT. or an exact size. */ - @ViewDebug.ExportedProperty(mapping = { + @ViewDebug.ExportedProperty(category = "layout", mapping = { @ViewDebug.IntToString(from = MATCH_PARENT, to = "MATCH_PARENT"), @ViewDebug.IntToString(from = WRAP_CONTENT, to = "WRAP_CONTENT") }) @@ -3511,7 +3516,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * constants FILL_PARENT (replaced by MATCH_PARENT , * in API Level 8) or WRAP_CONTENT. or an exact size. */ - @ViewDebug.ExportedProperty(mapping = { + @ViewDebug.ExportedProperty(category = "layout", mapping = { @ViewDebug.IntToString(from = MATCH_PARENT, to = "MATCH_PARENT"), @ViewDebug.IntToString(from = WRAP_CONTENT, to = "WRAP_CONTENT") }) @@ -3636,25 +3641,25 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager /** * The left margin in pixels of the child. */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "layout") public int leftMargin; /** * The top margin in pixels of the child. */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "layout") public int topMargin; /** * The right margin in pixels of the child. */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "layout") public int rightMargin; /** * The bottom margin in pixels of the child. */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "layout") public int bottomMargin; /** diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewRoot.java index 03efea964c26..57c9055552b0 100644 --- a/core/java/android/view/ViewRoot.java +++ b/core/java/android/view/ViewRoot.java @@ -16,8 +16,10 @@ package android.view; +import com.android.internal.view.BaseSurfaceHolder; import com.android.internal.view.IInputMethodCallback; import com.android.internal.view.IInputMethodSession; +import com.android.internal.view.RootViewSurfaceTaker; import android.graphics.Canvas; import android.graphics.PixelFormat; @@ -26,12 +28,12 @@ import android.graphics.Rect; import android.graphics.Region; import android.os.*; import android.os.Process; -import android.os.SystemProperties; import android.util.AndroidRuntimeException; import android.util.Config; import android.util.DisplayMetrics; import android.util.Log; import android.util.EventLog; +import android.util.Slog; import android.util.SparseArray; import android.view.View.MeasureSpec; import android.view.accessibility.AccessibilityEvent; @@ -76,6 +78,7 @@ public final class ViewRoot extends Handler implements ViewParent, /** @noinspection PointlessBooleanExpression*/ private static final boolean DEBUG_DRAW = false || LOCAL_LOGV; private static final boolean DEBUG_LAYOUT = false || LOCAL_LOGV; + private static final boolean DEBUG_INPUT = true || LOCAL_LOGV; private static final boolean DEBUG_INPUT_RESIZE = false || LOCAL_LOGV; private static final boolean DEBUG_ORIENTATION = false || LOCAL_LOGV; private static final boolean DEBUG_TRACKBALL = false || LOCAL_LOGV; @@ -133,6 +136,11 @@ public final class ViewRoot extends Handler implements ViewParent, int mViewVisibility; boolean mAppVisible = true; + SurfaceHolder.Callback2 mSurfaceHolderCallback; + BaseSurfaceHolder mSurfaceHolder; + boolean mIsCreating; + boolean mDrawingAllowed; + final Region mTransparentRegion; final Region mPreviousTransparentRegion; @@ -144,7 +152,10 @@ public final class ViewRoot extends Handler implements ViewParent, CompatibilityInfo.Translator mTranslator; final View.AttachInfo mAttachInfo; - + InputChannel mInputChannel; + InputQueue.Callback mInputQueueCallback; + InputQueue mInputQueue; + final Rect mTempRect; // used in the transaction to not thrash the heap. final Rect mVisRect; // used to retrieve visible rect of focused view. @@ -210,7 +221,7 @@ public final class ViewRoot extends Handler implements ViewParent, AudioManager mAudioManager; private final int mDensity; - + public static IWindowSession getWindowSession(Looper mainLooper) { synchronized (mStaticInit) { if (!mInitialized) { @@ -435,6 +446,14 @@ public final class ViewRoot extends Handler implements ViewParent, mView = view; mWindowAttributes.copyFrom(attrs); attrs = mWindowAttributes; + if (view instanceof RootViewSurfaceTaker) { + mSurfaceHolderCallback = + ((RootViewSurfaceTaker)view).willYouTakeTheSurface(); + if (mSurfaceHolderCallback != null) { + mSurfaceHolder = new TakenSurfaceHolder(); + mSurfaceHolder.setFormat(PixelFormat.UNKNOWN); + } + } Resources resources = mView.getContext().getResources(); CompatibilityInfo compatibilityInfo = resources.getCompatibilityInfo(); mTranslator = compatibilityInfo.getTranslator(); @@ -473,13 +492,16 @@ public final class ViewRoot extends Handler implements ViewParent, // manager, to make sure we do the relayout before receiving // any other events from the system. requestLayout(); + mInputChannel = new InputChannel(); try { res = sWindowSession.add(mWindow, mWindowAttributes, - getHostVisibility(), mAttachInfo.mContentInsets); + getHostVisibility(), mAttachInfo.mContentInsets, + mInputChannel); } catch (RemoteException e) { mAdded = false; mView = null; mAttachInfo.mRootView = null; + mInputChannel = null; unscheduleTraversals(); throw new RuntimeException("Adding window failed", e); } finally { @@ -487,13 +509,13 @@ public final class ViewRoot extends Handler implements ViewParent, attrs.restore(); } } - + if (mTranslator != null) { mTranslator.translateRectInScreenToAppWindow(mAttachInfo.mContentInsets); } mPendingContentInsets.set(mAttachInfo.mContentInsets); mPendingVisibleInsets.set(0, 0, 0, 0); - if (Config.LOGV) Log.v("ViewRoot", "Added window " + mWindow); + if (Config.LOGV) Log.v(TAG, "Added window " + mWindow); if (res < WindowManagerImpl.ADD_OKAY) { mView = null; mAttachInfo.mRootView = null; @@ -533,6 +555,19 @@ public final class ViewRoot extends Handler implements ViewParent, throw new RuntimeException( "Unable to add window -- unknown error code " + res); } + + if (view instanceof RootViewSurfaceTaker) { + mInputQueueCallback = + ((RootViewSurfaceTaker)view).willYouTakeTheInputQueue(); + } + if (mInputQueueCallback != null) { + mInputQueue = new InputQueue(mInputChannel); + mInputQueueCallback.onInputQueueCreated(mInputQueue); + } else { + InputQueue.registerInputChannel(mInputChannel, mInputHandler, + Looper.myQueue()); + } + view.assignParent(this); mAddedTouchMode = (res&WindowManagerImpl.ADD_FLAG_IN_TOUCH_MODE) != 0; mAppVisible = (res&WindowManagerImpl.ADD_FLAG_APP_VISIBLE) != 0; @@ -682,6 +717,7 @@ public final class ViewRoot extends Handler implements ViewParent, boolean windowResizesToFitContent = false; boolean fullRedrawNeeded = mFullRedrawNeeded; boolean newSurface = false; + boolean surfaceChanged = false; WindowManager.LayoutParams lp = mWindowAttributes; int desiredWindowWidth; @@ -700,6 +736,7 @@ public final class ViewRoot extends Handler implements ViewParent, WindowManager.LayoutParams params = null; if (mWindowAttributesChanged) { mWindowAttributesChanged = false; + surfaceChanged = true; params = lp; } Rect frame = mWinFrame; @@ -717,7 +754,7 @@ public final class ViewRoot extends Handler implements ViewParent, // object is not initialized to its backing store, but soon it // will be (assuming the window is visible). attachInfo.mSurface = mSurface; - attachInfo.mTranslucentWindow = lp.format != PixelFormat.OPAQUE; + attachInfo.mTranslucentWindow = PixelFormat.formatHasAlpha(lp.format); attachInfo.mHasWindowFocus = false; attachInfo.mWindowVisibility = viewVisibility; attachInfo.mRecomputeGlobalAttributes = false; @@ -731,7 +768,7 @@ public final class ViewRoot extends Handler implements ViewParent, desiredWindowWidth = frame.width(); desiredWindowHeight = frame.height(); if (desiredWindowWidth != mWidth || desiredWindowHeight != mHeight) { - if (DEBUG_ORIENTATION) Log.v("ViewRoot", + if (DEBUG_ORIENTATION) Log.v(TAG, "View " + host + " resized to: " + frame); fullRedrawNeeded = true; mLayoutRequested = true; @@ -795,7 +832,7 @@ public final class ViewRoot extends Handler implements ViewParent, childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height); // Ask host how big it wants to be - if (DEBUG_ORIENTATION || DEBUG_LAYOUT) Log.v("ViewRoot", + if (DEBUG_ORIENTATION || DEBUG_LAYOUT) Log.v(TAG, "Measuring " + host + " in display " + desiredWindowWidth + "x" + desiredWindowHeight + "..."); host.measure(childWidthMeasureSpec, childHeightMeasureSpec); @@ -886,11 +923,16 @@ public final class ViewRoot extends Handler implements ViewParent, } } + if (mSurfaceHolder != null) { + mSurfaceHolder.mSurfaceLock.lock(); + mDrawingAllowed = true; + } + boolean initialized = false; boolean contentInsetsChanged = false; boolean visibleInsetsChanged; + boolean hadSurface = mSurface.isValid(); try { - boolean hadSurface = mSurface.isValid(); int fl = 0; if (params != null) { fl = params.flags; @@ -965,8 +1007,9 @@ public final class ViewRoot extends Handler implements ViewParent, } } catch (RemoteException e) { } + if (DEBUG_ORIENTATION) Log.v( - "ViewRoot", "Relayout returned: frame=" + frame + ", surface=" + mSurface); + TAG, "Relayout returned: frame=" + frame + ", surface=" + mSurface); attachInfo.mWindowLeft = frame.left; attachInfo.mWindowTop = frame.top; @@ -977,6 +1020,57 @@ public final class ViewRoot extends Handler implements ViewParent, mWidth = frame.width(); mHeight = frame.height(); + if (mSurfaceHolder != null) { + // The app owns the surface; tell it about what is going on. + if (mSurface.isValid()) { + // XXX .copyFrom() doesn't work! + //mSurfaceHolder.mSurface.copyFrom(mSurface); + mSurfaceHolder.mSurface = mSurface; + } + mSurfaceHolder.mSurfaceLock.unlock(); + if (mSurface.isValid()) { + if (!hadSurface) { + mSurfaceHolder.ungetCallbacks(); + + mIsCreating = true; + mSurfaceHolderCallback.surfaceCreated(mSurfaceHolder); + SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks(); + if (callbacks != null) { + for (SurfaceHolder.Callback c : callbacks) { + c.surfaceCreated(mSurfaceHolder); + } + } + surfaceChanged = true; + } + if (surfaceChanged) { + mSurfaceHolderCallback.surfaceChanged(mSurfaceHolder, + lp.format, mWidth, mHeight); + SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks(); + if (callbacks != null) { + for (SurfaceHolder.Callback c : callbacks) { + c.surfaceChanged(mSurfaceHolder, lp.format, + mWidth, mHeight); + } + } + } + mIsCreating = false; + } else if (hadSurface) { + mSurfaceHolder.ungetCallbacks(); + SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks(); + mSurfaceHolderCallback.surfaceDestroyed(mSurfaceHolder); + if (callbacks != null) { + for (SurfaceHolder.Callback c : callbacks) { + c.surfaceDestroyed(mSurfaceHolder); + } + } + mSurfaceHolder.mSurfaceLock.lock(); + // Make surface invalid. + //mSurfaceHolder.mSurface.copyFrom(mSurface); + mSurfaceHolder.mSurface = new Surface(); + mSurfaceHolder.mSurfaceLock.unlock(); + } + } + if (initialized) { mGlCanvas.setViewport((int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f)); @@ -1036,7 +1130,7 @@ public final class ViewRoot extends Handler implements ViewParent, mLayoutRequested = false; mScrollMayChange = true; if (DEBUG_ORIENTATION || DEBUG_LAYOUT) Log.v( - "ViewRoot", "Laying out " + host + " to (" + + TAG, "Laying out " + host + " to (" + host.mMeasuredWidth + ", " + host.mMeasuredHeight + ")"); long startTime = 0L; if (Config.DEBUG && ViewDebug.profileLayout) { @@ -1165,9 +1259,21 @@ public final class ViewRoot extends Handler implements ViewParent, if ((relayoutResult&WindowManagerImpl.RELAYOUT_FIRST_TIME) != 0 || mReportNextDraw) { if (LOCAL_LOGV) { - Log.v("ViewRoot", "FINISHED DRAWING: " + mWindowAttributes.getTitle()); + Log.v(TAG, "FINISHED DRAWING: " + mWindowAttributes.getTitle()); } mReportNextDraw = false; + if (mSurfaceHolder != null && mSurface.isValid()) { + mSurfaceHolderCallback.surfaceRedrawNeeded(mSurfaceHolder); + SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks(); + if (callbacks != null) { + for (SurfaceHolder.Callback c : callbacks) { + if (c instanceof SurfaceHolder.Callback2) { + ((SurfaceHolder.Callback2)c).surfaceRedrawNeeded( + mSurfaceHolder); + } + } + } + } try { sWindowSession.finishDrawing(mWindow); } catch (RemoteException e) { @@ -1268,6 +1374,12 @@ public final class ViewRoot extends Handler implements ViewParent, boolean scalingRequired = mAttachInfo.mScalingRequired; Rect dirty = mDirty; + if (mSurfaceHolder != null) { + // The app owns the surface, we won't draw. + dirty.setEmpty(); + return; + } + if (mUseGL) { if (!dirty.isEmpty()) { Canvas canvas = mGlCanvas; @@ -1324,7 +1436,7 @@ public final class ViewRoot extends Handler implements ViewParent, } if (DEBUG_ORIENTATION || DEBUG_DRAW) { - Log.v("ViewRoot", "Draw " + mView + "/" + Log.v(TAG, "Draw " + mView + "/" + mWindowAttributes.getTitle() + ": dirty={" + dirty.left + "," + dirty.top + "," + dirty.right + "," + dirty.bottom + "} surface=" @@ -1332,107 +1444,109 @@ public final class ViewRoot extends Handler implements ViewParent, appScale + ", width=" + mWidth + ", height=" + mHeight); } - Canvas canvas; - try { - int left = dirty.left; - int top = dirty.top; - int right = dirty.right; - int bottom = dirty.bottom; - canvas = surface.lockCanvas(dirty); - - if (left != dirty.left || top != dirty.top || right != dirty.right || - bottom != dirty.bottom) { - mAttachInfo.mIgnoreDirtyState = true; - } - - // TODO: Do this in native - canvas.setDensity(mDensity); - } catch (Surface.OutOfResourcesException e) { - Log.e("ViewRoot", "OutOfResourcesException locking surface", e); - // TODO: we should ask the window manager to do something! - // for now we just do nothing - return; - } catch (IllegalArgumentException e) { - Log.e("ViewRoot", "IllegalArgumentException locking surface", e); - // TODO: we should ask the window manager to do something! - // for now we just do nothing - return; - } + if (!dirty.isEmpty() || mIsAnimating) { + Canvas canvas; + try { + int left = dirty.left; + int top = dirty.top; + int right = dirty.right; + int bottom = dirty.bottom; + canvas = surface.lockCanvas(dirty); + + if (left != dirty.left || top != dirty.top || right != dirty.right || + bottom != dirty.bottom) { + mAttachInfo.mIgnoreDirtyState = true; + } - try { - if (!dirty.isEmpty() || mIsAnimating) { - long startTime = 0L; + // TODO: Do this in native + canvas.setDensity(mDensity); + } catch (Surface.OutOfResourcesException e) { + Log.e(TAG, "OutOfResourcesException locking surface", e); + // TODO: we should ask the window manager to do something! + // for now we just do nothing + return; + } catch (IllegalArgumentException e) { + Log.e(TAG, "IllegalArgumentException locking surface", e); + // TODO: we should ask the window manager to do something! + // for now we just do nothing + return; + } - if (DEBUG_ORIENTATION || DEBUG_DRAW) { - Log.v("ViewRoot", "Surface " + surface + " drawing to bitmap w=" - + canvas.getWidth() + ", h=" + canvas.getHeight()); - //canvas.drawARGB(255, 255, 0, 0); - } + try { + if (!dirty.isEmpty() || mIsAnimating) { + long startTime = 0L; - if (Config.DEBUG && ViewDebug.profileDrawing) { - startTime = SystemClock.elapsedRealtime(); - } + if (DEBUG_ORIENTATION || DEBUG_DRAW) { + Log.v(TAG, "Surface " + surface + " drawing to bitmap w=" + + canvas.getWidth() + ", h=" + canvas.getHeight()); + //canvas.drawARGB(255, 255, 0, 0); + } - // If this bitmap's format includes an alpha channel, we - // need to clear it before drawing so that the child will - // properly re-composite its drawing on a transparent - // background. This automatically respects the clip/dirty region - // or - // If we are applying an offset, we need to clear the area - // where the offset doesn't appear to avoid having garbage - // left in the blank areas. - if (!canvas.isOpaque() || yoff != 0) { - canvas.drawColor(0, PorterDuff.Mode.CLEAR); - } + if (Config.DEBUG && ViewDebug.profileDrawing) { + startTime = SystemClock.elapsedRealtime(); + } - dirty.setEmpty(); - mIsAnimating = false; - mAttachInfo.mDrawingTime = SystemClock.uptimeMillis(); - mView.mPrivateFlags |= View.DRAWN; + // If this bitmap's format includes an alpha channel, we + // need to clear it before drawing so that the child will + // properly re-composite its drawing on a transparent + // background. This automatically respects the clip/dirty region + // or + // If we are applying an offset, we need to clear the area + // where the offset doesn't appear to avoid having garbage + // left in the blank areas. + if (!canvas.isOpaque() || yoff != 0) { + canvas.drawColor(0, PorterDuff.Mode.CLEAR); + } - if (DEBUG_DRAW) { - Context cxt = mView.getContext(); - Log.i(TAG, "Drawing: package:" + cxt.getPackageName() + - ", metrics=" + cxt.getResources().getDisplayMetrics() + - ", compatibilityInfo=" + cxt.getResources().getCompatibilityInfo()); - } - int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG); - try { - canvas.translate(0, -yoff); - if (mTranslator != null) { - mTranslator.translateCanvas(canvas); + dirty.setEmpty(); + mIsAnimating = false; + mAttachInfo.mDrawingTime = SystemClock.uptimeMillis(); + mView.mPrivateFlags |= View.DRAWN; + + if (DEBUG_DRAW) { + Context cxt = mView.getContext(); + Log.i(TAG, "Drawing: package:" + cxt.getPackageName() + + ", metrics=" + cxt.getResources().getDisplayMetrics() + + ", compatibilityInfo=" + cxt.getResources().getCompatibilityInfo()); + } + int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG); + try { + canvas.translate(0, -yoff); + if (mTranslator != null) { + mTranslator.translateCanvas(canvas); + } + canvas.setScreenDensity(scalingRequired + ? DisplayMetrics.DENSITY_DEVICE : 0); + mView.draw(canvas); + } finally { + mAttachInfo.mIgnoreDirtyState = false; + canvas.restoreToCount(saveCount); } - canvas.setScreenDensity(scalingRequired - ? DisplayMetrics.DENSITY_DEVICE : 0); - mView.draw(canvas); - } finally { - mAttachInfo.mIgnoreDirtyState = false; - canvas.restoreToCount(saveCount); - } - if (Config.DEBUG && ViewDebug.consistencyCheckEnabled) { - mView.dispatchConsistencyCheck(ViewDebug.CONSISTENCY_DRAWING); - } + if (Config.DEBUG && ViewDebug.consistencyCheckEnabled) { + mView.dispatchConsistencyCheck(ViewDebug.CONSISTENCY_DRAWING); + } - if (SHOW_FPS || Config.DEBUG && ViewDebug.showFps) { - int now = (int)SystemClock.elapsedRealtime(); - if (sDrawTime != 0) { - nativeShowFPS(canvas, now - sDrawTime); + if (SHOW_FPS || Config.DEBUG && ViewDebug.showFps) { + int now = (int)SystemClock.elapsedRealtime(); + if (sDrawTime != 0) { + nativeShowFPS(canvas, now - sDrawTime); + } + sDrawTime = now; } - sDrawTime = now; - } - if (Config.DEBUG && ViewDebug.profileDrawing) { - EventLog.writeEvent(60000, SystemClock.elapsedRealtime() - startTime); + if (Config.DEBUG && ViewDebug.profileDrawing) { + EventLog.writeEvent(60000, SystemClock.elapsedRealtime() - startTime); + } } - } - } finally { - surface.unlockCanvasAndPost(canvas); + } finally { + surface.unlockCanvasAndPost(canvas); + } } if (LOCAL_LOGV) { - Log.v("ViewRoot", "Surface " + surface + " unlockCanvasAndPost"); + Log.v(TAG, "Surface " + surface + " unlockCanvasAndPost"); } if (scrolling) { @@ -1624,7 +1738,7 @@ public final class ViewRoot extends Handler implements ViewParent, } void dispatchDetachedFromWindow() { - if (Config.LOGV) Log.v("ViewRoot", "Detaching in " + this + " of " + mSurface); + if (Config.LOGV) Log.v(TAG, "Detaching in " + this + " of " + mSurface); if (mView != null) { mView.dispatchDetachedFromWindow(); @@ -1639,10 +1753,26 @@ public final class ViewRoot extends Handler implements ViewParent, } mSurface.release(); + if (mInputChannel != null) { + if (mInputQueueCallback != null) { + mInputQueueCallback.onInputQueueDestroyed(mInputQueue); + mInputQueueCallback = null; + } else { + InputQueue.unregisterInputChannel(mInputChannel); + } + } + try { sWindowSession.remove(mWindow); } catch (RemoteException e) { } + + // Dispose the input channel after removing the window so the Window Manager + // doesn't interpret the input channel being closed as an abnormal termination. + if (mInputChannel != null) { + mInputChannel.dispose(); + mInputChannel = null; + } } void updateConfiguration(Configuration config, boolean force) { @@ -1736,118 +1866,27 @@ public final class ViewRoot extends Handler implements ViewParent, break; case DISPATCH_KEY: if (LOCAL_LOGV) Log.v( - "ViewRoot", "Dispatching key " + TAG, "Dispatching key " + msg.obj + " to " + mView); - deliverKeyEvent((KeyEvent)msg.obj, true); + deliverKeyEvent((KeyEvent)msg.obj, msg.arg1 != 0); break; case DISPATCH_POINTER: { - MotionEvent event = (MotionEvent)msg.obj; - boolean callWhenDone = msg.arg1 != 0; - - if (event == null) { - try { - long timeBeforeGettingEvents; - if (MEASURE_LATENCY) { - timeBeforeGettingEvents = System.nanoTime(); - } - - event = sWindowSession.getPendingPointerMove(mWindow); - - if (MEASURE_LATENCY && event != null) { - lt.sample("9 Client got events ", System.nanoTime() - event.getEventTimeNano()); - lt.sample("8 Client getting events ", timeBeforeGettingEvents - event.getEventTimeNano()); - } - } catch (RemoteException e) { - } - callWhenDone = false; - } - if (event != null && mTranslator != null) { - mTranslator.translateEventInScreenToAppWindow(event); - } + MotionEvent event = (MotionEvent) msg.obj; try { - boolean handled; - if (mView != null && mAdded && event != null) { - - // enter touch mode on the down - boolean isDown = event.getAction() == MotionEvent.ACTION_DOWN; - if (isDown) { - ensureTouchMode(true); - } - if(Config.LOGV) { - captureMotionLog("captureDispatchPointer", event); - } - if (mCurScrollY != 0) { - event.offsetLocation(0, mCurScrollY); - } - if (MEASURE_LATENCY) { - lt.sample("A Dispatching TouchEvents", System.nanoTime() - event.getEventTimeNano()); - } - handled = mView.dispatchTouchEvent(event); - if (MEASURE_LATENCY) { - lt.sample("B Dispatched TouchEvents ", System.nanoTime() - event.getEventTimeNano()); - } - if (!handled && isDown) { - int edgeSlop = mViewConfiguration.getScaledEdgeSlop(); - - final int edgeFlags = event.getEdgeFlags(); - int direction = View.FOCUS_UP; - int x = (int)event.getX(); - int y = (int)event.getY(); - final int[] deltas = new int[2]; - - if ((edgeFlags & MotionEvent.EDGE_TOP) != 0) { - direction = View.FOCUS_DOWN; - if ((edgeFlags & MotionEvent.EDGE_LEFT) != 0) { - deltas[0] = edgeSlop; - x += edgeSlop; - } else if ((edgeFlags & MotionEvent.EDGE_RIGHT) != 0) { - deltas[0] = -edgeSlop; - x -= edgeSlop; - } - } else if ((edgeFlags & MotionEvent.EDGE_BOTTOM) != 0) { - direction = View.FOCUS_UP; - if ((edgeFlags & MotionEvent.EDGE_LEFT) != 0) { - deltas[0] = edgeSlop; - x += edgeSlop; - } else if ((edgeFlags & MotionEvent.EDGE_RIGHT) != 0) { - deltas[0] = -edgeSlop; - x -= edgeSlop; - } - } else if ((edgeFlags & MotionEvent.EDGE_LEFT) != 0) { - direction = View.FOCUS_RIGHT; - } else if ((edgeFlags & MotionEvent.EDGE_RIGHT) != 0) { - direction = View.FOCUS_LEFT; - } - - if (edgeFlags != 0 && mView instanceof ViewGroup) { - View nearest = FocusFinder.getInstance().findNearestTouchable( - ((ViewGroup) mView), x, y, direction, deltas); - if (nearest != null) { - event.offsetLocation(deltas[0], deltas[1]); - event.setEdgeFlags(0); - mView.dispatchTouchEvent(event); - } - } - } - } + deliverPointerEvent(event); } finally { - if (callWhenDone) { - try { - sWindowSession.finishKey(mWindow); - } catch (RemoteException e) { - } - } - if (event != null) { - event.recycle(); - } + event.recycle(); if (LOCAL_LOGV || WATCH_POINTER) Log.i(TAG, "Done dispatching!"); - // Let the exception fall through -- the looper will catch - // it and take care of the bad app for us. } } break; - case DISPATCH_TRACKBALL: - deliverTrackballEvent((MotionEvent)msg.obj, msg.arg1 != 0); - break; + case DISPATCH_TRACKBALL: { + MotionEvent event = (MotionEvent) msg.obj; + try { + deliverTrackballEvent(event); + } finally { + event.recycle(); + } + } break; case DISPATCH_APP_VISIBILITY: handleAppVisibility(msg.arg1 != 0); break; @@ -1949,7 +1988,7 @@ public final class ViewRoot extends Handler implements ViewParent, break; case DISPATCH_KEY_FROM_IME: { if (LOCAL_LOGV) Log.v( - "ViewRoot", "Dispatching key " + TAG, "Dispatching key " + msg.obj + " from IME to " + mView); KeyEvent event = (KeyEvent)msg.obj; if ((event.getFlags()&KeyEvent.FLAG_FROM_SYSTEM) != 0) { @@ -1979,7 +2018,19 @@ public final class ViewRoot extends Handler implements ViewParent, } break; } } + + private void finishKeyEvent(KeyEvent event) { + if (LOCAL_LOGV) Log.v(TAG, "Telling window manager key is finished"); + if (mFinishedCallback != null) { + mFinishedCallback.run(); + mFinishedCallback = null; + } else { + Slog.w(TAG, "Attempted to tell the input queue that the current key event " + + "is finished but there is no key event actually in progress."); + } + } + /** * Something in the current window tells us we need to change the touch mode. For * example, we are not in touch mode, and the user touches the screen. @@ -2101,50 +2152,95 @@ public final class ViewRoot extends Handler implements ViewParent, return false; } + private void deliverPointerEvent(MotionEvent event) { + if (mTranslator != null) { + mTranslator.translateEventInScreenToAppWindow(event); + } + + boolean handled; + if (mView != null && mAdded) { - private void deliverTrackballEvent(MotionEvent event, boolean callWhenDone) { - if (event == null) { - try { - event = sWindowSession.getPendingTrackballMove(mWindow); - } catch (RemoteException e) { + // enter touch mode on the down + boolean isDown = event.getAction() == MotionEvent.ACTION_DOWN; + if (isDown) { + ensureTouchMode(true); + } + if(Config.LOGV) { + captureMotionLog("captureDispatchPointer", event); + } + if (mCurScrollY != 0) { + event.offsetLocation(0, mCurScrollY); + } + if (MEASURE_LATENCY) { + lt.sample("A Dispatching TouchEvents", System.nanoTime() - event.getEventTimeNano()); + } + handled = mView.dispatchTouchEvent(event); + if (MEASURE_LATENCY) { + lt.sample("B Dispatched TouchEvents ", System.nanoTime() - event.getEventTimeNano()); + } + if (!handled && isDown) { + int edgeSlop = mViewConfiguration.getScaledEdgeSlop(); + + final int edgeFlags = event.getEdgeFlags(); + int direction = View.FOCUS_UP; + int x = (int)event.getX(); + int y = (int)event.getY(); + final int[] deltas = new int[2]; + + if ((edgeFlags & MotionEvent.EDGE_TOP) != 0) { + direction = View.FOCUS_DOWN; + if ((edgeFlags & MotionEvent.EDGE_LEFT) != 0) { + deltas[0] = edgeSlop; + x += edgeSlop; + } else if ((edgeFlags & MotionEvent.EDGE_RIGHT) != 0) { + deltas[0] = -edgeSlop; + x -= edgeSlop; + } + } else if ((edgeFlags & MotionEvent.EDGE_BOTTOM) != 0) { + direction = View.FOCUS_UP; + if ((edgeFlags & MotionEvent.EDGE_LEFT) != 0) { + deltas[0] = edgeSlop; + x += edgeSlop; + } else if ((edgeFlags & MotionEvent.EDGE_RIGHT) != 0) { + deltas[0] = -edgeSlop; + x -= edgeSlop; + } + } else if ((edgeFlags & MotionEvent.EDGE_LEFT) != 0) { + direction = View.FOCUS_RIGHT; + } else if ((edgeFlags & MotionEvent.EDGE_RIGHT) != 0) { + direction = View.FOCUS_LEFT; + } + + if (edgeFlags != 0 && mView instanceof ViewGroup) { + View nearest = FocusFinder.getInstance().findNearestTouchable( + ((ViewGroup) mView), x, y, direction, deltas); + if (nearest != null) { + event.offsetLocation(deltas[0], deltas[1]); + event.setEdgeFlags(0); + mView.dispatchTouchEvent(event); + } + } } - callWhenDone = false; } + } + private void deliverTrackballEvent(MotionEvent event) { if (DEBUG_TRACKBALL) Log.v(TAG, "Motion event:" + event); boolean handled = false; - try { - if (event == null) { - handled = true; - } else if (mView != null && mAdded) { - handled = mView.dispatchTrackballEvent(event); - if (!handled) { - // we could do something here, like changing the focus - // or something? - } - } - } finally { + if (mView != null && mAdded) { + handled = mView.dispatchTrackballEvent(event); if (handled) { - if (callWhenDone) { - try { - sWindowSession.finishKey(mWindow); - } catch (RemoteException e) { - } - } - if (event != null) { - event.recycle(); - } // If we reach this, we delivered a trackball event to mView and // mView consumed it. Because we will not translate the trackball // event into a key event, touch mode will not exit, so we exit // touch mode here. ensureTouchMode(false); - //noinspection ReturnInsideFinallyBlock return; } - // Let the exception fall through -- the looper will catch - // it and take care of the bad app for us. + + // Otherwise we could do something here, like changing the focus + // or something? } final TrackballAxis x = mTrackballAxisX; @@ -2159,100 +2255,86 @@ public final class ViewRoot extends Handler implements ViewParent, mLastTrackballTime = curTime; } - try { - final int action = event.getAction(); - final int metastate = event.getMetaState(); - switch (action) { - case MotionEvent.ACTION_DOWN: - x.reset(2); - y.reset(2); - deliverKeyEvent(new KeyEvent(curTime, curTime, - KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_CENTER, - 0, metastate), false); - break; - case MotionEvent.ACTION_UP: - x.reset(2); - y.reset(2); - deliverKeyEvent(new KeyEvent(curTime, curTime, - KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DPAD_CENTER, - 0, metastate), false); - break; - } - - if (DEBUG_TRACKBALL) Log.v(TAG, "TB X=" + x.position + " step=" - + x.step + " dir=" + x.dir + " acc=" + x.acceleration - + " move=" + event.getX() - + " / Y=" + y.position + " step=" - + y.step + " dir=" + y.dir + " acc=" + y.acceleration - + " move=" + event.getY()); - final float xOff = x.collect(event.getX(), event.getEventTime(), "X"); - final float yOff = y.collect(event.getY(), event.getEventTime(), "Y"); - - // Generate DPAD events based on the trackball movement. - // We pick the axis that has moved the most as the direction of - // the DPAD. When we generate DPAD events for one axis, then the - // other axis is reset -- we don't want to perform DPAD jumps due - // to slight movements in the trackball when making major movements - // along the other axis. - int keycode = 0; - int movement = 0; - float accel = 1; - if (xOff > yOff) { - movement = x.generate((2/event.getXPrecision())); - if (movement != 0) { - keycode = movement > 0 ? KeyEvent.KEYCODE_DPAD_RIGHT - : KeyEvent.KEYCODE_DPAD_LEFT; - accel = x.acceleration; - y.reset(2); - } - } else if (yOff > 0) { - movement = y.generate((2/event.getYPrecision())); - if (movement != 0) { - keycode = movement > 0 ? KeyEvent.KEYCODE_DPAD_DOWN - : KeyEvent.KEYCODE_DPAD_UP; - accel = y.acceleration; - x.reset(2); - } - } - - if (keycode != 0) { - if (movement < 0) movement = -movement; - int accelMovement = (int)(movement * accel); - if (DEBUG_TRACKBALL) Log.v(TAG, "Move: movement=" + movement - + " accelMovement=" + accelMovement - + " accel=" + accel); - if (accelMovement > movement) { - if (DEBUG_TRACKBALL) Log.v("foo", "Delivering fake DPAD: " - + keycode); - movement--; - deliverKeyEvent(new KeyEvent(curTime, curTime, - KeyEvent.ACTION_MULTIPLE, keycode, - accelMovement-movement, metastate), false); - } - while (movement > 0) { - if (DEBUG_TRACKBALL) Log.v("foo", "Delivering fake DPAD: " - + keycode); - movement--; - curTime = SystemClock.uptimeMillis(); - deliverKeyEvent(new KeyEvent(curTime, curTime, - KeyEvent.ACTION_DOWN, keycode, 0, event.getMetaState()), false); - deliverKeyEvent(new KeyEvent(curTime, curTime, - KeyEvent.ACTION_UP, keycode, 0, metastate), false); - } - mLastTrackballTime = curTime; - } - } finally { - if (callWhenDone) { - try { - sWindowSession.finishKey(mWindow); - } catch (RemoteException e) { - } - if (event != null) { - event.recycle(); - } + final int action = event.getAction(); + final int metastate = event.getMetaState(); + switch (action) { + case MotionEvent.ACTION_DOWN: + x.reset(2); + y.reset(2); + deliverKeyEvent(new KeyEvent(curTime, curTime, + KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_CENTER, + 0, metastate), false); + break; + case MotionEvent.ACTION_UP: + x.reset(2); + y.reset(2); + deliverKeyEvent(new KeyEvent(curTime, curTime, + KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DPAD_CENTER, + 0, metastate), false); + break; + } + + if (DEBUG_TRACKBALL) Log.v(TAG, "TB X=" + x.position + " step=" + + x.step + " dir=" + x.dir + " acc=" + x.acceleration + + " move=" + event.getX() + + " / Y=" + y.position + " step=" + + y.step + " dir=" + y.dir + " acc=" + y.acceleration + + " move=" + event.getY()); + final float xOff = x.collect(event.getX(), event.getEventTime(), "X"); + final float yOff = y.collect(event.getY(), event.getEventTime(), "Y"); + + // Generate DPAD events based on the trackball movement. + // We pick the axis that has moved the most as the direction of + // the DPAD. When we generate DPAD events for one axis, then the + // other axis is reset -- we don't want to perform DPAD jumps due + // to slight movements in the trackball when making major movements + // along the other axis. + int keycode = 0; + int movement = 0; + float accel = 1; + if (xOff > yOff) { + movement = x.generate((2/event.getXPrecision())); + if (movement != 0) { + keycode = movement > 0 ? KeyEvent.KEYCODE_DPAD_RIGHT + : KeyEvent.KEYCODE_DPAD_LEFT; + accel = x.acceleration; + y.reset(2); + } + } else if (yOff > 0) { + movement = y.generate((2/event.getYPrecision())); + if (movement != 0) { + keycode = movement > 0 ? KeyEvent.KEYCODE_DPAD_DOWN + : KeyEvent.KEYCODE_DPAD_UP; + accel = y.acceleration; + x.reset(2); + } + } + + if (keycode != 0) { + if (movement < 0) movement = -movement; + int accelMovement = (int)(movement * accel); + if (DEBUG_TRACKBALL) Log.v(TAG, "Move: movement=" + movement + + " accelMovement=" + accelMovement + + " accel=" + accel); + if (accelMovement > movement) { + if (DEBUG_TRACKBALL) Log.v("foo", "Delivering fake DPAD: " + + keycode); + movement--; + deliverKeyEvent(new KeyEvent(curTime, curTime, + KeyEvent.ACTION_MULTIPLE, keycode, + accelMovement-movement, metastate), false); + } + while (movement > 0) { + if (DEBUG_TRACKBALL) Log.v("foo", "Delivering fake DPAD: " + + keycode); + movement--; + curTime = SystemClock.uptimeMillis(); + deliverKeyEvent(new KeyEvent(curTime, curTime, + KeyEvent.ACTION_DOWN, keycode, 0, event.getMetaState()), false); + deliverKeyEvent(new KeyEvent(curTime, curTime, + KeyEvent.ACTION_UP, keycode, 0, metastate), false); } - // Let the exception fall through -- the looper will catch - // it and take care of the bad app for us. + mLastTrackballTime = curTime; } } @@ -2405,12 +2487,7 @@ public final class ViewRoot extends Handler implements ViewParent, ? mView.dispatchKeyEventPreIme(event) : true; if (handled) { if (sendDone) { - if (LOCAL_LOGV) Log.v( - "ViewRoot", "Telling window manager key is finished"); - try { - sWindowSession.finishKey(mWindow); - } catch (RemoteException e) { - } + finishKeyEvent(event); } return; } @@ -2441,14 +2518,9 @@ public final class ViewRoot extends Handler implements ViewParent, deliverKeyEventToViewHierarchy(event, sendDone); return; } else if (sendDone) { - if (LOCAL_LOGV) Log.v( - "ViewRoot", "Telling window manager key is finished"); - try { - sWindowSession.finishKey(mWindow); - } catch (RemoteException e) { - } + finishKeyEvent(event); } else { - Log.w("ViewRoot", "handleFinishedEvent(seq=" + seq + Log.w(TAG, "handleFinishedEvent(seq=" + seq + " handled=" + handled + " ev=" + event + ") neither delivering nor finishing key"); } @@ -2519,12 +2591,7 @@ public final class ViewRoot extends Handler implements ViewParent, } finally { if (sendDone) { - if (LOCAL_LOGV) Log.v( - "ViewRoot", "Telling window manager key is finished"); - try { - sWindowSession.finishKey(mWindow); - } catch (RemoteException e) { - } + finishKeyEvent(event); } // Let the exception fall through -- the looper will catch // it and take care of the bad app for us. @@ -2646,7 +2713,7 @@ public final class ViewRoot extends Handler implements ViewParent, void doDie() { checkThread(); - if (Config.LOGV) Log.v("ViewRoot", "DIE in " + this + " of " + mSurface); + if (Config.LOGV) Log.v(TAG, "DIE in " + this + " of " + mSurface); synchronized (this) { if (mAdded && !mFirst) { int viewVisibility = mView.getVisibility(); @@ -2702,11 +2769,36 @@ public final class ViewRoot extends Handler implements ViewParent, msg.obj = ri; sendMessage(msg); } + + private Runnable mFinishedCallback; + + private final InputHandler mInputHandler = new InputHandler() { + public void handleKey(KeyEvent event, Runnable finishedCallback) { + if (mFinishedCallback != null) { + Slog.w(TAG, "Received a new key event from the input queue but there is " + + "already an unfinished key event in progress."); + } + + mFinishedCallback = finishedCallback; + + dispatchKey(event, true); + } + + public void handleMotion(MotionEvent event, Runnable finishedCallback) { + finishedCallback.run(); + + dispatchMotion(event); + } + }; public void dispatchKey(KeyEvent event) { - if (event.getAction() == KeyEvent.ACTION_DOWN) { - //noinspection ConstantConditions - if (false && event.getKeyCode() == KeyEvent.KEYCODE_CAMERA) { + dispatchKey(event, false); + } + + private void dispatchKey(KeyEvent event, boolean sendDone) { + //noinspection ConstantConditions + if (false && event.getAction() == KeyEvent.ACTION_DOWN) { + if (event.getKeyCode() == KeyEvent.KEYCODE_CAMERA) { if (Config.LOGD) Log.d("keydisp", "==================================================="); if (Config.LOGD) Log.d("keydisp", "Focused view Hierarchy is:"); @@ -2719,29 +2811,38 @@ public final class ViewRoot extends Handler implements ViewParent, Message msg = obtainMessage(DISPATCH_KEY); msg.obj = event; + msg.arg1 = sendDone ? 1 : 0; if (LOCAL_LOGV) Log.v( - "ViewRoot", "sending key " + event + " to " + mView); + TAG, "sending key " + event + " to " + mView); sendMessageAtTime(msg, event.getEventTime()); } + + public void dispatchMotion(MotionEvent event) { + int source = event.getSource(); + if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) { + dispatchPointer(event); + } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) { + dispatchTrackball(event); + } else { + // TODO + Log.v(TAG, "Dropping unsupported motion event (unimplemented): " + event); + } + } - public void dispatchPointer(MotionEvent event, long eventTime, - boolean callWhenDone) { + public void dispatchPointer(MotionEvent event) { Message msg = obtainMessage(DISPATCH_POINTER); msg.obj = event; - msg.arg1 = callWhenDone ? 1 : 0; - sendMessageAtTime(msg, eventTime); + sendMessageAtTime(msg, event.getEventTime()); } - public void dispatchTrackball(MotionEvent event, long eventTime, - boolean callWhenDone) { + public void dispatchTrackball(MotionEvent event) { Message msg = obtainMessage(DISPATCH_TRACKBALL); msg.obj = event; - msg.arg1 = callWhenDone ? 1 : 0; - sendMessageAtTime(msg, eventTime); + sendMessageAtTime(msg, event.getEventTime()); } - + public void dispatchAppVisibility(boolean visible) { Message msg = obtainMessage(DISPATCH_APP_VISIBILITY); msg.arg1 = visible ? 1 : 0; @@ -2813,6 +2914,46 @@ public final class ViewRoot extends Handler implements ViewParent, return scrollToRectOrFocus(rectangle, immediate); } + class TakenSurfaceHolder extends BaseSurfaceHolder { + @Override + public boolean onAllowLockCanvas() { + return mDrawingAllowed; + } + + @Override + public void onRelayoutContainer() { + // Not currently interesting -- from changing between fixed and layout size. + } + + public void setFormat(int format) { + ((RootViewSurfaceTaker)mView).setSurfaceFormat(format); + } + + public void setType(int type) { + ((RootViewSurfaceTaker)mView).setSurfaceType(type); + } + + @Override + public void onUpdateSurface() { + // We take care of format and type changes on our own. + throw new IllegalStateException("Shouldn't be here"); + } + + public boolean isCreating() { + return mIsCreating; + } + + @Override + public void setFixedSize(int width, int height) { + throw new UnsupportedOperationException( + "Currently only support sizing from layout"); + } + + public void setKeepScreenOn(boolean screenOn) { + ((RootViewSurfaceTaker)mView).setSurfaceKeepScreenOn(screenOn); + } + } + static class InputMethodCallback extends IInputMethodCallback.Stub { private WeakReference<ViewRoot> mViewRoot; @@ -2832,71 +2973,11 @@ public final class ViewRoot extends Handler implements ViewParent, } } - static class EventCompletion extends Handler { - final IWindow mWindow; - final KeyEvent mKeyEvent; - final boolean mIsPointer; - final MotionEvent mMotionEvent; - - EventCompletion(Looper looper, IWindow window, KeyEvent key, - boolean isPointer, MotionEvent motion) { - super(looper); - mWindow = window; - mKeyEvent = key; - mIsPointer = isPointer; - mMotionEvent = motion; - sendEmptyMessage(0); - } - - @Override - public void handleMessage(Message msg) { - if (mKeyEvent != null) { - try { - sWindowSession.finishKey(mWindow); - } catch (RemoteException e) { - } - } else if (mIsPointer) { - boolean didFinish; - MotionEvent event = mMotionEvent; - if (event == null) { - try { - event = sWindowSession.getPendingPointerMove(mWindow); - } catch (RemoteException e) { - } - didFinish = true; - } else { - didFinish = event.getAction() == MotionEvent.ACTION_OUTSIDE; - } - if (!didFinish) { - try { - sWindowSession.finishKey(mWindow); - } catch (RemoteException e) { - } - } - } else { - MotionEvent event = mMotionEvent; - if (event == null) { - try { - event = sWindowSession.getPendingTrackballMove(mWindow); - } catch (RemoteException e) { - } - } else { - try { - sWindowSession.finishKey(mWindow); - } catch (RemoteException e) { - } - } - } - } - } - static class W extends IWindow.Stub { private final WeakReference<ViewRoot> mViewRoot; - private final Looper mMainLooper; public W(ViewRoot viewRoot, Context context) { mViewRoot = new WeakReference<ViewRoot>(viewRoot); - mMainLooper = context.getMainLooper(); } public void resized(int w, int h, Rect coveredInsets, @@ -2908,40 +2989,6 @@ public final class ViewRoot extends Handler implements ViewParent, } } - public void dispatchKey(KeyEvent event) { - final ViewRoot viewRoot = mViewRoot.get(); - if (viewRoot != null) { - viewRoot.dispatchKey(event); - } else { - Log.w("ViewRoot.W", "Key event " + event + " but no ViewRoot available!"); - new EventCompletion(mMainLooper, this, event, false, null); - } - } - - public void dispatchPointer(MotionEvent event, long eventTime, - boolean callWhenDone) { - final ViewRoot viewRoot = mViewRoot.get(); - if (viewRoot != null) { - if (MEASURE_LATENCY) { - // Note: eventTime is in milliseconds - ViewRoot.lt.sample("* ViewRoot b4 dispatchPtr", System.nanoTime() - eventTime * 1000000); - } - viewRoot.dispatchPointer(event, eventTime, callWhenDone); - } else { - new EventCompletion(mMainLooper, this, null, true, event); - } - } - - public void dispatchTrackball(MotionEvent event, long eventTime, - boolean callWhenDone) { - final ViewRoot viewRoot = mViewRoot.get(); - if (viewRoot != null) { - viewRoot.dispatchTrackball(event, eventTime, callWhenDone); - } else { - new EventCompletion(mMainLooper, this, null, false, event); - } - } - public void dispatchAppVisibility(boolean visible) { final ViewRoot viewRoot = mViewRoot.get(); if (viewRoot != null) { diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java index 7dd5085f1075..11c09c1b2d45 100644 --- a/core/java/android/view/Window.java +++ b/core/java/android/view/Window.java @@ -473,6 +473,21 @@ public abstract class Window { } /** + * Take ownership of this window's surface. The window's view hierarchy + * will no longer draw into the surface, though it will otherwise continue + * to operate (such as for receiving input events). The given SurfaceHolder + * callback will be used to tell you about state changes to the surface. + */ + public abstract void takeSurface(SurfaceHolder.Callback2 callback); + + /** + * Take ownership of this window's InputQueue. The window will no + * longer read and dispatch input events from the queue; it is your + * responsibility to do so. + */ + public abstract void takeInputQueue(InputQueue.Callback callback); + + /** * Return whether this window is being displayed with a floating style * (based on the {@link android.R.attr#windowIsFloating} attribute in * the style/theme). diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index adceeb2074c2..eebbc931d0a3 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -580,9 +580,10 @@ public interface WindowManager extends ViewManager { * If the keyguard is currently active and is secure (requires an * unlock pattern) than the user will still need to confirm it before * seeing this window, unless {@link #FLAG_SHOW_WHEN_LOCKED} has - * also been set. */ + * also been set. + */ public static final int FLAG_DISMISS_KEYGUARD = 0x00400000; - + /** Window flag: *sigh* The lock screen wants to continue running its * animation while it is fading. A kind-of hack to allow this. Maybe * in the future we just make this the default behavior. diff --git a/core/java/android/view/WindowManagerPolicy.java b/core/java/android/view/WindowManagerPolicy.java index b39cb9d903e5..76701a937886 100644 --- a/core/java/android/view/WindowManagerPolicy.java +++ b/core/java/android/view/WindowManagerPolicy.java @@ -74,6 +74,8 @@ public interface WindowManagerPolicy { public final static int FLAG_MENU = 0x00000040; public final static int FLAG_LAUNCHER = 0x00000080; + public final static int FLAG_INJECTED = 0x01000000; + public final static int FLAG_WOKE_HERE = 0x10000000; public final static int FLAG_BRIGHT_HERE = 0x20000000; @@ -549,23 +551,26 @@ public interface WindowManagerPolicy { public Animation createForceHideEnterAnimation(); /** - * Called from the key queue thread before a key is dispatched to the - * input thread. + * Called from the input reader thread before a key is enqueued. * * <p>There are some actions that need to be handled here because they * affect the power state of the device, for example, the power keys. * Generally, it's best to keep as little as possible in the queue thread * because it's the most fragile. + * @param whenNanos The event time in uptime nanoseconds. + * @param keyCode The key code. + * @param down True if the key is down. + * @param policyFlags The policy flags associated with the key. + * @param isScreenOn True if the screen is already on * - * @param event the raw input event as read from the driver - * @param screenIsOn true if the screen is already on * @return The bitwise or of the {@link #ACTION_PASS_TO_USER}, * {@link #ACTION_POKE_USER_ACTIVITY} and {@link #ACTION_GO_TO_SLEEP} flags. */ - public int interceptKeyTq(RawInputEvent event, boolean screenIsOn); + public int interceptKeyBeforeQueueing(long whenNanos, int keyCode, boolean down, int policyFlags, + boolean isScreenOn); /** - * Called from the input thread before a key is dispatched to a window. + * Called from the input dispatcher thread before a key is dispatched to a window. * * <p>Allows you to define * behavior for keys that can not be overridden by applications or redirect @@ -577,16 +582,17 @@ public interface WindowManagerPolicy { * * @param win The window that currently has focus. This is where the key * event will normally go. - * @param code Key code. - * @param metaKeys bit mask of meta keys that are held. - * @param down Is this a key press (true) or release (false)? + * @param action The key event action. + * @param flags The key event flags. + * @param keyCode The key code. + * @param metaState bit mask of meta keys that are held. * @param repeatCount Number of times a key down has repeated. - * @param flags event's flags. + * @param policyFlags The policy flags associated with the key. * @return Returns true if the policy consumed the event and it should * not be further dispatched. */ - public boolean interceptKeyTi(WindowState win, int code, - int metaKeys, boolean down, int repeatCount, int flags); + public boolean interceptKeyBeforeDispatching(WindowState win, int action, int flags, + int keyCode, int metaState, int repeatCount, int policyFlags); /** * Called when layout of the windows is about to start. @@ -695,81 +701,13 @@ public interface WindowManagerPolicy { * Return whether the screen is currently on. */ public boolean isScreenOn(); - - /** - * Perform any initial processing of a low-level input event before the - * window manager handles special keys and generates a high-level event - * that is dispatched to the application. - * - * @param event The input event that has occurred. - * - * @return Return true if you have consumed the event and do not want - * further processing to occur; return false for normal processing. - */ - public boolean preprocessInputEventTq(RawInputEvent event); - - /** - * Determine whether a given key code is used to cause an app switch - * to occur (most often the HOME key, also often ENDCALL). If you return - * true, then the system will go into a special key processing state - * where it drops any pending events that it cans and adjusts timeouts to - * try to get to this key as quickly as possible. - * - * <p>Note that this function is called from the low-level input queue - * thread, with either/or the window or input lock held; be very careful - * about what you do here. You absolutely should never acquire a lock - * that you would ever hold elsewhere while calling out into the window - * manager or view hierarchy. - * - * @param keycode The key that should be checked for performing an - * app switch before delivering to the application. - * - * @return Return true if this is an app switch key and special processing - * should happen; return false for normal processing. - */ - public boolean isAppSwitchKeyTqTiLwLi(int keycode); - - /** - * Determine whether a given key code is used for movement within a UI, - * and does not generally cause actions to be performed (normally the DPAD - * movement keys, NOT the DPAD center press key). This is called - * when {@link #isAppSwitchKeyTiLi} returns true to remove any pending events - * in the key queue that are not needed to switch applications. - * - * <p>Note that this function is called from the low-level input queue - * thread; be very careful about what you do here. - * - * @param keycode The key that is waiting to be delivered to the - * application. - * - * @return Return true if this is a purely navigation key and can be - * dropped without negative consequences; return false to keep it. - */ - public boolean isMovementKeyTi(int keycode); - - /** - * Given the current state of the world, should this relative movement - * wake up the device? - * - * @param device The device the movement came from. - * @param classes The input classes associated with the device. - * @param event The input event that occurred. - * @return - */ - public boolean isWakeRelMovementTq(int device, int classes, - RawInputEvent event); - + /** - * Given the current state of the world, should this absolute movement - * wake up the device? - * - * @param device The device the movement came from. - * @param classes The input classes associated with the device. - * @param event The input event that occurred. - * @return + * Tell the policy that the lid switch has changed state. + * @param whenNanos The time when the change occurred in uptime nanoseconds. + * @param lidOpen True if the lid is now open. */ - public boolean isWakeAbsMovementTq(int device, int classes, - RawInputEvent event); + public void notifyLidSwitchChanged(long whenNanos, boolean lidOpen); /** * Tell the policy if anyone is requesting that keyguard not come on. @@ -843,23 +781,6 @@ public interface WindowManagerPolicy { */ public void enableScreenAfterBoot(); - /** - * Returns true if the user's cheek has been pressed against the phone. This is - * determined by comparing the event's size attribute with a threshold value. - * For example for a motion event like down or up or move, if the size exceeds - * the threshold, it is considered as cheek press. - * @param ev the motion event generated when the cheek is pressed - * against the phone - * @return Returns true if the user's cheek has been pressed against the phone - * screen resulting in an invalid motion event - */ - public boolean isCheekPressedAgainstScreen(MotionEvent ev); - - /** - * Called every time the window manager is dispatching a pointer event. - */ - public void dispatchedPointerEventLw(MotionEvent ev, int targetX, int targetY); - public void setCurrentOrientationLw(int newOrientation); /** @@ -868,13 +789,6 @@ public interface WindowManagerPolicy { public boolean performHapticFeedbackLw(WindowState win, int effectId, boolean always); /** - * A special function that is called from the very low-level input queue - * to provide feedback to the user. Currently only called for virtual - * keys. - */ - public void keyFeedbackFromInput(KeyEvent event); - - /** * Called when we have stopped keeping the screen on because a window * requesting this is no longer visible. */ diff --git a/core/java/android/view/WindowOrientationListener.java b/core/java/android/view/WindowOrientationListener.java index 25df1f41b18d..55d11bb19a95 100755 --- a/core/java/android/view/WindowOrientationListener.java +++ b/core/java/android/view/WindowOrientationListener.java @@ -68,7 +68,7 @@ public abstract class WindowOrientationListener { mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); if (mSensor != null) { // Create listener only if sensors do exist - mSensorEventListener = new SensorEventListenerImpl(); + mSensorEventListener = new SensorEventListenerImpl(this); } } @@ -103,14 +103,41 @@ public abstract class WindowOrientationListener { } } - public int getCurrentRotation() { + public int getCurrentRotation(int lastRotation) { if (mEnabled) { - return mSensorEventListener.getCurrentRotation(); + return mSensorEventListener.getCurrentRotation(lastRotation); } - return -1; + return lastRotation; } - - class SensorEventListenerImpl implements SensorEventListener { + + /** + * This class filters the raw accelerometer data and tries to detect actual changes in + * orientation. This is a very ill-defined problem so there are a lot of tweakable parameters, + * but here's the outline: + * + * - Convert the acceleromter vector from cartesian to spherical coordinates. Since we're + * dealing with rotation of the device, this is the sensible coordinate system to work in. The + * zenith direction is the Z-axis, i.e. the direction the screen is facing. The radial distance + * is referred to as the magnitude below. The elevation angle is referred to as the "tilt" + * below. The azimuth angle is referred to as the "orientation" below (and the azimuth axis is + * the Y-axis). See http://en.wikipedia.org/wiki/Spherical_coordinate_system for reference. + * + * - Low-pass filter the tilt and orientation angles to avoid "twitchy" behavior. + * + * - When the orientation angle reaches a certain threshold, transition to the corresponding + * orientation. These thresholds have some hysteresis built-in to avoid oscillation. + * + * - Use the magnitude to judge the accuracy of the data. Under ideal conditions, the magnitude + * should equal to that of gravity. When it differs significantly, we know the device is under + * external acceleration and we can't trust the data. + * + * - Use the tilt angle to judge the accuracy of orientation data. When the tilt angle is high + * in magnitude, we distrust the orientation data, because when the device is nearly flat, small + * physical movements produce large changes in orientation angle. + * + * Details are explained below. + */ + static class SensorEventListenerImpl implements SensorEventListener { // We work with all angles in degrees in this class. private static final float RADIANS_TO_DEGREES = (float) (180 / Math.PI); @@ -125,54 +152,56 @@ public abstract class WindowOrientationListener { private static final int ROTATION_90 = 1; private static final int ROTATION_270 = 2; - // Current orientation state - private int mRotation = ROTATION_0; - // Mapping our internal aliases into actual Surface rotation values - private final int[] SURFACE_ROTATIONS = new int[] {Surface.ROTATION_0, Surface.ROTATION_90, - Surface.ROTATION_270}; + private static final int[] INTERNAL_TO_SURFACE_ROTATION = new int[] { + Surface.ROTATION_0, Surface.ROTATION_90, Surface.ROTATION_270}; + + // Mapping Surface rotation values to internal aliases. + // We have no constant for Surface.ROTATION_180. That should never happen, but if it + // does, we'll arbitrarily choose a mapping. + private static final int[] SURFACE_TO_INTERNAL_ROTATION = new int[] { + ROTATION_0, ROTATION_90, ROTATION_90, ROTATION_270}; // Threshold ranges of orientation angle to transition into other orientation states. // The first list is for transitions from ROTATION_0, the next for ROTATION_90, etc. // ROTATE_TO defines the orientation each threshold range transitions to, and must be kept // in sync with this. - // The thresholds are nearly regular -- we generally transition about the halfway point - // between two states with a swing of 30 degrees for hysteresis. For ROTATION_180, - // however, we enforce stricter thresholds, pushing the thresholds 15 degrees closer to 180. - private final int[][][] THRESHOLDS = new int[][][] { + // We generally transition about the halfway point between two states with a swing of 30 + // degrees for hysteresis. + private static final int[][][] THRESHOLDS = new int[][][] { {{60, 180}, {180, 300}}, + {{0, 30}, {195, 315}, {315, 360}}, {{0, 45}, {45, 165}, {330, 360}}, - {{0, 30}, {195, 315}, {315, 360}} }; // See THRESHOLDS - private final int[][] ROTATE_TO = new int[][] { - {ROTATION_270, ROTATION_90}, + private static final int[][] ROTATE_TO = new int[][] { + {ROTATION_90, ROTATION_270}, {ROTATION_0, ROTATION_270, ROTATION_0}, - {ROTATION_0, ROTATION_90, ROTATION_0} + {ROTATION_0, ROTATION_90, ROTATION_0}, }; - // Maximum absolute tilt angle at which to consider orientation changes. Beyond this (i.e. - // when screen is facing the sky or ground), we refuse to make any orientation changes. - private static final int MAX_TILT = 65; + // Maximum absolute tilt angle at which to consider orientation data. Beyond this (i.e. + // when screen is facing the sky or ground), we completely ignore orientation data. + private static final int MAX_TILT = 75; // Additional limits on tilt angle to transition to each new orientation. We ignore all - // vectors with tilt beyond MAX_TILT, but we can set stricter limits on transition to a + // data with tilt beyond MAX_TILT, but we can set stricter limits on transitions to a // particular orientation here. - private final int[] MAX_TRANSITION_TILT = new int[] {MAX_TILT, MAX_TILT, MAX_TILT}; + private static final int[] MAX_TRANSITION_TILT = new int[] {MAX_TILT, 65, 65}; // Between this tilt angle and MAX_TILT, we'll allow orientation changes, but we'll filter // with a higher time constant, making us less sensitive to change. This primarily helps // prevent momentary orientation changes when placing a device on a table from the side (or // picking one up). - private static final int PARTIAL_TILT = 45; + private static final int PARTIAL_TILT = 50; // Maximum allowable deviation of the magnitude of the sensor vector from that of gravity, // in m/s^2. Beyond this, we assume the phone is under external forces and we can't trust // the sensor data. However, under constantly vibrating conditions (think car mount), we // still want to pick up changes, so rather than ignore the data, we filter it with a very // high time constant. - private static final int MAX_DEVIATION_FROM_GRAVITY = 1; + private static final float MAX_DEVIATION_FROM_GRAVITY = 1.5f; // Actual sampling period corresponding to SensorManager.SENSOR_DELAY_NORMAL. There's no // way to get this information from SensorManager. @@ -185,28 +214,50 @@ public abstract class WindowOrientationListener { // background. // When device is near-vertical (screen approximately facing the horizon) - private static final int DEFAULT_TIME_CONSTANT_MS = 200; + private static final int DEFAULT_TIME_CONSTANT_MS = 50; // When device is partially tilted towards the sky or ground - private static final int TILTED_TIME_CONSTANT_MS = 600; + private static final int TILTED_TIME_CONSTANT_MS = 300; // When device is under external acceleration, i.e. not just gravity. We heavily distrust // such readings. - private static final int ACCELERATING_TIME_CONSTANT_MS = 5000; + private static final int ACCELERATING_TIME_CONSTANT_MS = 2000; private static final float DEFAULT_LOWPASS_ALPHA = - (float) SAMPLING_PERIOD_MS / (DEFAULT_TIME_CONSTANT_MS + SAMPLING_PERIOD_MS); + computeLowpassAlpha(DEFAULT_TIME_CONSTANT_MS); private static final float TILTED_LOWPASS_ALPHA = - (float) SAMPLING_PERIOD_MS / (TILTED_TIME_CONSTANT_MS + SAMPLING_PERIOD_MS); + computeLowpassAlpha(TILTED_TIME_CONSTANT_MS); private static final float ACCELERATING_LOWPASS_ALPHA = - (float) SAMPLING_PERIOD_MS / (ACCELERATING_TIME_CONSTANT_MS + SAMPLING_PERIOD_MS); + computeLowpassAlpha(ACCELERATING_TIME_CONSTANT_MS); - // The low-pass filtered accelerometer data - private float[] mFilteredVector = new float[] {0, 0, 0}; + private WindowOrientationListener mOrientationListener; + private int mRotation = ROTATION_0; // Current orientation state + private float mTiltAngle = 0; // low-pass filtered + private float mOrientationAngle = 0; // low-pass filtered - int getCurrentRotation() { - return SURFACE_ROTATIONS[mRotation]; + /* + * Each "distrust" counter represents our current level of distrust in the data based on + * a certain signal. For each data point that is deemed unreliable based on that signal, + * the counter increases; otherwise, the counter decreases. Exact rules vary. + */ + private int mAccelerationDistrust = 0; // based on magnitude != gravity + private int mTiltDistrust = 0; // based on tilt close to +/- 90 degrees + + public SensorEventListenerImpl(WindowOrientationListener orientationListener) { + mOrientationListener = orientationListener; + } + + private static float computeLowpassAlpha(int timeConstantMs) { + return (float) SAMPLING_PERIOD_MS / (timeConstantMs + SAMPLING_PERIOD_MS); + } + + int getCurrentRotation(int lastRotation) { + if (mTiltDistrust > 0) { + // we really don't know the current orientation, so trust what's currently displayed + mRotation = SURFACE_TO_INTERNAL_ROTATION[lastRotation]; + } + return INTERNAL_TO_SURFACE_ROTATION[mRotation]; } - private void calculateNewRotation(int orientation, int tiltAngle) { + private void calculateNewRotation(float orientation, float tiltAngle) { if (localLOGV) Log.i(TAG, orientation + ", " + tiltAngle + ", " + mRotation); int thresholdRanges[][] = THRESHOLDS[mRotation]; int row = -1; @@ -226,7 +277,7 @@ public abstract class WindowOrientationListener { if (localLOGV) Log.i(TAG, " new rotation = " + rotation); mRotation = rotation; - onOrientationChanged(SURFACE_ROTATIONS[rotation]); + mOrientationListener.onOrientationChanged(INTERNAL_TO_SURFACE_ROTATION[mRotation]); } private float lowpassFilter(float newValue, float oldValue, float alpha) { @@ -238,11 +289,11 @@ public abstract class WindowOrientationListener { } /** - * Absolute angle between upVector and the x-y plane (the plane of the screen), in [0, 90]. - * 90 degrees = screen facing the sky or ground. + * Angle between upVector and the x-y plane (the plane of the screen), in [-90, 90]. + * +/- 90 degrees = screen facing the sky or ground. */ private float tiltAngle(float z, float magnitude) { - return Math.abs((float) Math.asin(z / magnitude) * RADIANS_TO_DEGREES); + return (float) Math.asin(z / magnitude) * RADIANS_TO_DEGREES; } public void onSensorChanged(SensorEvent event) { @@ -253,34 +304,122 @@ public abstract class WindowOrientationListener { float z = event.values[_DATA_Z]; float magnitude = vectorMagnitude(x, y, z); float deviation = Math.abs(magnitude - SensorManager.STANDARD_GRAVITY); - float tiltAngle = tiltAngle(z, magnitude); + handleAccelerationDistrust(deviation); + + // only filter tilt when we're accelerating + float alpha = 1; + if (mAccelerationDistrust > 0) { + alpha = ACCELERATING_LOWPASS_ALPHA; + } + float newTiltAngle = tiltAngle(z, magnitude); + mTiltAngle = lowpassFilter(newTiltAngle, mTiltAngle, alpha); + + float absoluteTilt = Math.abs(mTiltAngle); + checkFullyTilted(absoluteTilt); + if (mTiltDistrust > 0) { + return; // when fully tilted, ignore orientation entirely + } + + float newOrientationAngle = computeNewOrientation(x, y); + filterOrientation(absoluteTilt, newOrientationAngle); + calculateNewRotation(mOrientationAngle, absoluteTilt); + } + + /** + * When accelerating, increment distrust; otherwise, decrement distrust. The idea is that + * if a single jolt happens among otherwise good data, we should keep trusting the good + * data. On the other hand, if a series of many bad readings comes in (as if the phone is + * being rapidly shaken), we should wait until things "settle down", i.e. we get a string + * of good readings. + * + * @param deviation absolute difference between the current magnitude and gravity + */ + private void handleAccelerationDistrust(float deviation) { + if (deviation > MAX_DEVIATION_FROM_GRAVITY) { + if (mAccelerationDistrust < 5) { + mAccelerationDistrust++; + } + } else if (mAccelerationDistrust > 0) { + mAccelerationDistrust--; + } + } + + /** + * Check if the phone is tilted towards the sky or ground and handle that appropriately. + * When fully tilted, we automatically push the tilt up to a fixed value; otherwise we + * decrement it. The idea is to distrust the first few readings after the phone gets + * un-tilted, no matter what, i.e. preventing an accidental transition when the phone is + * picked up from a table. + * + * We also reset the orientation angle to the center of the current screen orientation. + * Since there is no real orientation of the phone, we want to ignore the most recent sensor + * data and reset it to this value to avoid a premature transition when the phone starts to + * get un-tilted. + * + * @param absoluteTilt the absolute value of the current tilt angle + */ + private void checkFullyTilted(float absoluteTilt) { + if (absoluteTilt > MAX_TILT) { + if (mRotation == ROTATION_0) { + mOrientationAngle = 0; + } else if (mRotation == ROTATION_90) { + mOrientationAngle = 90; + } else { // ROTATION_270 + mOrientationAngle = 270; + } + + if (mTiltDistrust < 3) { + mTiltDistrust = 3; + } + } else if (mTiltDistrust > 0) { + mTiltDistrust--; + } + } + + /** + * Angle between the x-y projection of upVector and the +y-axis, increasing + * clockwise. + * 0 degrees = speaker end towards the sky + * 90 degrees = right edge of device towards the sky + */ + private float computeNewOrientation(float x, float y) { + float orientationAngle = (float) -Math.atan2(-x, y) * RADIANS_TO_DEGREES; + // atan2 returns [-180, 180]; normalize to [0, 360] + if (orientationAngle < 0) { + orientationAngle += 360; + } + return orientationAngle; + } + + /** + * Compute a new filtered orientation angle. + */ + private void filterOrientation(float absoluteTilt, float orientationAngle) { float alpha = DEFAULT_LOWPASS_ALPHA; - if (tiltAngle > MAX_TILT) { - return; - } else if (deviation > MAX_DEVIATION_FROM_GRAVITY) { + if (mAccelerationDistrust > 1) { + // when under more than a transient acceleration, distrust heavily alpha = ACCELERATING_LOWPASS_ALPHA; - } else if (tiltAngle > PARTIAL_TILT) { + } else if (absoluteTilt > PARTIAL_TILT || mAccelerationDistrust == 1) { + // when tilted partway, or under transient acceleration, distrust lightly alpha = TILTED_LOWPASS_ALPHA; } - x = mFilteredVector[0] = lowpassFilter(x, mFilteredVector[0], alpha); - y = mFilteredVector[1] = lowpassFilter(y, mFilteredVector[1], alpha); - z = mFilteredVector[2] = lowpassFilter(z, mFilteredVector[2], alpha); - magnitude = vectorMagnitude(x, y, z); - tiltAngle = tiltAngle(z, magnitude); - - // Angle between the x-y projection of upVector and the +y-axis, increasing - // counter-clockwise. - // 0 degrees = speaker end towards the sky - // 90 degrees = left edge of device towards the sky - float orientationAngle = (float) Math.atan2(-x, y) * RADIANS_TO_DEGREES; - int orientation = Math.round(orientationAngle); - // atan2 returns (-180, 180]; normalize to [0, 360) - if (orientation < 0) { - orientation += 360; + // since we're lowpass filtering a value with periodic boundary conditions, we need to + // adjust the new value to filter in the right direction... + float deltaOrientation = orientationAngle - mOrientationAngle; + if (deltaOrientation > 180) { + orientationAngle -= 360; + } else if (deltaOrientation < -180) { + orientationAngle += 360; + } + mOrientationAngle = lowpassFilter(orientationAngle, mOrientationAngle, alpha); + // ...and then adjust back to ensure we're in the range [0, 360] + if (mOrientationAngle > 360) { + mOrientationAngle -= 360; + } else if (mOrientationAngle < 0) { + mOrientationAngle += 360; } - calculateNewRotation(orientation, Math.round(tiltAngle)); } public void onAccuracyChanged(Sensor sensor, int accuracy) { diff --git a/core/java/android/view/accessibility/AccessibilityEvent.java b/core/java/android/view/accessibility/AccessibilityEvent.java index c22f991183b0..fc617003f898 100644 --- a/core/java/android/view/accessibility/AccessibilityEvent.java +++ b/core/java/android/view/accessibility/AccessibilityEvent.java @@ -622,6 +622,7 @@ public final class AccessibilityEvent implements Parcelable { mPackageName = null; mContentDescription = null; mBeforeText = null; + mParcelableData = null; mText.clear(); } diff --git a/core/java/android/view/inputmethod/BaseInputConnection.java b/core/java/android/view/inputmethod/BaseInputConnection.java index 6ac16338f6d2..380194824da4 100644 --- a/core/java/android/view/inputmethod/BaseInputConnection.java +++ b/core/java/android/view/inputmethod/BaseInputConnection.java @@ -84,9 +84,14 @@ public class BaseInputConnection implements InputConnection { } } } - + public static void setComposingSpans(Spannable text) { - final Object[] sps = text.getSpans(0, text.length(), Object.class); + setComposingSpans(text, 0, text.length()); + } + + /** @hide */ + public static void setComposingSpans(Spannable text, int start, int end) { + final Object[] sps = text.getSpans(start, end, Object.class); if (sps != null) { for (int i=sps.length-1; i>=0; i--) { final Object o = sps[i]; @@ -94,18 +99,19 @@ public class BaseInputConnection implements InputConnection { text.removeSpan(o); continue; } + final int fl = text.getSpanFlags(o); if ((fl&(Spanned.SPAN_COMPOSING|Spanned.SPAN_POINT_MARK_MASK)) != (Spanned.SPAN_COMPOSING|Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)) { text.setSpan(o, text.getSpanStart(o), text.getSpanEnd(o), - (fl&Spanned.SPAN_POINT_MARK_MASK) + (fl & ~Spanned.SPAN_POINT_MARK_MASK) | Spanned.SPAN_COMPOSING | Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } } } - - text.setSpan(COMPOSING, 0, text.length(), + + text.setSpan(COMPOSING, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING); } @@ -312,6 +318,31 @@ public class BaseInputConnection implements InputConnection { } /** + * The default implementation returns the text currently selected, or null if none is + * selected. + */ + public CharSequence getSelectedText(int flags) { + final Editable content = getEditable(); + if (content == null) return null; + + int a = Selection.getSelectionStart(content); + int b = Selection.getSelectionEnd(content); + + if (a > b) { + int tmp = a; + a = b; + b = tmp; + } + + if (a == b) return null; + + if ((flags&GET_TEXT_WITH_STYLES) != 0) { + return content.subSequence(a, b); + } + return TextUtils.substring(content, a, b); + } + + /** * The default implementation returns the given amount of text from the * current cursor position in the buffer. */ @@ -385,6 +416,38 @@ public class BaseInputConnection implements InputConnection { return true; } + public boolean setComposingRegion(int start, int end) { + final Editable content = getEditable(); + if (content != null) { + beginBatchEdit(); + removeComposingSpans(content); + int a = start; + int b = end; + if (a > b) { + int tmp = a; + a = b; + b = tmp; + } + if (a < 0) a = 0; + if (b > content.length()) b = content.length(); + + ensureDefaultComposingSpans(); + if (mDefaultComposingSpans != null) { + for (int i = 0; i < mDefaultComposingSpans.length; ++i) { + content.setSpan(mDefaultComposingSpans[i], a, b, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING); + } + } + + content.setSpan(COMPOSING, a, b, + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING); + + endBatchEdit(); + sendCurrentText(); + } + return true; + } + /** * The default implementation changes the selection position in the * current editable text. @@ -479,7 +542,32 @@ public class BaseInputConnection implements InputConnection { content.clear(); } } - + + private void ensureDefaultComposingSpans() { + if (mDefaultComposingSpans == null) { + Context context; + if (mTargetView != null) { + context = mTargetView.getContext(); + } else if (mIMM.mServedView != null) { + context = mIMM.mServedView.getContext(); + } else { + context = null; + } + if (context != null) { + TypedArray ta = context.getTheme() + .obtainStyledAttributes(new int[] { + com.android.internal.R.attr.candidatesTextStyleSpans + }); + CharSequence style = ta.getText(0); + ta.recycle(); + if (style != null && style instanceof Spanned) { + mDefaultComposingSpans = ((Spanned)style).getSpans( + 0, style.length(), Object.class); + } + } + } + } + private void replaceText(CharSequence text, int newCursorPosition, boolean composing) { final Editable content = getEditable(); @@ -520,32 +608,11 @@ public class BaseInputConnection implements InputConnection { if (!(text instanceof Spannable)) { sp = new SpannableStringBuilder(text); text = sp; - if (mDefaultComposingSpans == null) { - Context context; - if (mTargetView != null) { - context = mTargetView.getContext(); - } else if (mIMM.mServedView != null) { - context = mIMM.mServedView.getContext(); - } else { - context = null; - } - if (context != null) { - TypedArray ta = context.getTheme() - .obtainStyledAttributes(new int[] { - com.android.internal.R.attr.candidatesTextStyleSpans - }); - CharSequence style = ta.getText(0); - ta.recycle(); - if (style != null && style instanceof Spanned) { - mDefaultComposingSpans = ((Spanned)style).getSpans( - 0, style.length(), Object.class); - } - } - } + ensureDefaultComposingSpans(); if (mDefaultComposingSpans != null) { for (int i = 0; i < mDefaultComposingSpans.length; ++i) { sp.setSpan(mDefaultComposingSpans[i], 0, sp.length(), - Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING); } } } else { diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java index 8b6831e65539..3b8a3642818c 100644 --- a/core/java/android/view/inputmethod/InputConnection.java +++ b/core/java/android/view/inputmethod/InputConnection.java @@ -80,6 +80,21 @@ public interface InputConnection { public CharSequence getTextAfterCursor(int n, int flags); /** + * Gets the selected text, if any. + * + * <p>This method may fail if either the input connection has become + * invalid (such as its process crashing) or the client is taking too + * long to respond with the text (it is given a couple of seconds to return). + * In either case, a null is returned. + * + * @param flags Supplies additional options controlling how the text is + * returned. May be either 0 or {@link #GET_TEXT_WITH_STYLES}. + * @return Returns the text that is currently selected, if any, or null if + * no text is selected. + */ + public CharSequence getSelectedText(int flags); + + /** * Retrieve the current capitalization mode in effect at the current * cursor position in the text. See * {@link android.text.TextUtils#getCapsMode TextUtils.getCapsMode} for @@ -162,6 +177,18 @@ public interface InputConnection { public boolean setComposingText(CharSequence text, int newCursorPosition); /** + * Mark a certain region of text as composing text. Any composing text set + * previously will be removed automatically. The default style for composing + * text is used. + * + * @param start the position in the text at which the composing region begins + * @param end the position in the text at which the composing region ends + * @return Returns true on success, false if the input connection is no longer + * valid. + */ + public boolean setComposingRegion(int start, int end); + + /** * Have the text editor finish whatever composing text is currently * active. This simply leaves the text as-is, removing any special * composing styling or other state that was around it. The cursor diff --git a/core/java/android/view/inputmethod/InputConnectionWrapper.java b/core/java/android/view/inputmethod/InputConnectionWrapper.java index 210559aed904..b73f9bb5eb7c 100644 --- a/core/java/android/view/inputmethod/InputConnectionWrapper.java +++ b/core/java/android/view/inputmethod/InputConnectionWrapper.java @@ -50,6 +50,10 @@ public class InputConnectionWrapper implements InputConnection { return mTarget.getTextAfterCursor(n, flags); } + public CharSequence getSelectedText(int flags) { + return mTarget.getSelectedText(flags); + } + public int getCursorCapsMode(int reqModes) { return mTarget.getCursorCapsMode(reqModes); } @@ -67,6 +71,10 @@ public class InputConnectionWrapper implements InputConnection { return mTarget.setComposingText(text, newCursorPosition); } + public boolean setComposingRegion(int start, int end) { + return mTarget.setComposingRegion(start, end); + } + public boolean finishComposingText() { return mTarget.finishComposingText(); } diff --git a/core/java/android/webkit/CacheManager.java b/core/java/android/webkit/CacheManager.java index d5058b0e0f8e..d17199028658 100644 --- a/core/java/android/webkit/CacheManager.java +++ b/core/java/android/webkit/CacheManager.java @@ -284,7 +284,7 @@ public final class CacheManager { // only called from WebCore Thread // make sure to call startCacheTransaction/endCacheTransaction in pair /** - * @deprecated + * @deprecated Always returns false. */ @Deprecated public static boolean startCacheTransaction() { @@ -294,7 +294,7 @@ public final class CacheManager { // only called from WebCore Thread // make sure to call startCacheTransaction/endCacheTransaction in pair /** - * @deprecated + * @deprecated Always returns false. */ @Deprecated public static boolean endCacheTransaction() { diff --git a/core/java/android/webkit/JWebCoreJavaBridge.java b/core/java/android/webkit/JWebCoreJavaBridge.java index e76669361d5e..1f8d53f2a841 100644 --- a/core/java/android/webkit/JWebCoreJavaBridge.java +++ b/core/java/android/webkit/JWebCoreJavaBridge.java @@ -20,6 +20,8 @@ import android.os.Handler; import android.os.Message; import android.util.Log; +import java.lang.ref.WeakReference; +import java.util.HashMap; import java.util.Set; final class JWebCoreJavaBridge extends Handler { @@ -44,7 +46,8 @@ final class JWebCoreJavaBridge extends Handler { // keep track of the main WebView attached to the current window so that we // can get the proper Context. - private WebView mCurrentMainWebView; + private static WeakReference<WebView> sCurrentMainWebView = + new WeakReference<WebView>(null); /* package */ static final int REFRESH_PLUGINS = 100; @@ -62,20 +65,20 @@ final class JWebCoreJavaBridge extends Handler { nativeFinalize(); } - synchronized void setActiveWebView(WebView webview) { - if (mCurrentMainWebView != null) { + static synchronized void setActiveWebView(WebView webview) { + if (sCurrentMainWebView.get() != null) { // it is possible if there is a sub-WebView. Do nothing. return; } - mCurrentMainWebView = webview; + sCurrentMainWebView = new WeakReference<WebView>(webview); } - synchronized void removeActiveWebView(WebView webview) { - if (mCurrentMainWebView != webview) { + static synchronized void removeActiveWebView(WebView webview) { + if (sCurrentMainWebView.get() != webview) { // it is possible if there is a sub-WebView. Do nothing. return; } - mCurrentMainWebView = null; + sCurrentMainWebView.clear(); } /** @@ -256,11 +259,12 @@ final class JWebCoreJavaBridge extends Handler { synchronized private String getSignedPublicKey(int index, String challenge, String url) { - if (mCurrentMainWebView != null) { + WebView current = sCurrentMainWebView.get(); + if (current != null) { // generateKeyPair expects organizations which we don't have. Ignore // url. return CertTool.getSignedPublicKey( - mCurrentMainWebView.getContext(), index, challenge); + current.getContext(), index, challenge); } else { Log.e(LOGTAG, "There is no active WebView for getSignedPublicKey"); return ""; diff --git a/core/java/android/webkit/MimeTypeMap.java b/core/java/android/webkit/MimeTypeMap.java index 034c88ae0731..ca9ad53c52a5 100644 --- a/core/java/android/webkit/MimeTypeMap.java +++ b/core/java/android/webkit/MimeTypeMap.java @@ -67,7 +67,7 @@ public class MimeTypeMap { // if the filename contains special characters, we don't // consider it valid for our matching purposes: if (filename.length() > 0 && - Pattern.matches("[a-zA-Z_0-9\\.\\-\\(\\)]+", filename)) { + Pattern.matches("[a-zA-Z_0-9\\.\\-\\(\\)\\%]+", filename)) { int dotPos = filename.lastIndexOf('.'); if (0 <= dotPos) { return filename.substring(dotPos + 1); diff --git a/core/java/android/webkit/PluginManager.java b/core/java/android/webkit/PluginManager.java index cdcb662e057c..e5eeb8cf59c5 100644 --- a/core/java/android/webkit/PluginManager.java +++ b/core/java/android/webkit/PluginManager.java @@ -21,9 +21,9 @@ import java.util.List; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; -import android.app.Service; import android.content.Context; import android.content.Intent; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; @@ -59,7 +59,9 @@ public class PluginManager { */ public static final String PLUGIN_PERMISSION = "android.webkit.permission.PLUGIN"; - private static final String LOGTAG = "webkit"; + private static final String LOGTAG = "PluginManager"; + + private static final String PLUGIN_SYSTEM_LIB = "/system/lib/plugins/"; private static final String PLUGIN_TYPE = "type"; private static final String TYPE_NATIVE = "native"; @@ -111,9 +113,8 @@ public class PluginManager { ArrayList<String> directories = new ArrayList<String>(); PackageManager pm = mContext.getPackageManager(); - List<ResolveInfo> plugins = pm.queryIntentServices(new Intent( - PLUGIN_ACTION), PackageManager.GET_SERVICES - | PackageManager.GET_META_DATA); + List<ResolveInfo> plugins = pm.queryIntentServices(new Intent(PLUGIN_ACTION), + PackageManager.GET_SERVICES | PackageManager.GET_META_DATA); synchronized(mPackageInfoCache) { @@ -143,10 +144,19 @@ public class PluginManager { continue; } - // check if their is a conflict in the lib directory names + /* + * find the location of the plugin's shared library. The default + * is to assume the app is either a user installed app or an + * updated system app. In both of these cases the library is + * stored in the app's data directory. + */ String directory = pkgInfo.applicationInfo.dataDir + "/lib"; - if (directories.contains(directory)) { - continue; + final int appFlags = pkgInfo.applicationInfo.flags; + final int updatedSystemFlags = ApplicationInfo.FLAG_SYSTEM | + ApplicationInfo.FLAG_UPDATED_SYSTEM_APP; + // preloaded system app with no user updates + if ((appFlags & updatedSystemFlags) == ApplicationInfo.FLAG_SYSTEM) { + directory = PLUGIN_SYSTEM_LIB + pkgInfo.packageName; } // check if the plugin has the required permissions @@ -236,7 +246,7 @@ public class PluginManager { // must be synchronized to ensure the consistency of the cache synchronized(mPackageInfoCache) { for (PackageInfo pkgInfo : mPackageInfoCache) { - if (pluginLib.startsWith(pkgInfo.applicationInfo.dataDir)) { + if (pluginLib.contains(pkgInfo.packageName)) { return pkgInfo.packageName; } } diff --git a/core/java/android/webkit/WebChromeClient.java b/core/java/android/webkit/WebChromeClient.java index 1d5aac7fb3c0..9f642c09a274 100644 --- a/core/java/android/webkit/WebChromeClient.java +++ b/core/java/android/webkit/WebChromeClient.java @@ -320,4 +320,22 @@ public class WebChromeClient { public void openFileChooser(ValueCallback<Uri> uploadFile) { uploadFile.onReceiveValue(null); } + + /** + * Tell the client that the selection has been initiated. + * @hide + */ + public void onSelectionStart(WebView view) { + // By default we cancel the selection again, thus disabling + // text selection unless the chrome client supports it. + view.notifySelectDialogDismissed(); + } + + /** + * Tell the client that the selection has been copied or canceled. + * @hide + */ + public void onSelectionDone(WebView view) { + } + } diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java index b767f11753dc..da0c61b2dc51 100644 --- a/core/java/android/webkit/WebSettings.java +++ b/core/java/android/webkit/WebSettings.java @@ -207,6 +207,7 @@ public class WebSettings { private boolean mBuiltInZoomControls = false; private boolean mAllowFileAccess = true; private boolean mLoadWithOverviewMode = false; + private boolean mUseWebViewBackgroundOverscrollBackground = true; // private WebSettings, not accessible by the host activity static private int mDoubleTapToastCount = 3; @@ -485,6 +486,23 @@ public class WebSettings { } /** + * Set whether the WebView uses its background for over scroll background. + * If true, it will use the WebView's background. If false, it will use an + * internal pattern. Default is true. + */ + public void setUseWebViewBackgroundForOverscrollBackground(boolean view) { + mUseWebViewBackgroundOverscrollBackground = view; + } + + /** + * Returns true if this WebView uses WebView's background instead of + * internal pattern for over scroll background. + */ + public boolean getUseWebViewBackgroundForOverscrollBackground() { + return mUseWebViewBackgroundOverscrollBackground; + } + + /** * Store whether the WebView is saving form data. */ public void setSaveFormData(boolean save) { @@ -1030,8 +1048,13 @@ public class WebSettings { } /** - * TODO: need to add @Deprecated + * Set a custom path to plugins used by the WebView. This method is + * obsolete since each plugin is now loaded from its own package. + * @param pluginsPath String path to the directory containing plugins. + * @deprecated This method is no longer used as plugins are loaded from + * their own APK via the system's package manager. */ + @Deprecated public synchronized void setPluginsPath(String pluginsPath) { } @@ -1201,8 +1224,13 @@ public class WebSettings { } /** - * TODO: need to add @Deprecated + * Returns the directory that contains the plugin libraries. This method is + * obsolete since each plugin is now loaded from its own package. + * @return An empty string. + * @deprecated This method is no longer used as plugins are loaded from + * their own APK via the system's package manager. */ + @Deprecated public synchronized String getPluginsPath() { return ""; } diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index bbd8b95c7bea..456e0d9dd6e2 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -16,13 +16,16 @@ package android.webkit; +import com.android.internal.R; + import android.annotation.Widget; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; -import android.content.Intent; import android.content.DialogInterface.OnCancelListener; +import android.content.Intent; import android.content.pm.PackageManager; +import android.content.res.Resources; import android.database.DataSetObserver; import android.graphics.Bitmap; import android.graphics.BitmapFactory; @@ -30,7 +33,6 @@ import android.graphics.BitmapShader; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Interpolator; -import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Picture; import android.graphics.Point; @@ -74,22 +76,22 @@ import android.webkit.WebViewCore.TouchEventData; import android.widget.AbsoluteLayout; import android.widget.Adapter; import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; import android.widget.ArrayAdapter; import android.widget.CheckedTextView; +import android.widget.EdgeGlow; import android.widget.FrameLayout; import android.widget.LinearLayout; import android.widget.ListView; -import android.widget.Scroller; +import android.widget.OverScroller; import android.widget.Toast; import android.widget.ZoomButtonsController; import android.widget.ZoomControls; -import android.widget.AdapterView.OnItemClickListener; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; -import java.io.IOException; import java.net.URLDecoder; import java.util.ArrayList; import java.util.HashMap; @@ -239,14 +241,14 @@ import junit.framework.Assert; * * <h3>Building web pages to support different screen densities</h3> * - * <p>A screen's density is based on it's screen resolution and physical size. A screen with low - * density has fewer available pixels per inch, where a screen with high density - * has more -- sometimes significantly more -- pixels per inch. The density of a + * <p>The screen density of a device is based on the screen resolution. A screen with low density + * has fewer available pixels per inch, where a screen with high density + * has more — sometimes significantly more — pixels per inch. The density of a * screen is important because, other things being equal, a UI element (such as a button) whose * height and width are defined in terms of screen pixels will appear larger on the lower density - * screen and smaller on the higher density screen. For simplicity, Android collapses all - * actual screen densities into three generalized densities:high, medium, and low. </p> - * + * screen and smaller on the higher density screen. + * For simplicity, Android collapses all actual screen densities into three generalized densities: + * high, medium, and low.</p> * <p>By default, WebView scales a web page so that it is drawn at a size that matches the default * appearance on a medium density screen. So, it applies 1.5x scaling on a high density screen * (because its pixels are smaller) and 0.75x scaling on a low density screen (because its pixels @@ -459,8 +461,12 @@ public class WebView extends AbsoluteLayout private static final int TOUCH_SHORTPRESS_MODE = 5; private static final int TOUCH_DOUBLE_TAP_MODE = 6; private static final int TOUCH_DONE_MODE = 7; - private static final int TOUCH_SELECT_MODE = 8; - private static final int TOUCH_PINCH_DRAG = 9; + private static final int TOUCH_PINCH_DRAG = 8; + + /** + * True if we have a touch panel capable of detecting smooth pan/scale at the same time + */ + private boolean mAllowPanAndScale; // Whether to forward the touch events to WebCore private boolean mForwardTouchEvents = false; @@ -558,7 +564,10 @@ public class WebView extends AbsoluteLayout // time for the longest scroll animation private static final int MAX_DURATION = 750; // milliseconds private static final int SLIDE_TITLE_DURATION = 500; // milliseconds - private Scroller mScroller; + private OverScroller mScroller; + private boolean mInOverScrollMode = false; + private static Paint mOverScrollBackground; + private static Paint mOverScrollBorder; private boolean mWrapContent; private static final int MOTIONLESS_FALSE = 0; @@ -755,6 +764,27 @@ public class WebView extends AbsoluteLayout private int mHorizontalScrollBarMode = SCROLLBAR_AUTO; private int mVerticalScrollBarMode = SCROLLBAR_AUTO; + /** + * Max distance to overscroll by in pixels. + * This how far content can be pulled beyond its normal bounds by the user. + */ + private int mOverscrollDistance; + + /** + * Max distance to overfling by in pixels. + * This is how far flinged content can move beyond the end of its normal bounds. + */ + private int mOverflingDistance; + + /* + * These manage the edge glow effect when flung or pulled beyond the edges. + * If one is not null, all are not null. Checking one for null is as good as checking each. + */ + private EdgeGlow mEdgeGlowTop; + private EdgeGlow mEdgeGlowBottom; + private EdgeGlow mEdgeGlowLeft; + private EdgeGlow mEdgeGlowRight; + // Used to match key downs and key ups private boolean mGotKeyDown; @@ -944,16 +974,18 @@ public class WebView extends AbsoluteLayout mViewManager = new ViewManager(this); mWebViewCore = new WebViewCore(context, this, mCallbackProxy, javascriptInterfaces); mDatabase = WebViewDatabase.getInstance(context); - mScroller = new Scroller(context); + mScroller = new OverScroller(context); updateMultiTouchSupport(context); } void updateMultiTouchSupport(Context context) { WebSettings settings = getSettings(); - mSupportMultiTouch = context.getPackageManager().hasSystemFeature( - PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH) + final PackageManager pm = context.getPackageManager(); + mSupportMultiTouch = pm.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH) && settings.supportZoom() && settings.getBuiltInZoomControls(); + mAllowPanAndScale = pm.hasSystemFeature( + PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT); if (mSupportMultiTouch && (mScaleDetector == null)) { mScaleDetector = new ScaleGestureDetector(context, new ScaleDetectorListener()); @@ -1006,6 +1038,29 @@ public class WebView extends AbsoluteLayout mMaxZoomScale = DEFAULT_MAX_ZOOM_SCALE; mMinZoomScale = DEFAULT_MIN_ZOOM_SCALE; mMaximumFling = configuration.getScaledMaximumFlingVelocity(); + mOverscrollDistance = configuration.getScaledOverscrollDistance(); + mOverflingDistance = configuration.getScaledOverflingDistance(); + } + + @Override + public void setOverscrollMode(int mode) { + super.setOverscrollMode(mode); + if (mode != OVERSCROLL_NEVER) { + if (mEdgeGlowTop == null) { + final Resources res = getContext().getResources(); + final Drawable edge = res.getDrawable(R.drawable.overscroll_edge); + final Drawable glow = res.getDrawable(R.drawable.overscroll_glow); + mEdgeGlowTop = new EdgeGlow(edge, glow); + mEdgeGlowBottom = new EdgeGlow(edge, glow); + mEdgeGlowLeft = new EdgeGlow(edge, glow); + mEdgeGlowRight = new EdgeGlow(edge, glow); + } + } else { + mEdgeGlowTop = null; + mEdgeGlowBottom = null; + mEdgeGlowLeft = null; + mEdgeGlowRight = null; + } } /* package */void updateDefaultZoomDensity(int zoomDensity) { @@ -1147,7 +1202,8 @@ public class WebView extends AbsoluteLayout * Return the amount of the titlebarview (if any) that is visible */ private int getVisibleTitleHeight() { - return Math.max(getTitleHeight() - mScrollY, 0); + // need to restrict mScrollY due to over scroll + return Math.max(getTitleHeight() - Math.max(0, mScrollY), 0); } /* @@ -1383,16 +1439,23 @@ public class WebView extends AbsoluteLayout final File temp = new File(dest.getPath() + ".writing"); new Thread(new Runnable() { public void run() { + FileOutputStream out = null; try { - FileOutputStream out = new FileOutputStream(temp); + out = new FileOutputStream(temp); p.writeToStream(out); - out.close(); // Writing the picture succeeded, rename the temporary file // to the destination. temp.renameTo(dest); } catch (Exception e) { // too late to do anything about it. } finally { + if (out != null) { + try { + out.close(); + } catch (Exception e) { + // Can't do anything about that + } + } temp.delete(); } } @@ -1445,20 +1508,23 @@ public class WebView extends AbsoluteLayout final Bundle copy = new Bundle(b); new Thread(new Runnable() { public void run() { - final Picture p = Picture.createFromStream(in); - if (p != null) { - // Post a runnable on the main thread to update the - // history picture fields. - mPrivateHandler.post(new Runnable() { - public void run() { - restoreHistoryPictureFields(p, copy); - } - }); - } try { - in.close(); - } catch (Exception e) { - // Nothing we can do now. + final Picture p = Picture.createFromStream(in); + if (p != null) { + // Post a runnable on the main thread to update the + // history picture fields. + mPrivateHandler.post(new Runnable() { + public void run() { + restoreHistoryPictureFields(p, copy); + } + }); + } + } finally { + try { + in.close(); + } catch (Exception e) { + // Nothing we can do now. + } } } }).start(); @@ -1777,7 +1843,7 @@ public class WebView extends AbsoluteLayout } nativeClearCursor(); // start next trackball movement from page edge if (bottom) { - return pinScrollTo(mScrollX, computeVerticalScrollRange(), true, 0); + return pinScrollTo(mScrollX, computeRealVerticalScrollRange(), true, 0); } // Page down. int h = getHeight(); @@ -2007,13 +2073,15 @@ public class WebView extends AbsoluteLayout // Expects x in view coordinates private int pinLocX(int x) { - return pinLoc(x, getViewWidth(), computeHorizontalScrollRange()); + if (mInOverScrollMode) return x; + return pinLoc(x, getViewWidth(), computeRealHorizontalScrollRange()); } // Expects y in view coordinates private int pinLocY(int y) { + if (mInOverScrollMode) return y; return pinLoc(y, getViewHeightWithTitle(), - computeVerticalScrollRange() + getTitleHeight()); + computeRealVerticalScrollRange() + getTitleHeight()); } /** @@ -2325,7 +2393,7 @@ public class WebView extends AbsoluteLayout // Sets r to be our visible rectangle in content coordinates private void calcOurContentVisibleRect(Rect r) { calcOurVisibleRect(r); - // pin the rect to the bounds of the content + // since we might overscroll, pin the rect to the bounds of the content r.left = Math.max(viewToContentX(r.left), 0); // viewToContentY will remove the total height of the title bar. Add // the visible height back in to account for the fact that if the title @@ -2406,8 +2474,7 @@ public class WebView extends AbsoluteLayout return false; } - @Override - protected int computeHorizontalScrollRange() { + private int computeRealHorizontalScrollRange() { if (mDrawHistory) { return mHistoryWidth; } else if (mHorizontalScrollBarMode == SCROLLBAR_ALWAYSOFF @@ -2421,7 +2488,27 @@ public class WebView extends AbsoluteLayout } @Override - protected int computeVerticalScrollRange() { + protected int computeHorizontalScrollRange() { + int range = computeRealHorizontalScrollRange(); + + // Adjust reported range if overscrolled to compress the scroll bars + final int scrollX = mScrollX; + final int overscrollRight = computeMaxScrollX(); + if (scrollX < 0) { + range -= scrollX; + } else if (scrollX > overscrollRight) { + range += scrollX - overscrollRight; + } + + return range; + } + + @Override + protected int computeHorizontalScrollOffset() { + return Math.max(mScrollX, 0); + } + + private int computeRealVerticalScrollRange() { if (mDrawHistory) { return mHistoryHeight; } else if (mVerticalScrollBarMode == SCROLLBAR_ALWAYSOFF @@ -2435,6 +2522,22 @@ public class WebView extends AbsoluteLayout } @Override + protected int computeVerticalScrollRange() { + int range = computeRealVerticalScrollRange(); + + // Adjust reported range if overscrolled to compress the scroll bars + final int scrollY = mScrollY; + final int overscrollBottom = computeMaxScrollY(); + if (scrollY < 0) { + range -= scrollY; + } else if (scrollY > overscrollBottom) { + range += scrollY - overscrollBottom; + } + + return range; + } + + @Override protected int computeVerticalScrollOffset() { return Math.max(mScrollY - getTitleHeight(), 0); } @@ -2449,10 +2552,31 @@ public class WebView extends AbsoluteLayout protected void onDrawVerticalScrollBar(Canvas canvas, Drawable scrollBar, int l, int t, int r, int b) { + if (mScrollY < 0) { + t -= mScrollY; + } scrollBar.setBounds(l, t + getVisibleTitleHeight(), r, b); scrollBar.draw(canvas); } + @Override + protected void onOverscrolled(int scrollX, int scrollY, boolean clampedX, + boolean clampedY) { + mInOverScrollMode = false; + int maxX = computeMaxScrollX(); + if (maxX == 0) { + // do not over scroll x if the page just fits the screen + scrollX = pinLocX(scrollX); + } else if (scrollX < 0 || scrollX > maxX) { + mInOverScrollMode = true; + } + if (scrollY < 0 || scrollY > computeMaxScrollY()) { + mInOverScrollMode = true; + } + + super.scrollTo(scrollX, scrollY); + } + /** * Get the url for the current page. This is not always the same as the url * passed to WebViewClient.onPageStarted because although the load for @@ -2683,6 +2807,14 @@ public class WebView extends AbsoluteLayout nativeSetFindIsUp(isUp); } + /** + * @hide + */ + public int findIndex() { + if (0 == mNativeClass) return -1; + return nativeFindIndex(); + } + // Used to know whether the find dialog is open. Affects whether // or not we draw the highlights for matches. private boolean mFindIsUp; @@ -2798,11 +2930,37 @@ public class WebView extends AbsoluteLayout if (mScroller.computeScrollOffset()) { int oldX = mScrollX; int oldY = mScrollY; - mScrollX = mScroller.getCurrX(); - mScrollY = mScroller.getCurrY(); - postInvalidate(); // So we draw again - if (oldX != mScrollX || oldY != mScrollY) { - onScrollChanged(mScrollX, mScrollY, oldX, oldY); + int x = mScroller.getCurrX(); + int y = mScroller.getCurrY(); + invalidate(); // So we draw again + + if (oldX != x || oldY != y) { + final int rangeX = computeMaxScrollX(); + final int rangeY = computeMaxScrollY(); + overscrollBy(x - oldX, y - oldY, oldX, oldY, + rangeX, rangeY, + mOverflingDistance, mOverflingDistance, false); + + if (mEdgeGlowTop != null) { + if (rangeY > 0 || getOverscrollMode() == OVERSCROLL_ALWAYS) { + if (y < 0 && oldY >= 0) { + mEdgeGlowTop.onAbsorb((int) mScroller.getCurrVelocity()); + } else if (y > rangeY && oldY <= rangeY) { + mEdgeGlowBottom.onAbsorb((int) mScroller.getCurrVelocity()); + } + } + + if (rangeX > 0) { + if (x < 0 && oldX >= 0) { + mEdgeGlowLeft.onAbsorb((int) mScroller.getCurrVelocity()); + } else if (x > rangeX && oldX <= rangeX) { + mEdgeGlowRight.onAbsorb((int) mScroller.getCurrVelocity()); + } + } + } + } + if (mScroller.isFinished()) { + mPrivateHandler.sendEmptyMessage(RESUME_WEBCORE_PRIORITY); } } else { super.computeScroll(); @@ -3250,6 +3408,36 @@ public class WebView extends AbsoluteLayout } int saveCount = canvas.save(); + if (mInOverScrollMode && !getSettings() + .getUseWebViewBackgroundForOverscrollBackground()) { + if (mOverScrollBackground == null) { + mOverScrollBackground = new Paint(); + Bitmap bm = BitmapFactory.decodeResource( + mContext.getResources(), + com.android.internal.R.drawable.status_bar_background); + mOverScrollBackground.setShader(new BitmapShader(bm, + Shader.TileMode.REPEAT, Shader.TileMode.REPEAT)); + mOverScrollBorder = new Paint(); + mOverScrollBorder.setStyle(Paint.Style.STROKE); + mOverScrollBorder.setStrokeWidth(0); + mOverScrollBorder.setColor(0xffbbbbbb); + } + + int top = 0; + int right = computeRealHorizontalScrollRange(); + int bottom = top + computeRealVerticalScrollRange(); + // first draw the background and anchor to the top of the view + canvas.save(); + canvas.translate(mScrollX, mScrollY); + canvas.clipRect(-mScrollX, top - mScrollY, right - mScrollX, bottom + - mScrollY, Region.Op.DIFFERENCE); + canvas.drawPaint(mOverScrollBackground); + canvas.restore(); + // then draw the border + canvas.drawRect(-1, top - 1, right, bottom, mOverScrollBorder); + // next clip the region for the content + canvas.clipRect(0, top, right, bottom); + } if (mTitleBar != null) { canvas.translate(0, (int) mTitleBar.getHeight()); } @@ -3275,6 +3463,7 @@ public class WebView extends AbsoluteLayout mScrollY + height); mTitleShadow.draw(canvas); } + if (AUTO_REDRAW_HACK && mAutoRedraw) { invalidate(); } @@ -3282,6 +3471,66 @@ public class WebView extends AbsoluteLayout } @Override + public void draw(Canvas canvas) { + super.draw(canvas); + if (mEdgeGlowTop != null && drawEdgeGlows(canvas)) { + invalidate(); + } + } + + /** + * Draw the glow effect along the sides of the widget. mEdgeGlow* must be non-null. + * + * @param canvas Canvas to draw into, transformed into view coordinates. + * @return true if glow effects are still animating and the view should invalidate again. + */ + private boolean drawEdgeGlows(Canvas canvas) { + final int scrollX = mScrollX; + final int scrollY = mScrollY; + final int width = getWidth(); + int height = getHeight(); + + boolean invalidateForGlow = false; + if (!mEdgeGlowTop.isFinished()) { + final int restoreCount = canvas.save(); + + canvas.translate(-width / 2 + scrollX, Math.min(0, scrollY)); + mEdgeGlowTop.setSize(width * 2, height); + invalidateForGlow |= mEdgeGlowTop.draw(canvas); + canvas.restoreToCount(restoreCount); + } + if (!mEdgeGlowBottom.isFinished()) { + final int restoreCount = canvas.save(); + + canvas.translate(-width / 2 + scrollX, Math.max(computeMaxScrollY(), scrollY) + height); + canvas.rotate(180, width, 0); + mEdgeGlowBottom.setSize(width * 2, height); + invalidateForGlow |= mEdgeGlowBottom.draw(canvas); + canvas.restoreToCount(restoreCount); + } + if (!mEdgeGlowLeft.isFinished()) { + final int restoreCount = canvas.save(); + + canvas.rotate(270); + canvas.translate(-height * 1.5f - scrollY, Math.min(0, scrollX)); + mEdgeGlowLeft.setSize(height * 2, width); + invalidateForGlow |= mEdgeGlowLeft.draw(canvas); + canvas.restoreToCount(restoreCount); + } + if (!mEdgeGlowRight.isFinished()) { + final int restoreCount = canvas.save(); + + canvas.rotate(90); + canvas.translate(-height / 2 + scrollY, + -(Math.max(computeMaxScrollX(), scrollX) + width)); + mEdgeGlowRight.setSize(height * 2, width); + invalidateForGlow |= mEdgeGlowRight.draw(canvas); + canvas.restoreToCount(restoreCount); + } + return invalidateForGlow; + } + + @Override public void setLayoutParams(ViewGroup.LayoutParams params) { if (params.height == LayoutParams.WRAP_CONTENT) { mWrapContent = true; @@ -3299,12 +3548,34 @@ public class WebView extends AbsoluteLayout // Send the click so that the textfield is in focus centerKeyPressOnTextField(); rebuildWebTextView(); + } else { + clearTextEntry(true); } if (inEditingMode()) { return mWebTextView.performLongClick(); - } else { - return super.performLongClick(); } + /* if long click brings up a context menu, the super function + * returns true and we're done. Otherwise, nothing happened when + * the user clicked. */ + if (super.performLongClick()) { + return true; + } + /* In the case where the application hasn't already handled the long + * click action, look for a word under the click. If one is found, + * animate the text selection into view. + * FIXME: no animation code yet */ + if (mSelectingText) return false; // long click does nothing on selection + int x = viewToContentX((int) mLastTouchX + mScrollX); + int y = viewToContentY((int) mLastTouchY + mScrollY); + setUpSelect(); + if (mNativeClass != 0 && nativeWordSelection(x, y)) { + nativeSetExtendSelection(); + WebChromeClient client = getWebChromeClient(); + if (client != null) client.onSelectionStart(this); + return true; + } + notifySelectDialogDismissed(); + return false; } boolean inAnimateZoom() { @@ -3455,19 +3726,12 @@ public class WebView extends AbsoluteLayout // decide which adornments to draw int extras = DRAW_EXTRAS_NONE; if (mFindIsUp) { - // When the FindDialog is up, only draw the matches if we are not in - // the process of scrolling them into view. - if (!animateScroll) { extras = DRAW_EXTRAS_FIND; - } - } else if (mShiftIsPressed && !nativeFocusIsPlugin()) { - if (!animateZoom && !mPreviewZoomOnly) { - extras = DRAW_EXTRAS_SELECTION; - nativeSetSelectionRegion(mTouchSelection || mExtendSelection); - nativeSetSelectionPointer(!mTouchSelection, mInvActualScale, - mSelectX, mSelectY - getTitleHeight(), - mExtendSelection); - } + } else if (mSelectingText) { + extras = DRAW_EXTRAS_SELECTION; + nativeSetSelectionPointer(mDrawSelectionPointer, + mInvActualScale, + mSelectX, mSelectY - getTitleHeight()); } else if (drawCursorRing) { extras = DRAW_EXTRAS_CURSOR_RING; } @@ -3476,11 +3740,6 @@ public class WebView extends AbsoluteLayout if (extras == DRAW_EXTRAS_CURSOR_RING) { if (mTouchMode == TOUCH_SHORTPRESS_START_MODE) { mTouchMode = TOUCH_SHORTPRESS_MODE; - HitTestResult hitTest = getHitTestResult(); - if (hitTest == null - || hitTest.mType == HitTestResult.UNKNOWN_TYPE) { - mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS); - } } } if (mFocusSizeChanged) { @@ -3788,6 +4047,19 @@ public class WebView extends AbsoluteLayout private boolean mGotCenterDown = false; @Override + public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) { + // send complex characters to webkit for use by JS and plugins + if (keyCode == KeyEvent.KEYCODE_UNKNOWN && event.getCharacters() != null) { + // pass the key to DOM + mWebViewCore.sendMessage(EventHub.KEY_DOWN, event); + mWebViewCore.sendMessage(EventHub.KEY_UP, event); + // return true as DOM handles the key + return true; + } + return false; + } + + @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (DebugFlags.WEB_VIEW) { Log.v(LOGTAG, "keyDown at " + System.currentTimeMillis() @@ -3819,8 +4091,8 @@ public class WebView extends AbsoluteLayout || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) { if (nativeFocusIsPlugin()) { mShiftIsPressed = true; - } else if (!nativeCursorWantsKeyEvents() && !mShiftIsPressed) { - setUpSelectXY(); + } else if (!nativeCursorWantsKeyEvents() && !mSelectingText) { + setUpSelect(); } } @@ -3831,7 +4103,7 @@ public class WebView extends AbsoluteLayout letPluginHandleNavKey(keyCode, event.getEventTime(), true); return true; } - if (mShiftIsPressed) { + if (mSelectingText) { int xRate = keyCode == KeyEvent.KEYCODE_DPAD_LEFT ? -1 : keyCode == KeyEvent.KEYCODE_DPAD_RIGHT ? 1 : 0; int yRate = keyCode == KeyEvent.KEYCODE_DPAD_UP ? @@ -3851,7 +4123,7 @@ public class WebView extends AbsoluteLayout if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) { switchOutDrawHistory(); if (event.getRepeatCount() == 0) { - if (mShiftIsPressed && !nativeFocusIsPlugin()) { + if (mSelectingText) { return true; // discard press if copy in progress } mGotCenterDown = true; @@ -3869,10 +4141,8 @@ public class WebView extends AbsoluteLayout if (keyCode != KeyEvent.KEYCODE_SHIFT_LEFT && keyCode != KeyEvent.KEYCODE_SHIFT_RIGHT) { // turn off copy select if a shift-key combo is pressed - mExtendSelection = mShiftIsPressed = false; - if (mTouchMode == TOUCH_SELECT_MODE) { - mTouchMode = TOUCH_INIT_MODE; - } + selectionDone(); + mShiftIsPressed = false; } if (getSettings().getNavDump()) { @@ -3962,7 +4232,8 @@ public class WebView extends AbsoluteLayout || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) { if (nativeFocusIsPlugin()) { mShiftIsPressed = false; - } else if (commitCopy()) { + } else if (copySelection()) { + selectionDone(); return true; } } @@ -3983,11 +4254,13 @@ public class WebView extends AbsoluteLayout mPrivateHandler.removeMessages(LONG_PRESS_CENTER); mGotCenterDown = false; - if (mShiftIsPressed && !nativeFocusIsPlugin()) { + if (mSelectingText) { if (mExtendSelection) { - commitCopy(); + copySelection(); + selectionDone(); } else { mExtendSelection = true; + nativeSetExtendSelection(); invalidate(); // draw the i-beam instead of the arrow } return true; // discard press if copy in progress @@ -4032,9 +4305,18 @@ public class WebView extends AbsoluteLayout return false; } - private void setUpSelectXY() { + /** + * @hide pending API council approval. + */ + public void setUpSelect() { + if (0 == mNativeClass) return; // client isn't initialized + if (inFullScreenMode()) return; + if (mSelectingText) return; mExtendSelection = false; - mShiftIsPressed = true; + mSelectingText = mDrawSelectionPointer = true; + // don't let the picture change during text selection + WebViewCore.pauseUpdatePicture(mWebViewCore); + nativeResetSelection(); if (nativeHasCursorNode()) { Rect rect = nativeCursorNodeBounds(); mSelectX = contentToViewX(rect.left); @@ -4054,40 +4336,83 @@ public class WebView extends AbsoluteLayout * Do not rely on this functionality; it will be deprecated in the future. */ public void emulateShiftHeld() { + setUpSelect(); + } + + /** + * @hide pending API council approval. + */ + public void selectAll() { if (0 == mNativeClass) return; // client isn't initialized - setUpSelectXY(); + if (inFullScreenMode()) return; + if (!mSelectingText) setUpSelect(); + nativeSelectAll(); + mDrawSelectionPointer = false; + mExtendSelection = true; + invalidate(); + } + + /** + * @hide pending API council approval. + */ + public boolean selectDialogIsUp() { + return mSelectingText; + } + + /** + * @hide pending API council approval. + */ + public void notifySelectDialogDismissed() { + mSelectingText = false; + WebViewCore.resumeUpdatePicture(mWebViewCore); + } + + /** + * @hide pending API council approval. + */ + public void selectionDone() { + if (mSelectingText) { + WebChromeClient client = getWebChromeClient(); + if (client != null) client.onSelectionDone(this); + invalidate(); // redraw without selection + notifySelectDialogDismissed(); + } } - private boolean commitCopy() { + /** + * @hide pending API council approval. + */ + public boolean copySelection() { boolean copiedSomething = false; - if (mExtendSelection) { - String selection = nativeGetSelection(); - if (selection != "") { - if (DebugFlags.WEB_VIEW) { - Log.v(LOGTAG, "commitCopy \"" + selection + "\""); - } - Toast.makeText(mContext - , com.android.internal.R.string.text_copied - , Toast.LENGTH_SHORT).show(); - copiedSomething = true; - try { - IClipboard clip = IClipboard.Stub.asInterface( - ServiceManager.getService("clipboard")); - clip.setClipboardText(selection); - } catch (android.os.RemoteException e) { - Log.e(LOGTAG, "Clipboard failed", e); - } + String selection = getSelection(); + if (selection != "") { + if (DebugFlags.WEB_VIEW) { + Log.v(LOGTAG, "copySelection \"" + selection + "\""); + } + Toast.makeText(mContext + , com.android.internal.R.string.text_copied + , Toast.LENGTH_SHORT).show(); + copiedSomething = true; + try { + IClipboard clip = IClipboard.Stub.asInterface( + ServiceManager.getService("clipboard")); + clip.setClipboardText(selection); + } catch (android.os.RemoteException e) { + Log.e(LOGTAG, "Clipboard failed", e); } - mExtendSelection = false; } - mShiftIsPressed = false; invalidate(); // remove selection region and pointer - if (mTouchMode == TOUCH_SELECT_MODE) { - mTouchMode = TOUCH_INIT_MODE; - } return copiedSomething; } + /** + * @hide pending API council approval. + */ + public String getSelection() { + if (mNativeClass == 0) return ""; + return nativeGetSelection(); + } + @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); @@ -4177,9 +4502,9 @@ public class WebView extends AbsoluteLayout public void onWindowFocusChanged(boolean hasWindowFocus) { setActive(hasWindowFocus); if (hasWindowFocus) { - BrowserFrame.sJavaBridge.setActiveWebView(this); + JWebCoreJavaBridge.setActiveWebView(this); } else { - BrowserFrame.sJavaBridge.removeActiveWebView(this); + JWebCoreJavaBridge.removeActiveWebView(this); } super.onWindowFocusChanged(hasWindowFocus); } @@ -4325,12 +4650,14 @@ public class WebView extends AbsoluteLayout @Override protected void onScrollChanged(int l, int t, int oldl, int oldt) { super.onScrollChanged(l, t, oldl, oldt); - sendOurVisibleRect(); - // update WebKit if visible title bar height changed. The logic is same - // as getVisibleTitleHeight. - int titleHeight = getTitleHeight(); - if (Math.max(titleHeight - t, 0) != Math.max(titleHeight - oldt, 0)) { - sendViewSizeZoom(); + if (!mInOverScrollMode) { + sendOurVisibleRect(); + // update WebKit if visible title bar height changed. The logic is same + // as getVisibleTitleHeight. + int titleHeight = getTitleHeight(); + if (Math.max(titleHeight - t, 0) != Math.max(titleHeight - oldt, 0)) { + sendViewSizeZoom(); + } } } @@ -4400,7 +4727,7 @@ public class WebView extends AbsoluteLayout public DragTrackerHandler(float x, float y, DragTracker proxy) { mProxy = proxy; - int docBottom = computeVerticalScrollRange() + getTitleHeight(); + int docBottom = computeRealVerticalScrollRange() + getTitleHeight(); int viewTop = getScrollY(); int viewBottom = viewTop + getHeight(); @@ -4413,7 +4740,7 @@ public class WebView extends AbsoluteLayout " up/down= " + mMinDY + " " + mMaxDY); } - int docRight = computeHorizontalScrollRange(); + int docRight = computeRealHorizontalScrollRange(); int viewLeft = getScrollX(); int viewRight = viewLeft + getWidth(); mStartX = x; @@ -4662,7 +4989,7 @@ public class WebView extends AbsoluteLayout private boolean shouldForwardTouchEvent() { return mFullScreenHolder != null || (mForwardTouchEvents - && mTouchMode != TOUCH_SELECT_MODE + && !mSelectingText && mPreventDefault != PREVENT_DEFAULT_IGNORE); } @@ -4688,11 +5015,13 @@ public class WebView extends AbsoluteLayout // FIXME: we may consider to give WebKit an option to handle multi-touch // events later. if (mSupportMultiTouch && ev.getPointerCount() > 1) { - if (mMinZoomScale < mMaxZoomScale) { + if (mAllowPanAndScale || mMinZoomScale < mMaxZoomScale) { mScaleDetector.onTouchEvent(ev); if (mScaleDetector.isInProgress()) { mLastTouchTime = eventTime; - return true; + if (!mAllowPanAndScale) { + return true; + } } x = mScaleDetector.getFocusX(); y = mScaleDetector.getFocusY(); @@ -4750,16 +5079,6 @@ public class WebView extends AbsoluteLayout mTouchMode = TOUCH_DRAG_START_MODE; mConfirmMove = true; mPrivateHandler.removeMessages(RESUME_WEBCORE_PRIORITY); - } else if (!inFullScreenMode() && mShiftIsPressed) { - mSelectX = mScrollX + (int) x; - mSelectY = mScrollY + (int) y; - mTouchMode = TOUCH_SELECT_MODE; - if (DebugFlags.WEB_VIEW) { - Log.v(LOGTAG, "select=" + mSelectX + "," + mSelectY); - } - nativeMoveSelection(contentX, contentY, false); - mTouchSelection = mExtendSelection = true; - invalidate(); // draw the i-beam instead of the arrow } else if (mPrivateHandler.hasMessages(RELEASE_SINGLE_TAP)) { mPrivateHandler.removeMessages(RELEASE_SINGLE_TAP); if (deltaX * deltaX + deltaY * deltaY < mDoubleTapSlopSquare) { @@ -4784,6 +5103,14 @@ public class WebView extends AbsoluteLayout EventLog.writeEvent(EventLogTags.BROWSER_DOUBLE_TAP_DURATION, (eventTime - mLastTouchUpTime), eventTime); } + if (mSelectingText) { + mDrawSelectionPointer = false; + mSelectionStarted = nativeStartSelection(contentX, contentY); + if (DebugFlags.WEB_VIEW) { + Log.v(LOGTAG, "select=" + contentX + "," + contentY); + } + invalidate(); + } } // Trigger the link if (mTouchMode == TOUCH_INIT_MODE @@ -4875,17 +5202,17 @@ public class WebView extends AbsoluteLayout + " mTouchMode = " + mTouchMode); } mVelocityTracker.addMovement(ev); - if (mTouchMode != TOUCH_DRAG_MODE) { - if (mTouchMode == TOUCH_SELECT_MODE) { - mSelectX = mScrollX + (int) x; - mSelectY = mScrollY + (int) y; - if (DebugFlags.WEB_VIEW) { - Log.v(LOGTAG, "xtend=" + mSelectX + "," + mSelectY); - } - nativeMoveSelection(contentX, contentY, true); - invalidate(); - break; + if (mSelectingText && mSelectionStarted) { + if (DebugFlags.WEB_VIEW) { + Log.v(LOGTAG, "extend=" + contentX + "," + contentY); } + nativeExtendSelection(contentX, contentY); + invalidate(); + break; + } + + if (mTouchMode != TOUCH_DRAG_MODE) { + if (!mConfirmMove) { break; } @@ -4896,15 +5223,21 @@ public class WebView extends AbsoluteLayout mLastTouchTime = eventTime; break; } - // if it starts nearly horizontal or vertical, enforce it - int ax = Math.abs(deltaX); - int ay = Math.abs(deltaY); - if (ax > MAX_SLOPE_FOR_DIAG * ay) { - mSnapScrollMode = SNAP_X; - mSnapPositive = deltaX > 0; - } else if (ay > MAX_SLOPE_FOR_DIAG * ax) { - mSnapScrollMode = SNAP_Y; - mSnapPositive = deltaY > 0; + + // Only lock dragging to one axis if we don't have a scale in progress. + // Scaling implies free-roaming movement. Note we'll only ever get here + // if mAllowPanAndScale is true. + if (mScaleDetector != null && !mScaleDetector.isInProgress()) { + // if it starts nearly horizontal or vertical, enforce it + int ax = Math.abs(deltaX); + int ay = Math.abs(deltaY); + if (ax > MAX_SLOPE_FOR_DIAG * ay) { + mSnapScrollMode = SNAP_X; + mSnapPositive = deltaX > 0; + } else if (ay > MAX_SLOPE_FOR_DIAG * ax) { + mSnapScrollMode = SNAP_Y; + mSnapPositive = deltaY > 0; + } } mTouchMode = TOUCH_DRAG_MODE; @@ -4923,18 +5256,6 @@ public class WebView extends AbsoluteLayout } // do pan - int newScrollX = pinLocX(mScrollX + deltaX); - int newDeltaX = newScrollX - mScrollX; - if (deltaX != newDeltaX) { - deltaX = newDeltaX; - fDeltaX = (float) newDeltaX; - } - int newScrollY = pinLocY(mScrollY + deltaY); - int newDeltaY = newScrollY - mScrollY; - if (deltaY != newDeltaY) { - deltaY = newDeltaY; - fDeltaY = (float) newDeltaY; - } boolean done = false; boolean keepScrollBarsVisible = false; if (Math.abs(fDeltaX) < 1.0f && Math.abs(fDeltaY) < 1.0f) { @@ -5052,10 +5373,6 @@ public class WebView extends AbsoluteLayout mTouchMode = TOUCH_DONE_MODE; } break; - case TOUCH_SELECT_MODE: - commitCopy(); - mTouchSelection = false; - break; case TOUCH_INIT_MODE: // tap case TOUCH_SHORTPRESS_START_MODE: case TOUCH_SHORTPRESS_MODE: @@ -5086,6 +5403,13 @@ public class WebView extends AbsoluteLayout break; } } else { + if (mSelectingText) { + if (nativeHitSelection(contentX, contentY)) { + copySelection(); + } + selectionDone(); + break; + } if (mTouchMode == TOUCH_INIT_MODE) { mPrivateHandler.sendEmptyMessageDelayed( RELEASE_SINGLE_TAP, ViewConfiguration @@ -5115,6 +5439,12 @@ public class WebView extends AbsoluteLayout mHeldMotionless = MOTIONLESS_IGNORE; doFling(); break; + } else { + if (mScroller.springback(mScrollX, mScrollY, 0, + computeMaxScrollX(), 0, + computeMaxScrollY())) { + invalidate(); + } } // redraw in high-quality, as we're done dragging mHeldMotionless = MOTIONLESS_TRUE; @@ -5134,6 +5464,8 @@ public class WebView extends AbsoluteLayout } case MotionEvent.ACTION_CANCEL: { if (mTouchMode == TOUCH_DRAG_MODE) { + mScroller.springback(mScrollX, mScrollY, 0, + computeMaxScrollX(), 0, computeMaxScrollY()); invalidate(); } cancelWebCoreTouchEvent(contentX, contentY, false); @@ -5197,7 +5529,34 @@ public class WebView extends AbsoluteLayout private void doDrag(int deltaX, int deltaY) { if ((deltaX | deltaY) != 0) { - scrollBy(deltaX, deltaY); + final int oldX = mScrollX; + final int oldY = mScrollY; + final int rangeX = computeMaxScrollX(); + final int rangeY = computeMaxScrollY(); + overscrollBy(deltaX, deltaY, oldX, oldY, + rangeX, rangeY, + mOverscrollDistance, mOverscrollDistance, true); + + if (mEdgeGlowTop != null) { + // Don't show left/right glows if we fit the whole content. + if (rangeX > 0) { + final int pulledToX = oldX + deltaX; + if (pulledToX < 0) { + mEdgeGlowLeft.onPull((float) deltaX / getWidth()); + } else if (pulledToX > rangeX) { + mEdgeGlowRight.onPull((float) deltaX / getWidth()); + } + } + + if (rangeY > 0 || getOverscrollMode() == OVERSCROLL_ALWAYS) { + final int pulledToY = oldY + deltaY; + if (pulledToY < 0) { + mEdgeGlowTop.onPull((float) deltaY / getHeight()); + } else if (pulledToY > rangeY) { + mEdgeGlowBottom.onPull((float) deltaY / getHeight()); + } + } + } } if (!getSettings().getBuiltInZoomControls()) { boolean showPlusMinus = mMinZoomScale < mMaxZoomScale; @@ -5224,6 +5583,14 @@ public class WebView extends AbsoluteLayout mVelocityTracker.recycle(); mVelocityTracker = null; } + + // Release any pulled glows + if (mEdgeGlowTop != null) { + mEdgeGlowTop.onRelease(); + mEdgeGlowBottom.onRelease(); + mEdgeGlowLeft.onRelease(); + mEdgeGlowRight.onRelease(); + } } private void cancelTouch() { @@ -5237,6 +5604,15 @@ public class WebView extends AbsoluteLayout mVelocityTracker.recycle(); mVelocityTracker = null; } + + // Release any pulled glows + if (mEdgeGlowTop != null) { + mEdgeGlowTop.onRelease(); + mEdgeGlowBottom.onRelease(); + mEdgeGlowLeft.onRelease(); + mEdgeGlowRight.onRelease(); + } + if (mTouchMode == TOUCH_DRAG_MODE) { WebViewCore.resumePriority(); WebViewCore.resumeUpdatePicture(mWebViewCore); @@ -5256,8 +5632,10 @@ public class WebView extends AbsoluteLayout private float mTrackballRemainsY = 0.0f; private int mTrackballXMove = 0; private int mTrackballYMove = 0; + private boolean mSelectingText = false; + private boolean mSelectionStarted = false; private boolean mExtendSelection = false; - private boolean mTouchSelection = false; + private boolean mDrawSelectionPointer = false; private static final int TRACKBALL_KEY_TIMEOUT = 1000; private static final int TRACKBALL_TIMEOUT = 200; private static final int TRACKBALL_WAIT = 100; @@ -5296,10 +5674,8 @@ public class WebView extends AbsoluteLayout if (ev.getY() < 0) pageUp(true); return true; } - boolean shiftPressed = mShiftIsPressed && (mNativeClass == 0 - || !nativeFocusIsPlugin()); if (ev.getAction() == MotionEvent.ACTION_DOWN) { - if (shiftPressed) { + if (mSelectingText) { return true; // discard press if copy in progress } mTrackballDown = true; @@ -5324,11 +5700,13 @@ public class WebView extends AbsoluteLayout mPrivateHandler.removeMessages(LONG_PRESS_CENTER); mTrackballDown = false; mTrackballUpTime = time; - if (shiftPressed) { + if (mSelectingText) { if (mExtendSelection) { - commitCopy(); + copySelection(); + selectionDone(); } else { mExtendSelection = true; + nativeSetExtendSelection(); invalidate(); // draw the i-beam instead of the arrow } return true; // discard press if copy in progress @@ -5395,8 +5773,7 @@ public class WebView extends AbsoluteLayout + " yRate=" + yRate ); } - nativeMoveSelection(viewToContentX(mSelectX), - viewToContentY(mSelectY), mExtendSelection); + nativeMoveSelection(viewToContentX(mSelectX), viewToContentY(mSelectY)); int scrollX = mSelectX < mScrollX ? -SELECT_CURSOR_OFFSET : mSelectX > maxX - SELECT_CURSOR_OFFSET ? SELECT_CURSOR_OFFSET : 0; @@ -5462,7 +5839,16 @@ public class WebView extends AbsoluteLayout float yRate = mTrackballRemainsY * 1000 / elapsed; int viewWidth = getViewWidth(); int viewHeight = getViewHeight(); - if (mShiftIsPressed && (mNativeClass == 0 || !nativeFocusIsPlugin())) { + if (mSelectingText) { + if (!mDrawSelectionPointer) { + // The last selection was made by touch, disabling drawing the + // selection pointer. Allow the trackball to adjust the + // position of the touch control. + mSelectX = contentToViewX(nativeSelectionX()); + mSelectY = contentToViewY(nativeSelectionY()); + mDrawSelectionPointer = mExtendSelection = true; + nativeSetExtendSelection(); + } moveSelection(scaleTrackballX(xRate, viewWidth), scaleTrackballY(yRate, viewHeight)); mTrackballRemainsX = mTrackballRemainsY = 0; @@ -5535,17 +5921,17 @@ public class WebView extends AbsoluteLayout } private int computeMaxScrollX() { - return Math.max(computeHorizontalScrollRange() - getViewWidth(), 0); + return Math.max(computeRealHorizontalScrollRange() - getViewWidth(), 0); } private int computeMaxScrollY() { - return Math.max(computeVerticalScrollRange() + getTitleHeight() + return Math.max(computeRealVerticalScrollRange() + getTitleHeight() - getViewHeightWithTitle(), 0); } public void flingScroll(int vx, int vy) { mScroller.fling(mScrollX, mScrollY, vx, vy, 0, computeMaxScrollX(), 0, - computeMaxScrollY()); + computeMaxScrollY(), mOverflingDistance, mOverflingDistance); invalidate(); } @@ -5575,6 +5961,10 @@ public class WebView extends AbsoluteLayout if ((maxX == 0 && vy == 0) || (maxY == 0 && vx == 0)) { WebViewCore.resumePriority(); WebViewCore.resumeUpdatePicture(mWebViewCore); + if (mScroller.springback(mScrollX, mScrollY, 0, computeMaxScrollX(), + 0, computeMaxScrollY())) { + invalidate(); + } return; } float currentVelocity = mScroller.getCurrVelocity(); @@ -5598,17 +5988,37 @@ public class WebView extends AbsoluteLayout + " maxX=" + maxX + " maxY=" + maxY + " mScrollX=" + mScrollX + " mScrollY=" + mScrollY); } + + // Allow sloppy flings without overscrolling at the edges. + if ((mScrollX == 0 || mScrollX == maxX) && Math.abs(vx) < Math.abs(vy)) { + vx = 0; + } + if ((mScrollY == 0 || mScrollY == maxY) && Math.abs(vy) < Math.abs(vx)) { + vy = 0; + } + + if (mOverscrollDistance < mOverflingDistance) { + if (mScrollX == -mOverscrollDistance || mScrollX == maxX + mOverscrollDistance) { + vx = 0; + } + if (mScrollY == -mOverscrollDistance || mScrollY == maxY + mOverscrollDistance) { + vy = 0; + } + } + mLastVelX = vx; mLastVelY = vy; mLastVelocity = (float) Math.hypot(vx, vy); - mScroller.fling(mScrollX, mScrollY, -vx, -vy, 0, maxX, 0, maxY); - // TODO: duration is calculated based on velocity, if the range is - // small, the animation will stop before duration is up. We may - // want to calculate how long the animation is going to run to precisely - // resume the webcore update. + // no horizontal overscroll if the content just fits + mScroller.fling(mScrollX, mScrollY, -vx, -vy, 0, maxX, 0, maxY, + maxX == 0 ? 0 : mOverflingDistance, mOverflingDistance); + // Duration is calculated based on velocity. With range boundaries and overscroll + // we may not know how long the final animation will take. (Hence the deprecation + // warning on the call below.) It's not a big deal for scroll bars but if webcore + // resumes during this effect we will take a performance hit. See computeScroll; + // we resume webcore there when the animation is finished. final int time = mScroller.getDuration(); - mPrivateHandler.sendEmptyMessageDelayed(RESUME_WEBCORE_PRIORITY, time); awakenScrollBars(time); invalidate(); } @@ -6656,6 +7066,10 @@ public class WebView extends AbsoluteLayout case MotionEvent.ACTION_CANCEL: if (mDeferTouchMode == TOUCH_DRAG_MODE) { // no fling in defer process + mScroller.springback(mScrollX, mScrollY, 0, + computeMaxScrollX(), 0, + computeMaxScrollY()); + invalidate(); WebViewCore.resumePriority(); WebViewCore.resumeUpdatePicture(mWebViewCore); } @@ -7382,6 +7796,7 @@ public class WebView extends AbsoluteLayout private native void nativeDebugDump(); private native void nativeDestroy(); private native boolean nativeEvaluateLayersAnimations(); + private native void nativeExtendSelection(int x, int y); private native void nativeDrawExtras(Canvas canvas, int extra); private native void nativeDumpDisplayTree(String urlOrNull); private native int nativeFindAll(String findLower, String findUpper); @@ -7410,6 +7825,7 @@ public class WebView extends AbsoluteLayout private native boolean nativeHasCursorNode(); private native boolean nativeHasFocusNode(); private native void nativeHideCursor(); + private native boolean nativeHitSelection(int x, int y); private native String nativeImageURI(int x, int y); private native void nativeInstrumentReport(); /* package */ native boolean nativeMoveCursorToNextTextInput(); @@ -7419,21 +7835,27 @@ public class WebView extends AbsoluteLayout private native boolean nativeMoveCursor(int keyCode, int count, boolean noScroll); private native int nativeMoveGeneration(); - private native void nativeMoveSelection(int x, int y, - boolean extendSelection); + private native void nativeMoveSelection(int x, int y); private native boolean nativePointInNavCache(int x, int y, int slop); // Like many other of our native methods, you must make sure that // mNativeClass is not null before calling this method. private native void nativeRecordButtons(boolean focused, boolean pressed, boolean invalidate); + private native void nativeResetSelection(); + private native void nativeSelectAll(); private native void nativeSelectBestAt(Rect rect); + private native int nativeSelectionX(); + private native int nativeSelectionY(); + private native int nativeFindIndex(); + private native void nativeSetExtendSelection(); private native void nativeSetFindIsEmpty(); private native void nativeSetFindIsUp(boolean isUp); private native void nativeSetFollowedLink(boolean followed); private native void nativeSetHeightCanMeasure(boolean measure); private native void nativeSetRootLayer(int layer); private native void nativeSetSelectionPointer(boolean set, - float scale, int x, int y, boolean extendSelection); + float scale, int x, int y); + private native boolean nativeStartSelection(int x, int y); private native void nativeSetSelectionRegion(boolean set); private native Rect nativeSubtractLayers(Rect content); private native int nativeTextGeneration(); @@ -7441,6 +7863,7 @@ public class WebView extends AbsoluteLayout // we always want to pass in our generation number. private native void nativeUpdateCachedTextfield(String updatedText, int generation); + private native boolean nativeWordSelection(int x, int y); // return NO_LEFTEDGE means failure. private static final int NO_LEFTEDGE = -1; private native int nativeGetBlockLeftEdge(int x, int y, float scale); diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java index 4118119e074e..bf4d95bbf4dc 100644 --- a/core/java/android/webkit/WebViewCore.java +++ b/core/java/android/webkit/WebViewCore.java @@ -1568,9 +1568,16 @@ final class WebViewCore { + evt); } int keyCode = evt.getKeyCode(); - if (!nativeKey(keyCode, evt.getUnicodeChar(), - evt.getRepeatCount(), evt.isShiftPressed(), evt.isAltPressed(), - evt.isSymPressed(), + int unicodeChar = evt.getUnicodeChar(); + + if (keyCode == KeyEvent.KEYCODE_UNKNOWN && evt.getCharacters() != null + && evt.getCharacters().length() > 0) { + // we should only receive individual complex characters + unicodeChar = evt.getCharacters().codePointAt(0); + } + + if (!nativeKey(keyCode, unicodeChar, evt.getRepeatCount(), evt.isShiftPressed(), + evt.isAltPressed(), evt.isSymPressed(), isDown) && keyCode != KeyEvent.KEYCODE_ENTER) { if (keyCode >= KeyEvent.KEYCODE_DPAD_UP && keyCode <= KeyEvent.KEYCODE_DPAD_RIGHT) { diff --git a/core/java/android/webkit/WebViewDatabase.java b/core/java/android/webkit/WebViewDatabase.java index b18419dc1768..7acd9baba8b4 100644 --- a/core/java/android/webkit/WebViewDatabase.java +++ b/core/java/android/webkit/WebViewDatabase.java @@ -727,6 +727,9 @@ public class WebViewDatabase { } long getCacheTotalSize() { + if (mCacheDatabase == null) { + return 0; + } long size = 0; Cursor cursor = null; final String query = "SELECT SUM(contentlength) as sum FROM cache"; diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index fcfecb30ca70..8f5c35ea7425 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -19,6 +19,7 @@ package android.widget; import com.android.internal.R; import android.content.Context; +import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Rect; @@ -33,6 +34,7 @@ import android.text.TextUtils; import android.text.TextWatcher; import android.util.AttributeSet; import android.util.Log; +import android.view.ContextMenu.ContextMenuInfo; import android.view.Gravity; import android.view.HapticFeedbackConstants; import android.view.KeyEvent; @@ -44,7 +46,7 @@ import android.view.ViewConfiguration; import android.view.ViewDebug; import android.view.ViewGroup; import android.view.ViewTreeObserver; -import android.view.ContextMenu.ContextMenuInfo; +import android.view.animation.AnimationUtils; import android.view.inputmethod.BaseInputConnection; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputConnection; @@ -128,6 +130,17 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te static final int TOUCH_MODE_FLING = 4; /** + * Indicates the touch gesture is an overscroll - a scroll beyond the beginning or end. + */ + static final int TOUCH_MODE_OVERSCROLL = 5; + + /** + * Indicates the view is being flung outside of normal content bounds + * and will spring back. + */ + static final int TOUCH_MODE_OVERFLING = 6; + + /** * Regular layout - usually an unsolicited layout from the view system */ static final int LAYOUT_NORMAL = 0; @@ -369,6 +382,16 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te private ContextMenuInfo mContextMenuInfo = null; /** + * Maximum distance to record overscroll + */ + int mOverscrollMax; + + /** + * Content height divided by this is the overscroll limit. + */ + static final int OVERSCROLL_LIMIT_DIVISOR = 3; + + /** * Used to request a layout when we changed touch mode */ private static final int TOUCH_MODE_UNKNOWN = -1; @@ -461,6 +484,43 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te private static final int INVALID_POINTER = -1; /** + * Maximum distance to overscroll by during edge effects + */ + int mOverscrollDistance; + + /** + * Maximum distance to overfling during edge effects + */ + int mOverflingDistance; + + // These two EdgeGlows are always set and used together. + // Checking one for null is as good as checking both. + + /** + * Tracks the state of the top edge glow. + */ + private EdgeGlow mEdgeGlowTop; + + /** + * Tracks the state of the bottom edge glow. + */ + private EdgeGlow mEdgeGlowBottom; + + /** + * An estimate of how many pixels are between the top of the list and + * the top of the first position in the adapter, based on the last time + * we saw it. Used to hint where to draw edge glows. + */ + private int mFirstPositionDistanceGuess; + + /** + * An estimate of how many pixels are between the bottom of the list and + * the bottom of the last position in the adapter, based on the last time + * we saw it. Used to hint where to draw edge glows. + */ + private int mLastPositionDistanceGuess; + + /** * Interface definition for a callback to be invoked when the list or grid * has been scrolled. */ @@ -575,9 +635,41 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te mTouchSlop = configuration.getScaledTouchSlop(); mMinimumVelocity = configuration.getScaledMinimumFlingVelocity(); mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); + mOverscrollDistance = configuration.getScaledOverscrollDistance(); + mOverflingDistance = configuration.getScaledOverflingDistance(); + mDensityScale = getContext().getResources().getDisplayMetrics().density; } + @Override + public void setOverscrollMode(int mode) { + if (mode != OVERSCROLL_NEVER) { + if (mEdgeGlowTop == null) { + final Resources res = getContext().getResources(); + final Drawable edge = res.getDrawable(R.drawable.overscroll_edge); + final Drawable glow = res.getDrawable(R.drawable.overscroll_glow); + mEdgeGlowTop = new EdgeGlow(edge, glow); + mEdgeGlowBottom = new EdgeGlow(edge, glow); + } + } else { + mEdgeGlowTop = null; + mEdgeGlowBottom = null; + } + super.setOverscrollMode(mode); + } + + /** + * @return true if all list content currently fits within the view boundaries + */ + private boolean contentFits() { + final int childCount = getChildCount(); + if (childCount != mItemCount) { + return false; + } + + return getChildAt(0).getTop() >= 0 && getChildAt(childCount - 1).getBottom() <= mBottom; + } + /** * Enables fast scrolling by letting the user quickly scroll through lists by * dragging the fast scroll thumb. The adapter attached to the list may want @@ -1074,6 +1166,10 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te int result; if (mSmoothScrollbarEnabled) { result = Math.max(mItemCount * 100, 0); + if (mScrollY != 0) { + // Compensate for overscroll + result += Math.abs((int) ((float) mScrollY / getHeight() * mItemCount * 100)); + } } else { result = mItemCount; } @@ -1146,6 +1242,8 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te layoutChildren(); mInLayout = false; + + mOverscrollMax = (b - t) / OVERSCROLL_LIMIT_DIVISOR; } /** @@ -1612,6 +1710,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te mFlingRunnable.endFling(); if (mScrollY != 0) { mScrollY = 0; + finishGlows(); invalidate(); } } @@ -1921,9 +2020,10 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te // Check if we have moved far enough that it looks more like a // scroll than a tap final int distance = Math.abs(deltaY); - if (distance > mTouchSlop) { + final boolean overscroll = mScrollY != 0; + if (overscroll || distance > mTouchSlop) { createScrollingCache(); - mTouchMode = TOUCH_MODE_SCROLL; + mTouchMode = overscroll ? TOUCH_MODE_OVERSCROLL : TOUCH_MODE_SCROLL; mMotionCorrection = deltaY; final Handler handler = getHandler(); // Handler should not be null unless the AbsListView is not attached to a @@ -1959,6 +2059,19 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te // touch mode). Force an initial layout to get rid of the selection. layoutChildren(); } + } else { + int touchMode = mTouchMode; + if (touchMode == TOUCH_MODE_OVERSCROLL || touchMode == TOUCH_MODE_OVERFLING) { + if (mFlingRunnable != null) { + mFlingRunnable.endFling(); + } + + if (mScrollY != 0) { + mScrollY = 0; + finishGlows(); + invalidate(); + } + } } } @@ -1989,49 +2102,63 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te switch (action & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_DOWN: { - mActivePointerId = ev.getPointerId(0); - final int x = (int) ev.getX(); - final int y = (int) ev.getY(); - int motionPosition = pointToPosition(x, y); - if (!mDataChanged) { - if ((mTouchMode != TOUCH_MODE_FLING) && (motionPosition >= 0) - && (getAdapter().isEnabled(motionPosition))) { - // User clicked on an actual view (and was not stopping a fling). It might be a - // click or a scroll. Assume it is a click until proven otherwise - mTouchMode = TOUCH_MODE_DOWN; - // FIXME Debounce - if (mPendingCheckForTap == null) { - mPendingCheckForTap = new CheckForTap(); - } - postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); - } else { - if (ev.getEdgeFlags() != 0 && motionPosition < 0) { - // If we couldn't find a view to click on, but the down event was touching - // the edge, we will bail out and try again. This allows the edge correcting - // code in ViewRoot to try to find a nearby view to select - return false; - } + switch (mTouchMode) { + case TOUCH_MODE_OVERFLING: { + mFlingRunnable.endFling(); + mTouchMode = TOUCH_MODE_OVERSCROLL; + mMotionY = mLastY = (int) ev.getY(); + mMotionCorrection = 0; + mActivePointerId = ev.getPointerId(0); + break; + } - if (mTouchMode == TOUCH_MODE_FLING) { - // Stopped a fling. It is a scroll. - createScrollingCache(); - mTouchMode = TOUCH_MODE_SCROLL; - mMotionCorrection = 0; - motionPosition = findMotionRow(y); - reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL); + default: { + mActivePointerId = ev.getPointerId(0); + final int x = (int) ev.getX(); + final int y = (int) ev.getY(); + int motionPosition = pointToPosition(x, y); + if (!mDataChanged) { + if ((mTouchMode != TOUCH_MODE_FLING) && (motionPosition >= 0) + && (getAdapter().isEnabled(motionPosition))) { + // User clicked on an actual view (and was not stopping a fling). It might be a + // click or a scroll. Assume it is a click until proven otherwise + mTouchMode = TOUCH_MODE_DOWN; + // FIXME Debounce + if (mPendingCheckForTap == null) { + mPendingCheckForTap = new CheckForTap(); + } + postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout()); + } else { + if (ev.getEdgeFlags() != 0 && motionPosition < 0) { + // If we couldn't find a view to click on, but the down event was touching + // the edge, we will bail out and try again. This allows the edge correcting + // code in ViewRoot to try to find a nearby view to select + return false; + } + + if (mTouchMode == TOUCH_MODE_FLING) { + // Stopped a fling. It is a scroll. + createScrollingCache(); + mTouchMode = TOUCH_MODE_SCROLL; + mMotionCorrection = 0; + motionPosition = findMotionRow(y); + reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL); + } } } - } - if (motionPosition >= 0) { - // Remember where the motion event started - v = getChildAt(motionPosition - mFirstPosition); - mMotionViewOriginalTop = v.getTop(); + if (motionPosition >= 0) { + // Remember where the motion event started + v = getChildAt(motionPosition - mFirstPosition); + mMotionViewOriginalTop = v.getTop(); + } + mMotionX = x; + mMotionY = y; + mMotionPosition = motionPosition; + mLastY = Integer.MIN_VALUE; + break; + } } - mMotionX = x; - mMotionY = y; - mMotionPosition = motionPosition; - mLastY = Integer.MIN_VALUE; break; } @@ -2056,9 +2183,33 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } if (y != mLastY) { + // We may be here after stopping a fling and continuing to scroll. + // If so, we haven't disallowed intercepting touch events yet. + // Make sure that we do so in case we're in a parent that can intercept. + if ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) == 0 && + Math.abs(deltaY) > mTouchSlop) { + requestDisallowInterceptTouchEvent(true); + } + + final int rawDeltaY = deltaY; deltaY -= mMotionCorrection; int incrementalDeltaY = mLastY != Integer.MIN_VALUE ? y - mLastY : deltaY; + final int motionIndex; + if (mMotionPosition >= 0) { + motionIndex = mMotionPosition - mFirstPosition; + } else { + // If we don't have a motion position that we can reliably track, + // pick something in the middle to make a best guess at things below. + motionIndex = getChildCount() / 2; + } + + int motionViewPrevTop = 0; + View motionView = this.getChildAt(motionIndex); + if (motionView != null) { + motionViewPrevTop = motionView.getTop(); + } + // No need to do all this work if we're not going to move anyway boolean atEdge = false; if (incrementalDeltaY != 0) { @@ -2066,23 +2217,99 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } // Check to see if we have bumped into the scroll limit - if (atEdge && getChildCount() > 0) { - // Treat this like we're starting a new scroll from the current - // position. This will let the user start scrolling back into - // content immediately rather than needing to scroll back to the - // point where they hit the limit first. - int motionPosition = findMotionRow(y); - if (motionPosition >= 0) { - final View motionView = getChildAt(motionPosition - mFirstPosition); - mMotionViewOriginalTop = motionView.getTop(); + motionView = this.getChildAt(motionIndex); + if (motionView != null) { + // Check if the top of the motion view is where it is + // supposed to be + final int motionViewRealTop = motionView.getTop(); + if (atEdge) { + // Apply overscroll + + int overscroll = -incrementalDeltaY - + (motionViewRealTop - motionViewPrevTop); + overscrollBy(0, overscroll, 0, mScrollY, 0, 0, + 0, mOverscrollDistance, true); + if (Math.abs(mOverscrollDistance) == Math.abs(mScrollY)) { + // Don't allow overfling if we're at the edge. + mVelocityTracker.clear(); + } + + final int overscrollMode = getOverscrollMode(); + if (overscrollMode == OVERSCROLL_ALWAYS || + (overscrollMode == OVERSCROLL_IF_CONTENT_SCROLLS && + !contentFits())) { + mTouchMode = TOUCH_MODE_OVERSCROLL; + if (rawDeltaY > 0) { + mEdgeGlowTop.onPull((float) overscroll / getHeight()); + } else if (rawDeltaY < 0) { + mEdgeGlowBottom.onPull((float) overscroll / getHeight()); + } + } } mMotionY = y; - mMotionPosition = motionPosition; invalidate(); } mLastY = y; } break; + + case TOUCH_MODE_OVERSCROLL: + if (y != mLastY) { + final int rawDeltaY = deltaY; + deltaY -= mMotionCorrection; + int incrementalDeltaY = mLastY != Integer.MIN_VALUE ? y - mLastY : deltaY; + + final int oldScroll = mScrollY; + final int newScroll = oldScroll - incrementalDeltaY; + + if ((oldScroll >= 0 && newScroll <= 0) || + (oldScroll <= 0 && newScroll >= 0)) { + // Coming back to 'real' list scrolling + incrementalDeltaY = -newScroll; + mScrollY = 0; + + // No need to do all this work if we're not going to move anyway + if (incrementalDeltaY != 0) { + trackMotionScroll(incrementalDeltaY, incrementalDeltaY); + } + + // Check to see if we are back in + View motionView = this.getChildAt(mMotionPosition - mFirstPosition); + if (motionView != null) { + mTouchMode = TOUCH_MODE_SCROLL; + + // We did not scroll the full amount. Treat this essentially like the + // start of a new touch scroll + final int motionPosition = findClosestMotionRow(y); + + mMotionCorrection = 0; + motionView = getChildAt(motionPosition - mFirstPosition); + mMotionViewOriginalTop = motionView.getTop(); + mMotionY = y; + mMotionPosition = motionPosition; + } + } else { + overscrollBy(0, -incrementalDeltaY, 0, mScrollY, 0, 0, + 0, mOverscrollDistance, true); + final int overscrollMode = getOverscrollMode(); + if (overscrollMode == OVERSCROLL_ALWAYS || + (overscrollMode == OVERSCROLL_IF_CONTENT_SCROLLS && + !contentFits())) { + if (rawDeltaY > 0) { + mEdgeGlowTop.onPull((float) -incrementalDeltaY / getHeight()); + } else if (rawDeltaY < 0) { + mEdgeGlowBottom.onPull((float) -incrementalDeltaY / getHeight()); + } + invalidate(); + } + if (Math.abs(mOverscrollDistance) == Math.abs(mScrollY)) { + // Don't allow overfling if we're at the edge. + mVelocityTracker.clear(); + } + } + mLastY = y; + } + break; } break; @@ -2154,18 +2381,29 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te case TOUCH_MODE_SCROLL: final int childCount = getChildCount(); if (childCount > 0) { - if (mFirstPosition == 0 && getChildAt(0).getTop() >= mListPadding.top && + final int firstChildTop = getChildAt(0).getTop(); + final int lastChildBottom = getChildAt(childCount - 1).getBottom(); + final int contentTop = mListPadding.top; + final int contentBottom = getHeight() - mListPadding.bottom; + if (mFirstPosition == 0 && firstChildTop >= contentTop && mFirstPosition + childCount < mItemCount && - getChildAt(childCount - 1).getBottom() <= - getHeight() - mListPadding.bottom) { + lastChildBottom <= getHeight() - contentBottom) { mTouchMode = TOUCH_MODE_REST; reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); } else { final VelocityTracker velocityTracker = mVelocityTracker; velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); final int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId); - - if (Math.abs(initialVelocity) > mMinimumVelocity) { + + // Fling if we have enough velocity and we aren't at a boundary. + // Since we can potentially overfling more than we can overscroll, don't + // allow the weird behavior where you can scroll to a boundary then + // fling further. + if (Math.abs(initialVelocity) > mMinimumVelocity && + !((mFirstPosition == 0 && + firstChildTop == contentTop - mOverscrollDistance) || + (mFirstPosition + childCount == mItemCount && + lastChildBottom == contentBottom + mOverscrollDistance))) { if (mFlingRunnable == null) { mFlingRunnable = new FlingRunnable(); } @@ -2182,10 +2420,32 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); } break; + + case TOUCH_MODE_OVERSCROLL: + if (mFlingRunnable == null) { + mFlingRunnable = new FlingRunnable(); + } + final VelocityTracker velocityTracker = mVelocityTracker; + velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); + final int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId); + + reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING); + if (Math.abs(initialVelocity) > mMinimumVelocity) { + mFlingRunnable.startOverfling(-initialVelocity); + } else { + mFlingRunnable.startSpringback(); + } + + break; } setPressed(false); + if (mEdgeGlowTop != null) { + mEdgeGlowTop.onRelease(); + mEdgeGlowBottom.onRelease(); + } + // Need to redraw since we probably aren't drawing the selector anymore invalidate(); @@ -2211,24 +2471,42 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } case MotionEvent.ACTION_CANCEL: { - mTouchMode = TOUCH_MODE_REST; - setPressed(false); - View motionView = this.getChildAt(mMotionPosition - mFirstPosition); - if (motionView != null) { - motionView.setPressed(false); - } - clearScrollingCache(); + switch (mTouchMode) { + case TOUCH_MODE_OVERSCROLL: + if (mFlingRunnable == null) { + mFlingRunnable = new FlingRunnable(); + } + mFlingRunnable.startSpringback(); + break; - final Handler handler = getHandler(); - if (handler != null) { - handler.removeCallbacks(mPendingCheckForLongPress); - } + case TOUCH_MODE_OVERFLING: + // Do nothing - let it play out. + break; - if (mVelocityTracker != null) { - mVelocityTracker.recycle(); - mVelocityTracker = null; + default: + mTouchMode = TOUCH_MODE_REST; + setPressed(false); + View motionView = this.getChildAt(mMotionPosition - mFirstPosition); + if (motionView != null) { + motionView.setPressed(false); + } + clearScrollingCache(); + + final Handler handler = getHandler(); + if (handler != null) { + handler.removeCallbacks(mPendingCheckForLongPress); + } + + if (mVelocityTracker != null) { + mVelocityTracker.recycle(); + mVelocityTracker = null; + } } + if (mEdgeGlowTop != null) { + mEdgeGlowTop.onRelease(); + mEdgeGlowBottom.onRelease(); + } mActivePointerId = INVALID_POINTER; break; } @@ -2253,10 +2531,61 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } @Override + protected void onOverscrolled(int scrollX, int scrollY, + boolean clampedX, boolean clampedY) { + mScrollY = scrollY; + + if (clampedY) { + // Velocity is broken by hitting the limit; don't start a fling off of this. + if (mVelocityTracker != null) { + mVelocityTracker.clear(); + } + } + awakenScrollBars(); + } + + @Override public void draw(Canvas canvas) { super.draw(canvas); + if (mEdgeGlowTop != null) { + final int scrollY = mScrollY; + if (!mEdgeGlowTop.isFinished()) { + final int restoreCount = canvas.save(); + final int width = getWidth(); + + canvas.translate(-width / 2, Math.min(0, scrollY + mFirstPositionDistanceGuess)); + mEdgeGlowTop.setSize(width * 2, getHeight()); + if (mEdgeGlowTop.draw(canvas)) { + invalidate(); + } + canvas.restoreToCount(restoreCount); + } + if (!mEdgeGlowBottom.isFinished()) { + final int restoreCount = canvas.save(); + final int width = getWidth(); + final int height = getHeight(); + + canvas.translate(-width / 2, + Math.max(height, scrollY + mLastPositionDistanceGuess)); + canvas.rotate(180, width, 0); + mEdgeGlowBottom.setSize(width * 2, height); + if (mEdgeGlowBottom.draw(canvas)) { + invalidate(); + } + canvas.restoreToCount(restoreCount); + } + } if (mFastScroller != null) { - mFastScroller.draw(canvas); + final int scrollY = mScrollY; + if (scrollY != 0) { + // Pin to the top/bottom during overscroll + int restoreCount = canvas.save(); + canvas.translate(0, (float) scrollY); + mFastScroller.draw(canvas); + canvas.restoreToCount(restoreCount); + } else { + mFastScroller.draw(canvas); + } } } @@ -2275,6 +2604,10 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te switch (action & MotionEvent.ACTION_MASK) { case MotionEvent.ACTION_DOWN: { int touchMode = mTouchMode; + if (touchMode == TOUCH_MODE_OVERFLING || touchMode == TOUCH_MODE_OVERSCROLL) { + mMotionCorrection = 0; + return true; + } final int x = (int) ev.getX(); final int y = (int) ev.getY(); @@ -2339,6 +2672,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te final int newPointerIndex = pointerIndex == 0 ? 1 : 0; mMotionX = (int) ev.getX(newPointerIndex); mMotionY = (int) ev.getY(newPointerIndex); + mMotionCorrection = 0; mActivePointerId = ev.getPointerId(newPointerIndex); if (mVelocityTracker != null) { mVelocityTracker.clear(); @@ -2394,7 +2728,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te /** * Tracks the decay of a fling scroll */ - private final Scroller mScroller; + private final OverScroller mScroller; /** * Y value reported by mScroller on the previous fling @@ -2402,7 +2736,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te private int mLastFlingY; FlingRunnable() { - mScroller = new Scroller(getContext()); + mScroller = new OverScroller(getContext()); } void start(int initialVelocity) { @@ -2421,6 +2755,42 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } } + void startSpringback() { + if (mScroller.springback(0, mScrollY, 0, 0, 0, 0)) { + mTouchMode = TOUCH_MODE_OVERFLING; + invalidate(); + post(this); + } else { + mTouchMode = TOUCH_MODE_REST; + } + } + + void startOverfling(int initialVelocity) { + final int min = mScrollY > 0 ? Integer.MIN_VALUE : 0; + final int max = mScrollY > 0 ? 0 : Integer.MAX_VALUE; + mScroller.fling(0, mScrollY, 0, initialVelocity, 0, 0, min, max, 0, getHeight()); + mTouchMode = TOUCH_MODE_OVERFLING; + invalidate(); + post(this); + } + + void edgeReached(int delta) { + mScroller.notifyVerticalEdgeReached(mScrollY, 0, mOverflingDistance); + final int overscrollMode = getOverscrollMode(); + if (overscrollMode == OVERSCROLL_ALWAYS || + (overscrollMode == OVERSCROLL_IF_CONTENT_SCROLLS && !contentFits())) { + mTouchMode = TOUCH_MODE_OVERFLING; + final int vel = (int) mScroller.getCurrVelocity(); + if (delta > 0) { + mEdgeGlowTop.onAbsorb(vel); + } else { + mEdgeGlowBottom.onAbsorb(vel); + } + } + invalidate(); + post(this); + } + void startScroll(int distance, int duration) { int initialY = distance < 0 ? Integer.MAX_VALUE : 0; mLastFlingY = initialY; @@ -2453,7 +2823,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te return; } - final Scroller scroller = mScroller; + final OverScroller scroller = mScroller; boolean more = scroller.computeScrollOffset(); final int y = scroller.getCurrY(); @@ -2482,7 +2852,24 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te delta = Math.max(-(getHeight() - mPaddingBottom - mPaddingTop - 1), delta); } + // Check to see if we have bumped into the scroll limit + View motionView = getChildAt(mMotionPosition - mFirstPosition); + int oldTop = 0; + if (motionView != null) { + oldTop = motionView.getTop(); + } + final boolean atEnd = trackMotionScroll(delta, delta); + if (atEnd) { + if (motionView != null) { + // Tweak the scroll for how far we overshot + int overshoot = -(delta - (motionView.getTop() - oldTop)); + overscrollBy(0, overshoot, 0, mScrollY, 0, 0, + 0, mOverflingDistance, false); + } + edgeReached(delta); + break; + } if (more && !atEnd) { invalidate(); @@ -2500,6 +2887,24 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } break; } + + case TOUCH_MODE_OVERFLING: { + final OverScroller scroller = mScroller; + if (scroller.computeScrollOffset()) { + final int scrollY = mScrollY; + final int deltaY = scroller.getCurrY() - scrollY; + if (overscrollBy(0, deltaY, 0, scrollY, 0, 0, + 0, mOverflingDistance, false)) { + startSpringback(); + } else { + invalidate(); + post(this); + } + } else { + endFling(); + } + break; + } } } @@ -2856,16 +3261,29 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te final int firstPosition = mFirstPosition; - if (firstPosition == 0 && firstTop >= listPadding.top && deltaY >= 0) { + // Update our guesses for where the first and last views are + if (firstPosition == 0) { + mFirstPositionDistanceGuess = firstTop - mListPadding.top; + } else { + mFirstPositionDistanceGuess += incrementalDeltaY; + } + if (firstPosition + childCount == mItemCount) { + mLastPositionDistanceGuess = lastBottom + mListPadding.bottom; + } else { + mLastPositionDistanceGuess += incrementalDeltaY; + } + + if (firstPosition == 0 && firstTop >= listPadding.top && incrementalDeltaY >= 0) { // Don't need to move views down if the top of the first position // is already visible - return true; + return incrementalDeltaY != 0; } - if (firstPosition + childCount == mItemCount && lastBottom <= end && deltaY <= 0) { + if (firstPosition + childCount == mItemCount && lastBottom <= end && + incrementalDeltaY <= 0) { // Don't need to move views up if the bottom of the last position // is already visible - return true; + return incrementalDeltaY != 0; } final boolean down = incrementalDeltaY < 0; @@ -3799,6 +4217,13 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te return result; } + private void finishGlows() { + if (mEdgeGlowTop != null) { + mEdgeGlowTop.finish(); + mEdgeGlowBottom.finish(); + } + } + /** * Sets the recycler listener to be notified whenever a View is set aside in * the recycler for later reuse. This listener can be used to free resources @@ -3822,7 +4247,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te * View type for this view, as returned by * {@link android.widget.Adapter#getItemViewType(int) } */ - @ViewDebug.ExportedProperty(mapping = { + @ViewDebug.ExportedProperty(category = "list", mapping = { @ViewDebug.IntToString(from = ITEM_VIEW_TYPE_IGNORE, to = "ITEM_VIEW_TYPE_IGNORE"), @ViewDebug.IntToString(from = ITEM_VIEW_TYPE_HEADER_OR_FOOTER, to = "ITEM_VIEW_TYPE_HEADER_OR_FOOTER") }) @@ -3834,7 +4259,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te * been added to the list view and whether they should be treated as * recycled views or not. */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "list") boolean recycledHeaderFooter; /** @@ -3845,7 +4270,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te * view to be attached to the window rather than just attached to the * parent. */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "list") boolean forceAdd; public LayoutParams(Context c, AttributeSet attrs) { diff --git a/core/java/android/widget/AdapterView.java b/core/java/android/widget/AdapterView.java index fe6d91ac9d63..8fcc2e87a966 100644 --- a/core/java/android/widget/AdapterView.java +++ b/core/java/android/widget/AdapterView.java @@ -56,7 +56,7 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup { /** * The position of the first child displayed */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "scrolling") int mFirstPosition = 0; /** @@ -141,7 +141,7 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup { * The position within the adapter's data set of the item to select * during the next layout. */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "list") int mNextSelectedPosition = INVALID_POSITION; /** @@ -152,7 +152,7 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup { /** * The position within the adapter's data set of the currently selected item. */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "list") int mSelectedPosition = INVALID_POSITION; /** @@ -168,7 +168,7 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup { /** * The number of items in the current adapter. */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "list") int mItemCount; /** @@ -808,7 +808,6 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup { mNextSelectedPosition = INVALID_POSITION; mNextSelectedRowId = INVALID_ROW_ID; mNeedSync = false; - checkSelectionChanged(); checkFocus(); requestLayout(); @@ -819,13 +818,21 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup { } } - private class SelectionNotifier extends Handler implements Runnable { + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + removeCallbacks(mSelectionNotifier); + } + + private class SelectionNotifier implements Runnable { public void run() { if (mDataChanged) { // Data has changed between when this SelectionNotifier // was posted and now. We need to wait until the AdapterView // has been synched to the new data. - post(this); + if (getAdapter() != null) { + post(this); + } } else { fireOnSelected(); } @@ -842,7 +849,7 @@ public abstract class AdapterView<T extends Adapter> extends ViewGroup { if (mSelectionNotifier == null) { mSelectionNotifier = new SelectionNotifier(); } - mSelectionNotifier.post(mSelectionNotifier); + post(mSelectionNotifier); } else { fireOnSelected(); } diff --git a/core/java/android/widget/CursorTreeAdapter.java b/core/java/android/widget/CursorTreeAdapter.java index 7b9b7bdafc0f..3fadf4c54075 100644 --- a/core/java/android/widget/CursorTreeAdapter.java +++ b/core/java/android/widget/CursorTreeAdapter.java @@ -134,14 +134,16 @@ public abstract class CursorTreeAdapter extends BaseExpandableListAdapter implem /** * Sets the group Cursor. * - * @param cursor The Cursor to set for the group. + * @param cursor The Cursor to set for the group. If there is an existing cursor + * it will be closed. */ public void setGroupCursor(Cursor cursor) { mGroupCursorHelper.changeCursor(cursor, false); } /** - * Sets the children Cursor for a particular group. + * Sets the children Cursor for a particular group. If there is an existing cursor + * it will be closed. * <p> * This is useful when asynchronously querying to prevent blocking the UI. * @@ -476,7 +478,7 @@ public abstract class CursorTreeAdapter extends BaseExpandableListAdapter implem mCursor.unregisterContentObserver(mContentObserver); mCursor.unregisterDataSetObserver(mDataSetObserver); - mCursor.deactivate(); + mCursor.close(); mCursor = null; } diff --git a/core/java/android/widget/DatePicker.java b/core/java/android/widget/DatePicker.java index a4ee68af791d..8aed454a4715 100644 --- a/core/java/android/widget/DatePicker.java +++ b/core/java/android/widget/DatePicker.java @@ -331,6 +331,7 @@ public class DatePicker extends FrameLayout { mYear = ss.getYear(); mMonth = ss.getMonth(); mDay = ss.getDay(); + updateSpinners(); } /** diff --git a/core/java/android/widget/EdgeGlow.java b/core/java/android/widget/EdgeGlow.java new file mode 100644 index 000000000000..93222e1c00fc --- /dev/null +++ b/core/java/android/widget/EdgeGlow.java @@ -0,0 +1,282 @@ +/* + * 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 android.widget; + +import android.graphics.Canvas; +import android.graphics.drawable.Drawable; +import android.view.animation.AnimationUtils; +import android.view.animation.DecelerateInterpolator; +import android.view.animation.Interpolator; + +/** + * This class performs the glow effect used at the edges of scrollable widgets. + * @hide + */ +public class EdgeGlow { + private static final String TAG = "EdgeGlow"; + + // Time it will take the effect to fully recede in ms + private static final int RECEDE_TIME = 1000; + + // Time it will take before a pulled glow begins receding + private static final int PULL_TIME = 250; + + // Time it will take for a pulled glow to decay to partial strength before release + private static final int PULL_DECAY_TIME = 1000; + + private static final float HELD_EDGE_ALPHA = 0.7f; + private static final float HELD_EDGE_SCALE_Y = 0.5f; + private static final float HELD_GLOW_ALPHA = 0.5f; + private static final float HELD_GLOW_SCALE_Y = 0.5f; + + private static final float MAX_GLOW_HEIGHT = 0.33f; + + private static final float PULL_GLOW_BEGIN = 0.5f; + private static final float PULL_EDGE_BEGIN = 0.6f; + + // Minimum velocity that will be absorbed + private static final int MIN_VELOCITY = 750; + + private static final float EPSILON = 0.001f; + + private Drawable mEdge; + private Drawable mGlow; + private int mWidth; + private int mHeight; + + private float mEdgeAlpha; + private float mEdgeScaleY; + private float mGlowAlpha; + private float mGlowScaleY; + + private float mEdgeAlphaStart; + private float mEdgeAlphaFinish; + private float mEdgeScaleYStart; + private float mEdgeScaleYFinish; + private float mGlowAlphaStart; + private float mGlowAlphaFinish; + private float mGlowScaleYStart; + private float mGlowScaleYFinish; + + private long mStartTime; + private int mDuration; + + private Interpolator mInterpolator; + + private static final int STATE_IDLE = 0; + private static final int STATE_PULL = 1; + private static final int STATE_ABSORB = 2; + private static final int STATE_RECEDE = 3; + private static final int STATE_PULL_DECAY = 4; + + private int mState = STATE_IDLE; + + private float mPullDistance; + + public EdgeGlow(Drawable edge, Drawable glow) { + mEdge = edge; + mGlow = glow; + + mInterpolator = new DecelerateInterpolator(); + } + + public void setSize(int width, int height) { + mWidth = width; + mHeight = height; + } + + public boolean isFinished() { + return mState == STATE_IDLE; + } + + public void finish() { + mState = STATE_IDLE; + } + + /** + * Call when the object is pulled by the user. + * @param deltaDistance Change in distance since the last call + */ + public void onPull(float deltaDistance) { + final long now = AnimationUtils.currentAnimationTimeMillis(); + if (mState == STATE_PULL_DECAY && now - mStartTime < mDuration) { + return; + } + if (mState != STATE_PULL) { + mGlowScaleY = PULL_GLOW_BEGIN; + } + mState = STATE_PULL; + + mStartTime = now; + mDuration = PULL_TIME; + + mPullDistance += deltaDistance; + float distance = Math.abs(mPullDistance); + + mEdgeAlpha = mEdgeAlphaStart = Math.max(PULL_EDGE_BEGIN, Math.min(distance, 1.f)); + mEdgeScaleY = mEdgeScaleYStart = Math.max(HELD_EDGE_SCALE_Y, Math.min(distance, 2.f)); + + mGlowAlpha = mGlowAlphaStart = Math.max(0.5f, + Math.min(mGlowAlpha + Math.abs(deltaDistance), 1.f)); + + float glowChange = Math.abs(deltaDistance); + if (deltaDistance > 0 && mPullDistance < 0) { + glowChange = -glowChange; + } + if (mPullDistance == 0) { + mGlowScaleY = 0; + } + mGlowScaleY = mGlowScaleYStart = Math.max(0, mGlowScaleY + glowChange * 2); + + mEdgeAlphaFinish = mEdgeAlpha; + mEdgeScaleYFinish = mEdgeScaleY; + mGlowAlphaFinish = mGlowAlpha; + mGlowScaleYFinish = mGlowScaleY; + } + + /** + * Call when the object is released after being pulled. + */ + public void onRelease() { + mPullDistance = 0; + + if (mState != STATE_PULL && mState != STATE_PULL_DECAY) { + return; + } + + mState = STATE_RECEDE; + mEdgeAlphaStart = mEdgeAlpha; + mEdgeScaleYStart = mEdgeScaleY; + mGlowAlphaStart = mGlowAlpha; + mGlowScaleYStart = mGlowScaleY; + + mEdgeAlphaFinish = 0.f; + mEdgeScaleYFinish = 0.1f; + mGlowAlphaFinish = 0.f; + mGlowScaleYFinish = 0.1f; + + mStartTime = AnimationUtils.currentAnimationTimeMillis(); + mDuration = RECEDE_TIME; + } + + /** + * Call when the effect absorbs an impact at the given velocity. + * @param velocity Velocity at impact in pixels per second. + */ + public void onAbsorb(int velocity) { + mState = STATE_ABSORB; + velocity = Math.max(MIN_VELOCITY, Math.abs(velocity)); + + mStartTime = AnimationUtils.currentAnimationTimeMillis(); + mDuration = (int) (velocity * 0.03f); + + mEdgeAlphaStart = 0.5f; + mEdgeScaleYStart = 0.2f; + mGlowAlphaStart = 0.5f; + mGlowScaleYStart = 0.f; + + mEdgeAlphaFinish = Math.max(0, Math.min(velocity * 0.01f, 1)); + mEdgeScaleYFinish = 1.f; + mGlowAlphaFinish = 1.f; + mGlowScaleYFinish = Math.min(velocity * 0.001f, 1); + } + + /** + * Draw into the provided canvas. + * Assumes that the canvas has been rotated accordingly and the size has been set. + * The effect will be drawn the full width of X=0 to X=width, emitting from Y=0 and extending + * to some factor < 1.f of height. + * + * @param canvas Canvas to draw into + * @return true if drawing should continue beyond this frame to continue the animation + */ + public boolean draw(Canvas canvas) { + update(); + + final int edgeHeight = mEdge.getIntrinsicHeight(); + final int glowHeight = mGlow.getIntrinsicHeight(); + + final float distScale = (float) mHeight / mWidth; + + mGlow.setAlpha((int) (Math.max(0, Math.min(mGlowAlpha, 1)) * 255)); + mGlow.setBounds(0, 0, mWidth, (int) Math.min(glowHeight * mGlowScaleY * distScale * 0.6f, + mHeight * MAX_GLOW_HEIGHT)); + mGlow.draw(canvas); + + mEdge.setAlpha((int) (Math.max(0, Math.min(mEdgeAlpha, 1)) * 255)); + mEdge.setBounds(0, + 0, + mWidth, + (int) (edgeHeight * mEdgeScaleY)); + mEdge.draw(canvas); + + return mState != STATE_IDLE; + } + + private void update() { + final long time = AnimationUtils.currentAnimationTimeMillis(); + final float t = Math.min((float) (time - mStartTime) / mDuration, 1.f); + + final float interp = mInterpolator.getInterpolation(t); + + mEdgeAlpha = mEdgeAlphaStart + (mEdgeAlphaFinish - mEdgeAlphaStart) * interp; + mEdgeScaleY = mEdgeScaleYStart + (mEdgeScaleYFinish - mEdgeScaleYStart) * interp; + mGlowAlpha = mGlowAlphaStart + (mGlowAlphaFinish - mGlowAlphaStart) * interp; + mGlowScaleY = mGlowScaleYStart + (mGlowScaleYFinish - mGlowScaleYStart) * interp; + + if (t >= 1.f - EPSILON) { + switch (mState) { + case STATE_ABSORB: + mState = STATE_RECEDE; + mStartTime = AnimationUtils.currentAnimationTimeMillis(); + mDuration = RECEDE_TIME; + + mEdgeAlphaStart = mEdgeAlpha; + mEdgeScaleYStart = mEdgeScaleY; + mGlowAlphaStart = mGlowAlpha; + mGlowScaleYStart = mGlowScaleY; + + mEdgeAlphaFinish = 0.f; + mEdgeScaleYFinish = 0.1f; + mGlowAlphaFinish = 0.f; + mGlowScaleYFinish = mGlowScaleY; + break; + case STATE_PULL: + mState = STATE_PULL_DECAY; + mStartTime = AnimationUtils.currentAnimationTimeMillis(); + mDuration = PULL_DECAY_TIME; + + mEdgeAlphaStart = mEdgeAlpha; + mEdgeScaleYStart = mEdgeScaleY; + mGlowAlphaStart = mGlowAlpha; + mGlowScaleYStart = mGlowScaleY; + + mEdgeAlphaFinish = Math.min(mEdgeAlphaStart, HELD_EDGE_ALPHA); + mEdgeScaleYFinish = Math.min(mEdgeScaleYStart, HELD_EDGE_SCALE_Y); + mGlowAlphaFinish = Math.min(mGlowAlphaStart, HELD_GLOW_ALPHA); + mGlowScaleYFinish = Math.min(mGlowScaleY, HELD_GLOW_SCALE_Y); + break; + case STATE_PULL_DECAY: + // Do nothing; wait for release + break; + case STATE_RECEDE: + mState = STATE_IDLE; + break; + } + } + } +} diff --git a/core/java/android/widget/FrameLayout.java b/core/java/android/widget/FrameLayout.java index e27bb4fe2073..e4451800752b 100644 --- a/core/java/android/widget/FrameLayout.java +++ b/core/java/android/widget/FrameLayout.java @@ -46,27 +46,32 @@ import android.widget.RemoteViews.RemoteView; */ @RemoteView public class FrameLayout extends ViewGroup { - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "measurement") boolean mMeasureAllChildren = false; - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "drawing") private Drawable mForeground; - @ViewDebug.ExportedProperty + + @ViewDebug.ExportedProperty(category = "padding") private int mForegroundPaddingLeft = 0; - @ViewDebug.ExportedProperty + + @ViewDebug.ExportedProperty(category = "padding") private int mForegroundPaddingTop = 0; - @ViewDebug.ExportedProperty + + @ViewDebug.ExportedProperty(category = "padding") private int mForegroundPaddingRight = 0; - @ViewDebug.ExportedProperty + + @ViewDebug.ExportedProperty(category = "padding") private int mForegroundPaddingBottom = 0; private final Rect mSelfBounds = new Rect(); private final Rect mOverlayBounds = new Rect(); - @ViewDebug.ExportedProperty + + @ViewDebug.ExportedProperty(category = "drawing") private int mForegroundGravity = Gravity.FILL; /** {@hide} */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "drawing") protected boolean mForegroundInPadding = true; boolean mForegroundBoundsChanged = false; diff --git a/core/java/android/widget/Gallery.java b/core/java/android/widget/Gallery.java index aea804291c0c..ccd37d35263b 100644 --- a/core/java/android/widget/Gallery.java +++ b/core/java/android/widget/Gallery.java @@ -1090,7 +1090,7 @@ public class Gallery extends AbsSpinner implements GestureDetector.OnGestureList @Override public boolean dispatchKeyEvent(KeyEvent event) { // Gallery steals all key events - return event.dispatch(this); + return event.dispatch(this, null, null); } /** diff --git a/core/java/android/widget/GridView.java b/core/java/android/widget/GridView.java index 2f86d756b07a..a7300c288bf3 100644 --- a/core/java/android/widget/GridView.java +++ b/core/java/android/widget/GridView.java @@ -1876,7 +1876,12 @@ public class GridView extends AbsListView { // TODO: Account for vertical spacing too final int numColumns = mNumColumns; final int rowCount = (mItemCount + numColumns - 1) / numColumns; - return Math.max(rowCount * 100, 0); + int result = Math.max(rowCount * 100, 0); + if (mScrollY != 0) { + // Compensate for overscroll + result += Math.abs((int) ((float) mScrollY / getHeight() * rowCount * 100)); + } + return result; } } diff --git a/core/java/android/widget/HorizontalScrollView.java b/core/java/android/widget/HorizontalScrollView.java index 32a91464f4be..0bb97dd359af 100644 --- a/core/java/android/widget/HorizontalScrollView.java +++ b/core/java/android/widget/HorizontalScrollView.java @@ -16,19 +16,24 @@ package android.widget; -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.Rect; +import com.android.internal.R; + import android.util.AttributeSet; -import android.view.FocusFinder; -import android.view.KeyEvent; -import android.view.MotionEvent; -import android.view.VelocityTracker; +import android.graphics.Canvas; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; import android.view.View; +import android.view.VelocityTracker; import android.view.ViewConfiguration; import android.view.ViewGroup; +import android.view.KeyEvent; +import android.view.FocusFinder; +import android.view.MotionEvent; import android.view.ViewParent; import android.view.animation.AnimationUtils; +import android.content.Context; +import android.content.res.Resources; +import android.content.res.TypedArray; import java.util.List; @@ -63,7 +68,9 @@ public class HorizontalScrollView extends FrameLayout { private long mLastScroll; private final Rect mTempRect = new Rect(); - private Scroller mScroller; + private OverScroller mScroller; + private EdgeGlow mEdgeGlowLeft; + private EdgeGlow mEdgeGlowRight; /** * Flag to indicate that we are moving focus ourselves. This is so the @@ -117,6 +124,9 @@ public class HorizontalScrollView extends FrameLayout { private int mMinimumVelocity; private int mMaximumVelocity; + private int mOverscrollDistance; + private int mOverflingDistance; + /** * ID of the active pointer. This is used to retain consistency during * drags/flings if multiple pointers are used. @@ -189,7 +199,7 @@ public class HorizontalScrollView extends FrameLayout { private void initScrollView() { - mScroller = new Scroller(getContext()); + mScroller = new OverScroller(getContext()); setFocusable(true); setDescendantFocusability(FOCUS_AFTER_DESCENDANTS); setWillNotDraw(false); @@ -197,6 +207,8 @@ public class HorizontalScrollView extends FrameLayout { mTouchSlop = configuration.getScaledTouchSlop(); mMinimumVelocity = configuration.getScaledMinimumFlingVelocity(); mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); + mOverscrollDistance = configuration.getScaledOverscrollDistance(); + mOverflingDistance = configuration.getScaledOverflingDistance(); } @Override @@ -456,6 +468,9 @@ public class HorizontalScrollView extends FrameLayout { /* Release the drag */ mIsBeingDragged = false; mActivePointerId = INVALID_POINTER; + if (mScroller.springback(mScrollX, mScrollY, 0, getScrollRange(), 0, 0)) { + invalidate(); + } break; case MotionEvent.ACTION_POINTER_UP: onSecondaryPointerUp(ev); @@ -513,7 +528,26 @@ public class HorizontalScrollView extends FrameLayout { final int deltaX = (int) (mLastMotionX - x); mLastMotionX = x; - scrollBy(deltaX, 0); + final int oldX = mScrollX; + final int oldY = mScrollY; + final int range = getScrollRange(); + if (overscrollBy(deltaX, 0, mScrollX, 0, range, 0, + mOverscrollDistance, 0, true)) { + // Break our velocity if we hit a scroll barrier. + mVelocityTracker.clear(); + } + onScrollChanged(mScrollX, mScrollY, oldX, oldY); + + final int overscrollMode = getOverscrollMode(); + if (overscrollMode == OVERSCROLL_ALWAYS || + (overscrollMode == OVERSCROLL_IF_CONTENT_SCROLLS && range > 0)) { + final int pulledToX = oldX + deltaX; + if (pulledToX < 0) { + mEdgeGlowLeft.onPull((float) deltaX / getWidth()); + } else if (pulledToX > range) { + mEdgeGlowRight.onPull((float) deltaX / getWidth()); + } + } } break; case MotionEvent.ACTION_UP: @@ -522,8 +556,15 @@ public class HorizontalScrollView extends FrameLayout { velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); int initialVelocity = (int) velocityTracker.getXVelocity(mActivePointerId); - if (getChildCount() > 0 && Math.abs(initialVelocity) > mMinimumVelocity) { - fling(-initialVelocity); + if (getChildCount() > 0) { + if ((Math.abs(initialVelocity) > mMinimumVelocity)) { + fling(-initialVelocity); + } else { + final int right = getScrollRange(); + if (mScroller.springback(mScrollX, mScrollY, 0, right, 0, 0)) { + invalidate(); + } + } } mActivePointerId = INVALID_POINTER; @@ -533,16 +574,27 @@ public class HorizontalScrollView extends FrameLayout { mVelocityTracker.recycle(); mVelocityTracker = null; } + if (mEdgeGlowLeft != null) { + mEdgeGlowLeft.onRelease(); + mEdgeGlowRight.onRelease(); + } } break; case MotionEvent.ACTION_CANCEL: if (mIsBeingDragged && getChildCount() > 0) { + if (mScroller.springback(mScrollX, mScrollY, 0, getScrollRange(), 0, 0)) { + invalidate(); + } mActivePointerId = INVALID_POINTER; mIsBeingDragged = false; if (mVelocityTracker != null) { mVelocityTracker.recycle(); mVelocityTracker = null; } + if (mEdgeGlowLeft != null) { + mEdgeGlowLeft.onRelease(); + mEdgeGlowRight.onRelease(); + } } break; case MotionEvent.ACTION_POINTER_UP: @@ -569,6 +621,22 @@ public class HorizontalScrollView extends FrameLayout { } } + @Override + protected void onOverscrolled(int scrollX, int scrollY, + boolean clampedX, boolean clampedY) { + // Treat animating scrolls differently; see #computeScroll() for why. + if (!mScroller.isFinished()) { + mScrollX = scrollX; + mScrollY = scrollY; + if (clampedX) { + mScroller.springback(mScrollX, mScrollY, 0, getScrollRange(), 0, 0); + } + } else { + super.scrollTo(scrollX, scrollY); + } + awakenScrollBars(); + } + private int getScrollRange() { int scrollRange = 0; if (getChildCount() > 0) { @@ -951,7 +1019,16 @@ public class HorizontalScrollView extends FrameLayout { return contentWidth; } - return getChildAt(0).getRight(); + int scrollRange = getChildAt(0).getRight(); + final int scrollX = mScrollX; + final int overscrollRight = Math.max(0, scrollRange - contentWidth); + if (scrollX < 0) { + scrollRange -= scrollX; + } else if (scrollX > overscrollRight) { + scrollRange += scrollX - overscrollRight; + } + + return scrollRange; } @Override @@ -1012,14 +1089,20 @@ public class HorizontalScrollView extends FrameLayout { int x = mScroller.getCurrX(); int y = mScroller.getCurrY(); - if (getChildCount() > 0) { - View child = getChildAt(0); - x = clamp(x, getWidth() - mPaddingRight - mPaddingLeft, child.getWidth()); - y = clamp(y, getHeight() - mPaddingBottom - mPaddingTop, child.getHeight()); - if (x != oldX || y != oldY) { - mScrollX = x; - mScrollY = y; - onScrollChanged(x, y, oldX, oldY); + if (oldX != x || oldY != y) { + overscrollBy(x - oldX, y - oldY, oldX, oldY, getScrollRange(), 0, + mOverflingDistance, 0, false); + onScrollChanged(mScrollX, mScrollY, oldX, oldY); + + final int range = getScrollRange(); + final int overscrollMode = getOverscrollMode(); + if (overscrollMode == OVERSCROLL_ALWAYS || + (overscrollMode == OVERSCROLL_IF_CONTENT_SCROLLS && range > 0)) { + if (x < 0 && oldX >= 0) { + mEdgeGlowLeft.onAbsorb((int) mScroller.getCurrVelocity()); + } else if (x > range && oldX <= range) { + mEdgeGlowRight.onAbsorb((int) mScroller.getCurrVelocity()); + } } } awakenScrollBars(); @@ -1256,7 +1339,7 @@ public class HorizontalScrollView extends FrameLayout { int right = getChildAt(0).getWidth(); mScroller.fling(mScrollX, mScrollY, velocityX, 0, 0, - Math.max(0, right - width), 0, 0); + Math.max(0, right - width), 0, 0, width/2, 0); final boolean movingRight = velocityX > 0; @@ -1294,6 +1377,56 @@ public class HorizontalScrollView extends FrameLayout { } } + @Override + public void setOverscrollMode(int mode) { + if (mode != OVERSCROLL_NEVER) { + if (mEdgeGlowLeft == null) { + final Resources res = getContext().getResources(); + final Drawable edge = res.getDrawable(R.drawable.overscroll_edge); + final Drawable glow = res.getDrawable(R.drawable.overscroll_glow); + mEdgeGlowLeft = new EdgeGlow(edge, glow); + mEdgeGlowRight = new EdgeGlow(edge, glow); + } + } else { + mEdgeGlowLeft = null; + mEdgeGlowRight = null; + } + super.setOverscrollMode(mode); + } + + @Override + public void draw(Canvas canvas) { + super.draw(canvas); + if (mEdgeGlowLeft != null) { + final int scrollX = mScrollX; + if (!mEdgeGlowLeft.isFinished()) { + final int restoreCount = canvas.save(); + final int height = getHeight(); + + canvas.rotate(270); + canvas.translate(-height * 1.5f, Math.min(0, scrollX)); + mEdgeGlowLeft.setSize(getHeight() * 2, getWidth()); + if (mEdgeGlowLeft.draw(canvas)) { + invalidate(); + } + canvas.restoreToCount(restoreCount); + } + if (!mEdgeGlowRight.isFinished()) { + final int restoreCount = canvas.save(); + final int width = getWidth(); + final int height = getHeight(); + + canvas.rotate(90); + canvas.translate(-height / 2, -(Math.max(getScrollRange(), scrollX) + width)); + mEdgeGlowRight.setSize(height * 2, width); + if (mEdgeGlowRight.draw(canvas)) { + invalidate(); + } + canvas.restoreToCount(restoreCount); + } + } + } + private int clamp(int n, int my, int child) { if (my >= child || n < 0) { return 0; diff --git a/core/java/android/widget/ImageView.java b/core/java/android/widget/ImageView.java index c77416b34ba1..46f7db484b45 100644 --- a/core/java/android/widget/ImageView.java +++ b/core/java/android/widget/ImageView.java @@ -260,9 +260,15 @@ public class ImageView extends View { /** * Sets a drawable as the content of this ImageView. - * + * + * <p class="note">This does Bitmap reading and decoding on the UI + * thread, which can cause a latency hiccup. If that's a concern, + * consider using {@link #setImageDrawable} or + * {@link #setImageBitmap} and + * {@link android.graphics.BitmapFactory} instead.</p> + * * @param resId the resource identifier of the the drawable - * + * * @attr ref android.R.styleable#ImageView_src */ @android.view.RemotableViewMethod @@ -279,7 +285,13 @@ public class ImageView extends View { /** * Sets the content of this ImageView to the specified Uri. - * + * + * <p class="note">This does Bitmap reading and decoding on the UI + * thread, which can cause a latency hiccup. If that's a concern, + * consider using {@link #setImageDrawable} or + * {@link #setImageBitmap} and + * {@link android.graphics.BitmapFactory} instead.</p> + * * @param uri The Uri of an image */ @android.view.RemotableViewMethod diff --git a/core/java/android/widget/LinearLayout.java b/core/java/android/widget/LinearLayout.java index 3d7940c7ef7c..faf082dcd9c6 100644 --- a/core/java/android/widget/LinearLayout.java +++ b/core/java/android/widget/LinearLayout.java @@ -53,7 +53,7 @@ public class LinearLayout extends ViewGroup { * Whether the children of this layout are baseline aligned. Only applicable * if {@link #mOrientation} is horizontal. */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "layout") private boolean mBaselineAligned = true; /** @@ -63,7 +63,7 @@ public class LinearLayout extends ViewGroup { * Note: this is orthogonal to {@link #mBaselineAligned}, which is concerned * with whether the children of this layout are baseline aligned. */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "layout") private int mBaselineAlignedChildIndex = -1; /** @@ -71,12 +71,13 @@ public class LinearLayout extends ViewGroup { * We'll calculate the baseline of this layout as we measure vertically; for * horizontal linear layouts, the offset of 0 is appropriate. */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "measurement") private int mBaselineChildTop = 0; - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "measurement") private int mOrientation; - @ViewDebug.ExportedProperty(mapping = { + + @ViewDebug.ExportedProperty(category = "measurement", mapping = { @ViewDebug.IntToString(from = -1, to = "NONE"), @ViewDebug.IntToString(from = Gravity.NO_GRAVITY, to = "NONE"), @ViewDebug.IntToString(from = Gravity.TOP, to = "TOP"), @@ -91,13 +92,14 @@ public class LinearLayout extends ViewGroup { @ViewDebug.IntToString(from = Gravity.FILL, to = "FILL") }) private int mGravity = Gravity.LEFT | Gravity.TOP; - @ViewDebug.ExportedProperty + + @ViewDebug.ExportedProperty(category = "measurement") private int mTotalLength; - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "layout") private float mWeightSum; - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "layout") private boolean mUseLargestChild; private int[] mMaxAscent; @@ -1367,7 +1369,7 @@ public class LinearLayout extends ViewGroup { * 0 if the view should not be stretched. Otherwise the extra pixels * will be pro-rated among all views whose weight is greater than 0. */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "layout") public float weight; /** @@ -1375,7 +1377,7 @@ public class LinearLayout extends ViewGroup { * * @see android.view.Gravity */ - @ViewDebug.ExportedProperty(mapping = { + @ViewDebug.ExportedProperty(category = "layout", mapping = { @ViewDebug.IntToString(from = -1, to = "NONE"), @ViewDebug.IntToString(from = Gravity.NO_GRAVITY, to = "NONE"), @ViewDebug.IntToString(from = Gravity.TOP, to = "TOP"), diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java index 3015406fd5f6..46cd45aa7663 100644 --- a/core/java/android/widget/ListView.java +++ b/core/java/android/widget/ListView.java @@ -121,6 +121,9 @@ public class ListView extends AbsListView { Drawable mDivider; int mDividerHeight; + Drawable mOverscrollHeader; + Drawable mOverscrollFooter; + private boolean mIsCacheColorOpaque; private boolean mDividerIsOpaque; private boolean mClipDivider; @@ -175,6 +178,16 @@ public class ListView extends AbsListView { setDivider(d); } + final Drawable osHeader = a.getDrawable(com.android.internal.R.styleable.ListView_overscrollHeader); + if (osHeader != null) { + setOverscrollHeader(osHeader); + } + + final Drawable osFooter = a.getDrawable(com.android.internal.R.styleable.ListView_overscrollFooter); + if (osFooter != null) { + setOverscrollFooter(osFooter); + } + // Use the height specified, zero being the default final int dividerHeight = a.getDimensionPixelSize( com.android.internal.R.styleable.ListView_dividerHeight, 0); @@ -1143,7 +1156,7 @@ public class ListView extends AbsListView { * UNSPECIFIED/AT_MOST modes, false otherwise. * @hide */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "list") protected boolean recycleOnMeasure() { return true; } @@ -2945,14 +2958,52 @@ public class ListView extends AbsListView { } super.setCacheColorHint(color); } - + + void drawOverscrollHeader(Canvas canvas, Drawable drawable, Rect bounds) { + final int height = drawable.getMinimumHeight(); + + canvas.save(); + canvas.clipRect(bounds); + + final int span = bounds.bottom - bounds.top; + if (span < height) { + bounds.top = bounds.bottom - height; + } + + drawable.setBounds(bounds); + drawable.draw(canvas); + + canvas.restore(); + } + + void drawOverscrollFooter(Canvas canvas, Drawable drawable, Rect bounds) { + final int height = drawable.getMinimumHeight(); + + canvas.save(); + canvas.clipRect(bounds); + + final int span = bounds.bottom - bounds.top; + if (span < height) { + bounds.bottom = bounds.top + height; + } + + drawable.setBounds(bounds); + drawable.draw(canvas); + + canvas.restore(); + } + @Override protected void dispatchDraw(Canvas canvas) { // Draw the dividers final int dividerHeight = mDividerHeight; + final Drawable overscrollHeader = mOverscrollHeader; + final Drawable overscrollFooter = mOverscrollFooter; + final boolean drawOverscrollHeader = overscrollHeader != null; + final boolean drawOverscrollFooter = overscrollFooter != null; final boolean drawDividers = dividerHeight > 0 && mDivider != null; - if (drawDividers) { + if (drawDividers || drawOverscrollHeader || drawOverscrollFooter) { // Only modify the top and bottom in the loop, we set the left and right here final Rect bounds = mTempRect; bounds.left = mPaddingLeft; @@ -2983,14 +3034,28 @@ public class ListView extends AbsListView { if (!mStackFromBottom) { int bottom = 0; + // Draw top divider or header for overscroll final int scrollY = mScrollY; + if (count > 0 && scrollY < 0) { + if (drawOverscrollHeader) { + bounds.bottom = 0; + bounds.top = scrollY; + drawOverscrollHeader(canvas, overscrollHeader, bounds); + } else if (drawDividers) { + bounds.bottom = 0; + bounds.top = -dividerHeight; + drawDivider(canvas, bounds, -1); + } + } + for (int i = 0; i < count; i++) { if ((headerDividers || first + i >= headerCount) && (footerDividers || first + i < footerLimit)) { View child = getChildAt(i); bottom = child.getBottom(); // Don't draw dividers next to items that are not enabled - if (drawDividers) { + if (drawDividers && + (bottom < listBottom && !(drawOverscrollFooter && i == count - 1))) { if ((areAllItemsSelectable || (adapter.isEnabled(first + i) && (i == count - 1 || adapter.isEnabled(first + i + 1))))) { @@ -3005,13 +3070,28 @@ public class ListView extends AbsListView { } } } + + final int overFooterBottom = mBottom + mScrollY; + if (drawOverscrollFooter && first + count == itemCount && + overFooterBottom > bottom) { + bounds.top = bottom; + bounds.bottom = overFooterBottom; + drawOverscrollFooter(canvas, overscrollFooter, bounds); + } } else { int top; int listTop = mListPadding.top; final int scrollY = mScrollY; - for (int i = 0; i < count; i++) { + if (count > 0 && drawOverscrollHeader) { + bounds.top = scrollY; + bounds.bottom = getChildAt(0).getTop(); + drawOverscrollHeader(canvas, overscrollHeader, bounds); + } + + final int start = drawOverscrollHeader ? 1 : 0; + for (int i = start; i < count; i++) { if ((headerDividers || first + i >= headerCount) && (footerDividers || first + i < footerLimit)) { View child = getChildAt(i); @@ -3037,10 +3117,17 @@ public class ListView extends AbsListView { } } - if (count > 0 && scrollY > 0 && drawDividers) { - bounds.top = listBottom; - bounds.bottom = listBottom + dividerHeight; - drawDivider(canvas, bounds, -1); + if (count > 0 && scrollY > 0) { + if (drawOverscrollFooter) { + final int absListBottom = mBottom; + bounds.top = absListBottom; + bounds.bottom = absListBottom + scrollY; + drawOverscrollFooter(canvas, overscrollFooter, bounds); + } else if (drawDividers) { + bounds.top = listBottom; + bounds.bottom = listBottom + dividerHeight; + drawDivider(canvas, bounds, -1); + } } } } @@ -3149,6 +3236,45 @@ public class ListView extends AbsListView { invalidate(); } + /** + * Sets the drawable that will be drawn above all other list content. + * This area can become visible when the user overscrolls the list. + * + * @param header The drawable to use + */ + public void setOverscrollHeader(Drawable header) { + mOverscrollHeader = header; + if (mScrollY < 0) { + invalidate(); + } + } + + /** + * @return The drawable that will be drawn above all other list content + */ + public Drawable getOverscrollHeader() { + return mOverscrollHeader; + } + + /** + * Sets the drawable that will be drawn below all other list content. + * This area can become visible when the user overscrolls the list, + * or when the list's content does not fully fill the container area. + * + * @param footer The drawable to use + */ + public void setOverscrollFooter(Drawable footer) { + mOverscrollFooter = footer; + invalidate(); + } + + /** + * @return The drawable that will be drawn below all other list content + */ + public Drawable getOverscrollFooter() { + return mOverscrollFooter; + } + @Override protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) { super.onFocusChanged(gainFocus, direction, previouslyFocusedRect); diff --git a/core/java/android/widget/MediaController.java b/core/java/android/widget/MediaController.java index c246c247a53e..39b1377c1e68 100644 --- a/core/java/android/widget/MediaController.java +++ b/core/java/android/widget/MediaController.java @@ -123,7 +123,7 @@ public class MediaController extends FrameLayout { } private void initFloatingWindow() { - mWindowManager = (WindowManager)mContext.getSystemService("window"); + mWindowManager = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE); mWindow = PolicyManager.makeNewWindow(mContext); mWindow.setWindowManager(mWindowManager, null, null); mWindow.requestFeature(Window.FEATURE_NO_TITLE); diff --git a/core/java/android/widget/OverScroller.java b/core/java/android/widget/OverScroller.java new file mode 100644 index 000000000000..78973ad435de --- /dev/null +++ b/core/java/android/widget/OverScroller.java @@ -0,0 +1,849 @@ +/* + * 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 android.widget; + +import android.content.Context; +import android.hardware.SensorManager; +import android.util.FloatMath; +import android.view.ViewConfiguration; +import android.view.animation.AnimationUtils; +import android.view.animation.Interpolator; + +/** + * This class encapsulates scrolling with the ability to overshoot the bounds + * of a scrolling operation. This class is a drop-in replacement for + * {@link android.widget.Scroller} in most cases. + */ +public class OverScroller { + private int mMode; + + private MagneticOverScroller mScrollerX; + private MagneticOverScroller mScrollerY; + + private final Interpolator mInterpolator; + + private static final int DEFAULT_DURATION = 250; + private static final int SCROLL_MODE = 0; + private static final int FLING_MODE = 1; + + /** + * Creates an OverScroller with a viscous fluid scroll interpolator. + * @param context + */ + public OverScroller(Context context) { + this(context, null); + } + + /** + * Creates an OverScroller with default edge bounce coefficients. + * @param context The context of this application. + * @param interpolator The scroll interpolator. If null, a default (viscous) interpolator will + * be used. + */ + public OverScroller(Context context, Interpolator interpolator) { + this(context, interpolator, MagneticOverScroller.DEFAULT_BOUNCE_COEFFICIENT, + MagneticOverScroller.DEFAULT_BOUNCE_COEFFICIENT); + } + + /** + * Creates an OverScroller. + * @param context The context of this application. + * @param interpolator The scroll interpolator. If null, a default (viscous) interpolator will + * be used. + * @param bounceCoefficientX A value between 0 and 1 that will determine the proportion of the + * velocity which is preserved in the bounce when the horizontal edge is reached. A null value + * means no bounce. + * @param bounceCoefficientY Same as bounceCoefficientX but for the vertical direction. + */ + public OverScroller(Context context, Interpolator interpolator, + float bounceCoefficientX, float bounceCoefficientY) { + mInterpolator = interpolator; + mScrollerX = new MagneticOverScroller(); + mScrollerY = new MagneticOverScroller(); + MagneticOverScroller.initializeFromContext(context); + + mScrollerX.setBounceCoefficient(bounceCoefficientX); + mScrollerY.setBounceCoefficient(bounceCoefficientY); + } + + /** + * + * Returns whether the scroller has finished scrolling. + * + * @return True if the scroller has finished scrolling, false otherwise. + */ + public final boolean isFinished() { + return mScrollerX.mFinished && mScrollerY.mFinished; + } + + /** + * Force the finished field to a particular value. Contrary to + * {@link #abortAnimation()}, forcing the animation to finished + * does NOT cause the scroller to move to the final x and y + * position. + * + * @param finished The new finished value. + */ + public final void forceFinished(boolean finished) { + mScrollerX.mFinished = mScrollerY.mFinished = finished; + } + + /** + * Returns the current X offset in the scroll. + * + * @return The new X offset as an absolute distance from the origin. + */ + public final int getCurrX() { + return mScrollerX.mCurrentPosition; + } + + /** + * Returns the current Y offset in the scroll. + * + * @return The new Y offset as an absolute distance from the origin. + */ + public final int getCurrY() { + return mScrollerY.mCurrentPosition; + } + + /** + * @hide + * Returns the current velocity. + * + * @return The original velocity less the deceleration, norm of the X and Y velocity vector. + */ + public float getCurrVelocity() { + float squaredNorm = mScrollerX.mCurrVelocity * mScrollerX.mCurrVelocity; + squaredNorm += mScrollerY.mCurrVelocity * mScrollerY.mCurrVelocity; + return FloatMath.sqrt(squaredNorm); + } + + /** + * Returns the start X offset in the scroll. + * + * @return The start X offset as an absolute distance from the origin. + */ + public final int getStartX() { + return mScrollerX.mStart; + } + + /** + * Returns the start Y offset in the scroll. + * + * @return The start Y offset as an absolute distance from the origin. + */ + public final int getStartY() { + return mScrollerY.mStart; + } + + /** + * Returns where the scroll will end. Valid only for "fling" scrolls. + * + * @return The final X offset as an absolute distance from the origin. + */ + public final int getFinalX() { + return mScrollerX.mFinal; + } + + /** + * Returns where the scroll will end. Valid only for "fling" scrolls. + * + * @return The final Y offset as an absolute distance from the origin. + */ + public final int getFinalY() { + return mScrollerY.mFinal; + } + + /** + * Returns how long the scroll event will take, in milliseconds. + * + * @return The duration of the scroll in milliseconds. + * + * @hide Pending removal once nothing depends on it + * @deprecated OverScrollers don't necessarily have a fixed duration. + * This function will lie to the best of its ability. + */ + public final int getDuration() { + return Math.max(mScrollerX.mDuration, mScrollerY.mDuration); + } + + /** + * Extend the scroll animation. This allows a running animation to scroll + * further and longer, when used with {@link #setFinalX(int)} or {@link #setFinalY(int)}. + * + * @param extend Additional time to scroll in milliseconds. + * @see #setFinalX(int) + * @see #setFinalY(int) + * + * @hide Pending removal once nothing depends on it + * @deprecated OverScrollers don't necessarily have a fixed duration. + * Instead of setting a new final position and extending + * the duration of an existing scroll, use startScroll + * to begin a new animation. + */ + public void extendDuration(int extend) { + mScrollerX.extendDuration(extend); + mScrollerY.extendDuration(extend); + } + + /** + * Sets the final position (X) for this scroller. + * + * @param newX The new X offset as an absolute distance from the origin. + * @see #extendDuration(int) + * @see #setFinalY(int) + * + * @hide Pending removal once nothing depends on it + * @deprecated OverScroller's final position may change during an animation. + * Instead of setting a new final position and extending + * the duration of an existing scroll, use startScroll + * to begin a new animation. + */ + public void setFinalX(int newX) { + mScrollerX.setFinalPosition(newX); + } + + /** + * Sets the final position (Y) for this scroller. + * + * @param newY The new Y offset as an absolute distance from the origin. + * @see #extendDuration(int) + * @see #setFinalX(int) + * + * @hide Pending removal once nothing depends on it + * @deprecated OverScroller's final position may change during an animation. + * Instead of setting a new final position and extending + * the duration of an existing scroll, use startScroll + * to begin a new animation. + */ + public void setFinalY(int newY) { + mScrollerY.setFinalPosition(newY); + } + + /** + * Call this when you want to know the new location. If it returns true, the + * animation is not yet finished. + */ + public boolean computeScrollOffset() { + if (isFinished()) { + return false; + } + + switch (mMode) { + case SCROLL_MODE: + long time = AnimationUtils.currentAnimationTimeMillis(); + // Any scroller can be used for time, since they were started + // together in scroll mode. We use X here. + final long elapsedTime = time - mScrollerX.mStartTime; + + final int duration = mScrollerX.mDuration; + if (elapsedTime < duration) { + float q = (float) (elapsedTime) / duration; + + if (mInterpolator == null) + q = Scroller.viscousFluid(q); + else + q = mInterpolator.getInterpolation(q); + + mScrollerX.updateScroll(q); + mScrollerY.updateScroll(q); + } else { + abortAnimation(); + } + break; + + case FLING_MODE: + if (!mScrollerX.mFinished) { + if (!mScrollerX.update()) { + if (!mScrollerX.continueWhenFinished()) { + mScrollerX.finish(); + } + } + } + + if (!mScrollerY.mFinished) { + if (!mScrollerY.update()) { + if (!mScrollerY.continueWhenFinished()) { + mScrollerY.finish(); + } + } + } + + break; + } + + return true; + } + + /** + * Start scrolling by providing a starting point and the distance to travel. + * The scroll will use the default value of 250 milliseconds for the + * duration. + * + * @param startX Starting horizontal scroll offset in pixels. Positive + * numbers will scroll the content to the left. + * @param startY Starting vertical scroll offset in pixels. Positive numbers + * will scroll the content up. + * @param dx Horizontal distance to travel. Positive numbers will scroll the + * content to the left. + * @param dy Vertical distance to travel. Positive numbers will scroll the + * content up. + */ + public void startScroll(int startX, int startY, int dx, int dy) { + startScroll(startX, startY, dx, dy, DEFAULT_DURATION); + } + + /** + * Start scrolling by providing a starting point and the distance to travel. + * + * @param startX Starting horizontal scroll offset in pixels. Positive + * numbers will scroll the content to the left. + * @param startY Starting vertical scroll offset in pixels. Positive numbers + * will scroll the content up. + * @param dx Horizontal distance to travel. Positive numbers will scroll the + * content to the left. + * @param dy Vertical distance to travel. Positive numbers will scroll the + * content up. + * @param duration Duration of the scroll in milliseconds. + */ + public void startScroll(int startX, int startY, int dx, int dy, int duration) { + mMode = SCROLL_MODE; + mScrollerX.startScroll(startX, dx, duration); + mScrollerY.startScroll(startY, dy, duration); + } + + /** + * Call this when you want to 'spring back' into a valid coordinate range. + * + * @param startX Starting X coordinate + * @param startY Starting Y coordinate + * @param minX Minimum valid X value + * @param maxX Maximum valid X value + * @param minY Minimum valid Y value + * @param maxY Minimum valid Y value + * @return true if a springback was initiated, false if startX and startY were + * already within the valid range. + */ + public boolean springback(int startX, int startY, int minX, int maxX, int minY, int maxY) { + mMode = FLING_MODE; + + // Make sure both methods are called. + final boolean spingbackX = mScrollerX.springback(startX, minX, maxX); + final boolean spingbackY = mScrollerY.springback(startY, minY, maxY); + return spingbackX || spingbackY; + } + + public void fling(int startX, int startY, int velocityX, int velocityY, + int minX, int maxX, int minY, int maxY) { + fling(startX, startY, velocityX, velocityY, minX, maxX, minY, maxY, 0, 0); + } + + /** + * Start scrolling based on a fling gesture. The distance traveled will + * depend on the initial velocity of the fling. + * + * @param startX Starting point of the scroll (X) + * @param startY Starting point of the scroll (Y) + * @param velocityX Initial velocity of the fling (X) measured in pixels per + * second. + * @param velocityY Initial velocity of the fling (Y) measured in pixels per + * second + * @param minX Minimum X value. The scroller will not scroll past this point + * unless overX > 0. If overfling is allowed, it will use minX as + * a springback boundary. + * @param maxX Maximum X value. The scroller will not scroll past this point + * unless overX > 0. If overfling is allowed, it will use maxX as + * a springback boundary. + * @param minY Minimum Y value. The scroller will not scroll past this point + * unless overY > 0. If overfling is allowed, it will use minY as + * a springback boundary. + * @param maxY Maximum Y value. The scroller will not scroll past this point + * unless overY > 0. If overfling is allowed, it will use maxY as + * a springback boundary. + * @param overX Overfling range. If > 0, horizontal overfling in either + * direction will be possible. + * @param overY Overfling range. If > 0, vertical overfling in either + * direction will be possible. + */ + public void fling(int startX, int startY, int velocityX, int velocityY, + int minX, int maxX, int minY, int maxY, int overX, int overY) { + mMode = FLING_MODE; + mScrollerX.fling(startX, velocityX, minX, maxX, overX); + mScrollerY.fling(startY, velocityY, minY, maxY, overY); + } + + /** + * Notify the scroller that we've reached a horizontal boundary. + * Normally the information to handle this will already be known + * when the animation is started, such as in a call to one of the + * fling functions. However there are cases where this cannot be known + * in advance. This function will transition the current motion and + * animate from startX to finalX as appropriate. + * + * @param startX Starting/current X position + * @param finalX Desired final X position + * @param overX Magnitude of overscroll allowed. This should be the maximum + * desired distance from finalX. Absolute value - must be positive. + */ + public void notifyHorizontalEdgeReached(int startX, int finalX, int overX) { + mScrollerX.notifyEdgeReached(startX, finalX, overX); + } + + /** + * Notify the scroller that we've reached a vertical boundary. + * Normally the information to handle this will already be known + * when the animation is started, such as in a call to one of the + * fling functions. However there are cases where this cannot be known + * in advance. This function will animate a parabolic motion from + * startY to finalY. + * + * @param startY Starting/current Y position + * @param finalY Desired final Y position + * @param overY Magnitude of overscroll allowed. This should be the maximum + * desired distance from finalY. + */ + public void notifyVerticalEdgeReached(int startY, int finalY, int overY) { + mScrollerY.notifyEdgeReached(startY, finalY, overY); + } + + /** + * Returns whether the current Scroller is currently returning to a valid position. + * Valid bounds were provided by the + * {@link #fling(int, int, int, int, int, int, int, int, int, int)} method. + * + * One should check this value before calling + * {@link #startScroll(int, int, int, int)} as the interpolation currently in progress + * to restore a valid position will then be stopped. The caller has to take into account + * the fact that the started scroll will start from an overscrolled position. + * + * @return true when the current position is overscrolled and in the process of + * interpolating back to a valid value. + */ + public boolean isOverscrolled() { + return ((!mScrollerX.mFinished && + mScrollerX.mState != MagneticOverScroller.TO_EDGE) || + (!mScrollerY.mFinished && + mScrollerY.mState != MagneticOverScroller.TO_EDGE)); + } + + /** + * Stops the animation. Contrary to {@link #forceFinished(boolean)}, + * aborting the animating causes the scroller to move to the final x and y + * positions. + * + * @see #forceFinished(boolean) + */ + public void abortAnimation() { + mScrollerX.finish(); + mScrollerY.finish(); + } + + /** + * Returns the time elapsed since the beginning of the scrolling. + * + * @return The elapsed time in milliseconds. + * + * @hide + */ + public int timePassed() { + final long time = AnimationUtils.currentAnimationTimeMillis(); + final long startTime = Math.min(mScrollerX.mStartTime, mScrollerY.mStartTime); + return (int) (time - startTime); + } + + static class MagneticOverScroller { + // Initial position + int mStart; + + // Current position + int mCurrentPosition; + + // Final position + int mFinal; + + // Initial velocity + int mVelocity; + + // Current velocity + float mCurrVelocity; + + // Constant current deceleration + float mDeceleration; + + // Animation starting time, in system milliseconds + long mStartTime; + + // Animation duration, in milliseconds + int mDuration; + + // Whether the animation is currently in progress + boolean mFinished; + + // Constant gravity value, used to scale deceleration + static float GRAVITY; + + static void initializeFromContext(Context context) { + final float ppi = context.getResources().getDisplayMetrics().density * 160.0f; + GRAVITY = SensorManager.GRAVITY_EARTH // g (m/s^2) + * 39.37f // inch/meter + * ppi // pixels per inch + * ViewConfiguration.getScrollFriction(); + } + + private static final int TO_EDGE = 0; + private static final int TO_BOUNDARY = 1; + private static final int TO_BOUNCE = 2; + + private int mState = TO_EDGE; + + // The allowed overshot distance before boundary is reached. + private int mOver; + + // Duration in milliseconds to go back from edge to edge. Springback is half of it. + private static final int OVERSCROLL_SPRINGBACK_DURATION = 200; + + // Oscillation period + private static final float TIME_COEF = + 1000.0f * (float) Math.PI / OVERSCROLL_SPRINGBACK_DURATION; + + // If the velocity is smaller than this value, no bounce is triggered + // when the edge limits are reached (would result in a zero pixels + // displacement anyway). + private static final float MINIMUM_VELOCITY_FOR_BOUNCE = Float.MAX_VALUE;//140.0f; + + // Proportion of the velocity that is preserved when the edge is reached. + private static final float DEFAULT_BOUNCE_COEFFICIENT = 0.16f; + + private float mBounceCoefficient = DEFAULT_BOUNCE_COEFFICIENT; + + MagneticOverScroller() { + mFinished = true; + } + + void updateScroll(float q) { + mCurrentPosition = mStart + Math.round(q * (mFinal - mStart)); + } + + /* + * Get a signed deceleration that will reduce the velocity. + */ + static float getDeceleration(int velocity) { + return velocity > 0 ? -GRAVITY : GRAVITY; + } + + /* + * Returns the time (in milliseconds) it will take to go from start to end. + */ + static int computeDuration(int start, int end, float initialVelocity, float deceleration) { + final int distance = start - end; + final float discriminant = initialVelocity * initialVelocity - 2.0f * deceleration + * distance; + if (discriminant >= 0.0f) { + float delta = (float) Math.sqrt(discriminant); + if (deceleration < 0.0f) { + delta = -delta; + } + return (int) (1000.0f * (-initialVelocity - delta) / deceleration); + } + + // End position can not be reached + return 0; + } + + void startScroll(int start, int distance, int duration) { + mFinished = false; + + mStart = start; + mFinal = start + distance; + + mStartTime = AnimationUtils.currentAnimationTimeMillis(); + mDuration = duration; + + // Unused + mDeceleration = 0.0f; + mVelocity = 0; + } + + void fling(int start, int velocity, int min, int max) { + mFinished = false; + + mStart = start; + mStartTime = AnimationUtils.currentAnimationTimeMillis(); + + mVelocity = velocity; + + mDeceleration = getDeceleration(velocity); + + // A start from an invalid position immediately brings back to a valid position + if (mStart < min) { + mDuration = 0; + mFinal = min; + return; + } + + if (mStart > max) { + mDuration = 0; + mFinal = max; + return; + } + + // Duration are expressed in milliseconds + mDuration = (int) (-1000.0f * velocity / mDeceleration); + + mFinal = start - Math.round((velocity * velocity) / (2.0f * mDeceleration)); + + // Clamp to a valid final position + if (mFinal < min) { + mFinal = min; + mDuration = computeDuration(mStart, min, mVelocity, mDeceleration); + } + + if (mFinal > max) { + mFinal = max; + mDuration = computeDuration(mStart, max, mVelocity, mDeceleration); + } + } + + void finish() { + mCurrentPosition = mFinal; + // Not reset since WebView relies on this value for fast fling. + // mCurrVelocity = 0.0f; + mFinished = true; + } + + void setFinalPosition(int position) { + mFinal = position; + mFinished = false; + } + + void extendDuration(int extend) { + final long time = AnimationUtils.currentAnimationTimeMillis(); + final int elapsedTime = (int) (time - mStartTime); + mDuration = elapsedTime + extend; + mFinished = false; + } + + void setBounceCoefficient(float coefficient) { + mBounceCoefficient = coefficient; + } + + boolean springback(int start, int min, int max) { + mFinished = true; + + mStart = start; + mVelocity = 0; + + mStartTime = AnimationUtils.currentAnimationTimeMillis(); + mDuration = 0; + + if (start < min) { + startSpringback(start, min, false); + } else if (start > max) { + startSpringback(start, max, true); + } + + return !mFinished; + } + + private void startSpringback(int start, int end, boolean positive) { + mFinished = false; + mState = TO_BOUNCE; + mStart = mFinal = end; + mDuration = OVERSCROLL_SPRINGBACK_DURATION; + mStartTime -= OVERSCROLL_SPRINGBACK_DURATION / 2; + mVelocity = (int) (Math.abs(end - start) * TIME_COEF * (positive ? 1.0 : -1.0f)); + } + + void fling(int start, int velocity, int min, int max, int over) { + mState = TO_EDGE; + mOver = over; + + mFinished = false; + + mStart = start; + mStartTime = AnimationUtils.currentAnimationTimeMillis(); + + mVelocity = velocity; + + mDeceleration = getDeceleration(velocity); + + // Duration are expressed in milliseconds + mDuration = (int) (-1000.0f * velocity / mDeceleration); + + mFinal = start - Math.round((velocity * velocity) / (2.0f * mDeceleration)); + + // Clamp to a valid final position + if (mFinal < min) { + mFinal = min; + mDuration = computeDuration(mStart, min, mVelocity, mDeceleration); + } + + if (mFinal > max) { + mFinal = max; + mDuration = computeDuration(mStart, max, mVelocity, mDeceleration); + } + + if (start > max) { + if (start >= max + over) { + springback(max + over, min, max); + } else { + if (velocity <= 0) { + springback(start, min, max); + } else { + long time = AnimationUtils.currentAnimationTimeMillis(); + final double durationSinceEdge = + Math.atan((start-max) * TIME_COEF / velocity) / TIME_COEF; + mStartTime = (int) (time - 1000.0f * durationSinceEdge); + + // Simulate a bounce that started from edge + mStart = max; + + mVelocity = (int) (velocity / Math.cos(durationSinceEdge * TIME_COEF)); + + onEdgeReached(); + } + } + } else { + if (start < min) { + if (start <= min - over) { + springback(min - over, min, max); + } else { + if (velocity >= 0) { + springback(start, min, max); + } else { + long time = AnimationUtils.currentAnimationTimeMillis(); + final double durationSinceEdge = + Math.atan((start-min) * TIME_COEF / velocity) / TIME_COEF; + mStartTime = (int) (time - 1000.0f * durationSinceEdge); + + // Simulate a bounce that started from edge + mStart = min; + + mVelocity = (int) (velocity / Math.cos(durationSinceEdge * TIME_COEF)); + + onEdgeReached(); + } + + } + } + } + } + + void notifyEdgeReached(int start, int end, int over) { + mDeceleration = getDeceleration(mVelocity); + + // Local time, used to compute edge crossing time. + float timeCurrent = mCurrVelocity / mDeceleration; + final int distance = end - start; + float timeEdge = -(float) Math.sqrt((2.0f * distance / mDeceleration) + + (timeCurrent * timeCurrent)); + + mVelocity = (int) (mDeceleration * timeEdge); + + // Simulate a symmetric bounce that started from edge + mStart = end; + + mOver = over; + + long time = AnimationUtils.currentAnimationTimeMillis(); + mStartTime = (int) (time - 1000.0f * (timeCurrent - timeEdge)); + + onEdgeReached(); + } + + private void onEdgeReached() { + // mStart, mVelocity and mStartTime were adjusted to their values when edge was reached. + final float distance = mVelocity / TIME_COEF; + + if (Math.abs(distance) < mOver) { + // Spring force will bring us back to final position + mState = TO_BOUNCE; + mFinal = mStart; + mDuration = OVERSCROLL_SPRINGBACK_DURATION; + } else { + // Velocity is too high, we will hit the boundary limit + mState = TO_BOUNDARY; + int over = mVelocity > 0 ? mOver : -mOver; + mFinal = mStart + over; + mDuration = (int) (1000.0f * Math.asin(over / distance) / TIME_COEF); + } + } + + boolean continueWhenFinished() { + switch (mState) { + case TO_EDGE: + // Duration from start to null velocity + int duration = (int) (-1000.0f * mVelocity / mDeceleration); + if (mDuration < duration) { + // If the animation was clamped, we reached the edge + mStart = mFinal; + // Speed when edge was reached + mVelocity = (int) (mVelocity + mDeceleration * mDuration / 1000.0f); + mStartTime += mDuration; + onEdgeReached(); + } else { + // Normal stop, no need to continue + return false; + } + break; + case TO_BOUNDARY: + mStartTime += mDuration; + startSpringback(mFinal, mFinal - (mVelocity > 0 ? mOver:-mOver), mVelocity > 0); + break; + case TO_BOUNCE: + //mVelocity = (int) (mVelocity * BOUNCE_COEFFICIENT); + mVelocity = (int) (mVelocity * mBounceCoefficient); + if (Math.abs(mVelocity) < MINIMUM_VELOCITY_FOR_BOUNCE) { + return false; + } + mStartTime += mDuration; + break; + } + + update(); + return true; + } + + /* + * Update the current position and velocity for current time. Returns + * true if update has been done and false if animation duration has been + * reached. + */ + boolean update() { + final long time = AnimationUtils.currentAnimationTimeMillis(); + final long duration = time - mStartTime; + + if (duration > mDuration) { + return false; + } + + double distance; + final float t = duration / 1000.0f; + if (mState == TO_EDGE) { + mCurrVelocity = mVelocity + mDeceleration * t; + distance = mVelocity * t + mDeceleration * t * t / 2.0f; + } else { + final float d = t * TIME_COEF; + mCurrVelocity = mVelocity * (float)Math.cos(d); + distance = mVelocity / TIME_COEF * Math.sin(d); + } + + mCurrentPosition = mStart + (int) distance; + return true; + } + } +} diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java index 202e65852149..ec7d9277d0f6 100644 --- a/core/java/android/widget/ProgressBar.java +++ b/core/java/android/widget/ProgressBar.java @@ -21,8 +21,9 @@ import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.BitmapShader; import android.graphics.Canvas; -import android.graphics.Shader; import android.graphics.Rect; +import android.graphics.Shader; +import android.graphics.drawable.Animatable; import android.graphics.drawable.AnimationDrawable; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.ClipDrawable; @@ -30,11 +31,14 @@ import android.graphics.drawable.Drawable; import android.graphics.drawable.LayerDrawable; import android.graphics.drawable.ShapeDrawable; import android.graphics.drawable.StateListDrawable; -import android.graphics.drawable.Animatable; import android.graphics.drawable.shapes.RoundRectShape; import android.graphics.drawable.shapes.Shape; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.SystemClock; import android.util.AttributeSet; import android.view.Gravity; +import android.view.RemotableViewMethod; import android.view.View; import android.view.ViewDebug; import android.view.animation.AlphaAnimation; @@ -44,9 +48,6 @@ import android.view.animation.Interpolator; import android.view.animation.LinearInterpolator; import android.view.animation.Transformation; import android.widget.RemoteViews.RemoteView; -import android.os.Parcel; -import android.os.Parcelable; -import android.os.SystemClock; import com.android.internal.R; @@ -336,7 +337,7 @@ public class ProgressBar extends View { * * @return true if the progress bar is in indeterminate mode */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "progress") public synchronized boolean isIndeterminate() { return mIndeterminate; } @@ -609,7 +610,7 @@ public class ProgressBar extends View { * @see #setMax(int) * @see #getMax() */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "progress") public synchronized int getProgress() { return mIndeterminate ? 0 : mProgress; } @@ -626,7 +627,7 @@ public class ProgressBar extends View { * @see #setMax(int) * @see #getMax() */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "progress") public synchronized int getSecondaryProgress() { return mIndeterminate ? 0 : mSecondaryProgress; } @@ -640,7 +641,7 @@ public class ProgressBar extends View { * @see #getProgress() * @see #getSecondaryProgress() */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "progress") public synchronized int getMax() { return mMax; } @@ -762,6 +763,7 @@ public class ProgressBar extends View { } @Override + @RemotableViewMethod public void setVisibility(int v) { if (getVisibility() != v) { super.setVisibility(v); @@ -947,4 +949,20 @@ public class ProgressBar extends View { setProgress(ss.progress); setSecondaryProgress(ss.secondaryProgress); } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + if (mIndeterminate) { + startAnimation(); + } + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + if (mIndeterminate) { + stopAnimation(); + } + } } diff --git a/core/java/android/widget/QuickContactBadge.java b/core/java/android/widget/QuickContactBadge.java index 07c3e4b67340..4bbb54048801 100644 --- a/core/java/android/widget/QuickContactBadge.java +++ b/core/java/android/widget/QuickContactBadge.java @@ -236,6 +236,7 @@ public class QuickContactBadge extends ImageView implements OnClickListener { trigger = true; createUri = Uri.fromParts("tel", (String)cookie, null); + //$FALL-THROUGH$ case TOKEN_PHONE_LOOKUP: { if (cursor != null && cursor.moveToFirst()) { long contactId = cursor.getLong(PHONE_ID_COLUMN_INDEX); @@ -249,12 +250,14 @@ public class QuickContactBadge extends ImageView implements OnClickListener { trigger = true; createUri = Uri.fromParts("mailto", (String)cookie, null); + //$FALL-THROUGH$ case TOKEN_EMAIL_LOOKUP: { if (cursor != null && cursor.moveToFirst()) { long contactId = cursor.getLong(EMAIL_ID_COLUMN_INDEX); String lookupKey = cursor.getString(EMAIL_LOOKUP_STRING_COLUMN_INDEX); lookupUri = Contacts.getLookupUri(contactId, lookupKey); } + break; } case TOKEN_CONTACT_LOOKUP_AND_TRIGGER: { diff --git a/core/java/android/widget/RelativeLayout.java b/core/java/android/widget/RelativeLayout.java index 1630e7f102bd..a47359f8a6a4 100644 --- a/core/java/android/widget/RelativeLayout.java +++ b/core/java/android/widget/RelativeLayout.java @@ -1014,7 +1014,7 @@ public class RelativeLayout extends ViewGroup { * @attr ref android.R.styleable#RelativeLayout_Layout_layout_centerVertical */ public static class LayoutParams extends ViewGroup.MarginLayoutParams { - @ViewDebug.ExportedProperty(resolveId = true, indexMapping = { + @ViewDebug.ExportedProperty(category = "layout", resolveId = true, indexMapping = { @ViewDebug.IntToString(from = ABOVE, to = "above"), @ViewDebug.IntToString(from = ALIGN_BASELINE, to = "alignBaseline"), @ViewDebug.IntToString(from = ALIGN_BOTTOM, to = "alignBottom"), @@ -1043,7 +1043,7 @@ public class RelativeLayout extends ViewGroup { * When true, uses the parent as the anchor if the anchor doesn't exist or if * the anchor's visibility is GONE. */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "layout") public boolean alignWithParent; public LayoutParams(Context c, AttributeSet attrs) { diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index 3003580089da..7a70c808293b 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -100,6 +100,7 @@ public class RemoteViews implements Parcelable, Filter { * Base class for all actions that can be performed on an * inflated view. * + * SUBCLASSES MUST BE IMMUTABLE SO CLONE WORKS!!!!! */ private abstract static class Action implements Parcelable { public abstract void apply(View root) throws ActionException; @@ -568,6 +569,14 @@ public class RemoteViews implements Parcelable, Filter { } } + public RemoteViews clone() { + final RemoteViews that = new RemoteViews(mPackage, mLayoutId); + if (mActions != null) { + that.mActions = (ArrayList<Action>)mActions.clone(); + } + return that; + } + public String getPackage() { return mPackage; } diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java index 959e982cc3c8..2ba1c4791705 100644 --- a/core/java/android/widget/ScrollView.java +++ b/core/java/android/widget/ScrollView.java @@ -19,8 +19,11 @@ package android.widget; import com.android.internal.R; import android.content.Context; +import android.content.res.Resources; import android.content.res.TypedArray; +import android.graphics.Canvas; import android.graphics.Rect; +import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.view.FocusFinder; import android.view.KeyEvent; @@ -59,7 +62,9 @@ public class ScrollView extends FrameLayout { private long mLastScroll; private final Rect mTempRect = new Rect(); - private Scroller mScroller; + private OverScroller mScroller; + private EdgeGlow mEdgeGlowTop; + private EdgeGlow mEdgeGlowBottom; /** * Flag to indicate that we are moving focus ourselves. This is so the @@ -113,6 +118,9 @@ public class ScrollView extends FrameLayout { private int mMinimumVelocity; private int mMaximumVelocity; + private int mOverscrollDistance; + private int mOverflingDistance; + /** * ID of the active pointer. This is used to retain consistency during * drags/flings if multiple pointers are used. @@ -185,7 +193,7 @@ public class ScrollView extends FrameLayout { private void initScrollView() { - mScroller = new Scroller(getContext()); + mScroller = new OverScroller(getContext()); setFocusable(true); setDescendantFocusability(FOCUS_AFTER_DESCENDANTS); setWillNotDraw(false); @@ -193,6 +201,8 @@ public class ScrollView extends FrameLayout { mTouchSlop = configuration.getScaledTouchSlop(); mMinimumVelocity = configuration.getScaledMinimumFlingVelocity(); mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); + mOverscrollDistance = configuration.getScaledOverscrollDistance(); + mOverflingDistance = configuration.getScaledOverflingDistance(); } @Override @@ -453,6 +463,9 @@ public class ScrollView extends FrameLayout { /* Release the drag */ mIsBeingDragged = false; mActivePointerId = INVALID_POINTER; + if (mScroller.springback(mScrollX, mScrollY, 0, 0, 0, getScrollRange())) { + invalidate(); + } break; case MotionEvent.ACTION_POINTER_UP: onSecondaryPointerUp(ev); @@ -510,7 +523,26 @@ public class ScrollView extends FrameLayout { final int deltaY = (int) (mLastMotionY - y); mLastMotionY = y; - scrollBy(0, deltaY); + final int oldX = mScrollX; + final int oldY = mScrollY; + final int range = getScrollRange(); + if (overscrollBy(0, deltaY, 0, mScrollY, 0, range, + 0, mOverscrollDistance, true)) { + // Break our velocity if we hit a scroll barrier. + mVelocityTracker.clear(); + } + onScrollChanged(mScrollX, mScrollY, oldX, oldY); + + final int overscrollMode = getOverscrollMode(); + if (overscrollMode == OVERSCROLL_ALWAYS || + (overscrollMode == OVERSCROLL_IF_CONTENT_SCROLLS && range > 0)) { + final int pulledToY = oldY + deltaY; + if (pulledToY < 0) { + mEdgeGlowTop.onPull((float) deltaY / getHeight()); + } else if (pulledToY > range) { + mEdgeGlowBottom.onPull((float) deltaY / getHeight()); + } + } } break; case MotionEvent.ACTION_UP: @@ -519,8 +551,15 @@ public class ScrollView extends FrameLayout { velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId); - if (getChildCount() > 0 && Math.abs(initialVelocity) > mMinimumVelocity) { - fling(-initialVelocity); + if (getChildCount() > 0) { + if ((Math.abs(initialVelocity) > mMinimumVelocity)) { + fling(-initialVelocity); + } else { + final int bottom = getScrollRange(); + if (mScroller.springback(mScrollX, mScrollY, 0, 0, 0, bottom)) { + invalidate(); + } + } } mActivePointerId = INVALID_POINTER; @@ -530,16 +569,27 @@ public class ScrollView extends FrameLayout { mVelocityTracker.recycle(); mVelocityTracker = null; } + if (mEdgeGlowTop != null) { + mEdgeGlowTop.onRelease(); + mEdgeGlowBottom.onRelease(); + } } break; case MotionEvent.ACTION_CANCEL: if (mIsBeingDragged && getChildCount() > 0) { + if (mScroller.springback(mScrollX, mScrollY, 0, 0, 0, getScrollRange())) { + invalidate(); + } mActivePointerId = INVALID_POINTER; mIsBeingDragged = false; if (mVelocityTracker != null) { mVelocityTracker.recycle(); mVelocityTracker = null; } + if (mEdgeGlowTop != null) { + mEdgeGlowTop.onRelease(); + mEdgeGlowBottom.onRelease(); + } } break; case MotionEvent.ACTION_POINTER_UP: @@ -566,6 +616,22 @@ public class ScrollView extends FrameLayout { } } + @Override + protected void onOverscrolled(int scrollX, int scrollY, + boolean clampedX, boolean clampedY) { + // Treat animating scrolls differently; see #computeScroll() for why. + if (!mScroller.isFinished()) { + mScrollX = scrollX; + mScrollY = scrollY; + if (clampedY) { + mScroller.springback(mScrollX, mScrollY, 0, 0, 0, getScrollRange()); + } + } else { + super.scrollTo(scrollX, scrollY); + } + awakenScrollBars(); + } + private int getScrollRange() { int scrollRange = 0; if (getChildCount() > 0) { @@ -952,7 +1018,16 @@ public class ScrollView extends FrameLayout { return contentHeight; } - return getChildAt(0).getBottom(); + int scrollRange = getChildAt(0).getBottom(); + final int scrollY = mScrollY; + final int overscrollBottom = Math.max(0, scrollRange - contentHeight); + if (scrollY < 0) { + scrollRange -= scrollY; + } else if (scrollY > overscrollBottom) { + scrollRange += scrollY - overscrollBottom; + } + + return scrollRange; } @Override @@ -1013,14 +1088,20 @@ public class ScrollView extends FrameLayout { int x = mScroller.getCurrX(); int y = mScroller.getCurrY(); - if (getChildCount() > 0) { - View child = getChildAt(0); - x = clamp(x, getWidth() - mPaddingRight - mPaddingLeft, child.getWidth()); - y = clamp(y, getHeight() - mPaddingBottom - mPaddingTop, child.getHeight()); - if (x != oldX || y != oldY) { - mScrollX = x; - mScrollY = y; - onScrollChanged(x, y, oldX, oldY); + if (oldX != x || oldY != y) { + overscrollBy(x - oldX, y - oldY, oldX, oldY, 0, getScrollRange(), + 0, mOverflingDistance, false); + onScrollChanged(mScrollX, mScrollY, oldX, oldY); + + final int range = getScrollRange(); + final int overscrollMode = getOverscrollMode(); + if (overscrollMode == OVERSCROLL_ALWAYS || + (overscrollMode == OVERSCROLL_IF_CONTENT_SCROLLS && range > 0)) { + if (y < 0 && oldY >= 0) { + mEdgeGlowTop.onAbsorb((int) mScroller.getCurrVelocity()); + } else if (y > range && oldY <= range) { + mEdgeGlowBottom.onAbsorb((int) mScroller.getCurrVelocity()); + } } } awakenScrollBars(); @@ -1258,7 +1339,7 @@ public class ScrollView extends FrameLayout { int bottom = getChildAt(0).getHeight(); mScroller.fling(mScrollX, mScrollY, 0, velocityY, 0, 0, 0, - Math.max(0, bottom - height)); + Math.max(0, bottom - height), 0, height/2); final boolean movingDown = velocityY > 0; @@ -1296,6 +1377,55 @@ public class ScrollView extends FrameLayout { } } + @Override + public void setOverscrollMode(int mode) { + if (mode != OVERSCROLL_NEVER) { + if (mEdgeGlowTop == null) { + final Resources res = getContext().getResources(); + final Drawable edge = res.getDrawable(R.drawable.overscroll_edge); + final Drawable glow = res.getDrawable(R.drawable.overscroll_glow); + mEdgeGlowTop = new EdgeGlow(edge, glow); + mEdgeGlowBottom = new EdgeGlow(edge, glow); + } + } else { + mEdgeGlowTop = null; + mEdgeGlowBottom = null; + } + super.setOverscrollMode(mode); + } + + @Override + public void draw(Canvas canvas) { + super.draw(canvas); + if (mEdgeGlowTop != null) { + final int scrollY = mScrollY; + if (!mEdgeGlowTop.isFinished()) { + final int restoreCount = canvas.save(); + final int width = getWidth(); + + canvas.translate(-width / 2, Math.min(0, scrollY)); + mEdgeGlowTop.setSize(width * 2, getHeight()); + if (mEdgeGlowTop.draw(canvas)) { + invalidate(); + } + canvas.restoreToCount(restoreCount); + } + if (!mEdgeGlowBottom.isFinished()) { + final int restoreCount = canvas.save(); + final int width = getWidth(); + final int height = getHeight(); + + canvas.translate(-width / 2, Math.max(getScrollRange(), scrollY) + height); + canvas.rotate(180, width, 0); + mEdgeGlowBottom.setSize(width * 2, height); + if (mEdgeGlowBottom.draw(canvas)) { + invalidate(); + } + canvas.restoreToCount(restoreCount); + } + } + } + private int clamp(int n, int my, int child) { if (my >= child || n < 0) { /* my >= child is this case: diff --git a/core/java/android/widget/Scroller.java b/core/java/android/widget/Scroller.java index 4cb0839036f2..23f72b6be989 100644 --- a/core/java/android/widget/Scroller.java +++ b/core/java/android/widget/Scroller.java @@ -50,8 +50,6 @@ public class Scroller { private float mDurationReciprocal; private float mDeltaX; private float mDeltaY; - private float mViscousFluidScale; - private float mViscousFluidNormalize; private boolean mFinished; private Interpolator mInterpolator; @@ -65,6 +63,17 @@ public class Scroller { private final float mDeceleration; + private static float sViscousFluidScale; + private static float sViscousFluidNormalize; + + static { + // This controls the viscous fluid effect (how much of it) + sViscousFluidScale = 8.0f; + // must be set to 1.0 (used in viscousFluid()) + sViscousFluidNormalize = 1.0f; + sViscousFluidNormalize = 1.0f / viscousFluid(1.0f); + } + /** * Create a Scroller with the default duration and interpolator. */ @@ -277,11 +286,6 @@ public class Scroller { mDeltaX = dx; mDeltaY = dy; mDurationReciprocal = 1.0f / (float) mDuration; - // This controls the viscous fluid effect (how much of it) - mViscousFluidScale = 8.0f; - // must be set to 1.0 (used in viscousFluid()) - mViscousFluidNormalize = 1.0f; - mViscousFluidNormalize = 1.0f / viscousFluid(1.0f); } /** @@ -339,11 +343,9 @@ public class Scroller { mFinalY = Math.max(mFinalY, mMinY); } - - - private float viscousFluid(float x) + static float viscousFluid(float x) { - x *= mViscousFluidScale; + x *= sViscousFluidScale; if (x < 1.0f) { x -= (1.0f - (float)Math.exp(-x)); } else { @@ -351,7 +353,7 @@ public class Scroller { x = 1.0f - (float)Math.exp(1.0f - x); x = start + x * (1.0f - start); } - x *= mViscousFluidNormalize; + x *= sViscousFluidNormalize; return x; } diff --git a/core/java/android/widget/SimpleCursorTreeAdapter.java b/core/java/android/widget/SimpleCursorTreeAdapter.java index a1c65f0a81ab..a0335426ddf5 100644 --- a/core/java/android/widget/SimpleCursorTreeAdapter.java +++ b/core/java/android/widget/SimpleCursorTreeAdapter.java @@ -38,6 +38,10 @@ import android.view.View; * binding can be found, an {@link IllegalStateException} is thrown. */ public abstract class SimpleCursorTreeAdapter extends ResourceCursorTreeAdapter { + + /** The name of the columns that contain the data to display for a group. */ + private String[] mGroupFromNames; + /** The indices of columns that contain data to display for a group. */ private int[] mGroupFrom; /** @@ -46,6 +50,9 @@ public abstract class SimpleCursorTreeAdapter extends ResourceCursorTreeAdapter */ private int[] mGroupTo; + /** The name of the columns that contain the data to display for a child. */ + private String[] mChildFromNames; + /** The indices of columns that contain data to display for a child. */ private int[] mChildFrom; /** @@ -171,38 +178,12 @@ public abstract class SimpleCursorTreeAdapter extends ResourceCursorTreeAdapter private void init(String[] groupFromNames, int[] groupTo, String[] childFromNames, int[] childTo) { + + mGroupFromNames = groupFromNames; mGroupTo = groupTo; + mChildFromNames = childFromNames; mChildTo = childTo; - - // Get the group cursor column indices, the child cursor column indices will come - // when needed - initGroupFromColumns(groupFromNames); - - // Get a temporary child cursor to init the column indices - if (getGroupCount() > 0) { - MyCursorHelper tmpCursorHelper = getChildrenCursorHelper(0, true); - if (tmpCursorHelper != null) { - initChildrenFromColumns(childFromNames, tmpCursorHelper.getCursor()); - deactivateChildrenCursorHelper(0); - } - } - } - - private void initFromColumns(Cursor cursor, String[] fromColumnNames, int[] fromColumns) { - for (int i = fromColumnNames.length - 1; i >= 0; i--) { - fromColumns[i] = cursor.getColumnIndexOrThrow(fromColumnNames[i]); - } - } - - private void initGroupFromColumns(String[] groupFromNames) { - mGroupFrom = new int[groupFromNames.length]; - initFromColumns(mGroupCursorHelper.getCursor(), groupFromNames, mGroupFrom); - } - - private void initChildrenFromColumns(String[] childFromNames, Cursor childCursor) { - mChildFrom = new int[childFromNames.length]; - initFromColumns(childCursor, childFromNames, mChildFrom); } /** @@ -257,13 +238,29 @@ public abstract class SimpleCursorTreeAdapter extends ResourceCursorTreeAdapter } } + private void initFromColumns(Cursor cursor, String[] fromColumnNames, int[] fromColumns) { + for (int i = fromColumnNames.length - 1; i >= 0; i--) { + fromColumns[i] = cursor.getColumnIndexOrThrow(fromColumnNames[i]); + } + } + @Override protected void bindChildView(View view, Context context, Cursor cursor, boolean isLastChild) { + if (mChildFrom == null) { + mChildFrom = new int[mChildFromNames.length]; + initFromColumns(cursor, mChildFromNames, mChildFrom); + } + bindView(view, context, cursor, mChildFrom, mChildTo); } @Override protected void bindGroupView(View view, Context context, Cursor cursor, boolean isExpanded) { + if (mGroupFrom == null) { + mGroupFrom = new int[mGroupFromNames.length]; + initFromColumns(cursor, mGroupFromNames, mGroupFrom); + } + bindView(view, context, cursor, mGroupFrom, mGroupTo); } diff --git a/core/java/android/widget/TableRow.java b/core/java/android/widget/TableRow.java index 48d12df8eb16..b612004c5b77 100644 --- a/core/java/android/widget/TableRow.java +++ b/core/java/android/widget/TableRow.java @@ -387,13 +387,13 @@ public class TableRow extends LinearLayout { /** * <p>The column index of the cell represented by the widget.</p> */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "layout") public int column; /** * <p>The number of columns the widgets spans over.</p> */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "layout") public int span; private static final int LOCATION = 0; diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 64c9c9964d16..e97bbfb89116 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -23,6 +23,7 @@ import org.xmlpull.v1.XmlPullParserException; import android.content.Context; import android.content.Intent; +import android.content.pm.PackageManager; import android.content.res.ColorStateList; import android.content.res.Resources; import android.content.res.TypedArray; @@ -34,6 +35,7 @@ import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Typeface; import android.graphics.drawable.Drawable; +import android.inputmethodservice.ExtractEditText; import android.os.Bundle; import android.os.Handler; import android.os.Message; @@ -61,6 +63,7 @@ import android.text.StaticLayout; import android.text.TextPaint; import android.text.TextUtils; import android.text.TextWatcher; +import android.text.method.ArrowKeyMovementMethod; import android.text.method.DateKeyListener; import android.text.method.DateTimeKeyListener; import android.text.method.DialerKeyListener; @@ -89,10 +92,11 @@ import android.view.LayoutInflater; import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; +import android.view.ViewConfiguration; import android.view.ViewDebug; +import android.view.ViewGroup.LayoutParams; import android.view.ViewRoot; import android.view.ViewTreeObserver; -import android.view.ViewGroup.LayoutParams; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.view.animation.AnimationUtils; @@ -185,7 +189,7 @@ import java.util.ArrayList; */ @RemoteView public class TextView extends View implements ViewTreeObserver.OnPreDrawListener { - static final String TAG = "TextView"; + static final String LOG_TAG = "TextView"; static final boolean DEBUG_EXTRACT = false; private static int PRIORITY = 100; @@ -321,6 +325,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener this(context, attrs, com.android.internal.R.attr.textViewStyle); } + @SuppressWarnings("deprecation") public TextView(Context context, AttributeSet attrs, int defStyle) { @@ -695,9 +700,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener try { setInputExtras(a.getResourceId(attr, 0)); } catch (XmlPullParserException e) { - Log.w("TextView", "Failure reading input extras", e); + Log.w(LOG_TAG, "Failure reading input extras", e); } catch (IOException e) { - Log.w("TextView", "Failure reading input extras", e); + Log.w(LOG_TAG, "Failure reading input extras", e); } break; } @@ -706,15 +711,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener BufferType bufferType = BufferType.EDITABLE; - if ((inputType&(EditorInfo.TYPE_MASK_CLASS - |EditorInfo.TYPE_MASK_VARIATION)) - == (EditorInfo.TYPE_CLASS_TEXT - |EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)) { + if ((inputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION)) + == (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)) { password = true; } if (inputMethod != null) { - Class c; + Class<?> c; try { c = Class.forName(inputMethod.toString()); @@ -797,6 +800,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } else if (editable) { mInput = TextKeyListener.getInstance(); mInputType = EditorInfo.TYPE_CLASS_TEXT; + if (!singleLine) { + mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE; + } } else { mInput = null; @@ -923,6 +929,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener setFocusable(focusable); setClickable(clickable); setLongClickable(longClickable); + + prepareCursorControllers(); } private void setTypefaceByIndex(int typefaceIndex, int styleIndex) { @@ -1128,6 +1136,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener setText(mText); fixFocusableAndClickableSettings(); + + // SelectionModifierCursorController depends on canSelectText, which depends on mMovement + prepareCursorControllers(); } private void fixFocusableAndClickableSettings() { @@ -2335,6 +2346,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return str + "}"; } + @SuppressWarnings("hiding") public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() { public SavedState createFromParcel(Parcel in) { @@ -2369,8 +2381,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener int end = 0; if (mText != null) { - start = Selection.getSelectionStart(mText); - end = Selection.getSelectionEnd(mText); + start = getSelectionStart(); + end = getSelectionEnd(); if (start >= 0 || end >= 0) { // Or save state if there is a selection save = true; @@ -2442,7 +2454,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener restored = "(restored) "; } - Log.e("TextView", "Saved cursor position " + ss.selStart + + Log.e(LOG_TAG, "Saved cursor position " + ss.selStart + "/" + ss.selEnd + " out of range for " + restored + "text " + mText); } else { @@ -2694,6 +2706,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (needEditableForNotification) { sendAfterTextChanged((Editable) text); } + + // SelectionModifierCursorController depends on canSelectText, which depends on text + prepareCursorControllers(); } /** @@ -2756,6 +2771,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return mChars[off + mStart]; } + @Override public String toString() { return new String(mChars, mStart, mLength); } @@ -2947,8 +2963,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener final int cls = type & EditorInfo.TYPE_MASK_CLASS; KeyListener input; if (cls == EditorInfo.TYPE_CLASS_TEXT) { - boolean autotext = (type & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) - != 0; + boolean autotext = (type & EditorInfo.TYPE_TEXT_FLAG_AUTO_CORRECT) != 0; TextKeyListener.Capitalize cap; if ((type & EditorInfo.TYPE_TEXT_FLAG_CAP_CHARACTERS) != 0) { cap = TextKeyListener.Capitalize.CHARACTERS; @@ -2981,7 +2996,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } else { input = TextKeyListener.getInstance(); } - mInputType = type; + setRawInputType(type); if (direct) mInput = input; else { setKeyListenerOnly(input); @@ -3198,7 +3213,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * * @param create If true, the extras will be created if they don't already * exist. Otherwise, null will be returned if none have been created. - * @see #setInputExtras(int)View + * @see #setInputExtras(int) * @see EditorInfo#extras * @attr ref android.R.styleable#TextView_editorExtras */ @@ -3312,7 +3327,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private static class ErrorPopup extends PopupWindow { private boolean mAbove = false; - private TextView mView; + private final TextView mView; ErrorPopup(TextView v, int width, int height) { super(v, width, height); @@ -3585,7 +3600,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } private void invalidateCursor() { - int where = Selection.getSelectionEnd(mText); + int where = getSelectionEnd(); invalidateCursor(where, where, where); } @@ -3660,8 +3675,22 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener boolean changed = false; + SelectionModifierCursorController selectionController = null; + if (mSelectionModifierCursorController != null) { + selectionController = (SelectionModifierCursorController) + mSelectionModifierCursorController; + } + + if (mMovement != null) { - int curs = Selection.getSelectionEnd(mText); + /* This code also provides auto-scrolling when a cursor is moved using a + * CursorController (insertion point or selection limits). + * For selection, ensure start or end is visible depending on controller's state. + */ + int curs = getSelectionEnd(); + if (selectionController != null && selectionController.isSelectionStartDragged()) { + curs = getSelectionStart(); + } /* * TODO: This should really only keep the end in view if @@ -3680,6 +3709,16 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener changed = bringTextIntoView(); } + // This has to be checked here since: + // - onFocusChanged cannot start it when focus is given to a view with selected text (after + // a screen rotation) since layout is not yet initialized at that point. + // - ExtractEditText does not call onFocus when it is displayed. Fixing this issue would + // allow to test for hasSelection in onFocusChanged, which would trigger a + // startTextSelectionMode here. TODO + if (selectionController != null && hasSelection()) { + startTextSelectionMode(); + } + mPreDrawState = PREDRAW_DONE; return !changed; } @@ -3954,8 +3993,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // XXX This is not strictly true -- a program could set the // selection manually if it really wanted to. if (mMovement != null && (isFocused() || isPressed())) { - selStart = Selection.getSelectionStart(mText); - selEnd = Selection.getSelectionEnd(mText); + selStart = getSelectionStart(); + selEnd = getSelectionEnd(); if (mCursorVisible && selStart >= 0 && isEnabled()) { if (mHighlightPath == null) @@ -4061,6 +4100,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener */ canvas.restore(); + + if (mInsertionPointCursorController != null) { + mInsertionPointCursorController.draw(canvas); + } + if (mSelectionModifierCursorController != null) { + mSelectionModifierCursorController.draw(canvas); + } } @Override @@ -4267,6 +4313,15 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (shouldAdvanceFocusOnEnter()) { return 0; } + break; + + // Has to be done on key down (and not on key up) to correctly be intercepted. + case KeyEvent.KEYCODE_BACK: + if (mIsInTextSelectionMode) { + stopTextSelectionMode(); + return -1; + } + break; } if (mInput != null) { @@ -4345,6 +4400,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return super.onKeyUp(keyCode, event); } + hideControllers(); + switch (keyCode) { case KeyEvent.KEYCODE_DPAD_CENTER: /* @@ -4418,6 +4475,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return super.onKeyUp(keyCode, event); } + break; } if (mInput != null) @@ -4475,8 +4533,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener outAttrs.hintText = mHint; if (mText instanceof Editable) { InputConnection ic = new EditableInputConnection(this); - outAttrs.initialSelStart = Selection.getSelectionStart(mText); - outAttrs.initialSelEnd = Selection.getSelectionEnd(mText); + outAttrs.initialSelStart = getSelectionStart(); + outAttrs.initialSelEnd = getSelectionEnd(); outAttrs.initialCapsMode = ic.getCursorCapsMode(mInputType); return ic; } @@ -4547,6 +4605,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener outText.text = TextUtils.substring(content, partialStartOffset, partialEndOffset); } + } else { + outText.partialStartOffset = 0; + outText.partialEndOffset = 0; + outText.text = ""; } outText.flags = 0; if (MetaKeyKeyListener.getMetaState(mText, MetaKeyKeyListener.META_SELECTING) != 0) { @@ -4556,8 +4618,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener outText.flags |= ExtractedText.FLAG_SINGLE_LINE; } outText.startOffset = 0; - outText.selectionStart = Selection.getSelectionStart(content); - outText.selectionEnd = Selection.getSelectionEnd(content); + outText.selectionStart = getSelectionStart(); + outText.selectionEnd = getSelectionEnd(); return true; } return false; @@ -4574,7 +4636,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (req != null) { InputMethodManager imm = InputMethodManager.peekInstance(); if (imm != null) { - if (DEBUG_EXTRACT) Log.v(TAG, "Retrieving extracted start=" + if (DEBUG_EXTRACT) Log.v(LOG_TAG, "Retrieving extracted start=" + ims.mChangedStart + " end=" + ims.mChangedEnd + " delta=" + ims.mChangedDelta); if (ims.mChangedStart < 0 && !contentChanged) { @@ -4582,7 +4644,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } if (extractTextInternal(req, ims.mChangedStart, ims.mChangedEnd, ims.mChangedDelta, ims.mTmpExtracted)) { - if (DEBUG_EXTRACT) Log.v(TAG, "Reporting extracted start=" + if (DEBUG_EXTRACT) Log.v(LOG_TAG, "Reporting extracted start=" + ims.mTmpExtracted.partialStartOffset + " end=" + ims.mTmpExtracted.partialEndOffset + ": " + ims.mTmpExtracted.text); @@ -4732,7 +4794,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener void updateAfterEdit() { invalidate(); - int curs = Selection.getSelectionStart(mText); + int curs = getSelectionStart(); if (curs >= 0 || (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) { @@ -4874,7 +4936,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener w, alignment, mSpacingMult, mSpacingAdd, boring, mIncludePad); } - // Log.e("aaa", "Boring: " + mTransformed); mSavedLayout = (BoringLayout) mLayout; } else if (shouldEllipsize && boring.width <= w) { @@ -4900,7 +4961,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener mLayout = new StaticLayout(mTransformed, mTextPaint, w, alignment, mSpacingMult, mSpacingAdd, mIncludePad); - // Log.e("aaa", "Boring but wide: " + mTransformed); } } else if (shouldEllipsize) { mLayout = new StaticLayout(mTransformed, @@ -4998,6 +5058,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } } + + // CursorControllers need a non-null mLayout + prepareCursorControllers(); } private boolean compressText(float width) { @@ -5469,7 +5532,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // FIXME: Is it okay to truncate this, or should we round? final int x = (int)mLayout.getPrimaryHorizontal(offset); final int top = mLayout.getLineTop(line); - final int bottom = mLayout.getLineTop(line+1); + final int bottom = mLayout.getLineTop(line + 1); int left = (int) FloatMath.floor(mLayout.getLineLeft(line)); int right = (int) FloatMath.ceil(mLayout.getLineRight(line)); @@ -5606,8 +5669,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // viewport coordinates, but requestRectangleOnScreen() // is in terms of content coordinates. - Rect r = new Rect(); - getInterestingRect(r, x, top, bottom, line); + Rect r = new Rect(x, top, x + 1, bottom); + getInterestingRect(r, line); r.offset(mScrollX, mScrollY); if (requestRectangleOnScreen(r)) { @@ -5623,13 +5686,15 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * to the user. This will not move the cursor if it represents more than * one character (a selection range). This will only work if the * TextView contains spannable text; otherwise it will do nothing. + * + * @return True if the cursor was actually moved, false otherwise. */ public boolean moveCursorToVisibleOffset() { if (!(mText instanceof Spannable)) { return false; } - int start = Selection.getSelectionStart(mText); - int end = Selection.getSelectionEnd(mText); + int start = getSelectionStart(); + int end = getSelectionEnd(); if (start != end) { return false; } @@ -5639,7 +5704,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener int line = mLayout.getLineForOffset(start); final int top = mLayout.getLineTop(line); - final int bottom = mLayout.getLineTop(line+1); + final int bottom = mLayout.getLineTop(line + 1); final int vspace = mBottom - mTop - getExtendedPaddingTop() - getExtendedPaddingBottom(); int vslack = (bottom - top) / 2; if (vslack > vspace / 4) @@ -5685,23 +5750,36 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } - private void getInterestingRect(Rect r, int h, int top, int bottom, - int line) { - int paddingTop = getExtendedPaddingTop(); - if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { - paddingTop += getVerticalOffset(false); - } - top += paddingTop; - bottom += paddingTop; - h += getCompoundPaddingLeft(); + private void getInterestingRect(Rect r, int line) { + convertFromViewportToContentCoordinates(r); + + // Rectangle can can be expanded on first and last line to take + // padding into account. + // TODO Take left/right padding into account too? + if (line == 0) r.top -= getExtendedPaddingTop(); + if (line == mLayout.getLineCount() - 1) r.bottom += getExtendedPaddingBottom(); + } - if (line == 0) - top -= getExtendedPaddingTop(); - if (line == mLayout.getLineCount() - 1) - bottom += getExtendedPaddingBottom(); + private void convertFromViewportToContentCoordinates(Rect r) { + final int horizontalOffset = viewportToContentHorizontalOffset(); + r.left += horizontalOffset; + r.right += horizontalOffset; - r.set(h, top, h+1, bottom); - r.offset(-mScrollX, -mScrollY); + final int verticalOffset = viewportToContentVerticalOffset(); + r.top += verticalOffset; + r.bottom += verticalOffset; + } + + private int viewportToContentHorizontalOffset() { + return getCompoundPaddingLeft() - mScrollX; + } + + private int viewportToContentVerticalOffset() { + int offset = getExtendedPaddingTop() - mScrollY; + if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) { + offset += getVerticalOffset(false); + } + return offset; } @Override @@ -5729,7 +5807,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener /** * Convenience for {@link Selection#getSelectionStart}. */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "text") public int getSelectionStart() { return Selection.getSelectionStart(getText()); } @@ -5737,7 +5815,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener /** * Convenience for {@link Selection#getSelectionEnd}. */ - @ViewDebug.ExportedProperty + @ViewDebug.ExportedProperty(category = "text") public int getSelectionEnd() { return Selection.getSelectionEnd(getText()); } @@ -5746,7 +5824,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * Return true iff there is a selection inside this text view. */ public boolean hasSelection() { - return getSelectionStart() != getSelectionEnd(); + final int selectionStart = getSelectionStart(); + final int selectionEnd = getSelectionEnd(); + + return selectionStart >= 0 && selectionStart != selectionEnd; } /** @@ -5868,6 +5949,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } else if (mBlink != null) { mBlink.removeCallbacks(mBlink); } + + // InsertionPointCursorController depends on mCursorVisible + prepareCursorControllers(); } private boolean canMarquee() { @@ -5926,7 +6010,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private final WeakReference<TextView> mView; private byte mStatus = MARQUEE_STOPPED; - private float mScrollUnit; + private final float mScrollUnit; private float mMaxScroll; float mMaxFadeScroll; private float mGhostStart; @@ -5938,7 +6022,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener Marquee(TextView v) { final float density = v.getContext().getResources().getDisplayMetrics().density; - mScrollUnit = (MARQUEE_PIXELS_PER_SECOND * density) / (float) MARQUEE_RESOLUTION; + mScrollUnit = (MARQUEE_PIXELS_PER_SECOND * density) / MARQUEE_RESOLUTION; mView = new WeakReference<TextView>(v); } @@ -6171,6 +6255,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener sendOnTextChanged(buffer, start, before, after); onTextChanged(buffer, start, before, after); + hideControllers(); } /** @@ -6282,7 +6367,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } } else { - if (DEBUG_EXTRACT) Log.v(TAG, "Span change outside of batch: " + if (DEBUG_EXTRACT) Log.v(LOG_TAG, "Span change outside of batch: " + oldStart + "-" + oldEnd + "," + newStart + "-" + newEnd + what); ims.mContentChanged = true; @@ -6298,7 +6383,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener public void beforeTextChanged(CharSequence buffer, int start, int before, int after) { - if (DEBUG_EXTRACT) Log.v(TAG, "beforeTextChanged start=" + start + if (DEBUG_EXTRACT) Log.v(LOG_TAG, "beforeTextChanged start=" + start + " before=" + before + " after=" + after + ": " + buffer); if (AccessibilityManager.getInstance(mContext).isEnabled() @@ -6311,7 +6396,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener public void onTextChanged(CharSequence buffer, int start, int before, int after) { - if (DEBUG_EXTRACT) Log.v(TAG, "onTextChanged start=" + start + if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onTextChanged start=" + start + " before=" + before + " after=" + after + ": " + buffer); TextView.this.handleTextChanged(buffer, start, before, after); @@ -6324,7 +6409,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } public void afterTextChanged(Editable buffer) { - if (DEBUG_EXTRACT) Log.v(TAG, "afterTextChanged: " + buffer); + if (DEBUG_EXTRACT) Log.v(LOG_TAG, "afterTextChanged: " + buffer); TextView.this.sendAfterTextChanged(buffer); if (MetaKeyKeyListener.getMetaState(buffer, @@ -6335,19 +6420,19 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener public void onSpanChanged(Spannable buf, Object what, int s, int e, int st, int en) { - if (DEBUG_EXTRACT) Log.v(TAG, "onSpanChanged s=" + s + " e=" + e + if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanChanged s=" + s + " e=" + e + " st=" + st + " en=" + en + " what=" + what + ": " + buf); TextView.this.spanChange(buf, what, s, st, e, en); } public void onSpanAdded(Spannable buf, Object what, int s, int e) { - if (DEBUG_EXTRACT) Log.v(TAG, "onSpanAdded s=" + s + " e=" + e + if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanAdded s=" + s + " e=" + e + " what=" + what + ": " + buf); TextView.this.spanChange(buf, what, -1, s, -1, e); } public void onSpanRemoved(Spannable buf, Object what, int s, int e) { - if (DEBUG_EXTRACT) Log.v(TAG, "onSpanRemoved s=" + s + " e=" + e + if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanRemoved s=" + s + " e=" + e + " what=" + what + ": " + buf); TextView.this.spanChange(buf, what, s, -1, e, -1); } @@ -6406,13 +6491,18 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener mShowCursor = SystemClock.uptimeMillis(); ensureEndedBatchEdit(); - + if (focused) { int selStart = getSelectionStart(); int selEnd = getSelectionEnd(); if (!mFrozenWithFocus || (selStart < 0 || selEnd < 0)) { - boolean selMoved = mSelectionMoved; + // Has to be done before onTakeFocus, which can be overloaded. + if (mLastTouchOffset >= 0) { + // Can happen when a TextView is displayed after its content has been deleted. + mLastTouchOffset = Math.min(mLastTouchOffset, mText.length()); + Selection.setSelection((Spannable) mText, mLastTouchOffset); + } if (mMovement != null) { mMovement.onTakeFocus(this, (Spannable) mText, direction); @@ -6422,7 +6512,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener Selection.setSelection((Spannable) mText, 0, mText.length()); } - if (selMoved && selStart >= 0 && selEnd >= 0) { + // The DecorView does not have focus when the 'Done' ExtractEditText button is + // pressed. Since it is the ViewRoot's mView, it requests focus before + // ExtractEditText clears focus, which gives focus to the ExtractEditText. + // This special case ensure that we keep current selection in that case. + // It would be better to know why the DecorView does not have focus at that time. + if (((this instanceof ExtractEditText) || mSelectionMoved) && + selStart >= 0 && selEnd >= 0) { /* * Someone intentionally set the selection, so let them * do whatever it is that they wanted to do instead of @@ -6432,7 +6528,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * just setting the selection in theirs and we still * need to go through that path. */ - Selection.setSelection((Spannable) mText, selStart, selEnd); } mTouchFocusSelected = true; @@ -6457,13 +6552,21 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } // Don't leave us in the middle of a batch edit. onEndBatchEdit(); + + hideInsertionPointCursorController(); + if (this instanceof ExtractEditText) { + // terminateTextSelectionMode would remove selection, which we want to keep when + // ExtractEditText goes out of focus. + mIsInTextSelectionMode = false; + } else { + terminateTextSelectionMode(); + } } startStopMarquee(focused); if (mTransformation != null) { - mTransformation.onFocusChanged(this, mText, focused, direction, - previouslyFocusedRect); + mTransformation.onFocusChanged(this, mText, focused, direction, previouslyFocusedRect); } super.onFocusChanged(focused, direction, previouslyFocusedRect); @@ -6522,38 +6625,66 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } + private void onTapUpEvent(int prevStart, int prevEnd) { + final int start = getSelectionStart(); + final int end = getSelectionEnd(); + + if (start == end) { + if (start >= prevStart && start < prevEnd) { + // Restore previous selection + Selection.setSelection((Spannable)mText, prevStart, prevEnd); + // Tapping inside the selection displays the cut/copy/paste context menu. + showContextMenu(); + return; + } else { + // Tapping outside stops selection mode, if any + stopTextSelectionMode(); + + if (mInsertionPointCursorController != null) { + mInsertionPointCursorController.show(); + } + } + } + } + class CommitSelectionReceiver extends ResultReceiver { - int mNewStart; - int mNewEnd; + private final int mPrevStart, mPrevEnd; - CommitSelectionReceiver() { + public CommitSelectionReceiver(int prevStart, int prevEnd) { super(getHandler()); + mPrevStart = prevStart; + mPrevEnd = prevEnd; } + @Override protected void onReceiveResult(int resultCode, Bundle resultData) { - if (resultCode != InputMethodManager.RESULT_SHOWN) { + // If this tap was actually used to show the IMM, leave cursor or selection unchanged + // by restoring its previous position. + if (resultCode == InputMethodManager.RESULT_SHOWN) { final int len = mText.length(); - if (mNewStart > len) { - mNewStart = len; + int start = Math.min(len, mPrevStart); + int end = Math.min(len, mPrevEnd); + Selection.setSelection((Spannable)mText, start, end); + + if (hasSelection()) { + startTextSelectionMode(); + } else if (mInsertionPointCursorController != null) { + mInsertionPointCursorController.show(); } - if (mNewEnd > len) { - mNewEnd = len; - } - Selection.setSelection((Spannable)mText, mNewStart, mNewEnd); } } } @Override public boolean onTouchEvent(MotionEvent event) { - final int action = event.getAction(); + final int action = event.getActionMasked(); if (action == MotionEvent.ACTION_DOWN) { // Reset this state; it will be re-set if super.onTouchEvent // causes focus to move to the view. mTouchFocusSelected = false; mScrolled = false; } - + final boolean superResult = super.onTouchEvent(event); /* @@ -6567,43 +6698,40 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } if ((mMovement != null || onCheckIsTextEditor()) && mText instanceof Spannable && mLayout != null) { - + + if (mInsertionPointCursorController != null) { + mInsertionPointCursorController.onTouchEvent(event); + } + if (mSelectionModifierCursorController != null) { + mSelectionModifierCursorController.onTouchEvent(event); + } + boolean handled = false; - - int oldSelStart = Selection.getSelectionStart(mText); - int oldSelEnd = Selection.getSelectionEnd(mText); + + // Save previous selection, in case this event is used to show the IME. + int oldSelStart = getSelectionStart(); + int oldSelEnd = getSelectionEnd(); if (mMovement != null) { handled |= mMovement.onTouchEvent(this, (Spannable) mText, event); } - if (mText instanceof Editable && onCheckIsTextEditor()) { + if (isTextEditable()) { if (action == MotionEvent.ACTION_UP && isFocused() && !mScrolled) { InputMethodManager imm = (InputMethodManager) - getContext().getSystemService(Context.INPUT_METHOD_SERVICE); - - // This is going to be gross... if tapping on the text view - // causes the IME to be displayed, we don't want the selection - // to change. But the selection has already changed, and - // we won't know right away whether the IME is getting - // displayed, so... - - int newSelStart = Selection.getSelectionStart(mText); - int newSelEnd = Selection.getSelectionEnd(mText); + getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + CommitSelectionReceiver csr = null; - if (newSelStart != oldSelStart || newSelEnd != oldSelEnd) { - csr = new CommitSelectionReceiver(); - csr.mNewStart = newSelStart; - csr.mNewEnd = newSelEnd; - } - - if (imm.showSoftInput(this, 0, csr) && csr != null) { - // The IME might get shown -- revert to the old - // selection, and change to the new when we finally - // find out of it is okay. - Selection.setSelection((Spannable)mText, oldSelStart, oldSelEnd); - handled = true; + if (getSelectionStart() != oldSelStart || getSelectionEnd() != oldSelEnd || + didTouchFocusSelect()) { + csr = new CommitSelectionReceiver(oldSelStart, oldSelEnd); } + + handled |= imm.showSoftInput(this, 0, csr) && (csr != null); + + // Cannot be done by CommitSelectionReceiver, which might not always be called, + // for instance when dealing with an ExtractEditText. + onTapUpEvent(oldSelStart, oldSelEnd); } } @@ -6615,6 +6743,34 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return superResult; } + private void prepareCursorControllers() { + // TODO Add an extra android:cursorController flag to disable the controller? + if (mCursorVisible && mLayout != null) { + if (mInsertionPointCursorController == null) { + mInsertionPointCursorController = new InsertionPointCursorController(); + } + } else { + mInsertionPointCursorController = null; + } + + if (canSelectText() && mLayout != null) { + if (mSelectionModifierCursorController == null) { + mSelectionModifierCursorController = new SelectionModifierCursorController(); + } + } else { + // Stop selection mode if the controller becomes unavailable. + stopTextSelectionMode(); + mSelectionModifierCursorController = null; + } + } + + /** + * @return True iff this TextView contains a text that can be edited. + */ + private boolean isTextEditable() { + return mText instanceof Editable && onCheckIsTextEditor(); + } + /** * Returns true, only while processing a touch gesture, if the initial * touch down event caused focus to move to the text view and as a result @@ -6648,7 +6804,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } private static class Blink extends Handler implements Runnable { - private WeakReference<TextView> mView; + private final WeakReference<TextView> mView; private boolean mCancelled; public Blink(TextView v) { @@ -6665,8 +6821,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener TextView tv = mView.get(); if (tv != null && tv.isFocused()) { - int st = Selection.getSelectionStart(tv.mText); - int en = Selection.getSelectionEnd(tv.mText); + int st = tv.getSelectionStart(); + int en = tv.getSelectionEnd(); if (st == en && st >= 0 && en >= 0) { if (tv.mLayout != null) { @@ -6847,21 +7003,16 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } private boolean canSelectAll() { - if (mText instanceof Spannable && mText.length() != 0 && - mMovement != null && mMovement.canSelectArbitrarily()) { - return true; - } - - return false; + return canSelectText() && mText.length() != 0; } private boolean canSelectText() { - if (mText instanceof Spannable && mText.length() != 0 && - mMovement != null && mMovement.canSelectArbitrarily()) { - return true; - } - - return false; + // prepareCursorController() relies on this method. + // If you change this condition, make sure prepareCursorController is called anywhere + // the value of this condition might be changed. + return (mText instanceof Spannable && + mMovement != null && + mMovement.canSelectArbitrarily()); } private boolean canCut() { @@ -6869,7 +7020,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return false; } - if (mText.length() > 0 && getSelectionStart() >= 0) { + if (mText.length() > 0 && hasSelection()) { if (mText instanceof Editable && mInput != null) { return true; } @@ -6883,7 +7034,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return false; } - if (mText.length() > 0 && getSelectionStart() >= 0) { + if (mText.length() > 0 && hasSelection()) { return true; } @@ -6891,23 +7042,23 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } private boolean canPaste() { - if (mText instanceof Editable && mInput != null && - getSelectionStart() >= 0 && getSelectionEnd() >= 0) { - ClipboardManager clip = (ClipboardManager)getContext() - .getSystemService(Context.CLIPBOARD_SERVICE); - if (clip.hasText()) { - return true; - } - } - - return false; + return (mText instanceof Editable && + mInput != null && + getSelectionStart() >= 0 && + getSelectionEnd() >= 0 && + ((ClipboardManager)getContext().getSystemService(Context.CLIPBOARD_SERVICE)). + hasText()); } /** - * Returns a word to add to the dictionary from the context menu, - * or null if there is no cursor or no word at the cursor. + * Returns the offsets delimiting the 'word' located at position offset. + * + * @param offset An offset in the text. + * @return The offsets for the start and end of the word located at <code>offset</code>. + * The two ints offsets are packed in a long, with the starting offset shifted by 32 bits. + * Returns a negative value if no valid word was found. */ - private String getWordForDictionary() { + private long getWordLimitsAt(int offset) { /* * Quick return if the input type is one where adding words * to the dictionary doesn't make any sense. @@ -6916,7 +7067,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (klass == InputType.TYPE_CLASS_NUMBER || klass == InputType.TYPE_CLASS_PHONE || klass == InputType.TYPE_CLASS_DATETIME) { - return null; + return -1; } int variation = mInputType & InputType.TYPE_MASK_VARIATION; @@ -6925,17 +7076,17 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener variation == InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD || variation == InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS || variation == InputType.TYPE_TEXT_VARIATION_FILTER) { - return null; + return -1; } - int end = getSelectionEnd(); + int len = mText.length(); + int end = Math.min(offset, len); if (end < 0) { - return null; + return -1; } int start = end; - int len = mText.length(); for (; start > 0; start--) { char c = mTransformed.charAt(start - 1); @@ -6965,6 +7116,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } + if (start == end) { + return -1; + } + + if (end - start > 48) { + return -1; + } + boolean hasLetter = false; for (int i = start; i < end; i++) { if (Character.isLetter(mTransformed.charAt(i))) { @@ -6972,21 +7131,105 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener break; } } + if (!hasLetter) { - return null; + return -1; } - if (start == end) { - return null; + // Two ints packed in a long + return (((long) start) << 32) | end; + } + + private void selectCurrentWord() { + // In case selection mode is started after an orientation change or after a select all, + // use the current selection instead of creating one + if (hasSelection()) { + return; } - if (end - start > 48) { - return null; + int selectionStart, selectionEnd; + + // selectionModifierCursorController is not null at that point + SelectionModifierCursorController selectionModifierCursorController = + ((SelectionModifierCursorController) mSelectionModifierCursorController); + int minOffset = selectionModifierCursorController.getMinTouchOffset(); + int maxOffset = selectionModifierCursorController.getMaxTouchOffset(); + + if (minOffset == maxOffset) { + int offset = Math.max(0, Math.min(minOffset, mTransformed.length())); + + // Tolerance, number of charaters around tapped position + final int range = 1; + final int max = mTransformed.length() - 1; + + // 'Smart' word selection: detect position between words + for (int i = -range; i <= range; i++) { + int index = offset + i; + if (index >= 0 && index <= max) { + if (Character.isSpaceChar(mTransformed.charAt(index))) { + // Select current space + selectionStart = index; + selectionEnd = selectionStart + 1; + + // Extend selection to maximum space range + while (selectionStart > 0 && + Character.isSpaceChar(mTransformed.charAt(selectionStart - 1))) { + selectionStart--; + } + while (selectionEnd < max && + Character.isSpaceChar(mTransformed.charAt(selectionEnd))) { + selectionEnd++; + } + + Selection.setSelection((Spannable) mText, selectionStart, selectionEnd); + return; + } + } + } + + // 'Smart' word selection: detect position at beginning or end of text. + if (offset <= range) { + Selection.setSelection((Spannable) mText, 0, 0); + return; + } + if (offset >= (max - range)) { + Selection.setSelection((Spannable) mText, max + 1, max + 1); + return; + } } - return TextUtils.substring(mTransformed, start, end); + long wordLimits = getWordLimitsAt(minOffset); + if (wordLimits >= 0) { + selectionStart = (int) (wordLimits >>> 32); + } else { + selectionStart = Math.max(minOffset - 5, 0); + } + + wordLimits = getWordLimitsAt(maxOffset); + if (wordLimits >= 0) { + selectionEnd = (int) (wordLimits & 0x00000000FFFFFFFFL); + } else { + selectionEnd = Math.min(maxOffset + 5, mText.length()); + } + + Selection.setSelection((Spannable) mText, selectionStart, selectionEnd); } + + private String getWordForDictionary() { + if (mLastTouchOffset < 0) { + return null; + } + long wordLimits = getWordLimitsAt(mLastTouchOffset); + if (wordLimits >= 0) { + int start = (int) (wordLimits >>> 32); + int end = (int) (wordLimits & 0x00000000FFFFFFFFL); + return mTransformed.subSequence(start, end).toString(); + } else { + return null; + } + } + @Override public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { if (!isShown()) { @@ -7028,114 +7271,98 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener super.onCreateContextMenu(menu); boolean added = false; - if (!isFocused()) { - if (isFocusable() && mInput != null) { - if (canCopy()) { - MenuHandler handler = new MenuHandler(); - int name = com.android.internal.R.string.copyAll; - - menu.add(0, ID_COPY, 0, name). - setOnMenuItemClickListener(handler). - setAlphabeticShortcut('c'); - menu.setHeaderTitle(com.android.internal.R.string. - editTextMenuTitle); - } + if (mIsInTextSelectionMode) { + MenuHandler handler = new MenuHandler(); + + if (canCut()) { + menu.add(0, ID_CUT, 0, com.android.internal.R.string.cut). + setOnMenuItemClickListener(handler). + setAlphabeticShortcut('x'); + added = true; } - return; - } - - MenuHandler handler = new MenuHandler(); - - if (canSelectAll()) { - menu.add(0, ID_SELECT_ALL, 0, - com.android.internal.R.string.selectAll). - setOnMenuItemClickListener(handler). - setAlphabeticShortcut('a'); - added = true; - } - - boolean selection = getSelectionStart() != getSelectionEnd(); - - if (canSelectText()) { - if (MetaKeyKeyListener.getMetaState(mText, MetaKeyKeyListener.META_SELECTING) != 0) { - menu.add(0, ID_STOP_SELECTING_TEXT, 0, - com.android.internal.R.string.stopSelectingText). - setOnMenuItemClickListener(handler); - added = true; - } else { - menu.add(0, ID_START_SELECTING_TEXT, 0, - com.android.internal.R.string.selectText). - setOnMenuItemClickListener(handler); + if (canCopy()) { + menu.add(0, ID_COPY, 0, com.android.internal.R.string.copy). + setOnMenuItemClickListener(handler). + setAlphabeticShortcut('c'); added = true; } - } - if (canCut()) { - int name; - if (selection) { - name = com.android.internal.R.string.cut; - } else { - name = com.android.internal.R.string.cutAll; + if (canPaste()) { + menu.add(0, ID_PASTE, 0, com.android.internal.R.string.paste). + setOnMenuItemClickListener(handler). + setAlphabeticShortcut('v'); + added = true; } + } else { + /* + if (!isFocused()) { + if (isFocusable() && mInput != null) { + if (canCopy()) { + MenuHandler handler = new MenuHandler(); + menu.add(0, ID_COPY, 0, com.android.internal.R.string.copy). + setOnMenuItemClickListener(handler). + setAlphabeticShortcut('c'); + menu.setHeaderTitle(com.android.internal.R.string.editTextMenuTitle); + } + } - menu.add(0, ID_CUT, 0, name). - setOnMenuItemClickListener(handler). - setAlphabeticShortcut('x'); - added = true; - } - - if (canCopy()) { - int name; - if (selection) { - name = com.android.internal.R.string.copy; - } else { - name = com.android.internal.R.string.copyAll; + //return; } + */ + MenuHandler handler = new MenuHandler(); - menu.add(0, ID_COPY, 0, name). - setOnMenuItemClickListener(handler). - setAlphabeticShortcut('c'); - added = true; - } - - if (canPaste()) { - menu.add(0, ID_PASTE, 0, com.android.internal.R.string.paste). - setOnMenuItemClickListener(handler). - setAlphabeticShortcut('v'); - added = true; - } + if (canSelectText()) { + menu.add(0, ID_START_SELECTING_TEXT, 0, com.android.internal.R.string.selectText). + setOnMenuItemClickListener(handler); + added = true; + } + + if (canSelectAll()) { + menu.add(0, ID_SELECT_ALL, 0, com.android.internal.R.string.selectAll). + setOnMenuItemClickListener(handler). + setAlphabeticShortcut('a'); + added = true; + } - if (mText instanceof Spanned) { - int selStart = getSelectionStart(); - int selEnd = getSelectionEnd(); + if (mText instanceof Spanned) { + int selStart = getSelectionStart(); + int selEnd = getSelectionEnd(); - int min = Math.min(selStart, selEnd); - int max = Math.max(selStart, selEnd); + int min = Math.min(selStart, selEnd); + int max = Math.max(selStart, selEnd); - URLSpan[] urls = ((Spanned) mText).getSpans(min, max, - URLSpan.class); - if (urls.length == 1) { - menu.add(0, ID_COPY_URL, 0, - com.android.internal.R.string.copyUrl). - setOnMenuItemClickListener(handler); + URLSpan[] urls = ((Spanned) mText).getSpans(min, max, + URLSpan.class); + if (urls.length == 1) { + menu.add(0, ID_COPY_URL, 0, com.android.internal.R.string.copyUrl). + setOnMenuItemClickListener(handler); + added = true; + } + } + + // Paste location is too imprecise. Only allow on empty text fields. + if (canPaste() && textIsOnlySpaces()) { + menu.add(0, ID_PASTE, 0, com.android.internal.R.string.paste). + setOnMenuItemClickListener(handler). + setAlphabeticShortcut('v'); added = true; } - } - if (isInputMethodTarget()) { - menu.add(1, ID_SWITCH_INPUT_METHOD, 0, com.android.internal.R.string.inputMethod). - setOnMenuItemClickListener(handler); - added = true; - } + if (isInputMethodTarget()) { + menu.add(1, ID_SWITCH_INPUT_METHOD, 0, com.android.internal.R.string.inputMethod). + setOnMenuItemClickListener(handler); + added = true; + } - String word = getWordForDictionary(); - if (word != null) { - menu.add(1, ID_ADD_TO_DICTIONARY, 0, + String word = getWordForDictionary(); + if (word != null) { + menu.add(1, ID_ADD_TO_DICTIONARY, 0, getContext().getString(com.android.internal.R.string.addToDictionary, word)). - setOnMenuItemClickListener(handler); - added = true; + setOnMenuItemClickListener(handler); + added = true; + } } if (added) { @@ -7143,6 +7370,16 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } + private boolean textIsOnlySpaces() { + final int length = mTransformed.length(); + for (int i = 0; i < length; i++) { + if (!Character.isSpaceChar(mTransformed.charAt(i))) { + return false; + } + } + return true; + } + /** * Returns whether this text view is a current input method target. The * default implementation just checks with {@link InputMethodManager}. @@ -7152,9 +7389,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return imm != null && imm.isActive(this); } + // Context menu entries private static final int ID_SELECT_ALL = android.R.id.selectAll; private static final int ID_START_SELECTING_TEXT = android.R.id.startSelectingText; - private static final int ID_STOP_SELECTING_TEXT = android.R.id.stopSelectingText; private static final int ID_CUT = android.R.id.cut; private static final int ID_COPY = android.R.id.copy; private static final int ID_PASTE = android.R.id.paste; @@ -7171,28 +7408,21 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener /** * Called when a context menu option for the text view is selected. Currently * this will be one of: {@link android.R.id#selectAll}, - * {@link android.R.id#startSelectingText}, {@link android.R.id#stopSelectingText}, + * {@link android.R.id#startSelectingText}, * {@link android.R.id#cut}, {@link android.R.id#copy}, * {@link android.R.id#paste}, {@link android.R.id#copyUrl}, * or {@link android.R.id#switchInputMethod}. */ public boolean onTextContextMenuItem(int id) { - int selStart = getSelectionStart(); - int selEnd = getSelectionEnd(); + int min = 0; + int max = mText.length(); - if (!isFocused()) { - selStart = 0; - selEnd = mText.length(); - } - - int min = Math.min(selStart, selEnd); - int max = Math.max(selStart, selEnd); + if (isFocused()) { + final int selStart = getSelectionStart(); + final int selEnd = getSelectionEnd(); - if (min < 0) { - min = 0; - } - if (max < 0) { - max = 0; + min = Math.max(0, Math.min(selStart, selEnd)); + max = Math.max(0, Math.max(selStart, selEnd)); } ClipboardManager clip = (ClipboardManager)getContext() @@ -7200,63 +7430,70 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener switch (id) { case ID_SELECT_ALL: - Selection.setSelection((Spannable) mText, 0, - mText.length()); + Selection.setSelection((Spannable) mText, 0, mText.length()); + startTextSelectionMode(); return true; case ID_START_SELECTING_TEXT: - MetaKeyKeyListener.startSelecting(this, (Spannable) mText); + startTextSelectionMode(); return true; - case ID_STOP_SELECTING_TEXT: - MetaKeyKeyListener.stopSelecting(this, (Spannable) mText); - Selection.setSelection((Spannable) mText, getSelectionEnd()); - return true; - - case ID_CUT: - MetaKeyKeyListener.stopSelecting(this, (Spannable) mText); - - if (min == max) { - min = 0; - max = mText.length(); - } - + case ID_CUT: clip.setText(mTransformed.subSequence(min, max)); ((Editable) mText).delete(min, max); + stopTextSelectionMode(); return true; case ID_COPY: - MetaKeyKeyListener.stopSelecting(this, (Spannable) mText); - - if (min == max) { - min = 0; - max = mText.length(); - } - clip.setText(mTransformed.subSequence(min, max)); + stopTextSelectionMode(); return true; case ID_PASTE: - MetaKeyKeyListener.stopSelecting(this, (Spannable) mText); - CharSequence paste = clip.getText(); - if (paste != null) { + if (paste != null && paste.length() > 0) { + // Paste adds/removes spaces before or after insertion as needed. + + if (Character.isSpaceChar(paste.charAt(0))) { + if (min > 0 && Character.isSpaceChar(mTransformed.charAt(min - 1))) { + // Two spaces at beginning of paste: remove one + ((Editable) mText).replace(min - 1, min, ""); + min = min - 1; + max = max - 1; + } + } else { + if (min > 0 && !Character.isSpaceChar(mTransformed.charAt(min - 1))) { + // No space at beginning of paste: add one + ((Editable) mText).replace(min, min, " "); + min = min + 1; + max = max + 1; + } + } + + if (Character.isSpaceChar(paste.charAt(paste.length() - 1))) { + if (max < mText.length() && Character.isSpaceChar(mTransformed.charAt(max))) { + // Two spaces at end of paste: remove one + ((Editable) mText).replace(max, max + 1, ""); + } + } else { + if (max < mText.length() && !Character.isSpaceChar(mTransformed.charAt(max))) { + // No space at end of paste: add one + ((Editable) mText).replace(max, max, " "); + } + } + Selection.setSelection((Spannable) mText, max); ((Editable) mText).replace(min, max, paste); + stopTextSelectionMode(); } - return true; case ID_COPY_URL: - MetaKeyKeyListener.stopSelecting(this, (Spannable) mText); - - URLSpan[] urls = ((Spanned) mText).getSpans(min, max, - URLSpan.class); + URLSpan[] urls = ((Spanned) mText).getSpans(min, max, URLSpan.class); if (urls.length == 1) { clip.setText(urls[0].getURL()); } - return true; case ID_SWITCH_INPUT_METHOD: @@ -7275,13 +7512,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener i.setFlags(i.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK); getContext().startActivity(i); } - return true; } return false; } + @Override public boolean performLongClick() { if (super.performLongClick()) { mEatTouchRelease = true; @@ -7291,6 +7528,618 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return false; } + private void startTextSelectionMode() { + if (!mIsInTextSelectionMode) { + if (mSelectionModifierCursorController == null) { + Log.w(LOG_TAG, "TextView has no selection controller. Action mode cancelled."); + return; + } + + if (!requestFocus()) { + return; + } + + selectCurrentWord(); + mSelectionModifierCursorController.show(); + mIsInTextSelectionMode = true; + } + } + + /** + * Same as {@link #stopTextSelectionMode()}, except that there is no cursor controller + * fade out animation. Needed since the drawable and their alpha values are shared by all + * TextViews. Switching from one TextView to another would fade the cursor controllers in the + * new one otherwise. + */ + private void terminateTextSelectionMode() { + stopTextSelectionMode(); + if (mSelectionModifierCursorController != null) { + SelectionModifierCursorController selectionModifierCursorController = + (SelectionModifierCursorController) mSelectionModifierCursorController; + selectionModifierCursorController.cancelFadeOutAnimation(); + } + } + + private void stopTextSelectionMode() { + if (mIsInTextSelectionMode) { + Selection.setSelection((Spannable) mText, getSelectionEnd()); + if (mSelectionModifierCursorController != null) { + mSelectionModifierCursorController.hide(); + } + + mIsInTextSelectionMode = false; + } + } + + /** + * A CursorController instance can be used to control a cursor in the text. + * + * It can be passed to an {@link ArrowKeyMovementMethod} which can intercepts events + * and send them to this object instead of the cursor. + */ + public interface CursorController { + /* Cursor fade-out animation duration, in milliseconds. */ + static final int FADE_OUT_DURATION = 400; + + /** + * Makes the cursor controller visible on screen. Will be drawn by {@link #draw(Canvas)}. + * See also {@link #hide()}. + */ + public void show(); + + /** + * Hide the cursor controller from screen. + * See also {@link #show()}. + */ + public void hide(); + + /** + * Update the controller's position. + */ + public void updatePosition(int x, int y); + + /** + * The controller and the cursor's positions can be link by a fixed offset, + * computed when the controller is touched, and then maintained as it moves + * @return Horizontal offset between the controller and the cursor. + */ + public float getOffsetX(); + + /** + * @return Vertical offset between the controller and the cursor. + */ + public float getOffsetY(); + + /** + * This method is called by {@link #onTouchEvent(MotionEvent)} and gives the controller + * a chance to become active and/or visible. + * @param event The touch event + */ + public void onTouchEvent(MotionEvent event); + + /** + * Draws a visual representation of the controller on the canvas. + * + * Called at the end of {@link #draw(Canvas)}, in the content coordinates system. + * @param canvas The Canvas used by this TextView. + */ + public void draw(Canvas canvas); + } + + private class Handle { + Drawable mDrawable; + // Vertical extension of the touch region + int mTopExtension, mBottomExtension; + // Position of the virtual finger position on screen + int mHotSpotVerticalPosition; + + Handle(Drawable drawable) { + mDrawable = drawable; + } + + void positionAtCursor(final int offset, boolean bottom) { + final int drawableWidth = mDrawable.getIntrinsicWidth(); + final int drawableHeight = mDrawable.getIntrinsicHeight(); + final int line = mLayout.getLineForOffset(offset); + final int lineTop = mLayout.getLineTop(line); + final int lineBottom = mLayout.getLineBottom(line); + + mHotSpotVerticalPosition = lineTop; + + final Rect bounds = sCursorControllerTempRect; + bounds.left = (int) (mLayout.getPrimaryHorizontal(offset) - drawableWidth / 2.0) + + mScrollX; + bounds.top = (bottom ? lineBottom : lineTop) - drawableHeight / 2 + mScrollY; + + mTopExtension = bottom ? 0 : drawableHeight / 2; + mBottomExtension = drawableHeight; + + // Extend touch region up when editing the last line of text (or a single line) so that + // it is easier to grab. + if (line == mLayout.getLineCount() - 1) { + mTopExtension = (lineBottom - lineTop) - drawableHeight / 2; + } + + bounds.right = bounds.left + drawableWidth; + bounds.bottom = bounds.top + drawableHeight; + + convertFromViewportToContentCoordinates(bounds); + mDrawable.setBounds(bounds); + postInvalidate(); + } + + boolean hasFingerOn(float x, float y) { + // Simulate a 'fat finger' to ease grabbing of the controller. + // Expands according to controller image size instead of using dip distance. + // Assumes controller imager has a sensible size, proportionnal to screen density. + final int drawableWidth = mDrawable.getIntrinsicWidth(); + final Rect fingerRect = sCursorControllerTempRect; + fingerRect.set((int) (x - drawableWidth / 2.0), + (int) (y - mBottomExtension), + (int) (x + drawableWidth / 2.0), + (int) (y + mTopExtension)); + fingerRect.offset(mScrollX, mScrollY); + return Rect.intersects(mDrawable.getBounds(), fingerRect); + } + + void postInvalidate() { + final Rect bounds = mDrawable.getBounds(); + TextView.this.postInvalidate(bounds.left, bounds.top, bounds.right, bounds.bottom); + } + + void postInvalidateDelayed(long delay) { + final Rect bounds = mDrawable.getBounds(); + TextView.this.postInvalidateDelayed(delay, bounds.left, bounds.top, + bounds.right, bounds.bottom); + } + } + + class InsertionPointCursorController implements CursorController { + private static final int DELAY_BEFORE_FADE_OUT = 2100; + + // Whether or not the cursor control is currently visible + private boolean mIsVisible = false; + // Starting time of the fade timer + private long mFadeOutTimerStart; + // The cursor controller image + private final Handle mHandle; + // Used to detect a tap (vs drag) on the controller + private long mOnDownTimerStart; + // Offset between finger hot point on cursor controller and actual cursor + private float mOffsetX, mOffsetY; + + InsertionPointCursorController() { + Resources res = mContext.getResources(); + mHandle = new Handle(res.getDrawable(com.android.internal.R.drawable.text_select_handle)); + } + + public void show() { + updateDrawablePosition(); + // Has to be done after updateDrawablePosition, so that previous position invalidate + // in only done if necessary. + mIsVisible = true; + } + + public void hide() { + if (mIsVisible) { + long time = System.currentTimeMillis(); + // Start fading out, only if not already in progress + if (time - mFadeOutTimerStart < DELAY_BEFORE_FADE_OUT) { + mFadeOutTimerStart = time - DELAY_BEFORE_FADE_OUT; + mHandle.postInvalidate(); + } + } + } + + public void draw(Canvas canvas) { + if (mIsVisible) { + int time = (int) (System.currentTimeMillis() - mFadeOutTimerStart); + if (time <= DELAY_BEFORE_FADE_OUT) { + mHandle.postInvalidateDelayed(DELAY_BEFORE_FADE_OUT - time); + } else { + time -= DELAY_BEFORE_FADE_OUT; + if (time <= FADE_OUT_DURATION) { + final int alpha = (int) + ((255.0 * (FADE_OUT_DURATION - time)) / FADE_OUT_DURATION); + mHandle.mDrawable.setAlpha(alpha); + mHandle.postInvalidateDelayed(30); + } else { + mHandle.mDrawable.setAlpha(0); + mIsVisible = false; + } + } + mHandle.mDrawable.draw(canvas); + } + } + + public void updatePosition(int x, int y) { + final int previousOffset = getSelectionStart(); + int offset = getHysteresisOffset(x, y, previousOffset); + + if (offset != previousOffset) { + Selection.setSelection((Spannable) mText, offset); + updateDrawablePosition(); + } + } + + private void updateDrawablePosition() { + if (mIsVisible) { + // Clear previous cursor controller before bounds are updated + mHandle.postInvalidate(); + } + + final int offset = getSelectionStart(); + + if (offset < 0) { + // Should never happen, safety check. + Log.w(LOG_TAG, "Update cursor controller position called with no cursor"); + mIsVisible = false; + return; + } + + mHandle.positionAtCursor(offset, true); + + mFadeOutTimerStart = System.currentTimeMillis(); + mHandle.mDrawable.setAlpha(255); + } + + public void onTouchEvent(MotionEvent event) { + if (isFocused() && isTextEditable() && mIsVisible) { + switch (event.getActionMasked()) { + case MotionEvent.ACTION_DOWN : { + final float x = event.getX(); + final float y = event.getY(); + + if (mHandle.hasFingerOn(x, y)) { + show(); + + if (mMovement instanceof ArrowKeyMovementMethod) { + ((ArrowKeyMovementMethod)mMovement).setCursorController(this); + } + + if (mParent != null) { + // Prevent possible scrollView parent from scrolling, so that + // we can use auto-scrolling. + mParent.requestDisallowInterceptTouchEvent(true); + } + + final Rect bounds = mHandle.mDrawable.getBounds(); + mOffsetX = (bounds.left + bounds.right) / 2.0f - x; + mOffsetY = mHandle.mHotSpotVerticalPosition - y; + + mOffsetX += viewportToContentHorizontalOffset(); + mOffsetY += viewportToContentVerticalOffset(); + + mOnDownTimerStart = event.getEventTime(); + } + break; + } + + case MotionEvent.ACTION_UP : { + int time = (int) (event.getEventTime() - mOnDownTimerStart); + + if (time <= ViewConfiguration.getTapTimeout()) { + // A tap on the controller (not a drag) will move the cursor + int offset = getOffset((int) event.getX(), (int) event.getY()); + Selection.setSelection((Spannable) mText, offset); + + // Modified by cancelLongPress and prevents the cursor from changing + mScrolled = false; + } + break; + } + } + } + } + + public float getOffsetX() { + return mOffsetX; + } + + public float getOffsetY() { + return mOffsetY; + } + } + + class SelectionModifierCursorController implements CursorController { + // Whether or not the selection controls are currently visible + private boolean mIsVisible = false; + // Whether that start or the end of selection controller is dragged + private boolean mStartIsDragged = false; + // Starting time of the fade timer + private long mFadeOutTimerStart; + // Used to detect a tap (vs drag) on the controller + private long mOnDownTimerStart; + // The cursor controller images + private final Handle mStartHandle, mEndHandle; + // Offset between finger hot point on active cursor controller and actual cursor + private float mOffsetX, mOffsetY; + // The offsets of that last touch down event. Remembered to start selection there. + private int mMinTouchOffset, mMaxTouchOffset; + + SelectionModifierCursorController() { + Resources res = mContext.getResources(); + mStartHandle = new Handle(res.getDrawable(com.android.internal.R.drawable.text_select_handle)); + mEndHandle = new Handle(res.getDrawable(com.android.internal.R.drawable.text_select_handle)); + } + + public void show() { + updateDrawablesPositions(); + // Has to be done after updateDrawablePositions, so that previous position invalidate + // in only done if necessary. + mIsVisible = true; + mFadeOutTimerStart = -1; + hideInsertionPointCursorController(); + } + + public void hide() { + if (mIsVisible && (mFadeOutTimerStart < 0)) { + mFadeOutTimerStart = System.currentTimeMillis(); + mStartHandle.postInvalidate(); + mEndHandle.postInvalidate(); + } + } + + public void cancelFadeOutAnimation() { + mIsVisible = false; + mStartHandle.postInvalidate(); + mEndHandle.postInvalidate(); + } + + public void draw(Canvas canvas) { + if (mIsVisible) { + if (mFadeOutTimerStart >= 0) { + int time = (int) (System.currentTimeMillis() - mFadeOutTimerStart); + if (time <= FADE_OUT_DURATION) { + final int alpha = 255 * (FADE_OUT_DURATION - time) / FADE_OUT_DURATION; + mStartHandle.mDrawable.setAlpha(alpha); + mEndHandle.mDrawable.setAlpha(alpha); + mStartHandle.postInvalidateDelayed(30); + mEndHandle.postInvalidateDelayed(30); + } else { + mStartHandle.mDrawable.setAlpha(0); + mEndHandle.mDrawable.setAlpha(0); + mIsVisible = false; + } + } + mStartHandle.mDrawable.draw(canvas); + mEndHandle.mDrawable.draw(canvas); + } + } + + public void updatePosition(int x, int y) { + int selectionStart = getSelectionStart(); + int selectionEnd = getSelectionEnd(); + + final int previousOffset = mStartIsDragged ? selectionStart : selectionEnd; + int offset = getHysteresisOffset(x, y, previousOffset); + + // Handle the case where start and end are swapped, making sure start <= end + if (mStartIsDragged) { + if (offset <= selectionEnd) { + if (selectionStart == offset) { + return; // no change, no need to redraw; + } + selectionStart = offset; + } else { + selectionStart = selectionEnd; + selectionEnd = offset; + mStartIsDragged = false; + } + } else { + if (offset >= selectionStart) { + if (selectionEnd == offset) { + return; // no change, no need to redraw; + } + selectionEnd = offset; + } else { + selectionEnd = selectionStart; + selectionStart = offset; + mStartIsDragged = true; + } + } + + Selection.setSelection((Spannable) mText, selectionStart, selectionEnd); + updateDrawablesPositions(); + } + + private void updateDrawablesPositions() { + if (mIsVisible) { + // Clear previous cursor controller before bounds are updated + mStartHandle.postInvalidate(); + mEndHandle.postInvalidate(); + } + + final int selectionStart = getSelectionStart(); + final int selectionEnd = getSelectionEnd(); + + if ((selectionStart < 0) || (selectionEnd < 0)) { + // Should never happen, safety check. + Log.w(LOG_TAG, "Update selection controller position called with no cursor"); + mIsVisible = false; + return; + } + + boolean oneLineSelection = mLayout.getLineForOffset(selectionStart) == + mLayout.getLineForOffset(selectionEnd); + mStartHandle.positionAtCursor(selectionStart, oneLineSelection); + mEndHandle.positionAtCursor(selectionEnd, true); + + mStartHandle.mDrawable.setAlpha(255); + mEndHandle.mDrawable.setAlpha(255); + } + + public void onTouchEvent(MotionEvent event) { + if (isTextEditable()) { + switch (event.getActionMasked()) { + case MotionEvent.ACTION_DOWN: + final int x = (int) event.getX(); + final int y = (int) event.getY(); + + // Remember finger down position, to be able to start selection from there + mMinTouchOffset = mMaxTouchOffset = mLastTouchOffset = getOffset(x, y); + + if (mIsVisible) { + if (mMovement instanceof ArrowKeyMovementMethod) { + boolean isOnStart = mStartHandle.hasFingerOn(x, y); + boolean isOnEnd = mEndHandle.hasFingerOn(x, y); + if (isOnStart || isOnEnd) { + if (mParent != null) { + // Prevent possible scrollView parent from scrolling, so + // that we can use auto-scrolling. + mParent.requestDisallowInterceptTouchEvent(true); + } + + // In case both controllers are under finger (very small + // selection region), arbitrarily pick end controller. + mStartIsDragged = !isOnEnd; + final Handle draggedHandle = + mStartIsDragged ? mStartHandle : mEndHandle; + final Rect bounds = draggedHandle.mDrawable.getBounds(); + mOffsetX = (bounds.left + bounds.right) / 2.0f - x; + mOffsetY = draggedHandle.mHotSpotVerticalPosition - y; + + mOffsetX += viewportToContentHorizontalOffset(); + mOffsetY += viewportToContentVerticalOffset(); + + mOnDownTimerStart = event.getEventTime(); + ((ArrowKeyMovementMethod)mMovement).setCursorController(this); + } + } + } + break; + + case MotionEvent.ACTION_UP: + int time = (int) (event.getEventTime() - mOnDownTimerStart); + + if (time <= ViewConfiguration.getTapTimeout()) { + // A tap on the controller (not a drag) opens the contextual Copy menu + showContextMenu(); + } + break; + + case MotionEvent.ACTION_POINTER_DOWN: + case MotionEvent.ACTION_POINTER_UP: + // Handle multi-point gestures. Keep min and max offset positions. + // Only activated for devices that correctly handle multi-touch. + if (mContext.getPackageManager().hasSystemFeature( + PackageManager.FEATURE_TOUCHSCREEN_MULTITOUCH_DISTINCT)) { + updateMinAndMaxOffsets(event); + } + break; + } + } + } + + /** + * @param event + */ + private void updateMinAndMaxOffsets(MotionEvent event) { + int pointerCount = event.getPointerCount(); + for (int index = 0; index < pointerCount; index++) { + final int x = (int) event.getX(index); + final int y = (int) event.getY(index); + int offset = getOffset(x, y); + if (offset < mMinTouchOffset) mMinTouchOffset = offset; + if (offset > mMaxTouchOffset) mMaxTouchOffset = offset; + } + } + + public int getMinTouchOffset() { + return mMinTouchOffset; + } + + public int getMaxTouchOffset() { + return mMaxTouchOffset; + } + + public float getOffsetX() { + return mOffsetX; + } + + public float getOffsetY() { + return mOffsetY; + } + + /** + * @return true iff this controller is currently used to move the selection start. + */ + public boolean isSelectionStartDragged() { + return mIsVisible && mStartIsDragged; + } + } + + private void hideInsertionPointCursorController() { + if (mInsertionPointCursorController != null) { + mInsertionPointCursorController.hide(); + } + } + + private void hideControllers() { + hideInsertionPointCursorController(); + stopTextSelectionMode(); + } + + private int getOffsetForHorizontal(int line, int x) { + x -= getTotalPaddingLeft(); + // Clamp the position to inside of the view. + x = Math.max(0, x); + x = Math.min(getWidth() - getTotalPaddingRight() - 1, x); + x += getScrollX(); + return getLayout().getOffsetForHorizontal(line, x); + } + + /** + * Get the offset character closest to the specified absolute position. + * + * @param x The horizontal absolute position of a point on screen + * @param y The vertical absolute position of a point on screen + * @return the character offset for the character whose position is closest to the specified + * position. Returns -1 if there is no layout. + * + * @hide + */ + public int getOffset(int x, int y) { + if (getLayout() == null) return -1; + + y -= getTotalPaddingTop(); + // Clamp the position to inside of the view. + y = Math.max(0, y); + y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y); + y += getScrollY(); + + final int line = getLayout().getLineForVertical(y); + final int offset = getOffsetForHorizontal(line, x); + return offset; + } + + int getHysteresisOffset(int x, int y, int previousOffset) { + final Layout layout = getLayout(); + if (layout == null) return -1; + + y -= getTotalPaddingTop(); + // Clamp the position to inside of the view. + y = Math.max(0, y); + y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y); + y += getScrollY(); + + int line = getLayout().getLineForVertical(y); + + final int previousLine = layout.getLineForOffset(previousOffset); + final int previousLineTop = layout.getLineTop(previousLine); + final int previousLineBottom = layout.getLineBottom(previousLine); + final int hysteresisThreshold = (previousLineBottom - previousLineTop) / 2; + + // If new line is just before or after previous line and y position is less than + // hysteresisThreshold away from previous line, keep cursor on previous line. + if (((line == previousLine + 1) && ((y - previousLineBottom) < hysteresisThreshold)) || + ((line == previousLine - 1) && ((previousLineTop - y) < hysteresisThreshold))) { + line = previousLine; + } + + return getOffsetForHorizontal(line, x); + } + @ViewDebug.ExportedProperty private CharSequence mText; private CharSequence mTransformed; @@ -7309,16 +8158,25 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private ArrayList<TextWatcher> mListeners = null; // display attributes - private TextPaint mTextPaint; + private final TextPaint mTextPaint; private boolean mUserSetTextScaleX; - private Paint mHighlightPaint; - private int mHighlightColor = 0xFFBBDDFF; + private final Paint mHighlightPaint; + private int mHighlightColor = 0xCC475925; private Layout mLayout; private long mShowCursor; private Blink mBlink; private boolean mCursorVisible = true; + // Cursor Controllers. Null when disabled. + private CursorController mInsertionPointCursorController; + private CursorController mSelectionModifierCursorController; + private boolean mIsInTextSelectionMode = false; + private int mLastTouchOffset = -1; + // Created once and shared by different CursorController helper methods. + // Only one cursor controller is active at any time which prevent race conditions. + private static Rect sCursorControllerTempRect = new Rect(); + private boolean mSelectAllOnFocus = false; private int mGravity = Gravity.TOP | Gravity.LEFT; diff --git a/core/java/com/android/internal/app/ExternalMediaFormatActivity.java b/core/java/com/android/internal/app/ExternalMediaFormatActivity.java index 7e9bbd198c12..98dcb8bcbf07 100644 --- a/core/java/com/android/internal/app/ExternalMediaFormatActivity.java +++ b/core/java/com/android/internal/app/ExternalMediaFormatActivity.java @@ -23,13 +23,10 @@ import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; import android.os.Bundle; -import android.os.Handler; import android.os.storage.IMountService; -import android.os.Message; import android.os.RemoteException; import android.os.ServiceManager; import android.os.Environment; -import android.widget.Toast; import android.util.Log; /** @@ -38,7 +35,7 @@ import android.util.Log; */ public class ExternalMediaFormatActivity extends AlertActivity implements DialogInterface.OnClickListener { - private static final int POSITIVE_BUTTON = AlertDialog.BUTTON1; + private static final int POSITIVE_BUTTON = AlertDialog.BUTTON_POSITIVE; /** Used to detect when the media state changes, in case we need to call finish() */ private BroadcastReceiver mStorageReceiver = new BroadcastReceiver() { diff --git a/core/java/com/android/internal/app/HeavyWeightSwitcherActivity.java b/core/java/com/android/internal/app/HeavyWeightSwitcherActivity.java new file mode 100644 index 000000000000..ada7f36e2af6 --- /dev/null +++ b/core/java/com/android/internal/app/HeavyWeightSwitcherActivity.java @@ -0,0 +1,156 @@ +/* + * 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.internal.app; + +import com.android.internal.R; + +import android.app.Activity; +import android.app.ActivityManagerNative; +import android.content.Intent; +import android.content.IntentSender; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.os.RemoteException; +import android.util.Log; +import android.view.View; +import android.view.Window; +import android.view.View.OnClickListener; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.TextView; + +/** + * This activity is displayed when the system attempts to start an Intent for + * which there is more than one matching activity, allowing the user to decide + * which to go to. It is not normally used directly by application developers. + */ +public class HeavyWeightSwitcherActivity extends Activity { + /** The PendingIntent of the new activity being launched. */ + public static final String KEY_INTENT = "intent"; + /** Set if the caller is requesting a result. */ + public static final String KEY_HAS_RESULT = "has_result"; + /** Package of current heavy-weight app. */ + public static final String KEY_CUR_APP = "cur_app"; + /** Task that current heavy-weight activity is running in. */ + public static final String KEY_CUR_TASK = "cur_task"; + /** Package of newly requested heavy-weight app. */ + public static final String KEY_NEW_APP = "new_app"; + + IntentSender mStartIntent; + boolean mHasResult; + String mCurApp; + int mCurTask; + String mNewApp; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + requestWindowFeature(Window.FEATURE_LEFT_ICON); + + mStartIntent = (IntentSender)getIntent().getParcelableExtra(KEY_INTENT); + mHasResult = getIntent().getBooleanExtra(KEY_HAS_RESULT, false); + mCurApp = getIntent().getStringExtra(KEY_CUR_APP); + mCurTask = getIntent().getIntExtra(KEY_CUR_TASK, 0); + mNewApp = getIntent().getStringExtra(KEY_NEW_APP); + + setContentView(com.android.internal.R.layout.heavy_weight_switcher); + + setIconAndText(R.id.old_app_icon, R.id.old_app_action, R.id.old_app_description, + mCurApp, R.string.old_app_action, R.string.old_app_description); + setIconAndText(R.id.new_app_icon, R.id.new_app_action, R.id.new_app_description, + mNewApp, R.string.new_app_action, R.string.new_app_description); + + View button = findViewById((R.id.switch_old)); + button.setOnClickListener(mSwitchOldListener); + button = findViewById((R.id.switch_new)); + button.setOnClickListener(mSwitchNewListener); + button = findViewById((R.id.cancel)); + button.setOnClickListener(mCancelListener); + + getWindow().setFeatureDrawableResource(Window.FEATURE_LEFT_ICON, + android.R.drawable.ic_dialog_alert); + } + + void setText(int id, CharSequence text) { + ((TextView)findViewById(id)).setText(text); + } + + void setDrawable(int id, Drawable dr) { + if (dr != null) { + ((ImageView)findViewById(id)).setImageDrawable(dr); + } + } + + void setIconAndText(int iconId, int actionId, int descriptionId, + String packageName, int actionStr, int descriptionStr) { + CharSequence appName = ""; + Drawable appIcon = null; + if (mCurApp != null) { + try { + ApplicationInfo info = getPackageManager().getApplicationInfo( + packageName, 0); + appName = info.loadLabel(getPackageManager()); + appIcon = info.loadIcon(getPackageManager()); + } catch (PackageManager.NameNotFoundException e) { + } + } + + setDrawable(iconId, appIcon); + setText(actionId, getString(actionStr, appName)); + setText(descriptionId, getText(descriptionStr)); + } + + private OnClickListener mSwitchOldListener = new OnClickListener() { + public void onClick(View v) { + try { + ActivityManagerNative.getDefault().moveTaskToFront(mCurTask); + } catch (RemoteException e) { + } + finish(); + } + }; + + private OnClickListener mSwitchNewListener = new OnClickListener() { + public void onClick(View v) { + try { + ActivityManagerNative.getDefault().finishHeavyWeightApp(); + } catch (RemoteException e) { + } + try { + if (mHasResult) { + startIntentSenderForResult(mStartIntent, -1, null, + Intent.FLAG_ACTIVITY_FORWARD_RESULT, + Intent.FLAG_ACTIVITY_FORWARD_RESULT, 0); + } else { + startIntentSenderForResult(mStartIntent, -1, null, 0, 0, 0); + } + } catch (IntentSender.SendIntentException ex) { + Log.w("HeavyWeightSwitcherActivity", "Failure starting", ex); + } + finish(); + } + }; + + private OnClickListener mCancelListener = new OnClickListener() { + public void onClick(View v) { + finish(); + } + }; +} diff --git a/core/java/com/android/internal/app/IBatteryStats.aidl b/core/java/com/android/internal/app/IBatteryStats.aidl index d5ccdeb3bfeb..bd87a0d05261 100644 --- a/core/java/com/android/internal/app/IBatteryStats.aidl +++ b/core/java/com/android/internal/app/IBatteryStats.aidl @@ -18,14 +18,25 @@ package com.android.internal.app; import com.android.internal.os.BatteryStatsImpl; +import android.os.WorkSource; import android.telephony.SignalStrength; interface IBatteryStats { byte[] getStatistics(); - void noteStartWakelock(int uid, String name, int type); - void noteStopWakelock(int uid, String name, int type); + void noteStartWakelock(int uid, int pid, String name, int type); + void noteStopWakelock(int uid, int pid, String name, int type); + + /* DO NOT CHANGE the position of noteStartSensor without updating + SensorService.cpp */ void noteStartSensor(int uid, int sensor); + + /* DO NOT CHANGE the position of noteStopSensor without updating + SensorService.cpp */ void noteStopSensor(int uid, int sensor); + + void noteStartWakelockFromSource(in WorkSource ws, int pid, String name, int type); + void noteStopWakelockFromSource(in WorkSource ws, int pid, String name, int type); + void noteStartGps(int uid); void noteStopGps(int uid); void noteScreenOn(); @@ -50,8 +61,13 @@ interface IBatteryStats { void noteScanWifiLockReleased(int uid); void noteWifiMulticastEnabled(int uid); void noteWifiMulticastDisabled(int uid); - void setOnBattery(boolean onBattery, int level); - void recordCurrentLevel(int level); + void noteFullWifiLockAcquiredFromSource(in WorkSource ws); + void noteFullWifiLockReleasedFromSource(in WorkSource ws); + void noteScanWifiLockAcquiredFromSource(in WorkSource ws); + void noteScanWifiLockReleasedFromSource(in WorkSource ws); + void noteWifiMulticastEnabledFromSource(in WorkSource ws); + void noteWifiMulticastDisabledFromSource(in WorkSource ws); + void setBatteryState(int status, int health, int plugType, int level, int temp, int volt); long getAwakeTimeBattery(); long getAwakeTimePlugged(); } diff --git a/core/java/com/android/internal/app/IMediaContainerService.aidl b/core/java/com/android/internal/app/IMediaContainerService.aidl index 0f817b7a6bb4..5d1f632797a7 100755 --- a/core/java/com/android/internal/app/IMediaContainerService.aidl +++ b/core/java/com/android/internal/app/IMediaContainerService.aidl @@ -19,6 +19,7 @@ package com.android.internal.app; import android.net.Uri; import android.os.ParcelFileDescriptor; import android.content.pm.PackageInfoLite; +import android.content.res.ObbInfo; interface IMediaContainerService { String copyResourceToContainer(in Uri packageURI, @@ -28,4 +29,5 @@ interface IMediaContainerService { in ParcelFileDescriptor outStream); PackageInfoLite getMinimalPackageInfo(in Uri fileUri, int flags); boolean checkFreeStorage(boolean external, in Uri fileUri); -}
\ No newline at end of file + ObbInfo getObbInfo(String filename); +} diff --git a/core/java/com/android/internal/app/NetInitiatedActivity.java b/core/java/com/android/internal/app/NetInitiatedActivity.java index 24818a84a130..36f45b24ee22 100755 --- a/core/java/com/android/internal/app/NetInitiatedActivity.java +++ b/core/java/com/android/internal/app/NetInitiatedActivity.java @@ -23,14 +23,9 @@ import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; import android.os.Bundle; -import android.os.Handler; -import android.os.Message; -import android.os.RemoteException; -import android.os.ServiceManager; import android.widget.Toast; import android.util.Log; import android.location.LocationManager; -import com.android.internal.location.GpsLocationProvider; import com.android.internal.location.GpsNetInitiatedHandler; /** @@ -44,8 +39,8 @@ public class NetInitiatedActivity extends AlertActivity implements DialogInterfa private static final boolean DEBUG = true; private static final boolean VERBOSE = false; - private static final int POSITIVE_BUTTON = AlertDialog.BUTTON1; - private static final int NEGATIVE_BUTTON = AlertDialog.BUTTON2; + private static final int POSITIVE_BUTTON = AlertDialog.BUTTON_POSITIVE; + private static final int NEGATIVE_BUTTON = AlertDialog.BUTTON_NEGATIVE; // Dialog button text public static final String BUTTON_TEXT_ACCEPT = "Accept"; diff --git a/core/java/com/android/internal/app/PlatLogoActivity.java b/core/java/com/android/internal/app/PlatLogoActivity.java new file mode 100644 index 000000000000..e1c5564bd528 --- /dev/null +++ b/core/java/com/android/internal/app/PlatLogoActivity.java @@ -0,0 +1,48 @@ +/* + * 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.internal.app; + +import android.app.Activity; +import android.os.Bundle; +import android.view.MotionEvent; +import android.widget.ImageView; +import android.widget.Toast; + +public class PlatLogoActivity extends Activity { + Toast mToast; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mToast = Toast.makeText(this, "Zombie art by Jack Larson", Toast.LENGTH_SHORT); + + ImageView content = new ImageView(this); + content.setImageResource(com.android.internal.R.drawable.platlogo); + content.setScaleType(ImageView.ScaleType.FIT_CENTER); + + setContentView(content); + } + + @Override + public boolean dispatchTouchEvent(MotionEvent ev) { + if (ev.getAction() == MotionEvent.ACTION_UP) { + mToast.show(); + } + return super.dispatchTouchEvent(ev); + } +} diff --git a/core/java/com/android/internal/app/RingtonePickerActivity.java b/core/java/com/android/internal/app/RingtonePickerActivity.java index ddddabefba33..5569ffe1bb72 100644 --- a/core/java/com/android/internal/app/RingtonePickerActivity.java +++ b/core/java/com/android/internal/app/RingtonePickerActivity.java @@ -223,7 +223,7 @@ public final class RingtonePickerActivity extends AlertActivity implements * On click of Ok/Cancel buttons */ public void onClick(DialogInterface dialog, int which) { - boolean positiveResult = which == BUTTON1; + boolean positiveResult = which == DialogInterface.BUTTON_POSITIVE; // Stop playing the previous ringtone mRingtoneManager.stopPreviousRingtone(); diff --git a/core/java/com/android/internal/app/ShutdownThread.java b/core/java/com/android/internal/app/ShutdownThread.java index a96253bb4bc4..d1aff2a0bfb8 100644 --- a/core/java/com/android/internal/app/ShutdownThread.java +++ b/core/java/com/android/internal/app/ShutdownThread.java @@ -84,7 +84,7 @@ public final class ShutdownThread extends Thread { public static void shutdown(final Context context, boolean confirm) { // ensure that only one thread is trying to power down. // any additional calls are just returned - synchronized (sIsStartedGuard){ + synchronized (sIsStartedGuard) { if (sIsStarted) { Log.d(TAG, "Request to shutdown already running, returning."); return; @@ -133,6 +133,10 @@ public final class ShutdownThread extends Thread { private static void beginShutdownSequence(Context context) { synchronized (sIsStartedGuard) { + if (sIsStarted) { + Log.d(TAG, "Request to shutdown already running, returning."); + return; + } sIsStarted = true; } diff --git a/core/java/com/android/internal/content/NativeLibraryHelper.java b/core/java/com/android/internal/content/NativeLibraryHelper.java new file mode 100644 index 000000000000..6e11cff2c208 --- /dev/null +++ b/core/java/com/android/internal/content/NativeLibraryHelper.java @@ -0,0 +1,326 @@ +package com.android.internal.content; + +import android.content.pm.PackageManager; +import android.os.Build; +import android.os.FileUtils; +import android.os.SystemProperties; +import android.util.Config; +import android.util.Log; +import android.util.Pair; +import android.util.Slog; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.Enumeration; +import java.util.LinkedList; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipException; +import java.util.zip.ZipFile; + +/** + * Native libraries helper. + * + * @hide + */ +public class NativeLibraryHelper { + private static final String TAG = "NativeHelper"; + + private static final boolean DEBUG_NATIVE = false; + + /* + * The following constants are returned by listPackageSharedLibsForAbiLI + * to indicate if native shared libraries were found in the package. + * Values are: + * PACKAGE_INSTALL_NATIVE_FOUND_LIBRARIES => native libraries found and installed + * PACKAGE_INSTALL_NATIVE_NO_LIBRARIES => no native libraries in package + * PACKAGE_INSTALL_NATIVE_ABI_MISMATCH => native libraries for another ABI found + * in package (and not installed) + * + */ + private static final int PACKAGE_INSTALL_NATIVE_FOUND_LIBRARIES = 0; + private static final int PACKAGE_INSTALL_NATIVE_NO_LIBRARIES = 1; + private static final int PACKAGE_INSTALL_NATIVE_ABI_MISMATCH = 2; + + // Directory in the APK that holds all the native shared libraries. + private static final String APK_LIB = "lib/"; + private static final int APK_LIB_LENGTH = APK_LIB.length(); + + // Prefix that native shared libraries must have. + private static final String LIB_PREFIX = "lib"; + private static final int LIB_PREFIX_LENGTH = LIB_PREFIX.length(); + + // Suffix that the native shared libraries must have. + private static final String LIB_SUFFIX = ".so"; + private static final int LIB_SUFFIX_LENGTH = LIB_SUFFIX.length(); + + // Name of the GDB binary. + private static final String GDBSERVER = "gdbserver"; + + // the minimum length of a valid native shared library of the form + // lib/<something>/lib<name>.so. + private static final int MIN_ENTRY_LENGTH = APK_LIB_LENGTH + 2 + LIB_PREFIX_LENGTH + 1 + + LIB_SUFFIX_LENGTH; + + /* + * Find all files of the form lib/<cpuAbi>/lib<name>.so in the .apk + * and add them to a list to be installed later. + * + * NOTE: this method may throw an IOException if the library cannot + * be copied to its final destination, e.g. if there isn't enough + * room left on the data partition, or a ZipException if the package + * file is malformed. + */ + private static int listPackageSharedLibsForAbiLI(ZipFile zipFile, + String cpuAbi, List<Pair<ZipEntry, String>> libEntries) throws IOException, + ZipException { + final int cpuAbiLen = cpuAbi.length(); + boolean hasNativeLibraries = false; + boolean installedNativeLibraries = false; + + if (DEBUG_NATIVE) { + Slog.d(TAG, "Checking " + zipFile.getName() + " for shared libraries of CPU ABI type " + + cpuAbi); + } + + Enumeration<? extends ZipEntry> entries = zipFile.entries(); + + while (entries.hasMoreElements()) { + ZipEntry entry = entries.nextElement(); + + // skip directories + if (entry.isDirectory()) { + continue; + } + String entryName = entry.getName(); + + /* + * Check that the entry looks like lib/<something>/lib<name>.so + * here, but don't check the ABI just yet. + * + * - must be sufficiently long + * - must end with LIB_SUFFIX, i.e. ".so" + * - must start with APK_LIB, i.e. "lib/" + */ + if (entryName.length() < MIN_ENTRY_LENGTH || !entryName.endsWith(LIB_SUFFIX) + || !entryName.startsWith(APK_LIB)) { + continue; + } + + // file name must start with LIB_PREFIX, i.e. "lib" + int lastSlash = entryName.lastIndexOf('/'); + + if (lastSlash < 0 + || !entryName.regionMatches(lastSlash + 1, LIB_PREFIX, 0, LIB_PREFIX_LENGTH)) { + continue; + } + + hasNativeLibraries = true; + + // check the cpuAbi now, between lib/ and /lib<name>.so + if (lastSlash != APK_LIB_LENGTH + cpuAbiLen + || !entryName.regionMatches(APK_LIB_LENGTH, cpuAbi, 0, cpuAbiLen)) + continue; + + /* + * Extract the library file name, ensure it doesn't contain + * weird characters. we're guaranteed here that it doesn't contain + * a directory separator though. + */ + String libFileName = entryName.substring(lastSlash+1); + if (!FileUtils.isFilenameSafe(new File(libFileName))) { + continue; + } + + installedNativeLibraries = true; + + if (DEBUG_NATIVE) { + Log.d(TAG, "Caching shared lib " + entry.getName()); + } + + libEntries.add(Pair.create(entry, libFileName)); + } + if (!hasNativeLibraries) + return PACKAGE_INSTALL_NATIVE_NO_LIBRARIES; + + if (!installedNativeLibraries) + return PACKAGE_INSTALL_NATIVE_ABI_MISMATCH; + + return PACKAGE_INSTALL_NATIVE_FOUND_LIBRARIES; + } + + /* + * Find the gdbserver executable program in a package at + * lib/<cpuAbi>/gdbserver and add it to the list of binaries + * to be copied out later. + * + * Returns PACKAGE_INSTALL_NATIVE_FOUND_LIBRARIES on success, + * or PACKAGE_INSTALL_NATIVE_NO_LIBRARIES otherwise. + */ + private static int listPackageGdbServerLI(ZipFile zipFile, String cpuAbi, + List<Pair<ZipEntry, String>> nativeFiles) throws IOException, ZipException { + final String apkGdbServerPath = "lib/" + cpuAbi + "/" + GDBSERVER; + + Enumeration<? extends ZipEntry> entries = zipFile.entries(); + + while (entries.hasMoreElements()) { + ZipEntry entry = entries.nextElement(); + // skip directories + if (entry.isDirectory()) { + continue; + } + String entryName = entry.getName(); + + if (!entryName.equals(apkGdbServerPath)) { + continue; + } + + if (Config.LOGD) { + Log.d(TAG, "Found gdbserver: " + entry.getName()); + } + + final String installGdbServerPath = APK_LIB + GDBSERVER; + nativeFiles.add(Pair.create(entry, installGdbServerPath)); + + return PACKAGE_INSTALL_NATIVE_FOUND_LIBRARIES; + } + return PACKAGE_INSTALL_NATIVE_NO_LIBRARIES; + } + + /* + * Examine shared libraries stored in the APK as + * lib/<cpuAbi>/lib<name>.so and add them to a list to be copied + * later. + * + * This function will first try the main CPU ABI defined by Build.CPU_ABI + * (which corresponds to ro.product.cpu.abi), and also try an alternate + * one if ro.product.cpu.abi2 is defined. + */ + public static int listPackageNativeBinariesLI(ZipFile zipFile, + List<Pair<ZipEntry, String>> nativeFiles) throws ZipException, IOException { + String cpuAbi = Build.CPU_ABI; + + int result = listPackageSharedLibsForAbiLI(zipFile, cpuAbi, nativeFiles); + + /* + * Some architectures are capable of supporting several CPU ABIs + * for example, 'armeabi-v7a' also supports 'armeabi' native code + * this is indicated by the definition of the ro.product.cpu.abi2 + * system property. + * + * only scan the package twice in case of ABI mismatch + */ + if (result == PACKAGE_INSTALL_NATIVE_ABI_MISMATCH) { + final String cpuAbi2 = SystemProperties.get("ro.product.cpu.abi2", null); + if (cpuAbi2 != null) { + result = listPackageSharedLibsForAbiLI(zipFile, cpuAbi2, nativeFiles); + } + + if (result == PACKAGE_INSTALL_NATIVE_ABI_MISMATCH) { + Slog.w(TAG, "Native ABI mismatch from package file"); + return PackageManager.INSTALL_FAILED_INVALID_APK; + } + + if (result == PACKAGE_INSTALL_NATIVE_FOUND_LIBRARIES) { + cpuAbi = cpuAbi2; + } + } + + /* + * Debuggable packages may have gdbserver embedded, so add it to + * the list to the list of items to be extracted (as lib/gdbserver) + * into the application's native library directory later. + */ + if (result == PACKAGE_INSTALL_NATIVE_FOUND_LIBRARIES) { + listPackageGdbServerLI(zipFile, cpuAbi, nativeFiles); + } + return PackageManager.INSTALL_SUCCEEDED; + } + + public static int copyNativeBinariesLI(File scanFile, File sharedLibraryDir) { + /* + * Check all the native files that need to be copied and add + * that to the container size. + */ + ZipFile zipFile; + try { + zipFile = new ZipFile(scanFile); + + List<Pair<ZipEntry, String>> nativeFiles = new LinkedList<Pair<ZipEntry, String>>(); + + NativeLibraryHelper.listPackageNativeBinariesLI(zipFile, nativeFiles); + + final int N = nativeFiles.size(); + + for (int i = 0; i < N; i++) { + final Pair<ZipEntry, String> entry = nativeFiles.get(i); + + File destFile = new File(sharedLibraryDir, entry.second); + copyNativeBinaryLI(zipFile, entry.first, sharedLibraryDir, destFile); + } + } catch (ZipException e) { + Slog.w(TAG, "Failed to extract data from package file", e); + return PackageManager.INSTALL_FAILED_INVALID_APK; + } catch (IOException e) { + Slog.w(TAG, "Failed to cache package shared libs", e); + return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE; + } + + return PackageManager.INSTALL_SUCCEEDED; + } + + private static void copyNativeBinaryLI(ZipFile zipFile, ZipEntry entry, + File binaryDir, File binaryFile) throws IOException { + InputStream inputStream = zipFile.getInputStream(entry); + try { + File tempFile = File.createTempFile("tmp", "tmp", binaryDir); + String tempFilePath = tempFile.getPath(); + // XXX package manager can't change owner, so the executable files for + // now need to be left as world readable and owned by the system. + if (!FileUtils.copyToFile(inputStream, tempFile) + || !tempFile.setLastModified(entry.getTime()) + || FileUtils.setPermissions(tempFilePath, FileUtils.S_IRUSR | FileUtils.S_IWUSR + | FileUtils.S_IRGRP | FileUtils.S_IXUSR | FileUtils.S_IXGRP + | FileUtils.S_IXOTH | FileUtils.S_IROTH, -1, -1) != 0 + || !tempFile.renameTo(binaryFile)) { + // Failed to properly write file. + tempFile.delete(); + throw new IOException("Couldn't create cached binary " + binaryFile + " in " + + binaryDir); + } + } finally { + inputStream.close(); + } + } + + // Remove the native binaries of a given package. This simply + // gets rid of the files in the 'lib' sub-directory. + public static void removeNativeBinariesLI(String nativeLibraryPath) { + if (DEBUG_NATIVE) { + Slog.w(TAG, "Deleting native binaries from: " + nativeLibraryPath); + } + + /* + * Just remove any file in the directory. Since the directory is owned + * by the 'system' UID, the application is not supposed to have written + * anything there. + */ + File binaryDir = new File(nativeLibraryPath); + if (binaryDir.exists()) { + File[] binaries = binaryDir.listFiles(); + if (binaries != null) { + for (int nn = 0; nn < binaries.length; nn++) { + if (DEBUG_NATIVE) { + Slog.d(TAG, " Deleting " + binaries[nn].getName()); + } + if (!binaries[nn].delete()) { + Slog.w(TAG, "Could not delete native binary: " + binaries[nn].getPath()); + } + } + } + // Do not delete 'lib' directory itself, or this will prevent + // installation of future updates. + } + } +} diff --git a/core/java/com/android/internal/content/PackageHelper.java b/core/java/com/android/internal/content/PackageHelper.java index 4d0a9e055ac6..d6c43f93ea07 100644 --- a/core/java/com/android/internal/content/PackageHelper.java +++ b/core/java/com/android/internal/content/PackageHelper.java @@ -56,22 +56,22 @@ public class PackageHelper { return null; } - public static String createSdDir(File tmpPackageFile, String cid, + public static String createSdDir(long sizeBytes, String cid, String sdEncKey, int uid) { // Create mount point via MountService IMountService mountService = getMountService(); - long len = tmpPackageFile.length(); - int mbLen = (int) (len >> 20); - if ((len - (mbLen * 1024 * 1024)) > 0) { - mbLen++; + int sizeMb = (int) (sizeBytes >> 20); + if ((sizeBytes - (sizeMb * 1024 * 1024)) > 0) { + sizeMb++; } // Add buffer size - mbLen++; - if (localLOGV) Log.i(TAG, "Size of container " + mbLen + " MB " + len + " bytes"); + sizeMb++; + if (localLOGV) + Log.i(TAG, "Size of container " + sizeMb + " MB " + sizeBytes + " bytes"); try { int rc = mountService.createSecureContainer( - cid, mbLen, "fat", sdEncKey, uid); + cid, sizeMb, "fat", sdEncKey, uid); if (rc != StorageResultCode.OperationSucceeded) { Log.e(TAG, "Failed to create secure container " + cid); return null; diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index aadb576d2c65..753dbf0e4141 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -20,12 +20,16 @@ import com.android.internal.util.JournaledFile; import android.bluetooth.BluetoothHeadset; import android.net.TrafficStats; +import android.os.BatteryManager; import android.os.BatteryStats; +import android.os.Handler; +import android.os.Message; import android.os.Parcel; import android.os.ParcelFormatException; import android.os.Parcelable; import android.os.Process; import android.os.SystemClock; +import android.os.WorkSource; import android.telephony.ServiceState; import android.telephony.SignalStrength; import android.telephony.TelephonyManager; @@ -56,17 +60,21 @@ import java.util.concurrent.atomic.AtomicInteger; public final class BatteryStatsImpl extends BatteryStats { private static final String TAG = "BatteryStatsImpl"; private static final boolean DEBUG = false; - + private static final boolean DEBUG_HISTORY = false; + // In-memory Parcel magic number, used to detect attempts to unmarshall bad data private static final int MAGIC = 0xBA757475; // 'BATSTATS' // Current on-disk Parcel version - private static final int VERSION = 43; + private static final int VERSION = 50; + // Maximum number of items we will record in the history. + private static final int MAX_HISTORY_ITEMS = 2000; + // The maximum number of names wakelocks we will keep track of // per uid; once the limit is reached, we batch the remaining wakelocks // in to one common name. - private static final int MAX_WAKELOCKS_PER_UID = 20; + private static final int MAX_WAKELOCKS_PER_UID = 30; private static final String BATCHED_WAKELOCK_NAME = "*overflow*"; @@ -74,6 +82,38 @@ public final class BatteryStatsImpl extends BatteryStats { private final JournaledFile mFile; + static final int MSG_UPDATE_WAKELOCKS = 1; + static final int MSG_REPORT_POWER_CHANGE = 2; + static final long DELAY_UPDATE_WAKELOCKS = 15*1000; + + public interface BatteryCallback { + public void batteryNeedsCpuUpdate(); + public void batteryPowerChanged(boolean onBattery); + } + + final class MyHandler extends Handler { + @Override + public void handleMessage(Message msg) { + BatteryCallback cb = mCallback; + switch (msg.what) { + case MSG_UPDATE_WAKELOCKS: + if (cb != null) { + cb.batteryNeedsCpuUpdate(); + } + break; + case MSG_REPORT_POWER_CHANGE: + if (cb != null) { + cb.batteryPowerChanged(msg.arg1 != 0); + } + break; + } + } + } + + private final MyHandler mHandler; + + private BatteryCallback mCallback; + /** * The statistics we have collected organized by uids. */ @@ -90,10 +130,25 @@ public final class BatteryStatsImpl extends BatteryStats { final SparseArray<ArrayList<StopwatchTimer>> mSensorTimers = new SparseArray<ArrayList<StopwatchTimer>>(); + // Last partial timers we use for distributing CPU usage. + final ArrayList<StopwatchTimer> mLastPartialTimers = new ArrayList<StopwatchTimer>(); + // These are the objects that will want to do something when the device // is unplugged from power. final ArrayList<Unpluggable> mUnpluggables = new ArrayList<Unpluggable>(); + boolean mShuttingDown; + + long mHistoryBaseTime; + boolean mHaveBatteryLevel = false; + boolean mRecordingHistory = true; + int mNumHistoryItems; + HistoryItem mHistory; + HistoryItem mHistoryEnd; + HistoryItem mHistoryLastEnd; + HistoryItem mHistoryCache; + final HistoryItem mHistoryCur = new HistoryItem(); + int mStartCount; long mBatteryUptime; @@ -166,7 +221,10 @@ public final class BatteryStatsImpl extends BatteryStats { * These keep track of battery levels (1-100) at the last plug event and the last unplug event. */ int mDischargeStartLevel; + int mDischargeUnplugLevel; int mDischargeCurrentLevel; + int mLowDischargeAmountSinceCharge; + int mHighDischargeAmountSinceCharge; long mLastWriteTime = 0; // Milliseconds @@ -220,6 +278,7 @@ public final class BatteryStatsImpl extends BatteryStats { // For debugging public BatteryStatsImpl() { mFile = null; + mHandler = null; } public static interface Unpluggable { @@ -232,28 +291,30 @@ public final class BatteryStatsImpl extends BatteryStats { */ public static class Counter extends BatteryStats.Counter implements Unpluggable { final AtomicInteger mCount = new AtomicInteger(); + final ArrayList<Unpluggable> mUnpluggables; int mLoadedCount; int mLastCount; int mUnpluggedCount; int mPluggedCount; Counter(ArrayList<Unpluggable> unpluggables, Parcel in) { + mUnpluggables = unpluggables; mPluggedCount = in.readInt(); mCount.set(mPluggedCount); mLoadedCount = in.readInt(); - mLastCount = in.readInt(); + mLastCount = 0; mUnpluggedCount = in.readInt(); unpluggables.add(this); } Counter(ArrayList<Unpluggable> unpluggables) { + mUnpluggables = unpluggables; unpluggables.add(this); } public void writeToParcel(Parcel out) { out.writeInt(mCount.get()); out.writeInt(mLoadedCount); - out.writeInt(mLastCount); out.writeInt(mUnpluggedCount); } @@ -289,9 +350,9 @@ public final class BatteryStatsImpl extends BatteryStats { val = mLastCount; } else { val = mCount.get(); - if (which == STATS_UNPLUGGED) { + if (which == STATS_SINCE_UNPLUGGED) { val -= mUnpluggedCount; - } else if (which != STATS_TOTAL) { + } else if (which != STATS_SINCE_CHARGED) { val -= mLoadedCount; } } @@ -310,16 +371,30 @@ public final class BatteryStatsImpl extends BatteryStats { mCount.incrementAndGet(); } + /** + * Clear state of this counter. + */ + void reset(boolean detachIfReset) { + mCount.set(0); + mLoadedCount = mLastCount = mPluggedCount = mUnpluggedCount = 0; + if (detachIfReset) { + detach(); + } + } + + void detach() { + mUnpluggables.remove(this); + } + void writeSummaryFromParcelLocked(Parcel out) { int count = mCount.get(); out.writeInt(count); - out.writeInt(count - mLoadedCount); } void readSummaryFromParcelLocked(Parcel in) { mLoadedCount = in.readInt(); mCount.set(mLoadedCount); - mLastCount = in.readInt(); + mLastCount = 0; mUnpluggedCount = mPluggedCount = mLoadedCount; } } @@ -344,7 +419,7 @@ public final class BatteryStatsImpl extends BatteryStats { */ public static abstract class Timer extends BatteryStats.Timer implements Unpluggable { final int mType; - + final ArrayList<Unpluggable> mUnpluggables; int mCount; int mLoadedCount; @@ -389,20 +464,22 @@ public final class BatteryStatsImpl extends BatteryStats { */ Timer(int type, ArrayList<Unpluggable> unpluggables, Parcel in) { mType = type; + mUnpluggables = unpluggables; mCount = in.readInt(); mLoadedCount = in.readInt(); - mLastCount = in.readInt(); + mLastCount = 0; mUnpluggedCount = in.readInt(); mTotalTime = in.readLong(); mLoadedTime = in.readLong(); - mLastTime = in.readLong(); + mLastTime = 0; mUnpluggedTime = in.readLong(); unpluggables.add(this); } Timer(int type, ArrayList<Unpluggable> unpluggables) { mType = type; + mUnpluggables = unpluggables; unpluggables.add(this); } @@ -410,15 +487,29 @@ public final class BatteryStatsImpl extends BatteryStats { protected abstract int computeCurrentCountLocked(); + /** + * Clear state of this timer. Returns true if the timer is inactive + * so can be completely dropped. + */ + boolean reset(BatteryStatsImpl stats, boolean detachIfReset) { + mTotalTime = mLoadedTime = mLastTime = 0; + mCount = mLoadedCount = mLastCount = 0; + if (detachIfReset) { + detach(); + } + return true; + } + + void detach() { + mUnpluggables.remove(this); + } public void writeToParcel(Parcel out, long batteryRealtime) { out.writeInt(mCount); out.writeInt(mLoadedCount); - out.writeInt(mLastCount); out.writeInt(mUnpluggedCount); out.writeLong(computeRunTimeLocked(batteryRealtime)); out.writeLong(mLoadedTime); - out.writeLong(mLastTime); out.writeLong(mUnpluggedTime); } @@ -474,9 +565,9 @@ public final class BatteryStatsImpl extends BatteryStats { val = mLastTime; } else { val = computeRunTimeLocked(batteryRealtime); - if (which == STATS_UNPLUGGED) { + if (which == STATS_SINCE_UNPLUGGED) { val -= mUnpluggedTime; - } else if (which != STATS_TOTAL) { + } else if (which != STATS_SINCE_CHARGED) { val -= mLoadedTime; } } @@ -491,9 +582,9 @@ public final class BatteryStatsImpl extends BatteryStats { val = mLastCount; } else { val = computeCurrentCountLocked(); - if (which == STATS_UNPLUGGED) { + if (which == STATS_SINCE_UNPLUGGED) { val -= mUnpluggedCount; - } else if (which != STATS_TOTAL) { + } else if (which != STATS_SINCE_CHARGED) { val -= mLoadedCount; } } @@ -516,18 +607,16 @@ public final class BatteryStatsImpl extends BatteryStats { long runTime = computeRunTimeLocked(batteryRealtime); // Divide by 1000 for backwards compatibility out.writeLong((runTime + 500) / 1000); - out.writeLong(((runTime - mLoadedTime) + 500) / 1000); out.writeInt(mCount); - out.writeInt(mCount - mLoadedCount); } void readSummaryFromParcelLocked(Parcel in) { // Multiply by 1000 for backwards compatibility mTotalTime = mLoadedTime = in.readLong() * 1000; - mLastTime = in.readLong() * 1000; + mLastTime = 0; mUnpluggedTime = mTotalTime; mCount = mLoadedCount = in.readInt(); - mLastCount = in.readInt(); + mLastCount = 0; mUnpluggedCount = mCount; } } @@ -664,6 +753,12 @@ public final class BatteryStatsImpl extends BatteryStats { out.writeInt(mTrackingReportedValues ? 1 : 0); } + boolean reset(BatteryStatsImpl stats, boolean detachIfReset) { + super.reset(stats, detachIfReset); + setStale(); + return true; + } + void writeSummaryFromParcelLocked(Parcel out, long batteryRealtime) { super.writeSummaryFromParcelLocked(out, batteryRealtime); out.writeLong(mCurrentReportedTotalTime); @@ -683,9 +778,10 @@ public final class BatteryStatsImpl extends BatteryStats { * State for keeping track of timing information. */ public static final class StopwatchTimer extends Timer { + final Uid mUid; final ArrayList<StopwatchTimer> mTimerPool; - int mNesting; + int mNesting; /** * The last time at which we updated the timer. If mNesting is > 0, @@ -695,23 +791,31 @@ public final class BatteryStatsImpl extends BatteryStats { long mUpdateTime; /** - * The total time at which the timer was acquired, to determine if + * The total time at which the timer was acquired, to determine if it * was actually held for an interesting duration. */ long mAcquireTime; long mTimeout; - StopwatchTimer(int type, ArrayList<StopwatchTimer> timerPool, + /** + * For partial wake locks, keep track of whether we are in the list + * to consume CPU cycles. + */ + boolean mInList; + + StopwatchTimer(Uid uid, int type, ArrayList<StopwatchTimer> timerPool, ArrayList<Unpluggable> unpluggables, Parcel in) { super(type, unpluggables, in); + mUid = uid; mTimerPool = timerPool; mUpdateTime = in.readLong(); } - StopwatchTimer(int type, ArrayList<StopwatchTimer> timerPool, + StopwatchTimer(Uid uid, int type, ArrayList<StopwatchTimer> timerPool, ArrayList<Unpluggable> unpluggables) { super(type, unpluggables); + mUid = uid; mTimerPool = timerPool; } @@ -836,6 +940,24 @@ public final class BatteryStatsImpl extends BatteryStats { return mCount; } + boolean reset(BatteryStatsImpl stats, boolean detachIfReset) { + boolean canDetach = mNesting <= 0; + super.reset(stats, canDetach && detachIfReset); + if (mNesting > 0) { + mUpdateTime = stats.getBatteryRealtimeLocked( + SystemClock.elapsedRealtime() * 1000); + } + mAcquireTime = mTotalTime; + return canDetach; + } + + void detach() { + super.detach(); + if (mTimerPool != null) { + mTimerPool.remove(this); + } + } + void readSummaryFromParcelLocked(Parcel in) { super.readSummaryFromParcelLocked(in); mNesting = 0; @@ -973,12 +1095,12 @@ public final class BatteryStatsImpl extends BatteryStats { } private void doDataPlug(long[] dataTransfer, long currentBytes) { - dataTransfer[STATS_LAST] = dataTransfer[STATS_UNPLUGGED]; - dataTransfer[STATS_UNPLUGGED] = -1; + dataTransfer[STATS_LAST] = dataTransfer[STATS_SINCE_UNPLUGGED]; + dataTransfer[STATS_SINCE_UNPLUGGED] = -1; } private void doDataUnplug(long[] dataTransfer, long currentBytes) { - dataTransfer[STATS_UNPLUGGED] = currentBytes; + dataTransfer[STATS_SINCE_UNPLUGGED] = currentBytes; } /** @@ -1042,7 +1164,81 @@ public final class BatteryStatsImpl extends BatteryStats { mBtHeadset = headset; } - public void doUnplug(long batteryUptime, long batteryRealtime) { + void addHistoryRecordLocked(long curTime) { + if (!mHaveBatteryLevel || !mRecordingHistory) { + return; + } + + // If the current time is basically the same as the last time, + // just collapse into one record. + if (mHistoryEnd != null && mHistoryEnd.cmd == HistoryItem.CMD_UPDATE + && (mHistoryBaseTime+curTime) < (mHistoryEnd.time+500)) { + // If the current is the same as the one before, then we no + // longer need the entry. + if (mHistoryLastEnd != null && mHistoryLastEnd.cmd == HistoryItem.CMD_UPDATE + && mHistoryLastEnd.same(mHistoryCur)) { + mHistoryLastEnd.next = null; + mHistoryEnd.next = mHistoryCache; + mHistoryCache = mHistoryEnd; + mHistoryEnd = mHistoryLastEnd; + mHistoryLastEnd = null; + } else { + mHistoryEnd.setTo(mHistoryEnd.time, HistoryItem.CMD_UPDATE, mHistoryCur); + } + return; + } + + if (mNumHistoryItems == MAX_HISTORY_ITEMS) { + addHistoryRecordLocked(curTime, HistoryItem.CMD_OVERFLOW); + } + + if (mNumHistoryItems >= MAX_HISTORY_ITEMS) { + // Once we've reached the maximum number of items, we only + // record changes to the battery level. + if (mHistoryEnd != null && mHistoryEnd.batteryLevel + == mHistoryCur.batteryLevel) { + return; + } + } + + addHistoryRecordLocked(curTime, HistoryItem.CMD_UPDATE); + } + + void addHistoryRecordLocked(long curTime, byte cmd) { + HistoryItem rec = mHistoryCache; + if (rec != null) { + mHistoryCache = rec.next; + } else { + rec = new HistoryItem(); + } + rec.setTo(mHistoryBaseTime + curTime, cmd, mHistoryCur); + + addHistoryRecordLocked(rec); + } + + void addHistoryRecordLocked(HistoryItem rec) { + mNumHistoryItems++; + rec.next = null; + mHistoryLastEnd = mHistoryEnd; + if (mHistoryEnd != null) { + mHistoryEnd.next = rec; + mHistoryEnd = rec; + } else { + mHistory = mHistoryEnd = rec; + } + } + + void clearHistoryLocked() { + if (mHistory != null) { + mHistoryEnd.next = mHistoryCache; + mHistoryCache = mHistory; + mHistory = mHistoryLastEnd = mHistoryEnd = null; + } + mNumHistoryItems = 0; + mHistoryBaseTime = 0; + } + + public void doUnplugLocked(long batteryUptime, long batteryRealtime) { for (int iu = mUidStats.size() - 1; iu >= 0; iu--) { Uid u = mUidStats.valueAt(iu); u.mStartedTcpBytesReceived = TrafficStats.getUidRxBytes(u.mUid); @@ -1066,7 +1262,7 @@ public final class BatteryStatsImpl extends BatteryStats { mBluetoothPingCount = 0; } - public void doPlug(long batteryUptime, long batteryRealtime) { + public void doPlugLocked(long batteryUptime, long batteryRealtime) { for (int iu = mUidStats.size() - 1; iu >= 0; iu--) { Uid u = mUidStats.valueAt(iu); if (u.mStartedTcpBytesReceived >= 0) { @@ -1094,31 +1290,264 @@ public final class BatteryStatsImpl extends BatteryStats { mBluetoothPingStart = -1; } - public void noteStartGps(int uid) { + int mWakeLockNesting; + + public void noteStartWakeLocked(int uid, int pid, String name, int type) { + if (type == WAKE_TYPE_PARTIAL) { + // Only care about partial wake locks, since full wake locks + // will be canceled when the user puts the screen to sleep. + if (mWakeLockNesting == 0) { + mHistoryCur.states |= HistoryItem.STATE_WAKE_LOCK_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "Start wake lock to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecordLocked(SystemClock.elapsedRealtime()); + } + mWakeLockNesting++; + } + if (uid >= 0) { + if (!mHandler.hasMessages(MSG_UPDATE_WAKELOCKS)) { + Message m = mHandler.obtainMessage(MSG_UPDATE_WAKELOCKS); + mHandler.sendMessageDelayed(m, DELAY_UPDATE_WAKELOCKS); + } + getUidStatsLocked(uid).noteStartWakeLocked(pid, name, type); + } + } + + public void noteStopWakeLocked(int uid, int pid, String name, int type) { + if (type == WAKE_TYPE_PARTIAL) { + mWakeLockNesting--; + if (mWakeLockNesting == 0) { + mHistoryCur.states &= ~HistoryItem.STATE_WAKE_LOCK_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "Stop wake lock to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecordLocked(SystemClock.elapsedRealtime()); + } + } + if (uid >= 0) { + if (!mHandler.hasMessages(MSG_UPDATE_WAKELOCKS)) { + Message m = mHandler.obtainMessage(MSG_UPDATE_WAKELOCKS); + mHandler.sendMessageDelayed(m, DELAY_UPDATE_WAKELOCKS); + } + getUidStatsLocked(uid).noteStopWakeLocked(pid, name, type); + } + } + + public void noteStartWakeFromSourceLocked(WorkSource ws, int pid, String name, int type) { + int N = ws.size(); + for (int i=0; i<N; i++) { + noteStartWakeLocked(ws.get(i), pid, name, type); + } + } + + public void noteStopWakeFromSourceLocked(WorkSource ws, int pid, String name, int type) { + int N = ws.size(); + for (int i=0; i<N; i++) { + noteStopWakeLocked(ws.get(i), pid, name, type); + } + } + + public int startAddingCpuLocked() { + mHandler.removeMessages(MSG_UPDATE_WAKELOCKS); + + if (mScreenOn) { + return 0; + } + + final int N = mPartialTimers.size(); + if (N == 0) { + mLastPartialTimers.clear(); + return 0; + } + + // How many timers should consume CPU? Only want to include ones + // that have already been in the list. + for (int i=0; i<N; i++) { + StopwatchTimer st = mPartialTimers.get(i); + if (st.mInList) { + Uid uid = st.mUid; + // We don't include the system UID, because it so often + // holds wake locks at one request or another of an app. + if (uid != null && uid.mUid != Process.SYSTEM_UID) { + return 50; + } + } + } + + return 0; + } + + public void finishAddingCpuLocked(int perc, int utime, int stime, long[] cpuSpeedTimes) { + final int N = mPartialTimers.size(); + if (perc != 0) { + int num = 0; + for (int i=0; i<N; i++) { + StopwatchTimer st = mPartialTimers.get(i); + if (st.mInList) { + Uid uid = st.mUid; + // We don't include the system UID, because it so often + // holds wake locks at one request or another of an app. + if (uid != null && uid.mUid != Process.SYSTEM_UID) { + num++; + } + } + } + if (num != 0) { + for (int i=0; i<N; i++) { + StopwatchTimer st = mPartialTimers.get(i); + if (st.mInList) { + Uid uid = st.mUid; + if (uid != null && uid.mUid != Process.SYSTEM_UID) { + int myUTime = utime/num; + int mySTime = stime/num; + utime -= myUTime; + stime -= mySTime; + num--; + Uid.Proc proc = uid.getProcessStatsLocked("*wakelock*"); + proc.addCpuTimeLocked(myUTime, mySTime); + proc.addSpeedStepTimes(cpuSpeedTimes); + } + } + } + } + + // Just in case, collect any lost CPU time. + if (utime != 0 || stime != 0) { + Uid uid = getUidStatsLocked(Process.SYSTEM_UID); + if (uid != null) { + Uid.Proc proc = uid.getProcessStatsLocked("*lost*"); + proc.addCpuTimeLocked(utime, stime); + proc.addSpeedStepTimes(cpuSpeedTimes); + } + } + } + + final int NL = mLastPartialTimers.size(); + boolean diff = N != NL; + for (int i=0; i<NL && !diff; i++) { + diff |= mPartialTimers.get(i) != mLastPartialTimers.get(i); + } + if (!diff) { + for (int i=0; i<NL; i++) { + mPartialTimers.get(i).mInList = true; + } + return; + } + + for (int i=0; i<NL; i++) { + mLastPartialTimers.get(i).mInList = false; + } + mLastPartialTimers.clear(); + for (int i=0; i<N; i++) { + StopwatchTimer st = mPartialTimers.get(i); + st.mInList = true; + mLastPartialTimers.add(st); + } + } + + public void noteProcessDiedLocked(int uid, int pid) { + Uid u = mUidStats.get(uid); + if (u != null) { + u.mPids.remove(pid); + } + } + + public long getProcessWakeTime(int uid, int pid, long realtime) { + Uid u = mUidStats.get(uid); + if (u != null) { + Uid.Pid p = u.mPids.get(pid); + if (p != null) { + return p.mWakeSum + (p.mWakeStart != 0 ? (realtime - p.mWakeStart) : 0); + } + } + return 0; + } + + public void reportExcessiveWakeLocked(int uid, String proc, long overTime, long usedTime) { + Uid u = mUidStats.get(uid); + if (u != null) { + u.reportExcessiveWakeLocked(proc, overTime, usedTime); + } + } + + int mSensorNesting; + + public void noteStartSensorLocked(int uid, int sensor) { + if (mSensorNesting == 0) { + mHistoryCur.states |= HistoryItem.STATE_SENSOR_ON_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "Start sensor to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecordLocked(SystemClock.elapsedRealtime()); + } + mSensorNesting++; + getUidStatsLocked(uid).noteStartSensor(sensor); + } + + public void noteStopSensorLocked(int uid, int sensor) { + mSensorNesting--; + if (mSensorNesting == 0) { + mHistoryCur.states &= ~HistoryItem.STATE_SENSOR_ON_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "Stop sensor to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecordLocked(SystemClock.elapsedRealtime()); + } + getUidStatsLocked(uid).noteStopSensor(sensor); + } + + int mGpsNesting; + + public void noteStartGpsLocked(int uid) { + if (mGpsNesting == 0) { + mHistoryCur.states |= HistoryItem.STATE_GPS_ON_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "Start GPS to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecordLocked(SystemClock.elapsedRealtime()); + } + mGpsNesting++; getUidStatsLocked(uid).noteStartGps(); } - public void noteStopGps(int uid) { + public void noteStopGpsLocked(int uid) { + mGpsNesting--; + if (mGpsNesting == 0) { + mHistoryCur.states &= ~HistoryItem.STATE_GPS_ON_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "Stop GPS to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecordLocked(SystemClock.elapsedRealtime()); + } getUidStatsLocked(uid).noteStopGps(); } public void noteScreenOnLocked() { if (!mScreenOn) { + mHistoryCur.states |= HistoryItem.STATE_SCREEN_ON_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "Screen on to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecordLocked(SystemClock.elapsedRealtime()); mScreenOn = true; mScreenOnTimer.startRunningLocked(this); if (mScreenBrightnessBin >= 0) { mScreenBrightnessTimer[mScreenBrightnessBin].startRunningLocked(this); } + + // Fake a wake lock, so we consider the device waked as long + // as the screen is on. + noteStartWakeLocked(-1, -1, "dummy", WAKE_TYPE_PARTIAL); } } public void noteScreenOffLocked() { if (mScreenOn) { + mHistoryCur.states &= ~HistoryItem.STATE_SCREEN_ON_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "Screen off to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecordLocked(SystemClock.elapsedRealtime()); mScreenOn = false; mScreenOnTimer.stopRunningLocked(this); if (mScreenBrightnessBin >= 0) { mScreenBrightnessTimer[mScreenBrightnessBin].stopRunningLocked(this); } + + noteStopWakeLocked(-1, -1, "dummy", WAKE_TYPE_PARTIAL); } } @@ -1128,6 +1557,11 @@ public final class BatteryStatsImpl extends BatteryStats { if (bin < 0) bin = 0; else if (bin >= NUM_SCREEN_BRIGHTNESS_BINS) bin = NUM_SCREEN_BRIGHTNESS_BINS-1; if (mScreenBrightnessBin != bin) { + mHistoryCur.states = (mHistoryCur.states&~HistoryItem.STATE_BRIGHTNESS_MASK) + | (bin << HistoryItem.STATE_BRIGHTNESS_SHIFT); + if (DEBUG_HISTORY) Slog.v(TAG, "Screen brightness " + bin + " to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecordLocked(SystemClock.elapsedRealtime()); if (mScreenOn) { if (mScreenBrightnessBin >= 0) { mScreenBrightnessTimer[mScreenBrightnessBin].stopRunningLocked(this); @@ -1148,6 +1582,10 @@ public final class BatteryStatsImpl extends BatteryStats { public void notePhoneOnLocked() { if (!mPhoneOn) { + mHistoryCur.states |= HistoryItem.STATE_PHONE_IN_CALL_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "Phone on to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecordLocked(SystemClock.elapsedRealtime()); mPhoneOn = true; mPhoneOnTimer.startRunningLocked(this); } @@ -1155,47 +1593,83 @@ public final class BatteryStatsImpl extends BatteryStats { public void notePhoneOffLocked() { if (mPhoneOn) { + mHistoryCur.states &= ~HistoryItem.STATE_PHONE_IN_CALL_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "Phone off to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecordLocked(SystemClock.elapsedRealtime()); mPhoneOn = false; mPhoneOnTimer.stopRunningLocked(this); } } + void stopAllSignalStrengthTimersLocked(int except) { + for (int i = 0; i < NUM_SIGNAL_STRENGTH_BINS; i++) { + if (i == except) { + continue; + } + while (mPhoneSignalStrengthsTimer[i].isRunningLocked()) { + mPhoneSignalStrengthsTimer[i].stopRunningLocked(this); + } + } + } + /** * Telephony stack updates the phone state. * @param state phone state from ServiceState.getState() */ public void notePhoneStateLocked(int state) { + boolean scanning = false; + int bin = mPhoneSignalStrengthBin; - boolean isAirplaneMode = state == ServiceState.STATE_POWER_OFF; - // Stop all timers - if (isAirplaneMode || state == ServiceState.STATE_OUT_OF_SERVICE) { - for (int i = 0; i < NUM_SIGNAL_STRENGTH_BINS; i++) { - while (mPhoneSignalStrengthsTimer[i].isRunningLocked()) { - mPhoneSignalStrengthsTimer[i].stopRunningLocked(this); - } - } - } - // Stop Signal Scanning timer, in case we're going into service - while (mPhoneSignalScanningTimer.isRunningLocked()) { - mPhoneSignalScanningTimer.stopRunningLocked(this); - } + + // If the phone is powered off, stop all timers. + if (state == ServiceState.STATE_POWER_OFF) { + stopAllSignalStrengthTimersLocked(-1); // If we're back in service or continuing in service, restart the old timer. - if (state == ServiceState.STATE_IN_SERVICE) { + } if (state == ServiceState.STATE_IN_SERVICE) { if (bin == -1) bin = SIGNAL_STRENGTH_NONE_OR_UNKNOWN; if (!mPhoneSignalStrengthsTimer[bin].isRunningLocked()) { mPhoneSignalStrengthsTimer[bin].startRunningLocked(this); } + + // If we're out of service, we are in the lowest signal strength + // bin and have the scanning bit set. } else if (state == ServiceState.STATE_OUT_OF_SERVICE) { + scanning = true; mPhoneSignalStrengthBin = SIGNAL_STRENGTH_NONE_OR_UNKNOWN; + stopAllSignalStrengthTimersLocked(mPhoneSignalStrengthBin); if (!mPhoneSignalStrengthsTimer[mPhoneSignalStrengthBin].isRunningLocked()) { mPhoneSignalStrengthsTimer[mPhoneSignalStrengthBin].startRunningLocked(this); } if (!mPhoneSignalScanningTimer.isRunningLocked()) { + mHistoryCur.states |= HistoryItem.STATE_PHONE_SCANNING_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "Phone started scanning to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecordLocked(SystemClock.elapsedRealtime()); mPhoneSignalScanningTimer.startRunningLocked(this); } } - mPhoneServiceState = state; + + if (!scanning) { + // If we are no longer scanning, then stop the scanning timer. + if (mPhoneSignalScanningTimer.isRunningLocked()) { + mHistoryCur.states &= ~HistoryItem.STATE_PHONE_SCANNING_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "Phone stopped scanning to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecordLocked(SystemClock.elapsedRealtime()); + mPhoneSignalScanningTimer.stopRunningLocked(this); + } + } + + if (mPhoneServiceState != state) { + mHistoryCur.states = (mHistoryCur.states&~HistoryItem.STATE_PHONE_STATE_MASK) + | (state << HistoryItem.STATE_PHONE_STATE_SHIFT); + if (DEBUG_HISTORY) Slog.v(TAG, "Phone state " + bin + " to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecordLocked(SystemClock.elapsedRealtime()); + mPhoneServiceState = state; + } } public void notePhoneSignalStrengthLocked(SignalStrength signalStrength) { @@ -1222,6 +1696,11 @@ public final class BatteryStatsImpl extends BatteryStats { else bin = SIGNAL_STRENGTH_POOR; } if (mPhoneSignalStrengthBin != bin) { + mHistoryCur.states = (mHistoryCur.states&~HistoryItem.STATE_SIGNAL_STRENGTH_MASK) + | (bin << HistoryItem.STATE_SIGNAL_STRENGTH_SHIFT); + if (DEBUG_HISTORY) Slog.v(TAG, "Signal strength " + bin + " to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecordLocked(SystemClock.elapsedRealtime()); if (mPhoneSignalStrengthBin >= 0) { mPhoneSignalStrengthsTimer[mPhoneSignalStrengthBin].stopRunningLocked(this); } @@ -1243,6 +1722,33 @@ public final class BatteryStatsImpl extends BatteryStats { case TelephonyManager.NETWORK_TYPE_UMTS: bin = DATA_CONNECTION_UMTS; break; + case TelephonyManager.NETWORK_TYPE_CDMA: + bin = DATA_CONNECTION_CDMA; + break; + case TelephonyManager.NETWORK_TYPE_EVDO_0: + bin = DATA_CONNECTION_EVDO_0; + break; + case TelephonyManager.NETWORK_TYPE_EVDO_A: + bin = DATA_CONNECTION_EVDO_A; + break; + case TelephonyManager.NETWORK_TYPE_1xRTT: + bin = DATA_CONNECTION_1xRTT; + break; + case TelephonyManager.NETWORK_TYPE_HSDPA: + bin = DATA_CONNECTION_HSDPA; + break; + case TelephonyManager.NETWORK_TYPE_HSUPA: + bin = DATA_CONNECTION_HSUPA; + break; + case TelephonyManager.NETWORK_TYPE_HSPA: + bin = DATA_CONNECTION_HSPA; + break; + case TelephonyManager.NETWORK_TYPE_IDEN: + bin = DATA_CONNECTION_IDEN; + break; + case TelephonyManager.NETWORK_TYPE_EVDO_B: + bin = DATA_CONNECTION_EVDO_B; + break; default: bin = DATA_CONNECTION_OTHER; break; @@ -1250,6 +1756,11 @@ public final class BatteryStatsImpl extends BatteryStats { } if (DEBUG) Log.i(TAG, "Phone Data Connection -> " + dataType + " = " + hasData); if (mPhoneDataConnectionType != bin) { + mHistoryCur.states = (mHistoryCur.states&~HistoryItem.STATE_DATA_CONNECTION_MASK) + | (bin << HistoryItem.STATE_DATA_CONNECTION_SHIFT); + if (DEBUG_HISTORY) Slog.v(TAG, "Data connection " + bin + " to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecordLocked(SystemClock.elapsedRealtime()); if (mPhoneDataConnectionType >= 0) { mPhoneDataConnectionsTimer[mPhoneDataConnectionType].stopRunningLocked(this); } @@ -1260,6 +1771,10 @@ public final class BatteryStatsImpl extends BatteryStats { public void noteWifiOnLocked(int uid) { if (!mWifiOn) { + mHistoryCur.states |= HistoryItem.STATE_WIFI_ON_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "WIFI on to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecordLocked(SystemClock.elapsedRealtime()); mWifiOn = true; mWifiOnTimer.startRunningLocked(this); } @@ -1274,6 +1789,10 @@ public final class BatteryStatsImpl extends BatteryStats { public void noteWifiOffLocked(int uid) { if (mWifiOn) { + mHistoryCur.states &= ~HistoryItem.STATE_WIFI_ON_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "WIFI off to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecordLocked(SystemClock.elapsedRealtime()); mWifiOn = false; mWifiOnTimer.stopRunningLocked(this); } @@ -1285,6 +1804,10 @@ public final class BatteryStatsImpl extends BatteryStats { public void noteAudioOnLocked(int uid) { if (!mAudioOn) { + mHistoryCur.states |= HistoryItem.STATE_AUDIO_ON_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "Audio on to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecordLocked(SystemClock.elapsedRealtime()); mAudioOn = true; mAudioOnTimer.startRunningLocked(this); } @@ -1293,6 +1816,10 @@ public final class BatteryStatsImpl extends BatteryStats { public void noteAudioOffLocked(int uid) { if (mAudioOn) { + mHistoryCur.states &= ~HistoryItem.STATE_AUDIO_ON_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "Audio off to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecordLocked(SystemClock.elapsedRealtime()); mAudioOn = false; mAudioOnTimer.stopRunningLocked(this); } @@ -1301,6 +1828,10 @@ public final class BatteryStatsImpl extends BatteryStats { public void noteVideoOnLocked(int uid) { if (!mVideoOn) { + mHistoryCur.states |= HistoryItem.STATE_VIDEO_ON_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "Video on to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecordLocked(SystemClock.elapsedRealtime()); mVideoOn = true; mVideoOnTimer.startRunningLocked(this); } @@ -1309,6 +1840,10 @@ public final class BatteryStatsImpl extends BatteryStats { public void noteVideoOffLocked(int uid) { if (mVideoOn) { + mHistoryCur.states &= ~HistoryItem.STATE_VIDEO_ON_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "Video off to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecordLocked(SystemClock.elapsedRealtime()); mVideoOn = false; mVideoOnTimer.stopRunningLocked(this); } @@ -1317,6 +1852,10 @@ public final class BatteryStatsImpl extends BatteryStats { public void noteWifiRunningLocked() { if (!mWifiRunning) { + mHistoryCur.states |= HistoryItem.STATE_WIFI_RUNNING_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "WIFI running to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecordLocked(SystemClock.elapsedRealtime()); mWifiRunning = true; mWifiRunningTimer.startRunningLocked(this); } @@ -1324,6 +1863,10 @@ public final class BatteryStatsImpl extends BatteryStats { public void noteWifiStoppedLocked() { if (mWifiRunning) { + mHistoryCur.states &= ~HistoryItem.STATE_WIFI_RUNNING_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "WIFI stopped to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecordLocked(SystemClock.elapsedRealtime()); mWifiRunning = false; mWifiRunningTimer.stopRunningLocked(this); } @@ -1331,6 +1874,10 @@ public final class BatteryStatsImpl extends BatteryStats { public void noteBluetoothOnLocked() { if (!mBluetoothOn) { + mHistoryCur.states |= HistoryItem.STATE_BLUETOOTH_ON_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "Bluetooth on to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecordLocked(SystemClock.elapsedRealtime()); mBluetoothOn = true; mBluetoothOnTimer.startRunningLocked(this); } @@ -1338,35 +1885,129 @@ public final class BatteryStatsImpl extends BatteryStats { public void noteBluetoothOffLocked() { if (mBluetoothOn) { + mHistoryCur.states &= ~HistoryItem.STATE_BLUETOOTH_ON_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "Bluetooth off to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecordLocked(SystemClock.elapsedRealtime()); mBluetoothOn = false; mBluetoothOnTimer.stopRunningLocked(this); } } + int mWifiFullLockNesting = 0; + public void noteFullWifiLockAcquiredLocked(int uid) { + if (mWifiFullLockNesting == 0) { + mHistoryCur.states |= HistoryItem.STATE_WIFI_FULL_LOCK_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "WIFI full lock on to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecordLocked(SystemClock.elapsedRealtime()); + } + mWifiFullLockNesting++; getUidStatsLocked(uid).noteFullWifiLockAcquiredLocked(); } public void noteFullWifiLockReleasedLocked(int uid) { + mWifiFullLockNesting--; + if (mWifiFullLockNesting == 0) { + mHistoryCur.states &= ~HistoryItem.STATE_WIFI_FULL_LOCK_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "WIFI full lock off to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecordLocked(SystemClock.elapsedRealtime()); + } getUidStatsLocked(uid).noteFullWifiLockReleasedLocked(); } + int mWifiScanLockNesting = 0; + public void noteScanWifiLockAcquiredLocked(int uid) { + if (mWifiScanLockNesting == 0) { + mHistoryCur.states |= HistoryItem.STATE_WIFI_SCAN_LOCK_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "WIFI scan lock on to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecordLocked(SystemClock.elapsedRealtime()); + } + mWifiScanLockNesting++; getUidStatsLocked(uid).noteScanWifiLockAcquiredLocked(); } public void noteScanWifiLockReleasedLocked(int uid) { + mWifiScanLockNesting--; + if (mWifiScanLockNesting == 0) { + mHistoryCur.states &= ~HistoryItem.STATE_WIFI_SCAN_LOCK_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "WIFI scan lock off to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecordLocked(SystemClock.elapsedRealtime()); + } getUidStatsLocked(uid).noteScanWifiLockReleasedLocked(); } + int mWifiMulticastNesting = 0; + public void noteWifiMulticastEnabledLocked(int uid) { + if (mWifiMulticastNesting == 0) { + mHistoryCur.states |= HistoryItem.STATE_WIFI_MULTICAST_ON_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "WIFI multicast on to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecordLocked(SystemClock.elapsedRealtime()); + } + mWifiMulticastNesting++; getUidStatsLocked(uid).noteWifiMulticastEnabledLocked(); } public void noteWifiMulticastDisabledLocked(int uid) { + mWifiMulticastNesting--; + if (mWifiMulticastNesting == 0) { + mHistoryCur.states &= ~HistoryItem.STATE_WIFI_MULTICAST_ON_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "WIFI multicast off to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecordLocked(SystemClock.elapsedRealtime()); + } getUidStatsLocked(uid).noteWifiMulticastDisabledLocked(); } + public void noteFullWifiLockAcquiredFromSourceLocked(WorkSource ws) { + int N = ws.size(); + for (int i=0; i<N; i++) { + noteFullWifiLockAcquiredLocked(ws.get(i)); + } + } + + public void noteFullWifiLockReleasedFromSourceLocked(WorkSource ws) { + int N = ws.size(); + for (int i=0; i<N; i++) { + noteFullWifiLockReleasedLocked(ws.get(i)); + } + } + + public void noteScanWifiLockAcquiredFromSourceLocked(WorkSource ws) { + int N = ws.size(); + for (int i=0; i<N; i++) { + noteScanWifiLockAcquiredLocked(ws.get(i)); + } + } + + public void noteScanWifiLockReleasedFromSourceLocked(WorkSource ws) { + int N = ws.size(); + for (int i=0; i<N; i++) { + noteScanWifiLockReleasedLocked(ws.get(i)); + } + } + + public void noteWifiMulticastEnabledFromSourceLocked(WorkSource ws) { + int N = ws.size(); + for (int i=0; i<N; i++) { + noteWifiMulticastEnabledLocked(ws.get(i)); + } + } + + public void noteWifiMulticastDisabledFromSourceLocked(WorkSource ws) { + int N = ws.size(); + for (int i=0; i<N; i++) { + noteWifiMulticastDisabledLocked(ws.get(i)); + } + } + @Override public long getScreenOnTime(long batteryRealtime, int which) { return mScreenOnTimer.getTotalTimeLocked(batteryRealtime, which); } @@ -1489,15 +2130,25 @@ public final class BatteryStatsImpl extends BatteryStats { */ final HashMap<String, Pkg> mPackageStats = new HashMap<String, Pkg>(); + /** + * The transient wake stats we have collected for this uid's pids. + */ + final SparseArray<Pid> mPids = new SparseArray<Pid>(); + public Uid(int uid) { mUid = uid; - mWifiTurnedOnTimer = new StopwatchTimer(WIFI_TURNED_ON, null, mUnpluggables); - mFullWifiLockTimer = new StopwatchTimer(FULL_WIFI_LOCK, null, mUnpluggables); - mScanWifiLockTimer = new StopwatchTimer(SCAN_WIFI_LOCK, null, mUnpluggables); - mWifiMulticastTimer = new StopwatchTimer(WIFI_MULTICAST_ENABLED, + mWifiTurnedOnTimer = new StopwatchTimer(Uid.this, WIFI_TURNED_ON, + null, mUnpluggables); + mFullWifiLockTimer = new StopwatchTimer(Uid.this, FULL_WIFI_LOCK, + null, mUnpluggables); + mScanWifiLockTimer = new StopwatchTimer(Uid.this, SCAN_WIFI_LOCK, + null, mUnpluggables); + mWifiMulticastTimer = new StopwatchTimer(Uid.this, WIFI_MULTICAST_ENABLED, + null, mUnpluggables); + mAudioTurnedOnTimer = new StopwatchTimer(Uid.this, AUDIO_TURNED_ON, + null, mUnpluggables); + mVideoTurnedOnTimer = new StopwatchTimer(Uid.this, VIDEO_TURNED_ON, null, mUnpluggables); - mAudioTurnedOnTimer = new StopwatchTimer(AUDIO_TURNED_ON, null, mUnpluggables); - mVideoTurnedOnTimer = new StopwatchTimer(VIDEO_TURNED_ON, null, mUnpluggables); } @Override @@ -1531,9 +2182,9 @@ public final class BatteryStatsImpl extends BatteryStats { return mLoadedTcpBytesReceived; } else { long current = computeCurrentTcpBytesReceived(); - if (which == STATS_UNPLUGGED) { + if (which == STATS_SINCE_UNPLUGGED) { current -= mTcpBytesReceivedAtLastUnplug; - } else if (which == STATS_TOTAL) { + } else if (which == STATS_SINCE_CHARGED) { current += mLoadedTcpBytesReceived; } return current; @@ -1551,9 +2202,9 @@ public final class BatteryStatsImpl extends BatteryStats { return mLoadedTcpBytesSent; } else { long current = computeCurrentTcpBytesSent(); - if (which == STATS_UNPLUGGED) { + if (which == STATS_SINCE_UNPLUGGED) { current -= mTcpBytesSentAtLastUnplug; - } else if (which == STATS_TOTAL) { + } else if (which == STATS_SINCE_CHARGED) { current += mLoadedTcpBytesSent; } return current; @@ -1564,6 +2215,10 @@ public final class BatteryStatsImpl extends BatteryStats { public void noteWifiTurnedOnLocked() { if (!mWifiTurnedOn) { mWifiTurnedOn = true; + if (mWifiTurnedOnTimer == null) { + mWifiTurnedOnTimer = new StopwatchTimer(Uid.this, WIFI_TURNED_ON, + null, mUnpluggables); + } mWifiTurnedOnTimer.startRunningLocked(BatteryStatsImpl.this); } } @@ -1580,43 +2235,15 @@ public final class BatteryStatsImpl extends BatteryStats { public void noteFullWifiLockAcquiredLocked() { if (!mFullWifiLockOut) { mFullWifiLockOut = true; + if (mFullWifiLockTimer == null) { + mFullWifiLockTimer = new StopwatchTimer(Uid.this, FULL_WIFI_LOCK, + null, mUnpluggables); + } mFullWifiLockTimer.startRunningLocked(BatteryStatsImpl.this); } } @Override - public void noteVideoTurnedOnLocked() { - if (!mVideoTurnedOn) { - mVideoTurnedOn = true; - mVideoTurnedOnTimer.startRunningLocked(BatteryStatsImpl.this); - } - } - - @Override - public void noteVideoTurnedOffLocked() { - if (mVideoTurnedOn) { - mVideoTurnedOn = false; - mVideoTurnedOnTimer.stopRunningLocked(BatteryStatsImpl.this); - } - } - - @Override - public void noteAudioTurnedOnLocked() { - if (!mAudioTurnedOn) { - mAudioTurnedOn = true; - mAudioTurnedOnTimer.startRunningLocked(BatteryStatsImpl.this); - } - } - - @Override - public void noteAudioTurnedOffLocked() { - if (mAudioTurnedOn) { - mAudioTurnedOn = false; - mAudioTurnedOnTimer.stopRunningLocked(BatteryStatsImpl.this); - } - } - - @Override public void noteFullWifiLockReleasedLocked() { if (mFullWifiLockOut) { mFullWifiLockOut = false; @@ -1628,6 +2255,10 @@ public final class BatteryStatsImpl extends BatteryStats { public void noteScanWifiLockAcquiredLocked() { if (!mScanWifiLockOut) { mScanWifiLockOut = true; + if (mScanWifiLockTimer == null) { + mScanWifiLockTimer = new StopwatchTimer(Uid.this, SCAN_WIFI_LOCK, + null, mUnpluggables); + } mScanWifiLockTimer.startRunningLocked(BatteryStatsImpl.this); } } @@ -1644,6 +2275,10 @@ public final class BatteryStatsImpl extends BatteryStats { public void noteWifiMulticastEnabledLocked() { if (!mWifiMulticastEnabled) { mWifiMulticastEnabled = true; + if (mWifiMulticastTimer == null) { + mWifiMulticastTimer = new StopwatchTimer(Uid.this, WIFI_MULTICAST_ENABLED, + null, mUnpluggables); + } mWifiMulticastTimer.startRunningLocked(BatteryStatsImpl.this); } } @@ -1656,37 +2291,95 @@ public final class BatteryStatsImpl extends BatteryStats { } } - @Override - public long getWifiTurnedOnTime(long batteryRealtime, int which) { - return mWifiTurnedOnTimer.getTotalTimeLocked(batteryRealtime, which); + @Override + public void noteAudioTurnedOnLocked() { + if (!mAudioTurnedOn) { + mAudioTurnedOn = true; + if (mAudioTurnedOnTimer == null) { + mAudioTurnedOnTimer = new StopwatchTimer(Uid.this, AUDIO_TURNED_ON, + null, mUnpluggables); + } + mAudioTurnedOnTimer.startRunningLocked(BatteryStatsImpl.this); + } } - @Override - public long getAudioTurnedOnTime(long batteryRealtime, int which) { - return mAudioTurnedOnTimer.getTotalTimeLocked(batteryRealtime, which); + @Override + public void noteAudioTurnedOffLocked() { + if (mAudioTurnedOn) { + mAudioTurnedOn = false; + mAudioTurnedOnTimer.stopRunningLocked(BatteryStatsImpl.this); + } + } + + @Override + public void noteVideoTurnedOnLocked() { + if (!mVideoTurnedOn) { + mVideoTurnedOn = true; + if (mVideoTurnedOnTimer == null) { + mVideoTurnedOnTimer = new StopwatchTimer(Uid.this, VIDEO_TURNED_ON, + null, mUnpluggables); + } + mVideoTurnedOnTimer.startRunningLocked(BatteryStatsImpl.this); + } + } + + @Override + public void noteVideoTurnedOffLocked() { + if (mVideoTurnedOn) { + mVideoTurnedOn = false; + mVideoTurnedOnTimer.stopRunningLocked(BatteryStatsImpl.this); + } } @Override - public long getVideoTurnedOnTime(long batteryRealtime, int which) { - return mVideoTurnedOnTimer.getTotalTimeLocked(batteryRealtime, which); + public long getWifiTurnedOnTime(long batteryRealtime, int which) { + if (mWifiTurnedOnTimer == null) { + return 0; + } + return mWifiTurnedOnTimer.getTotalTimeLocked(batteryRealtime, which); } @Override public long getFullWifiLockTime(long batteryRealtime, int which) { + if (mFullWifiLockTimer == null) { + return 0; + } return mFullWifiLockTimer.getTotalTimeLocked(batteryRealtime, which); } @Override public long getScanWifiLockTime(long batteryRealtime, int which) { + if (mScanWifiLockTimer == null) { + return 0; + } return mScanWifiLockTimer.getTotalTimeLocked(batteryRealtime, which); } @Override public long getWifiMulticastTime(long batteryRealtime, int which) { + if (mWifiMulticastTimer == null) { + return 0; + } return mWifiMulticastTimer.getTotalTimeLocked(batteryRealtime, which); } + @Override + public long getAudioTurnedOnTime(long batteryRealtime, int which) { + if (mAudioTurnedOnTimer == null) { + return 0; + } + return mAudioTurnedOnTimer.getTotalTimeLocked(batteryRealtime, which); + } + + @Override + public long getVideoTurnedOnTime(long batteryRealtime, int which) { + if (mVideoTurnedOnTimer == null) { + return 0; + } + return mVideoTurnedOnTimer.getTotalTimeLocked(batteryRealtime, which); + } + @Override public void noteUserActivityLocked(int type) { if (mUserActivityCounters == null) { @@ -1722,6 +2415,136 @@ public final class BatteryStatsImpl extends BatteryStats { ? (TrafficStats.getUidTxBytes(mUid) - mStartedTcpBytesSent) : 0); } + /** + * Clear all stats for this uid. Returns true if the uid is completely + * inactive so can be dropped. + */ + boolean reset() { + boolean active = false; + + if (mWifiTurnedOnTimer != null) { + active |= !mWifiTurnedOnTimer.reset(BatteryStatsImpl.this, false); + active |= mWifiTurnedOn; + } + if (mFullWifiLockTimer != null) { + active |= !mFullWifiLockTimer.reset(BatteryStatsImpl.this, false); + active |= mFullWifiLockOut; + } + if (mScanWifiLockTimer != null) { + active |= !mScanWifiLockTimer.reset(BatteryStatsImpl.this, false); + active |= mScanWifiLockOut; + } + if (mWifiMulticastTimer != null) { + active |= !mWifiMulticastTimer.reset(BatteryStatsImpl.this, false); + active |= mWifiMulticastEnabled; + } + if (mAudioTurnedOnTimer != null) { + active |= !mAudioTurnedOnTimer.reset(BatteryStatsImpl.this, false); + active |= mAudioTurnedOn; + } + if (mVideoTurnedOnTimer != null) { + active |= !mVideoTurnedOnTimer.reset(BatteryStatsImpl.this, false); + active |= mVideoTurnedOn; + } + + mLoadedTcpBytesReceived = mLoadedTcpBytesSent = 0; + mCurrentTcpBytesReceived = mCurrentTcpBytesSent = 0; + + if (mUserActivityCounters != null) { + for (int i=0; i<NUM_USER_ACTIVITY_TYPES; i++) { + mUserActivityCounters[i].reset(false); + } + } + + if (mWakelockStats.size() > 0) { + Iterator<Map.Entry<String, Wakelock>> it = mWakelockStats.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry<String, Wakelock> wakelockEntry = it.next(); + Wakelock wl = wakelockEntry.getValue(); + if (wl.reset()) { + it.remove(); + } else { + active = true; + } + } + } + if (mSensorStats.size() > 0) { + Iterator<Map.Entry<Integer, Sensor>> it = mSensorStats.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry<Integer, Sensor> sensorEntry = it.next(); + Sensor s = sensorEntry.getValue(); + if (s.reset()) { + it.remove(); + } else { + active = true; + } + } + } + if (mProcessStats.size() > 0) { + Iterator<Map.Entry<String, Proc>> it = mProcessStats.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry<String, Proc> procEntry = it.next(); + procEntry.getValue().detach(); + } + mProcessStats.clear(); + } + if (mPids.size() > 0) { + for (int i=0; !active && i<mPids.size(); i++) { + Pid pid = mPids.valueAt(i); + if (pid.mWakeStart != 0) { + active = true; + } + } + } + if (mPackageStats.size() > 0) { + Iterator<Map.Entry<String, Pkg>> it = mPackageStats.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry<String, Pkg> pkgEntry = it.next(); + Pkg p = pkgEntry.getValue(); + p.detach(); + if (p.mServiceStats.size() > 0) { + Iterator<Map.Entry<String, Pkg.Serv>> it2 + = p.mServiceStats.entrySet().iterator(); + while (it2.hasNext()) { + Map.Entry<String, Pkg.Serv> servEntry = it2.next(); + servEntry.getValue().detach(); + } + } + } + mPackageStats.clear(); + } + + mPids.clear(); + + if (!active) { + if (mWifiTurnedOnTimer != null) { + mWifiTurnedOnTimer.detach(); + } + if (mFullWifiLockTimer != null) { + mFullWifiLockTimer.detach(); + } + if (mScanWifiLockTimer != null) { + mScanWifiLockTimer.detach(); + } + if (mWifiMulticastTimer != null) { + mWifiMulticastTimer.detach(); + } + if (mAudioTurnedOnTimer != null) { + mAudioTurnedOnTimer.detach(); + } + if (mVideoTurnedOnTimer != null) { + mVideoTurnedOnTimer.detach(); + } + if (mUserActivityCounters != null) { + for (int i=0; i<NUM_USER_ACTIVITY_TYPES; i++) { + mUserActivityCounters[i].detach(); + } + } + } + + return !active; + } + void writeToParcelLocked(Parcel out, long batteryRealtime) { out.writeInt(mWakelockStats.size()); for (Map.Entry<String, Uid.Wakelock> wakelockEntry : mWakelockStats.entrySet()) { @@ -1757,19 +2580,49 @@ public final class BatteryStatsImpl extends BatteryStats { out.writeLong(computeCurrentTcpBytesSent()); out.writeLong(mTcpBytesReceivedAtLastUnplug); out.writeLong(mTcpBytesSentAtLastUnplug); - mWifiTurnedOnTimer.writeToParcel(out, batteryRealtime); - mFullWifiLockTimer.writeToParcel(out, batteryRealtime); - mAudioTurnedOnTimer.writeToParcel(out, batteryRealtime); - mVideoTurnedOnTimer.writeToParcel(out, batteryRealtime); - mScanWifiLockTimer.writeToParcel(out, batteryRealtime); - mWifiMulticastTimer.writeToParcel(out, batteryRealtime); - if (mUserActivityCounters == null) { + if (mWifiTurnedOnTimer != null) { + out.writeInt(1); + mWifiTurnedOnTimer.writeToParcel(out, batteryRealtime); + } else { out.writeInt(0); + } + if (mFullWifiLockTimer != null) { + out.writeInt(1); + mFullWifiLockTimer.writeToParcel(out, batteryRealtime); } else { + out.writeInt(0); + } + if (mScanWifiLockTimer != null) { + out.writeInt(1); + mScanWifiLockTimer.writeToParcel(out, batteryRealtime); + } else { + out.writeInt(0); + } + if (mWifiMulticastTimer != null) { + out.writeInt(1); + mWifiMulticastTimer.writeToParcel(out, batteryRealtime); + } else { + out.writeInt(0); + } + if (mAudioTurnedOnTimer != null) { + out.writeInt(1); + mAudioTurnedOnTimer.writeToParcel(out, batteryRealtime); + } else { + out.writeInt(0); + } + if (mVideoTurnedOnTimer != null) { + out.writeInt(1); + mVideoTurnedOnTimer.writeToParcel(out, batteryRealtime); + } else { + out.writeInt(0); + } + if (mUserActivityCounters != null) { out.writeInt(1); for (int i=0; i<NUM_USER_ACTIVITY_TYPES; i++) { mUserActivityCounters[i].writeToParcel(out); } + } else { + out.writeInt(0); } } @@ -1822,25 +2675,54 @@ public final class BatteryStatsImpl extends BatteryStats { mTcpBytesReceivedAtLastUnplug = in.readLong(); mTcpBytesSentAtLastUnplug = in.readLong(); mWifiTurnedOn = false; - mWifiTurnedOnTimer = new StopwatchTimer(WIFI_TURNED_ON, null, mUnpluggables, in); + if (in.readInt() != 0) { + mWifiTurnedOnTimer = new StopwatchTimer(Uid.this, WIFI_TURNED_ON, + null, mUnpluggables, in); + } else { + mWifiTurnedOnTimer = null; + } mFullWifiLockOut = false; - mFullWifiLockTimer = new StopwatchTimer(FULL_WIFI_LOCK, null, mUnpluggables, in); - mAudioTurnedOn = false; - mAudioTurnedOnTimer = new StopwatchTimer(AUDIO_TURNED_ON, null, mUnpluggables, in); - mVideoTurnedOn = false; - mVideoTurnedOnTimer = new StopwatchTimer(VIDEO_TURNED_ON, null, mUnpluggables, in); + if (in.readInt() != 0) { + mFullWifiLockTimer = new StopwatchTimer(Uid.this, FULL_WIFI_LOCK, + null, mUnpluggables, in); + } else { + mFullWifiLockTimer = null; + } mScanWifiLockOut = false; - mScanWifiLockTimer = new StopwatchTimer(SCAN_WIFI_LOCK, null, mUnpluggables, in); + if (in.readInt() != 0) { + mScanWifiLockTimer = new StopwatchTimer(Uid.this, SCAN_WIFI_LOCK, + null, mUnpluggables, in); + } else { + mScanWifiLockTimer = null; + } mWifiMulticastEnabled = false; - mWifiMulticastTimer = new StopwatchTimer(WIFI_MULTICAST_ENABLED, - null, mUnpluggables, in); - if (in.readInt() == 0) { - mUserActivityCounters = null; + if (in.readInt() != 0) { + mWifiMulticastTimer = new StopwatchTimer(Uid.this, WIFI_MULTICAST_ENABLED, + null, mUnpluggables, in); + } else { + mWifiMulticastTimer = null; + } + mAudioTurnedOn = false; + if (in.readInt() != 0) { + mAudioTurnedOnTimer = new StopwatchTimer(Uid.this, AUDIO_TURNED_ON, + null, mUnpluggables, in); } else { + mAudioTurnedOnTimer = null; + } + mVideoTurnedOn = false; + if (in.readInt() != 0) { + mVideoTurnedOnTimer = new StopwatchTimer(Uid.this, VIDEO_TURNED_ON, + null, mUnpluggables, in); + } else { + mVideoTurnedOnTimer = null; + } + if (in.readInt() != 0) { mUserActivityCounters = new Counter[NUM_USER_ACTIVITY_TYPES]; for (int i=0; i<NUM_USER_ACTIVITY_TYPES; i++) { mUserActivityCounters[i] = new Counter(mUnpluggables, in); } + } else { + mUserActivityCounters = null; } } @@ -1876,9 +2758,37 @@ public final class BatteryStatsImpl extends BatteryStats { return null; } - return new StopwatchTimer(type, pool, unpluggables, in); + return new StopwatchTimer(Uid.this, type, pool, unpluggables, in); } + boolean reset() { + boolean wlactive = false; + if (mTimerFull != null) { + wlactive |= !mTimerFull.reset(BatteryStatsImpl.this, false); + } + if (mTimerPartial != null) { + wlactive |= !mTimerPartial.reset(BatteryStatsImpl.this, false); + } + if (mTimerWindow != null) { + wlactive |= !mTimerWindow.reset(BatteryStatsImpl.this, false); + } + if (!wlactive) { + if (mTimerFull != null) { + mTimerFull.detach(); + mTimerFull = null; + } + if (mTimerPartial != null) { + mTimerPartial.detach(); + mTimerPartial = null; + } + if (mTimerWindow != null) { + mTimerWindow.detach(); + mTimerWindow = null; + } + } + return !wlactive; + } + void readFromParcelLocked(ArrayList<Unpluggable> unpluggables, Parcel in) { mTimerPartial = readTimerFromParcel(WAKE_TYPE_PARTIAL, mPartialTimers, unpluggables, in); @@ -1924,9 +2834,17 @@ public final class BatteryStatsImpl extends BatteryStats { pool = new ArrayList<StopwatchTimer>(); mSensorTimers.put(mHandle, pool); } - return new StopwatchTimer(0, pool, unpluggables, in); + return new StopwatchTimer(Uid.this, 0, pool, unpluggables, in); } + boolean reset() { + if (mTimer.reset(BatteryStatsImpl.this, true)) { + mTimer = null; + return true; + } + return false; + } + void readFromParcelLocked(ArrayList<Unpluggable> unpluggables, Parcel in) { mTimer = readTimerFromParcel(unpluggables, in); } @@ -2032,12 +2950,11 @@ public final class BatteryStatsImpl extends BatteryStats { SamplingCounter[] mSpeedBins; + ArrayList<ExcessiveWake> mExcessiveWake; + Proc() { mUnpluggables.add(this); mSpeedBins = new SamplingCounter[getCpuSpeedSteps()]; - for (int i = 0; i < mSpeedBins.length; i++) { - mSpeedBins[i] = new SamplingCounter(mUnpluggables); - } } public void unplug(long batteryUptime, long batteryRealtime) { @@ -2050,10 +2967,70 @@ public final class BatteryStatsImpl extends BatteryStats { public void plug(long batteryUptime, long batteryRealtime) { } + void detach() { + mUnpluggables.remove(this); + for (int i = 0; i < mSpeedBins.length; i++) { + SamplingCounter c = mSpeedBins[i]; + if (c != null) { + mUnpluggables.remove(c); + mSpeedBins[i] = null; + } + } + } + + public int countExcessiveWakes() { + return mExcessiveWake != null ? mExcessiveWake.size() : 0; + } + + public ExcessiveWake getExcessiveWake(int i) { + if (mExcessiveWake != null) { + return mExcessiveWake.get(i); + } + return null; + } + + public void addExcessiveWake(long overTime, long usedTime) { + if (mExcessiveWake == null) { + mExcessiveWake = new ArrayList<ExcessiveWake>(); + } + ExcessiveWake ew = new ExcessiveWake(); + ew.overTime = overTime; + ew.usedTime = usedTime; + mExcessiveWake.add(ew); + } + + void writeExcessiveWakeToParcelLocked(Parcel out) { + if (mExcessiveWake == null) { + out.writeInt(0); + return; + } + + final int N = mExcessiveWake.size(); + out.writeInt(N); + for (int i=0; i<N; i++) { + ExcessiveWake ew = mExcessiveWake.get(i); + out.writeLong(ew.overTime); + out.writeLong(ew.usedTime); + } + } + + void readExcessiveWakeFromParcelLocked(Parcel in) { + final int N = in.readInt(); + if (N == 0) { + mExcessiveWake = null; + return; + } + + mExcessiveWake = new ArrayList<ExcessiveWake>(); + for (int i=0; i<N; i++) { + ExcessiveWake ew = new ExcessiveWake(); + ew.overTime = in.readLong(); + ew.usedTime = in.readLong(); + mExcessiveWake.add(ew); + } + } + void writeToParcelLocked(Parcel out) { - final long uSecRealtime = SystemClock.elapsedRealtime() * 1000; - final long batteryRealtime = getBatteryRealtimeLocked(uSecRealtime); - out.writeLong(mUserTime); out.writeLong(mSystemTime); out.writeLong(mForegroundTime); @@ -2062,10 +3039,6 @@ public final class BatteryStatsImpl extends BatteryStats { out.writeLong(mLoadedSystemTime); out.writeLong(mLoadedForegroundTime); out.writeInt(mLoadedStarts); - out.writeLong(mLastUserTime); - out.writeLong(mLastSystemTime); - out.writeLong(mLastForegroundTime); - out.writeInt(mLastStarts); out.writeLong(mUnpluggedUserTime); out.writeLong(mUnpluggedSystemTime); out.writeLong(mUnpluggedForegroundTime); @@ -2073,8 +3046,16 @@ public final class BatteryStatsImpl extends BatteryStats { out.writeInt(mSpeedBins.length); for (int i = 0; i < mSpeedBins.length; i++) { - mSpeedBins[i].writeToParcel(out); + SamplingCounter c = mSpeedBins[i]; + if (c != null) { + out.writeInt(1); + c.writeToParcel(out); + } else { + out.writeInt(0); + } } + + writeExcessiveWakeToParcelLocked(out); } void readFromParcelLocked(Parcel in) { @@ -2086,20 +3067,25 @@ public final class BatteryStatsImpl extends BatteryStats { mLoadedSystemTime = in.readLong(); mLoadedForegroundTime = in.readLong(); mLoadedStarts = in.readInt(); - mLastUserTime = in.readLong(); - mLastSystemTime = in.readLong(); - mLastForegroundTime = in.readLong(); - mLastStarts = in.readInt(); + mLastUserTime = 0; + mLastSystemTime = 0; + mLastForegroundTime = 0; + mLastStarts = 0; mUnpluggedUserTime = in.readLong(); mUnpluggedSystemTime = in.readLong(); mUnpluggedForegroundTime = in.readLong(); mUnpluggedStarts = in.readInt(); int bins = in.readInt(); - mSpeedBins = new SamplingCounter[bins]; + int steps = getCpuSpeedSteps(); + mSpeedBins = new SamplingCounter[bins >= steps ? bins : steps]; for (int i = 0; i < bins; i++) { - mSpeedBins[i] = new SamplingCounter(mUnpluggables, in); + if (in.readInt() != 0) { + mSpeedBins[i] = new SamplingCounter(mUnpluggables, in); + } } + + readExcessiveWakeFromParcelLocked(in); } public BatteryStatsImpl getBatteryStats() { @@ -2128,7 +3114,7 @@ public final class BatteryStatsImpl extends BatteryStats { val = mUserTime; if (which == STATS_CURRENT) { val -= mLoadedUserTime; - } else if (which == STATS_UNPLUGGED) { + } else if (which == STATS_SINCE_UNPLUGGED) { val -= mUnpluggedUserTime; } } @@ -2144,7 +3130,7 @@ public final class BatteryStatsImpl extends BatteryStats { val = mSystemTime; if (which == STATS_CURRENT) { val -= mLoadedSystemTime; - } else if (which == STATS_UNPLUGGED) { + } else if (which == STATS_SINCE_UNPLUGGED) { val -= mUnpluggedSystemTime; } } @@ -2160,7 +3146,7 @@ public final class BatteryStatsImpl extends BatteryStats { val = mForegroundTime; if (which == STATS_CURRENT) { val -= mLoadedForegroundTime; - } else if (which == STATS_UNPLUGGED) { + } else if (which == STATS_SINCE_UNPLUGGED) { val -= mUnpluggedForegroundTime; } } @@ -2176,7 +3162,7 @@ public final class BatteryStatsImpl extends BatteryStats { val = mStarts; if (which == STATS_CURRENT) { val -= mLoadedStarts; - } else if (which == STATS_UNPLUGGED) { + } else if (which == STATS_SINCE_UNPLUGGED) { val -= mUnpluggedStarts; } } @@ -2186,14 +3172,22 @@ public final class BatteryStatsImpl extends BatteryStats { /* Called by ActivityManagerService when CPU times are updated. */ public void addSpeedStepTimes(long[] values) { for (int i = 0; i < mSpeedBins.length && i < values.length; i++) { - mSpeedBins[i].addCountAtomic(values[i]); + long amt = values[i]; + if (amt != 0) { + SamplingCounter c = mSpeedBins[i]; + if (c == null) { + mSpeedBins[i] = c = new SamplingCounter(mUnpluggables); + } + c.addCountAtomic(values[i]); + } } } @Override public long getTimeAtCpuSpeedStep(int speedStep, int which) { if (speedStep < mSpeedBins.length) { - return mSpeedBins[speedStep].getCountLocked(which); + SamplingCounter c = mSpeedBins[speedStep]; + return c != null ? c.getCountLocked(which) : 0; } else { return 0; } @@ -2244,10 +3238,14 @@ public final class BatteryStatsImpl extends BatteryStats { public void plug(long batteryUptime, long batteryRealtime) { } + void detach() { + mUnpluggables.remove(this); + } + void readFromParcelLocked(Parcel in) { mWakeups = in.readInt(); mLoadedWakeups = in.readInt(); - mLastWakeups = in.readInt(); + mLastWakeups = 0; mUnpluggedWakeups = in.readInt(); int numServs = in.readInt(); @@ -2264,7 +3262,6 @@ public final class BatteryStatsImpl extends BatteryStats { void writeToParcelLocked(Parcel out) { out.writeInt(mWakeups); out.writeInt(mLoadedWakeups); - out.writeInt(mLastWakeups); out.writeInt(mUnpluggedWakeups); out.writeInt(mServiceStats.size()); @@ -2290,7 +3287,7 @@ public final class BatteryStatsImpl extends BatteryStats { val = mWakeups; if (which == STATS_CURRENT) { val -= mLoadedWakeups; - } else if (which == STATS_UNPLUGGED) { + } else if (which == STATS_SINCE_UNPLUGGED) { val -= mUnpluggedWakeups; } } @@ -2405,6 +3402,10 @@ public final class BatteryStatsImpl extends BatteryStats { public void plug(long batteryUptime, long batteryRealtime) { } + void detach() { + mUnpluggables.remove(this); + } + void readFromParcelLocked(Parcel in) { mStartTime = in.readLong(); mRunningSince = in.readLong(); @@ -2417,9 +3418,9 @@ public final class BatteryStatsImpl extends BatteryStats { mLoadedStartTime = in.readLong(); mLoadedStarts = in.readInt(); mLoadedLaunches = in.readInt(); - mLastStartTime = in.readLong(); - mLastStarts = in.readInt(); - mLastLaunches = in.readInt(); + mLastStartTime = 0; + mLastStarts = 0; + mLastLaunches = 0; mUnpluggedStartTime = in.readLong(); mUnpluggedStarts = in.readInt(); mUnpluggedLaunches = in.readInt(); @@ -2437,9 +3438,6 @@ public final class BatteryStatsImpl extends BatteryStats { out.writeLong(mLoadedStartTime); out.writeInt(mLoadedStarts); out.writeInt(mLoadedLaunches); - out.writeLong(mLastStartTime); - out.writeInt(mLastStarts); - out.writeInt(mLastLaunches); out.writeLong(mUnpluggedStartTime); out.writeInt(mUnpluggedStarts); out.writeInt(mUnpluggedLaunches); @@ -2509,7 +3507,7 @@ public final class BatteryStatsImpl extends BatteryStats { val = mLaunches; if (which == STATS_CURRENT) { val -= mLoadedLaunches; - } else if (which == STATS_UNPLUGGED) { + } else if (which == STATS_SINCE_UNPLUGGED) { val -= mUnpluggedLaunches; } } @@ -2526,7 +3524,7 @@ public final class BatteryStatsImpl extends BatteryStats { val = getStartTimeToNowLocked(now); if (which == STATS_CURRENT) { val -= mLoadedStartTime; - } else if (which == STATS_UNPLUGGED) { + } else if (which == STATS_SINCE_UNPLUGGED) { val -= mUnpluggedStartTime; } } @@ -2543,7 +3541,7 @@ public final class BatteryStatsImpl extends BatteryStats { val = mStarts; if (which == STATS_CURRENT) { val -= mLoadedStarts; - } else if (which == STATS_UNPLUGGED) { + } else if (which == STATS_SINCE_UNPLUGGED) { val -= mUnpluggedStarts; } } @@ -2579,6 +3577,19 @@ public final class BatteryStatsImpl extends BatteryStats { return ps; } + public SparseArray<? extends Pid> getPidStats() { + return mPids; + } + + public Pid getPidStatsLocked(int pid) { + Pid p = mPids.get(pid); + if (p == null) { + p = new Pid(); + mPids.put(pid, p); + } + return p; + } + /** * Retrieve the statistics object for a particular service, creating * if needed. @@ -2625,21 +3636,24 @@ public final class BatteryStatsImpl extends BatteryStats { case WAKE_TYPE_PARTIAL: t = wl.mTimerPartial; if (t == null) { - t = new StopwatchTimer(WAKE_TYPE_PARTIAL, mPartialTimers, mUnpluggables); + t = new StopwatchTimer(Uid.this, WAKE_TYPE_PARTIAL, + mPartialTimers, mUnpluggables); wl.mTimerPartial = t; } return t; case WAKE_TYPE_FULL: t = wl.mTimerFull; if (t == null) { - t = new StopwatchTimer(WAKE_TYPE_FULL, mFullTimers, mUnpluggables); + t = new StopwatchTimer(Uid.this, WAKE_TYPE_FULL, + mFullTimers, mUnpluggables); wl.mTimerFull = t; } return t; case WAKE_TYPE_WINDOW: t = wl.mTimerWindow; if (t == null) { - t = new StopwatchTimer(WAKE_TYPE_WINDOW, mWindowTimers, mUnpluggables); + t = new StopwatchTimer(Uid.this, WAKE_TYPE_WINDOW, + mWindowTimers, mUnpluggables); wl.mTimerWindow = t; } return t; @@ -2666,23 +3680,43 @@ public final class BatteryStatsImpl extends BatteryStats { timers = new ArrayList<StopwatchTimer>(); mSensorTimers.put(sensor, timers); } - t = new StopwatchTimer(BatteryStats.SENSOR, timers, mUnpluggables); + t = new StopwatchTimer(Uid.this, BatteryStats.SENSOR, timers, mUnpluggables); se.mTimer = t; return t; } - public void noteStartWakeLocked(String name, int type) { + public void noteStartWakeLocked(int pid, String name, int type) { StopwatchTimer t = getWakeTimerLocked(name, type); if (t != null) { t.startRunningLocked(BatteryStatsImpl.this); } + if (pid >= 0 && type == WAKE_TYPE_PARTIAL) { + Pid p = getPidStatsLocked(pid); + if (p.mWakeStart == 0) { + p.mWakeStart = SystemClock.elapsedRealtime(); + } + } } - public void noteStopWakeLocked(String name, int type) { + public void noteStopWakeLocked(int pid, String name, int type) { StopwatchTimer t = getWakeTimerLocked(name, type); if (t != null) { t.stopRunningLocked(BatteryStatsImpl.this); } + if (pid >= 0 && type == WAKE_TYPE_PARTIAL) { + Pid p = mPids.get(pid); + if (p != null && p.mWakeStart != 0) { + p.mWakeSum += SystemClock.elapsedRealtime() - p.mWakeStart; + p.mWakeStart = 0; + } + } + } + + public void reportExcessiveWakeLocked(String proc, long overTime, long usedTime) { + Proc p = getProcessStatsLocked(proc); + if (p != null) { + p.addExcessiveWake(overTime, usedTime); + } } public void noteStartSensor(int sensor) { @@ -2721,25 +3755,28 @@ public final class BatteryStatsImpl extends BatteryStats { public BatteryStatsImpl(String filename) { mFile = new JournaledFile(new File(filename), new File(filename + ".tmp")); + mHandler = new MyHandler(); mStartCount++; - mScreenOnTimer = new StopwatchTimer(-1, null, mUnpluggables); + mScreenOnTimer = new StopwatchTimer(null, -1, null, mUnpluggables); for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) { - mScreenBrightnessTimer[i] = new StopwatchTimer(-100-i, null, mUnpluggables); + mScreenBrightnessTimer[i] = new StopwatchTimer(null, -100-i, null, mUnpluggables); } mInputEventCounter = new Counter(mUnpluggables); - mPhoneOnTimer = new StopwatchTimer(-2, null, mUnpluggables); + mPhoneOnTimer = new StopwatchTimer(null, -2, null, mUnpluggables); for (int i=0; i<NUM_SIGNAL_STRENGTH_BINS; i++) { - mPhoneSignalStrengthsTimer[i] = new StopwatchTimer(-200-i, null, mUnpluggables); + mPhoneSignalStrengthsTimer[i] = new StopwatchTimer(null, -200-i, null, mUnpluggables); } - mPhoneSignalScanningTimer = new StopwatchTimer(-200+1, null, mUnpluggables); + mPhoneSignalScanningTimer = new StopwatchTimer(null, -200+1, null, mUnpluggables); for (int i=0; i<NUM_DATA_CONNECTION_TYPES; i++) { - mPhoneDataConnectionsTimer[i] = new StopwatchTimer(-300-i, null, mUnpluggables); + mPhoneDataConnectionsTimer[i] = new StopwatchTimer(null, -300-i, null, mUnpluggables); } - mWifiOnTimer = new StopwatchTimer(-3, null, mUnpluggables); - mWifiRunningTimer = new StopwatchTimer(-4, null, mUnpluggables); - mBluetoothOnTimer = new StopwatchTimer(-5, null, mUnpluggables); - mAudioOnTimer = new StopwatchTimer(-6, null, mUnpluggables); + mWifiOnTimer = new StopwatchTimer(null, -3, null, mUnpluggables); + mWifiRunningTimer = new StopwatchTimer(null, -4, null, mUnpluggables); + mBluetoothOnTimer = new StopwatchTimer(null, -5, null, mUnpluggables); + mAudioOnTimer = new StopwatchTimer(null, -6, null, mUnpluggables); + mVideoOnTimer = new StopwatchTimer(null, -7, null, mUnpluggables); mOnBattery = mOnBatteryInternal = false; + initTimes(); mTrackBatteryPastUptime = 0; mTrackBatteryPastRealtime = 0; mUptimeStart = mTrackBatteryUptimeStart = SystemClock.uptimeMillis() * 1000; @@ -2747,14 +3784,22 @@ public final class BatteryStatsImpl extends BatteryStats { mUnpluggedBatteryUptime = getBatteryUptimeLocked(mUptimeStart); mUnpluggedBatteryRealtime = getBatteryRealtimeLocked(mRealtimeStart); mDischargeStartLevel = 0; + mDischargeUnplugLevel = 0; mDischargeCurrentLevel = 0; + mLowDischargeAmountSinceCharge = 0; + mHighDischargeAmountSinceCharge = 0; } public BatteryStatsImpl(Parcel p) { mFile = null; + mHandler = null; readFromParcel(p); } + public void setCallback(BatteryCallback cb) { + mCallback = cb; + } + public void setNumSpeedSteps(int steps) { if (sNumSpeedSteps == 0) sNumSpeedSteps = steps; } @@ -2766,6 +3811,16 @@ public final class BatteryStatsImpl extends BatteryStats { } @Override + public HistoryItem getHistory() { + return mHistory; + } + + @Override + public long getHistoryBaseTime() { + return mHistoryBaseTime; + } + + @Override public int getStartCount() { return mStartCount; } @@ -2774,39 +3829,188 @@ public final class BatteryStatsImpl extends BatteryStats { return mOnBattery; } - public void setOnBattery(boolean onBattery, int level) { + public boolean isScreenOn() { + return mScreenOn; + } + + void initTimes() { + mBatteryRealtime = mTrackBatteryPastUptime = 0; + mBatteryUptime = mTrackBatteryPastRealtime = 0; + mUptimeStart = mTrackBatteryUptimeStart = SystemClock.uptimeMillis() * 1000; + mRealtimeStart = mTrackBatteryRealtimeStart = SystemClock.elapsedRealtime() * 1000; + mUnpluggedBatteryUptime = getBatteryUptimeLocked(mUptimeStart); + mUnpluggedBatteryRealtime = getBatteryRealtimeLocked(mRealtimeStart); + } + + public void resetAllStatsLocked() { + mStartCount = 0; + initTimes(); + mScreenOnTimer.reset(this, false); + for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) { + mScreenBrightnessTimer[i].reset(this, false); + } + mInputEventCounter.reset(false); + mPhoneOnTimer.reset(this, false); + mAudioOnTimer.reset(this, false); + mVideoOnTimer.reset(this, false); + for (int i=0; i<NUM_SIGNAL_STRENGTH_BINS; i++) { + mPhoneSignalStrengthsTimer[i].reset(this, false); + } + mPhoneSignalScanningTimer.reset(this, false); + for (int i=0; i<NUM_DATA_CONNECTION_TYPES; i++) { + mPhoneDataConnectionsTimer[i].reset(this, false); + } + mWifiOnTimer.reset(this, false); + mWifiRunningTimer.reset(this, false); + mBluetoothOnTimer.reset(this, false); + + for (int i=0; i<mUidStats.size(); i++) { + if (mUidStats.valueAt(i).reset()) { + mUidStats.remove(mUidStats.keyAt(i)); + i--; + } + } + + if (mKernelWakelockStats.size() > 0) { + for (SamplingTimer timer : mKernelWakelockStats.values()) { + mUnpluggables.remove(timer); + } + mKernelWakelockStats.clear(); + } + + clearHistoryLocked(); + } + + void setOnBattery(boolean onBattery, int oldStatus, int level) { synchronized(this) { - updateKernelWakelocksLocked(); - if (mOnBattery != onBattery) { - mOnBattery = mOnBatteryInternal = onBattery; - - long uptime = SystemClock.uptimeMillis() * 1000; - long mSecRealtime = SystemClock.elapsedRealtime(); - long realtime = mSecRealtime * 1000; - if (onBattery) { - mTrackBatteryUptimeStart = uptime; - mTrackBatteryRealtimeStart = realtime; - mUnpluggedBatteryUptime = getBatteryUptimeLocked(uptime); - mUnpluggedBatteryRealtime = getBatteryRealtimeLocked(realtime); - mDischargeCurrentLevel = mDischargeStartLevel = level; - doUnplug(mUnpluggedBatteryUptime, mUnpluggedBatteryRealtime); - } else { - mTrackBatteryPastUptime += uptime - mTrackBatteryUptimeStart; - mTrackBatteryPastRealtime += realtime - mTrackBatteryRealtimeStart; - mDischargeCurrentLevel = level; - doPlug(getBatteryUptimeLocked(uptime), getBatteryRealtimeLocked(realtime)); - } - if ((mLastWriteTime + (60 * 1000)) < mSecRealtime) { - if (mFile != null) { - writeLocked(); - } + boolean doWrite = false; + Message m = mHandler.obtainMessage(MSG_REPORT_POWER_CHANGE); + m.arg1 = onBattery ? 1 : 0; + mHandler.sendMessage(m); + mOnBattery = mOnBatteryInternal = onBattery; + + long uptime = SystemClock.uptimeMillis() * 1000; + long mSecRealtime = SystemClock.elapsedRealtime(); + long realtime = mSecRealtime * 1000; + if (onBattery) { + // We will reset our status if we are unplugging after the + // battery was last full, or the level is at 100, or + // we have gone through a significant charge (from a very low + // level to a now very high level). + if (oldStatus == BatteryManager.BATTERY_STATUS_FULL + || level >= 100 + || (mDischargeCurrentLevel < 20 && level > 90)) { + doWrite = true; + resetAllStatsLocked(); + mDischargeStartLevel = level; + mLowDischargeAmountSinceCharge = 0; + mHighDischargeAmountSinceCharge = 0; + } + updateKernelWakelocksLocked(); + mHistoryCur.batteryLevel = (byte)level; + mHistoryCur.states &= ~HistoryItem.STATE_BATTERY_PLUGGED_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "Battery unplugged to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecordLocked(mSecRealtime); + mTrackBatteryUptimeStart = uptime; + mTrackBatteryRealtimeStart = realtime; + mUnpluggedBatteryUptime = getBatteryUptimeLocked(uptime); + mUnpluggedBatteryRealtime = getBatteryRealtimeLocked(realtime); + mDischargeCurrentLevel = mDischargeUnplugLevel = level; + doUnplugLocked(mUnpluggedBatteryUptime, mUnpluggedBatteryRealtime); + } else { + updateKernelWakelocksLocked(); + mHistoryCur.batteryLevel = (byte)level; + mHistoryCur.states |= HistoryItem.STATE_BATTERY_PLUGGED_FLAG; + if (DEBUG_HISTORY) Slog.v(TAG, "Battery plugged to: " + + Integer.toHexString(mHistoryCur.states)); + addHistoryRecordLocked(mSecRealtime); + mTrackBatteryPastUptime += uptime - mTrackBatteryUptimeStart; + mTrackBatteryPastRealtime += realtime - mTrackBatteryRealtimeStart; + mDischargeCurrentLevel = level; + if (level < mDischargeUnplugLevel) { + mLowDischargeAmountSinceCharge += mDischargeUnplugLevel-level-1; + mHighDischargeAmountSinceCharge += mDischargeUnplugLevel-level; + } + doPlugLocked(getBatteryUptimeLocked(uptime), getBatteryRealtimeLocked(realtime)); + } + if (doWrite || (mLastWriteTime + (60 * 1000)) < mSecRealtime) { + if (mFile != null) { + writeLocked(); } } } } - public void recordCurrentLevel(int level) { - mDischargeCurrentLevel = level; + // This should probably be exposed in the API, though it's not critical + private static final int BATTERY_PLUGGED_NONE = 0; + + public void setBatteryState(int status, int health, int plugType, int level, + int temp, int volt) { + boolean onBattery = plugType == BATTERY_PLUGGED_NONE; + int oldStatus = mHistoryCur.batteryStatus; + if (!mHaveBatteryLevel) { + mHaveBatteryLevel = true; + // We start out assuming that the device is plugged in (not + // on battery). If our first report is now that we are indeed + // plugged in, then twiddle our state to correctly reflect that + // since we won't be going through the full setOnBattery(). + if (onBattery == mOnBattery) { + if (onBattery) { + mHistoryCur.states &= ~HistoryItem.STATE_BATTERY_PLUGGED_FLAG; + } else { + mHistoryCur.states |= HistoryItem.STATE_BATTERY_PLUGGED_FLAG; + } + } + oldStatus = status; + } + if (onBattery) { + mDischargeCurrentLevel = level; + mRecordingHistory = true; + } + if (onBattery != mOnBattery) { + mHistoryCur.batteryLevel = (byte)level; + mHistoryCur.batteryStatus = (byte)status; + mHistoryCur.batteryHealth = (byte)health; + mHistoryCur.batteryPlugType = (byte)plugType; + mHistoryCur.batteryTemperature = (char)temp; + mHistoryCur.batteryVoltage = (char)volt; + setOnBattery(onBattery, oldStatus, level); + } else { + boolean changed = false; + if (mHistoryCur.batteryLevel != level) { + mHistoryCur.batteryLevel = (byte)level; + changed = true; + } + if (mHistoryCur.batteryStatus != status) { + mHistoryCur.batteryStatus = (byte)status; + changed = true; + } + if (mHistoryCur.batteryHealth != health) { + mHistoryCur.batteryHealth = (byte)health; + changed = true; + } + if (mHistoryCur.batteryPlugType != plugType) { + mHistoryCur.batteryPlugType = (byte)plugType; + changed = true; + } + if (mHistoryCur.batteryTemperature != temp) { + mHistoryCur.batteryTemperature = (char)temp; + changed = true; + } + if (mHistoryCur.batteryVoltage != volt) { + mHistoryCur.batteryVoltage = (char)volt; + changed = true; + } + if (changed) { + addHistoryRecordLocked(SystemClock.elapsedRealtime()); + } + } + if (!onBattery && status == BatteryManager.BATTERY_STATUS_FULL) { + // We don't record history while we are plugged in and fully charged. + // The next time we are unplugged, history will be cleared. + mRecordingHistory = false; + } } public void updateKernelWakelocksLocked() { @@ -2855,10 +4059,10 @@ public final class BatteryStatsImpl extends BatteryStats { @Override public long computeUptime(long curTime, int which) { switch (which) { - case STATS_TOTAL: return mUptime + (curTime-mUptimeStart); + case STATS_SINCE_CHARGED: return mUptime + (curTime-mUptimeStart); case STATS_LAST: return mLastUptime; case STATS_CURRENT: return (curTime-mUptimeStart); - case STATS_UNPLUGGED: return (curTime-mTrackBatteryUptimeStart); + case STATS_SINCE_UNPLUGGED: return (curTime-mTrackBatteryUptimeStart); } return 0; } @@ -2866,10 +4070,10 @@ public final class BatteryStatsImpl extends BatteryStats { @Override public long computeRealtime(long curTime, int which) { switch (which) { - case STATS_TOTAL: return mRealtime + (curTime-mRealtimeStart); + case STATS_SINCE_CHARGED: return mRealtime + (curTime-mRealtimeStart); case STATS_LAST: return mLastRealtime; case STATS_CURRENT: return (curTime-mRealtimeStart); - case STATS_UNPLUGGED: return (curTime-mTrackBatteryRealtimeStart); + case STATS_SINCE_UNPLUGGED: return (curTime-mTrackBatteryRealtimeStart); } return 0; } @@ -2877,13 +4081,13 @@ public final class BatteryStatsImpl extends BatteryStats { @Override public long computeBatteryUptime(long curTime, int which) { switch (which) { - case STATS_TOTAL: + case STATS_SINCE_CHARGED: return mBatteryUptime + getBatteryUptime(curTime); case STATS_LAST: return mBatteryLastUptime; case STATS_CURRENT: return getBatteryUptime(curTime); - case STATS_UNPLUGGED: + case STATS_SINCE_UNPLUGGED: return getBatteryUptimeLocked(curTime) - mUnpluggedBatteryUptime; } return 0; @@ -2892,13 +4096,13 @@ public final class BatteryStatsImpl extends BatteryStats { @Override public long computeBatteryRealtime(long curTime, int which) { switch (which) { - case STATS_TOTAL: + case STATS_SINCE_CHARGED: return mBatteryRealtime + getBatteryRealtimeLocked(curTime); case STATS_LAST: return mBatteryLastRealtime; case STATS_CURRENT: return getBatteryRealtimeLocked(curTime); - case STATS_UNPLUGGED: + case STATS_SINCE_UNPLUGGED: return getBatteryRealtimeLocked(curTime) - mUnpluggedBatteryRealtime; } return 0; @@ -2938,14 +4142,14 @@ public final class BatteryStatsImpl extends BatteryStats { if (which == STATS_LAST) { return dataBytes[STATS_LAST]; } else { - if (which == STATS_UNPLUGGED) { - if (dataBytes[STATS_UNPLUGGED] < 0) { + if (which == STATS_SINCE_UNPLUGGED) { + if (dataBytes[STATS_SINCE_UNPLUGGED] < 0) { return dataBytes[STATS_LAST]; } else { - return current - dataBytes[STATS_UNPLUGGED]; + return current - dataBytes[STATS_SINCE_UNPLUGGED]; } - } else if (which == STATS_TOTAL) { - return (current - dataBytes[STATS_CURRENT]) + dataBytes[STATS_TOTAL]; + } else if (which == STATS_SINCE_CHARGED) { + return (current - dataBytes[STATS_CURRENT]) + dataBytes[STATS_SINCE_CHARGED]; } return current - dataBytes[STATS_CURRENT]; } @@ -2979,7 +4183,7 @@ public final class BatteryStatsImpl extends BatteryStats { } public int getDischargeStartLevelLocked() { - return mDischargeStartLevel; + return mDischargeUnplugLevel; } @Override @@ -2994,6 +4198,20 @@ public final class BatteryStatsImpl extends BatteryStats { } @Override + public int getLowDischargeAmountSinceCharge() { + synchronized(this) { + return mLowDischargeAmountSinceCharge; + } + } + + @Override + public int getHighDischargeAmountSinceCharge() { + synchronized(this) { + return mHighDischargeAmountSinceCharge; + } + } + + @Override public int getCpuSpeedSteps() { return sNumSpeedSteps; } @@ -3062,17 +4280,21 @@ public final class BatteryStatsImpl extends BatteryStats { return u.getServiceStatsLocked(pkg, name); } - private static JournaledFile makeJournaledFile() { - final String base = "/data/system/device_policies.xml"; - return new JournaledFile(new File(base), new File(base + ".tmp")); + public void shutdownLocked() { + writeLocked(); + mShuttingDown = true; } - + public void writeLocked() { if (mFile == null) { Slog.w("BatteryStats", "writeLocked: no file associated with this instance"); return; } + if (mShuttingDown) { + return; + } + try { FileOutputStream stream = new FileOutputStream(mFile.chooseForWrite()); Parcel out = Parcel.obtain(); @@ -3140,12 +4362,47 @@ public final class BatteryStatsImpl extends BatteryStats { } catch(java.io.IOException e) { Slog.e("BatteryStats", "Error reading battery statistics", e); } + + addHistoryRecordLocked(SystemClock.elapsedRealtime(), HistoryItem.CMD_START); } public int describeContents() { return 0; } + void readHistory(Parcel in) { + mHistory = mHistoryEnd = mHistoryCache = null; + mHistoryBaseTime = 0; + long time; + while ((time=in.readLong()) >= 0) { + HistoryItem rec = new HistoryItem(time, in); + addHistoryRecordLocked(rec); + if (rec.time > mHistoryBaseTime) { + mHistoryBaseTime = rec.time; + } + } + + long oldnow = SystemClock.elapsedRealtime() - (5*60*100); + if (oldnow > 0) { + // If the system process has restarted, but not the entire + // system, then the mHistoryBaseTime already accounts for + // much of the elapsed time. We thus want to adjust it back, + // to avoid large gaps in the data. We determine we are + // in this case by arbitrarily saying it is so if at this + // point in boot the elapsed time is already more than 5 seconds. + mHistoryBaseTime -= oldnow; + } + } + + void writeHistory(Parcel out) { + HistoryItem rec = mHistory; + while (rec != null) { + if (rec.time >= 0) rec.writeToParcel(out, 0); + rec = rec.next; + } + out.writeLong(-1); + } + private void readSummaryFromParcel(Parcel in) { final int version = in.readInt(); if (version != VERSION) { @@ -3154,17 +4411,17 @@ public final class BatteryStatsImpl extends BatteryStats { return; } + readHistory(in); + mStartCount = in.readInt(); mBatteryUptime = in.readLong(); - mBatteryLastUptime = in.readLong(); mBatteryRealtime = in.readLong(); - mBatteryLastRealtime = in.readLong(); mUptime = in.readLong(); - mLastUptime = in.readLong(); mRealtime = in.readLong(); - mLastRealtime = in.readLong(); - mDischargeStartLevel = in.readInt(); + mDischargeUnplugLevel = in.readInt(); mDischargeCurrentLevel = in.readInt(); + mLowDischargeAmountSinceCharge = in.readInt(); + mHighDischargeAmountSinceCharge = in.readInt(); mStartCount++; @@ -3215,17 +4472,29 @@ public final class BatteryStatsImpl extends BatteryStats { mUidStats.put(uid, u); u.mWifiTurnedOn = false; - u.mWifiTurnedOnTimer.readSummaryFromParcelLocked(in); + if (in.readInt() != 0) { + u.mWifiTurnedOnTimer.readSummaryFromParcelLocked(in); + } u.mFullWifiLockOut = false; - u.mFullWifiLockTimer.readSummaryFromParcelLocked(in); - u.mAudioTurnedOn = false; - u.mAudioTurnedOnTimer.readSummaryFromParcelLocked(in); - u.mVideoTurnedOn = false; - u.mVideoTurnedOnTimer.readSummaryFromParcelLocked(in); + if (in.readInt() != 0) { + u.mFullWifiLockTimer.readSummaryFromParcelLocked(in); + } u.mScanWifiLockOut = false; - u.mScanWifiLockTimer.readSummaryFromParcelLocked(in); + if (in.readInt() != 0) { + u.mScanWifiLockTimer.readSummaryFromParcelLocked(in); + } u.mWifiMulticastEnabled = false; - u.mWifiMulticastTimer.readSummaryFromParcelLocked(in); + if (in.readInt() != 0) { + u.mWifiMulticastTimer.readSummaryFromParcelLocked(in); + } + u.mAudioTurnedOn = false; + if (in.readInt() != 0) { + u.mAudioTurnedOnTimer.readSummaryFromParcelLocked(in); + } + u.mVideoTurnedOn = false; + if (in.readInt() != 0) { + u.mVideoTurnedOnTimer.readSummaryFromParcelLocked(in); + } if (in.readInt() != 0) { if (u.mUserActivityCounters == null) { @@ -3276,11 +4545,9 @@ public final class BatteryStatsImpl extends BatteryStats { String procName = in.readString(); Uid.Proc p = u.getProcessStatsLocked(procName); p.mUserTime = p.mLoadedUserTime = in.readLong(); - p.mLastUserTime = in.readLong(); p.mSystemTime = p.mLoadedSystemTime = in.readLong(); - p.mLastSystemTime = in.readLong(); p.mStarts = p.mLoadedStarts = in.readInt(); - p.mLastStarts = in.readInt(); + p.readExcessiveWakeFromParcelLocked(in); } NP = in.readInt(); @@ -3292,17 +4559,13 @@ public final class BatteryStatsImpl extends BatteryStats { String pkgName = in.readString(); Uid.Pkg p = u.getPackageStatsLocked(pkgName); p.mWakeups = p.mLoadedWakeups = in.readInt(); - p.mLastWakeups = in.readInt(); final int NS = in.readInt(); for (int is = 0; is < NS; is++) { String servName = in.readString(); Uid.Pkg.Serv s = u.getServiceStatsLocked(pkgName, servName); s.mStartTime = s.mLoadedStartTime = in.readLong(); - s.mLastStartTime = in.readLong(); s.mStarts = s.mLoadedStarts = in.readInt(); - s.mLastStarts = in.readInt(); s.mLaunches = s.mLoadedLaunches = in.readInt(); - s.mLastLaunches = in.readInt(); } } @@ -3325,18 +4588,17 @@ public final class BatteryStatsImpl extends BatteryStats { out.writeInt(VERSION); + writeHistory(out); + out.writeInt(mStartCount); - out.writeLong(computeBatteryUptime(NOW_SYS, STATS_TOTAL)); - out.writeLong(computeBatteryUptime(NOW_SYS, STATS_CURRENT)); - out.writeLong(computeBatteryRealtime(NOWREAL_SYS, STATS_TOTAL)); - out.writeLong(computeBatteryRealtime(NOWREAL_SYS, STATS_CURRENT)); - out.writeLong(computeUptime(NOW_SYS, STATS_TOTAL)); - out.writeLong(computeUptime(NOW_SYS, STATS_CURRENT)); - out.writeLong(computeRealtime(NOWREAL_SYS, STATS_TOTAL)); - out.writeLong(computeRealtime(NOWREAL_SYS, STATS_CURRENT)); - out.writeInt(mDischargeStartLevel); + out.writeLong(computeBatteryUptime(NOW_SYS, STATS_SINCE_CHARGED)); + out.writeLong(computeBatteryRealtime(NOWREAL_SYS, STATS_SINCE_CHARGED)); + out.writeLong(computeUptime(NOW_SYS, STATS_SINCE_CHARGED)); + out.writeLong(computeRealtime(NOWREAL_SYS, STATS_SINCE_CHARGED)); + out.writeInt(mDischargeUnplugLevel); out.writeInt(mDischargeCurrentLevel); - + out.writeInt(mLowDischargeAmountSinceCharge); + out.writeInt(mHighDischargeAmountSinceCharge); mScreenOnTimer.writeSummaryFromParcelLocked(out, NOWREAL); for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) { @@ -3374,12 +4636,42 @@ public final class BatteryStatsImpl extends BatteryStats { out.writeInt(mUidStats.keyAt(iu)); Uid u = mUidStats.valueAt(iu); - u.mWifiTurnedOnTimer.writeSummaryFromParcelLocked(out, NOWREAL); - u.mFullWifiLockTimer.writeSummaryFromParcelLocked(out, NOWREAL); - u.mAudioTurnedOnTimer.writeSummaryFromParcelLocked(out, NOWREAL); - u.mVideoTurnedOnTimer.writeSummaryFromParcelLocked(out, NOWREAL); - u.mScanWifiLockTimer.writeSummaryFromParcelLocked(out, NOWREAL); - u.mWifiMulticastTimer.writeSummaryFromParcelLocked(out, NOWREAL); + if (u.mWifiTurnedOnTimer != null) { + out.writeInt(1); + u.mWifiTurnedOnTimer.writeSummaryFromParcelLocked(out, NOWREAL); + } else { + out.writeInt(0); + } + if (u.mFullWifiLockTimer != null) { + out.writeInt(1); + u.mFullWifiLockTimer.writeSummaryFromParcelLocked(out, NOWREAL); + } else { + out.writeInt(0); + } + if (u.mScanWifiLockTimer != null) { + out.writeInt(1); + u.mScanWifiLockTimer.writeSummaryFromParcelLocked(out, NOWREAL); + } else { + out.writeInt(0); + } + if (u.mWifiMulticastTimer != null) { + out.writeInt(1); + u.mWifiMulticastTimer.writeSummaryFromParcelLocked(out, NOWREAL); + } else { + out.writeInt(0); + } + if (u.mAudioTurnedOnTimer != null) { + out.writeInt(1); + u.mAudioTurnedOnTimer.writeSummaryFromParcelLocked(out, NOWREAL); + } else { + out.writeInt(0); + } + if (u.mVideoTurnedOnTimer != null) { + out.writeInt(1); + u.mVideoTurnedOnTimer.writeSummaryFromParcelLocked(out, NOWREAL); + } else { + out.writeInt(0); + } if (u.mUserActivityCounters == null) { out.writeInt(0); @@ -3442,11 +4734,9 @@ public final class BatteryStatsImpl extends BatteryStats { out.writeString(ent.getKey()); Uid.Proc ps = ent.getValue(); out.writeLong(ps.mUserTime); - out.writeLong(ps.mUserTime - ps.mLoadedUserTime); out.writeLong(ps.mSystemTime); - out.writeLong(ps.mSystemTime - ps.mLoadedSystemTime); out.writeInt(ps.mStarts); - out.writeInt(ps.mStarts - ps.mLoadedStarts); + ps.writeExcessiveWakeToParcelLocked(out); } } @@ -3458,7 +4748,6 @@ public final class BatteryStatsImpl extends BatteryStats { out.writeString(ent.getKey()); Uid.Pkg ps = ent.getValue(); out.writeInt(ps.mWakeups); - out.writeInt(ps.mWakeups - ps.mLoadedWakeups); final int NS = ps.mServiceStats.size(); out.writeInt(NS); if (NS > 0) { @@ -3468,18 +4757,15 @@ public final class BatteryStatsImpl extends BatteryStats { BatteryStatsImpl.Uid.Pkg.Serv ss = sent.getValue(); long time = ss.getStartTimeToNowLocked(NOW); out.writeLong(time); - out.writeLong(time - ss.mLoadedStartTime); out.writeInt(ss.mStarts); - out.writeInt(ss.mStarts - ss.mLoadedStarts); out.writeInt(ss.mLaunches); - out.writeInt(ss.mLaunches - ss.mLoadedLaunches); } } } } - out.writeLong(u.getTcpBytesReceived(STATS_TOTAL)); - out.writeLong(u.getTcpBytesSent(STATS_TOTAL)); + out.writeLong(u.getTcpBytesReceived(STATS_SINCE_CHARGED)); + out.writeLong(u.getTcpBytesSent(STATS_SINCE_CHARGED)); } } @@ -3493,38 +4779,43 @@ public final class BatteryStatsImpl extends BatteryStats { throw new ParcelFormatException("Bad magic number"); } + readHistory(in); + mStartCount = in.readInt(); mBatteryUptime = in.readLong(); - mBatteryLastUptime = in.readLong(); + mBatteryLastUptime = 0; mBatteryRealtime = in.readLong(); - mBatteryLastRealtime = in.readLong(); + mBatteryLastRealtime = 0; mScreenOn = false; - mScreenOnTimer = new StopwatchTimer(-1, null, mUnpluggables, in); + mScreenOnTimer = new StopwatchTimer(null, -1, null, mUnpluggables, in); for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) { - mScreenBrightnessTimer[i] = new StopwatchTimer(-100-i, null, mUnpluggables, in); + mScreenBrightnessTimer[i] = new StopwatchTimer(null, -100-i, + null, mUnpluggables, in); } mInputEventCounter = new Counter(mUnpluggables, in); mPhoneOn = false; - mPhoneOnTimer = new StopwatchTimer(-2, null, mUnpluggables, in); + mPhoneOnTimer = new StopwatchTimer(null, -2, null, mUnpluggables, in); for (int i=0; i<NUM_SIGNAL_STRENGTH_BINS; i++) { - mPhoneSignalStrengthsTimer[i] = new StopwatchTimer(-200-i, null, mUnpluggables, in); + mPhoneSignalStrengthsTimer[i] = new StopwatchTimer(null, -200-i, + null, mUnpluggables, in); } - mPhoneSignalScanningTimer = new StopwatchTimer(-200+1, null, mUnpluggables, in); + mPhoneSignalScanningTimer = new StopwatchTimer(null, -200+1, null, mUnpluggables, in); for (int i=0; i<NUM_DATA_CONNECTION_TYPES; i++) { - mPhoneDataConnectionsTimer[i] = new StopwatchTimer(-300-i, null, mUnpluggables, in); + mPhoneDataConnectionsTimer[i] = new StopwatchTimer(null, -300-i, + null, mUnpluggables, in); } mWifiOn = false; - mWifiOnTimer = new StopwatchTimer(-2, null, mUnpluggables, in); + mWifiOnTimer = new StopwatchTimer(null, -2, null, mUnpluggables, in); mWifiRunning = false; - mWifiRunningTimer = new StopwatchTimer(-2, null, mUnpluggables, in); + mWifiRunningTimer = new StopwatchTimer(null, -2, null, mUnpluggables, in); mBluetoothOn = false; - mBluetoothOnTimer = new StopwatchTimer(-2, null, mUnpluggables, in); + mBluetoothOnTimer = new StopwatchTimer(null, -2, null, mUnpluggables, in); mUptime = in.readLong(); mUptimeStart = in.readLong(); - mLastUptime = in.readLong(); + mLastUptime = 0; mRealtime = in.readLong(); mRealtimeStart = in.readLong(); - mLastRealtime = in.readLong(); + mLastRealtime = 0; mOnBattery = in.readInt() != 0; mOnBatteryInternal = false; // we are no longer really running. mTrackBatteryPastUptime = in.readLong(); @@ -3533,18 +4824,20 @@ public final class BatteryStatsImpl extends BatteryStats { mTrackBatteryRealtimeStart = in.readLong(); mUnpluggedBatteryUptime = in.readLong(); mUnpluggedBatteryRealtime = in.readLong(); - mDischargeStartLevel = in.readInt(); + mDischargeUnplugLevel = in.readInt(); mDischargeCurrentLevel = in.readInt(); + mLowDischargeAmountSinceCharge = in.readInt(); + mHighDischargeAmountSinceCharge = in.readInt(); mLastWriteTime = in.readLong(); mMobileDataRx[STATS_LAST] = in.readLong(); - mMobileDataRx[STATS_UNPLUGGED] = -1; + mMobileDataRx[STATS_SINCE_UNPLUGGED] = -1; mMobileDataTx[STATS_LAST] = in.readLong(); - mMobileDataTx[STATS_UNPLUGGED] = -1; + mMobileDataTx[STATS_SINCE_UNPLUGGED] = -1; mTotalDataRx[STATS_LAST] = in.readLong(); - mTotalDataRx[STATS_UNPLUGGED] = -1; + mTotalDataRx[STATS_SINCE_UNPLUGGED] = -1; mTotalDataTx[STATS_LAST] = in.readLong(); - mTotalDataTx[STATS_UNPLUGGED] = -1; + mTotalDataTx[STATS_SINCE_UNPLUGGED] = -1; mRadioDataUptime = in.readLong(); mRadioDataStart = -1; @@ -3580,22 +4873,27 @@ public final class BatteryStatsImpl extends BatteryStats { } public void writeToParcel(Parcel out, int flags) { - writeToParcelLocked(out, flags); + writeToParcelLocked(out, true, flags); + } + + public void writeToParcelWithoutUids(Parcel out, int flags) { + writeToParcelLocked(out, false, flags); } @SuppressWarnings("unused") - void writeToParcelLocked(Parcel out, int flags) { + void writeToParcelLocked(Parcel out, boolean inclUids, int flags) { final long uSecUptime = SystemClock.uptimeMillis() * 1000; final long uSecRealtime = SystemClock.elapsedRealtime() * 1000; final long batteryUptime = getBatteryUptimeLocked(uSecUptime); final long batteryRealtime = getBatteryRealtimeLocked(uSecRealtime); out.writeInt(MAGIC); + + writeHistory(out); + out.writeInt(mStartCount); out.writeLong(mBatteryUptime); - out.writeLong(mBatteryLastUptime); out.writeLong(mBatteryRealtime); - out.writeLong(mBatteryLastRealtime); mScreenOnTimer.writeToParcel(out, batteryRealtime); for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) { mScreenBrightnessTimer[i].writeToParcel(out, batteryRealtime); @@ -3614,10 +4912,8 @@ public final class BatteryStatsImpl extends BatteryStats { mBluetoothOnTimer.writeToParcel(out, batteryRealtime); out.writeLong(mUptime); out.writeLong(mUptimeStart); - out.writeLong(mLastUptime); out.writeLong(mRealtime); out.writeLong(mRealtimeStart); - out.writeLong(mLastRealtime); out.writeInt(mOnBattery ? 1 : 0); out.writeLong(batteryUptime); out.writeLong(mTrackBatteryUptimeStart); @@ -3625,41 +4921,51 @@ public final class BatteryStatsImpl extends BatteryStats { out.writeLong(mTrackBatteryRealtimeStart); out.writeLong(mUnpluggedBatteryUptime); out.writeLong(mUnpluggedBatteryRealtime); - out.writeInt(mDischargeStartLevel); + out.writeInt(mDischargeUnplugLevel); out.writeInt(mDischargeCurrentLevel); + out.writeInt(mLowDischargeAmountSinceCharge); + out.writeInt(mHighDischargeAmountSinceCharge); out.writeLong(mLastWriteTime); - out.writeLong(getMobileTcpBytesReceived(STATS_UNPLUGGED)); - out.writeLong(getMobileTcpBytesSent(STATS_UNPLUGGED)); - out.writeLong(getTotalTcpBytesReceived(STATS_UNPLUGGED)); - out.writeLong(getTotalTcpBytesSent(STATS_UNPLUGGED)); + out.writeLong(getMobileTcpBytesReceived(STATS_SINCE_UNPLUGGED)); + out.writeLong(getMobileTcpBytesSent(STATS_SINCE_UNPLUGGED)); + out.writeLong(getTotalTcpBytesReceived(STATS_SINCE_UNPLUGGED)); + out.writeLong(getTotalTcpBytesSent(STATS_SINCE_UNPLUGGED)); // Write radio uptime for data out.writeLong(getRadioDataUptime()); out.writeInt(getBluetoothPingCount()); - out.writeInt(mKernelWakelockStats.size()); - for (Map.Entry<String, SamplingTimer> ent : mKernelWakelockStats.entrySet()) { - SamplingTimer kwlt = ent.getValue(); - if (kwlt != null) { - out.writeInt(1); - out.writeString(ent.getKey()); - Timer.writeTimerToParcel(out, kwlt, batteryRealtime); - } else { - out.writeInt(0); + if (inclUids) { + out.writeInt(mKernelWakelockStats.size()); + for (Map.Entry<String, SamplingTimer> ent : mKernelWakelockStats.entrySet()) { + SamplingTimer kwlt = ent.getValue(); + if (kwlt != null) { + out.writeInt(1); + out.writeString(ent.getKey()); + Timer.writeTimerToParcel(out, kwlt, batteryRealtime); + } else { + out.writeInt(0); + } } + } else { + out.writeInt(0); } out.writeInt(sNumSpeedSteps); - int size = mUidStats.size(); - out.writeInt(size); - for (int i = 0; i < size; i++) { - out.writeInt(mUidStats.keyAt(i)); - Uid uid = mUidStats.valueAt(i); + if (inclUids) { + int size = mUidStats.size(); + out.writeInt(size); + for (int i = 0; i < size; i++) { + out.writeInt(mUidStats.keyAt(i)); + Uid uid = mUidStats.valueAt(i); - uid.writeToParcelLocked(out, batteryRealtime); + uid.writeToParcelLocked(out, batteryRealtime); + } + } else { + out.writeInt(0); } } diff --git a/core/java/com/android/internal/os/PowerProfile.java b/core/java/com/android/internal/os/PowerProfile.java index 2369d25bd472..127ed68dff98 100644 --- a/core/java/com/android/internal/os/PowerProfile.java +++ b/core/java/com/android/internal/os/PowerProfile.java @@ -126,6 +126,11 @@ public class PowerProfile { public static final String POWER_CPU_SPEEDS = "cpu.speeds"; + /** + * Battery capacity in milliAmpHour (mAh). + */ + public static final String POWER_BATTERY_CAPACITY = "battery.capacity"; + static final HashMap<String, Object> sPowerMap = new HashMap<String, Object>(); private static final String TAG_DEVICE = "device"; @@ -243,6 +248,19 @@ public class PowerProfile { } } + /** + * Returns the battery capacity, if available, in milli Amp Hours. If not available, + * it returns zero. + * @return the battery capacity in mAh + */ + public double getBatteryCapacity() { + return getAveragePower(POWER_BATTERY_CAPACITY); + } + + /** + * Returns the number of speeds that the CPU can be run at. + * @return + */ public int getNumSpeedSteps() { Object value = sPowerMap.get(POWER_CPU_SPEEDS); if (value != null && value instanceof Double[]) { diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java index 599a7fe4b681..59600dcdc455 100644 --- a/core/java/com/android/internal/os/RuntimeInit.java +++ b/core/java/com/android/internal/os/RuntimeInit.java @@ -342,6 +342,10 @@ public class RuntimeInit { mApplicationObject = app; } + public static final IBinder getApplicationObject() { + return mApplicationObject; + } + /** * Enable debugging features. */ diff --git a/core/java/com/android/internal/os/SamplingProfilerIntegration.java b/core/java/com/android/internal/os/SamplingProfilerIntegration.java index 5f5c7a47e219..a3efbc87317b 100644 --- a/core/java/com/android/internal/os/SamplingProfilerIntegration.java +++ b/core/java/com/android/internal/os/SamplingProfilerIntegration.java @@ -18,10 +18,12 @@ package com.android.internal.os; import dalvik.system.SamplingProfiler; +import java.io.BufferedOutputStream; import java.io.File; +import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; -import java.io.FileNotFoundException; +import java.io.PrintStream; import java.util.concurrent.Executor; import java.util.concurrent.Executors; @@ -48,6 +50,8 @@ public class SamplingProfilerIntegration { } } + private static SamplingProfiler INSTANCE; + /** * Is profiling enabled? */ @@ -59,8 +63,13 @@ public class SamplingProfilerIntegration { * Starts the profiler if profiling is enabled. */ public static void start() { - if (!enabled) return; - SamplingProfiler.getInstance().start(10); + if (!enabled) { + return; + } + ThreadGroup group = Thread.currentThread().getThreadGroup(); + SamplingProfiler.ThreadSet threadSet = SamplingProfiler.newThreadGroupTheadSet(group); + INSTANCE = new SamplingProfiler(4, threadSet); + INSTANCE.start(10); } /** Whether or not we've created the snapshots dir. */ @@ -73,7 +82,9 @@ public class SamplingProfilerIntegration { * Writes a snapshot to the SD card if profiling is enabled. */ public static void writeSnapshot(final String name) { - if (!enabled) return; + if (!enabled) { + return; + } /* * If we're already writing a snapshot, don't bother enqueing another @@ -109,18 +120,22 @@ public class SamplingProfilerIntegration { * Writes the zygote's snapshot to internal storage if profiling is enabled. */ public static void writeZygoteSnapshot() { - if (!enabled) return; + if (!enabled) { + return; + } String dir = "/data/zygote/snapshots"; new File(dir).mkdirs(); writeSnapshot(dir, "zygote"); + INSTANCE.shutdown(); + INSTANCE = null; } private static void writeSnapshot(String dir, String name) { - byte[] snapshot = SamplingProfiler.getInstance().snapshot(); - if (snapshot == null) { + if (!enabled) { return; } + INSTANCE.stop(); /* * We use the current time as a unique ID. We can't use a counter @@ -128,39 +143,40 @@ public class SamplingProfilerIntegration { * we capture two snapshots in rapid succession. */ long start = System.currentTimeMillis(); - String path = dir + "/" + name.replace(':', '.') + "-" + + String path = dir + "/" + name.replace(':', '.') + "-" + System.currentTimeMillis() + ".snapshot"; - try { - // Try to open the file a few times. The SD card may not be mounted. - FileOutputStream out; - int count = 0; - while (true) { - try { - out = new FileOutputStream(path); - break; - } catch (FileNotFoundException e) { - if (++count > 3) { - Log.e(TAG, "Could not open " + path + "."); - return; - } - // Sleep for a bit and then try again. - try { - Thread.sleep(2500); - } catch (InterruptedException e1) { /* ignore */ } + // Try to open the file a few times. The SD card may not be mounted. + PrintStream out; + int count = 0; + while (true) { + try { + out = new PrintStream(new BufferedOutputStream(new FileOutputStream(path))); + break; + } catch (FileNotFoundException e) { + if (++count > 3) { + Log.e(TAG, "Could not open " + path + "."); + return; } - } - try { - out.write(snapshot); - } finally { - out.close(); + // Sleep for a bit and then try again. + try { + Thread.sleep(2500); + } catch (InterruptedException e1) { /* ignore */ } } + } + + try { + INSTANCE.writeHprofData(out); + } finally { + out.close(); + } + if (out.checkError()) { + Log.e(TAG, "Error writing snapshot."); + } else { long elapsed = System.currentTimeMillis() - start; Log.i(TAG, "Wrote snapshot for " + name - + " in " + elapsed + "ms."); - } catch (IOException e) { - Log.e(TAG, "Error writing snapshot.", e); + + " in " + elapsed + "ms."); } } } diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java index b677b1e60489..a409ec844248 100644 --- a/core/java/com/android/internal/os/ZygoteInit.java +++ b/core/java/com/android/internal/os/ZygoteInit.java @@ -66,10 +66,6 @@ public class ZygoteInit { /** when preloading, GC after allocating this many bytes */ private static final int PRELOAD_GC_THRESHOLD = 50000; - /** throw on missing preload, only if this looks like a developer */ - private static final boolean THROW_ON_MISSING_PRELOAD = - "1".equals(SystemProperties.get("persist.service.adb.enable")); - public static final String USAGE_STRING = " <\"true\"|\"false\" for startSystemServer>"; @@ -287,7 +283,6 @@ public class ZygoteInit { int count = 0; String line; - String missingClasses = null; while ((line = br.readLine()) != null) { // Skip comments and blank lines. line = line.trim(); @@ -311,12 +306,7 @@ public class ZygoteInit { } count++; } catch (ClassNotFoundException e) { - Log.e(TAG, "Class not found for preloading: " + line); - if (missingClasses == null) { - missingClasses = line; - } else { - missingClasses += " " + line; - } + Log.w(TAG, "Class not found for preloading: " + line); } catch (Throwable t) { Log.e(TAG, "Error preloading " + line + ".", t); if (t instanceof Error) { @@ -329,13 +319,6 @@ public class ZygoteInit { } } - if (THROW_ON_MISSING_PRELOAD && - missingClasses != null) { - throw new IllegalStateException( - "Missing class(es) for preloading, update preloaded-classes [" - + missingClasses + "]"); - } - Log.i(TAG, "...preloaded " + count + " classes in " + (SystemClock.uptimeMillis()-startTime) + "ms."); } catch (IOException e) { @@ -592,12 +575,8 @@ public class ZygoteInit { EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_END, SystemClock.uptimeMillis()); - if (SamplingProfilerIntegration.isEnabled()) { - SamplingProfiler sp = SamplingProfiler.getInstance(); - sp.pause(); - SamplingProfilerIntegration.writeZygoteSnapshot(); - sp.shutDown(); - } + // Finish profiling the zygote initialization. + SamplingProfilerIntegration.writeZygoteSnapshot(); // Do an initial gc to clean up after startup gc(); diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl new file mode 100644 index 000000000000..4501bd769496 --- /dev/null +++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2007, 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.internal.statusbar; + +import com.android.internal.statusbar.StatusBarIcon; +import com.android.internal.statusbar.StatusBarNotification; + +/** @hide */ +oneway interface IStatusBar +{ + void setIcon(int index, in StatusBarIcon icon); + void removeIcon(int index); + void addNotification(IBinder key, in StatusBarNotification notification); + void updateNotification(IBinder key, in StatusBarNotification notification); + void removeNotification(IBinder key); + void disable(int state); + void animateExpand(); + void animateCollapse(); +} + diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl new file mode 100644 index 000000000000..852630dfa443 --- /dev/null +++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2007, 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.internal.statusbar; + +import com.android.internal.statusbar.IStatusBar; +import com.android.internal.statusbar.StatusBarIcon; +import com.android.internal.statusbar.StatusBarIconList; +import com.android.internal.statusbar.StatusBarNotification; + +/** @hide */ +interface IStatusBarService +{ + void expand(); + void collapse(); + void disable(int what, IBinder token, String pkg); + void setIcon(String slot, String iconPackage, int iconId, int iconLevel); + void setIconVisibility(String slot, boolean visible); + void removeIcon(String slot); + + // ---- Methods below are for use by the status bar policy services ---- + // You need the STATUS_BAR_SERVICE permission + void registerStatusBar(IStatusBar callbacks, out StatusBarIconList iconList, + out List<IBinder> notificationKeys, out List<StatusBarNotification> notifications); + void onPanelRevealed(); + void onNotificationClick(String pkg, String tag, int id); + void onNotificationError(String pkg, String tag, int id, + int uid, int initialPid, String message); + void onClearAllNotifications(); +} diff --git a/core/java/android/app/IStatusBar.aidl b/core/java/com/android/internal/statusbar/StatusBarIcon.aidl index c64fa50f1dcd..311a0770ad36 100644 --- a/core/java/android/app/IStatusBar.aidl +++ b/core/java/com/android/internal/statusbar/StatusBarIcon.aidl @@ -1,5 +1,5 @@ -/** - * Copyright (c) 2007, The Android Open Source Project +/* + * 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. @@ -13,17 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -package android.app; -/** @hide */ -interface IStatusBar -{ - void activate(); - void deactivate(); - void toggle(); - void disable(int what, IBinder token, String pkg); - IBinder addIcon(String slot, String iconPackage, int iconId, int iconLevel); - void updateIcon(IBinder key, String slot, String iconPackage, int iconId, int iconLevel); - void removeIcon(IBinder key); -} +package com.android.internal.statusbar; + +parcelable StatusBarIcon; + diff --git a/core/java/com/android/internal/statusbar/StatusBarIcon.java b/core/java/com/android/internal/statusbar/StatusBarIcon.java new file mode 100644 index 000000000000..ae2cac2a68f7 --- /dev/null +++ b/core/java/com/android/internal/statusbar/StatusBarIcon.java @@ -0,0 +1,105 @@ +/* + * 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.internal.statusbar; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * @hide + */ +public class StatusBarIcon implements Parcelable { + public String iconPackage; + public int iconId; + public int iconLevel; + public boolean visible = true; + public int number; + + private StatusBarIcon() { + } + + public StatusBarIcon(String iconPackage, int iconId, int iconLevel) { + this.iconPackage = iconPackage; + this.iconId = iconId; + this.iconLevel = iconLevel; + } + + public StatusBarIcon(String iconPackage, int iconId, int iconLevel, int number) { + this.iconPackage = iconPackage; + this.iconId = iconId; + this.iconLevel = iconLevel; + this.number = number; + } + + public String toString() { + return "StatusBarIcon(pkg=" + this.iconPackage + " id=0x" + Integer.toHexString(this.iconId) + + " level=" + this.iconLevel + " visible=" + visible + + " num=" + this.number + " )"; + } + + public StatusBarIcon clone() { + StatusBarIcon that = new StatusBarIcon(this.iconPackage, this.iconId, this.iconLevel); + that.visible = this.visible; + that.number = this.number; + return that; + } + + /** + * Unflatten the StatusBarIcon from a parcel. + */ + public StatusBarIcon(Parcel in) { + readFromParcel(in); + } + + public void readFromParcel(Parcel in) { + this.iconPackage = in.readString(); + this.iconId = in.readInt(); + this.iconLevel = in.readInt(); + this.visible = in.readInt() != 0; + this.number = in.readInt(); + } + + public void writeToParcel(Parcel out, int flags) { + out.writeString(this.iconPackage); + out.writeInt(this.iconId); + out.writeInt(this.iconLevel); + out.writeInt(this.visible ? 1 : 0); + out.writeInt(this.number); + } + + public int describeContents() { + return 0; + } + + /** + * Parcelable.Creator that instantiates StatusBarIcon objects + */ + public static final Parcelable.Creator<StatusBarIcon> CREATOR + = new Parcelable.Creator<StatusBarIcon>() + { + public StatusBarIcon createFromParcel(Parcel parcel) + { + return new StatusBarIcon(parcel); + } + + public StatusBarIcon[] newArray(int size) + { + return new StatusBarIcon[size]; + } + }; +} + diff --git a/core/java/com/android/internal/statusbar/StatusBarIconList.aidl b/core/java/com/android/internal/statusbar/StatusBarIconList.aidl new file mode 100644 index 000000000000..c74512050092 --- /dev/null +++ b/core/java/com/android/internal/statusbar/StatusBarIconList.aidl @@ -0,0 +1,20 @@ +/* + * 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.internal.statusbar; + +parcelable StatusBarIconList; + diff --git a/core/java/com/android/internal/statusbar/StatusBarIconList.java b/core/java/com/android/internal/statusbar/StatusBarIconList.java new file mode 100644 index 000000000000..478d245eb8cd --- /dev/null +++ b/core/java/com/android/internal/statusbar/StatusBarIconList.java @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2007 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.internal.statusbar; + +import android.os.Parcel; +import android.os.Parcelable; + +import java.io.PrintWriter; + +public class StatusBarIconList implements Parcelable { + private String[] mSlots; + private StatusBarIcon[] mIcons; + + public StatusBarIconList() { + } + + public StatusBarIconList(Parcel in) { + readFromParcel(in); + } + + public void readFromParcel(Parcel in) { + this.mSlots = in.readStringArray(); + final int N = in.readInt(); + if (N < 0) { + mIcons = null; + } else { + mIcons = new StatusBarIcon[N]; + for (int i=0; i<N; i++) { + if (in.readInt() != 0) { + mIcons[i] = new StatusBarIcon(in); + } + } + } + } + + public void writeToParcel(Parcel out, int flags) { + out.writeStringArray(mSlots); + if (mIcons == null) { + out.writeInt(-1); + } else { + final int N = mIcons.length; + out.writeInt(N); + for (int i=0; i<N; i++) { + StatusBarIcon ic = mIcons[i]; + if (ic == null) { + out.writeInt(0); + } else { + out.writeInt(1); + ic.writeToParcel(out, flags); + } + } + } + } + + public int describeContents() { + return 0; + } + + /** + * Parcelable.Creator that instantiates StatusBarIconList objects + */ + public static final Parcelable.Creator<StatusBarIconList> CREATOR + = new Parcelable.Creator<StatusBarIconList>() + { + public StatusBarIconList createFromParcel(Parcel parcel) + { + return new StatusBarIconList(parcel); + } + + public StatusBarIconList[] newArray(int size) + { + return new StatusBarIconList[size]; + } + }; + + public void defineSlots(String[] slots) { + final int N = slots.length; + String[] s = mSlots = new String[N]; + for (int i=0; i<N; i++) { + s[i] = slots[i]; + } + mIcons = new StatusBarIcon[N]; + } + + public int getSlotIndex(String slot) { + final int N = mSlots.length; + for (int i=0; i<N; i++) { + if (slot.equals(mSlots[i])) { + return i; + } + } + return -1; + } + + public int size() { + return mSlots.length; + } + + public void setIcon(int index, StatusBarIcon icon) { + mIcons[index] = icon.clone(); + } + + public void removeIcon(int index) { + mIcons[index] = null; + } + + public String getSlot(int index) { + return mSlots[index]; + } + + public StatusBarIcon getIcon(int index) { + return mIcons[index]; + } + + public int getViewIndex(int index) { + int count = 0; + for (int i=0; i<index; i++) { + if (mIcons[i] != null) { + count++; + } + } + return count; + } + + public void copyFrom(StatusBarIconList that) { + if (that.mSlots == null) { + this.mSlots = null; + this.mIcons = null; + } else { + final int N = that.mSlots.length; + this.mSlots = new String[N]; + this.mIcons = new StatusBarIcon[N]; + for (int i=0; i<N; i++) { + this.mSlots[i] = that.mSlots[i]; + this.mIcons[i] = that.mIcons[i] != null ? that.mIcons[i].clone() : null; + } + } + } + + public void dump(PrintWriter pw) { + final int N = mSlots.length; + pw.println("Icon list:"); + for (int i=0; i<N; i++) { + pw.printf(" %2d: (%s) %s\n", i, mSlots[i], mIcons[i]); + } + } +} diff --git a/core/java/com/android/internal/statusbar/StatusBarNotification.aidl b/core/java/com/android/internal/statusbar/StatusBarNotification.aidl new file mode 100644 index 000000000000..bd9e89ce8a42 --- /dev/null +++ b/core/java/com/android/internal/statusbar/StatusBarNotification.aidl @@ -0,0 +1,20 @@ +/* + * 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.internal.statusbar; + +parcelable StatusBarNotification; + diff --git a/core/java/com/android/internal/statusbar/StatusBarNotification.java b/core/java/com/android/internal/statusbar/StatusBarNotification.java new file mode 100644 index 000000000000..aa340fba4ef9 --- /dev/null +++ b/core/java/com/android/internal/statusbar/StatusBarNotification.java @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2008 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.internal.statusbar; + +import android.app.Notification; +import android.os.Parcel; +import android.os.Parcelable; +import android.widget.RemoteViews; + + +/* +boolean clearable = !n.ongoingEvent && ((notification.flags & Notification.FLAG_NO_CLEAR) == 0); + + +// TODO: make this restriction do something smarter like never fill +// more than two screens. "Why would anyone need more than 80 characters." :-/ +final int maxTickerLen = 80; +if (truncatedTicker != null && truncatedTicker.length() > maxTickerLen) { + truncatedTicker = truncatedTicker.subSequence(0, maxTickerLen); +} +*/ + +public class StatusBarNotification implements Parcelable { + public String pkg; + public int id; + public String tag; + public int uid; + public int initialPid; + public Notification notification; + + public StatusBarNotification() { + } + + public StatusBarNotification(String pkg, int id, String tag, + int uid, int initialPid, Notification notification) { + if (pkg == null) throw new NullPointerException(); + if (notification == null) throw new NullPointerException(); + + this.pkg = pkg; + this.id = id; + this.tag = tag; + this.uid = uid; + this.initialPid = initialPid; + this.notification = notification; + } + + public StatusBarNotification(Parcel in) { + readFromParcel(in); + } + + public void readFromParcel(Parcel in) { + this.pkg = in.readString(); + this.id = in.readInt(); + if (in.readInt() != 0) { + this.tag = in.readString(); + } else { + this.tag = null; + } + this.uid = in.readInt(); + this.initialPid = in.readInt(); + this.notification = new Notification(in); + } + + public void writeToParcel(Parcel out, int flags) { + out.writeString(this.pkg); + out.writeInt(this.id); + if (this.tag != null) { + out.writeInt(1); + out.writeString(this.tag); + } else { + out.writeInt(0); + } + out.writeInt(this.uid); + out.writeInt(this.initialPid); + this.notification.writeToParcel(out, flags); + } + + public int describeContents() { + return 0; + } + + public static final Parcelable.Creator<StatusBarNotification> CREATOR + = new Parcelable.Creator<StatusBarNotification>() + { + public StatusBarNotification createFromParcel(Parcel parcel) + { + return new StatusBarNotification(parcel); + } + + public StatusBarNotification[] newArray(int size) + { + return new StatusBarNotification[size]; + } + }; + + public StatusBarNotification clone() { + return new StatusBarNotification(this.pkg, this.id, this.tag, + this.uid, this.initialPid, this.notification.clone()); + } + + public String toString() { + return "StatusBarNotification(package=" + pkg + " id=" + id + " tag=" + tag + + " notification=" + notification + ")"; + } + + public boolean isOngoing() { + return (notification.flags & Notification.FLAG_ONGOING_EVENT) != 0; + } + +} + + diff --git a/core/java/com/android/internal/statusbar/StatusBarNotificationList.aidl b/core/java/com/android/internal/statusbar/StatusBarNotificationList.aidl new file mode 100644 index 000000000000..10abeee94e15 --- /dev/null +++ b/core/java/com/android/internal/statusbar/StatusBarNotificationList.aidl @@ -0,0 +1,20 @@ +/* + * 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.internal.statusbar; + +parcelable StatusBarNotificationList; + diff --git a/core/java/com/android/internal/util/HierarchicalStateMachine.java b/core/java/com/android/internal/util/HierarchicalStateMachine.java index 9911f486d5ac..c599d68e7a3c 100644 --- a/core/java/com/android/internal/util/HierarchicalStateMachine.java +++ b/core/java/com/android/internal/util/HierarchicalStateMachine.java @@ -51,7 +51,7 @@ import java.util.HashMap; mS2 mS1 ----> initial state </code> * After the state machine is created and started, messages are sent to a state - * machine using <code>sendMessage</code and the messages are created using + * machine using <code>sendMessage</code> and the messages are created using * <code>obtainMessage</code>. When the state machine receives a message the * current state's <code>processMessage</code> is invoked. In the above example * mS1.processMessage will be invoked first. The state may use <code>transitionTo</code> @@ -59,9 +59,9 @@ import java.util.HashMap; * * Each state in the state machine may have a zero or one parent states and if * a child state is unable to handle a message it may have the message processed - * by its parent by returning false. If a message is never processed <code>unhandledMessage</code> - * will be invoked to give one last chance for the state machine to process - * the message. + * by its parent by returning false or NOT_HANDLED. If a message is never processed + * <code>unhandledMessage</code> will be invoked to give one last chance for the state machine + * to process the message. * * When all processing is completed a state machine may choose to call * <code>transitionToHaltingState</code>. When the current <code>processingMessage</code> @@ -95,7 +95,7 @@ import java.util.HashMap; * any other messages that are on the queue or might be added later. Both of * these are protected and may only be invoked from within a state machine. * - * To illustrate some of these properties we'll use state machine with 8 + * To illustrate some of these properties we'll use state machine with an 8 * state hierarchy: <code> mP0 @@ -109,44 +109,19 @@ import java.util.HashMap; * * After starting mS5 the list of active states is mP0, mP1, mS1 and mS5. * So the order of calling processMessage when a message is received is mS5, - * mS1, mP1, mP0 assuming each processMessage indicates it can't handle this - * message by returning false. + * mS1, mP1, mP0 assuming each processMessage indicates it can't handle this + * message by returning false or NOT_HANDLED. * * Now assume mS5.processMessage receives a message it can handle, and during - * the handling determines the machine should changes states. It would call - * transitionTo(mS4) and return true. Immediately after returning from + * the handling determines the machine should change states. It could call + * transitionTo(mS4) and return true or HANDLED. Immediately after returning from * processMessage the state machine runtime will find the common parent, * which is mP1. It will then call mS5.exit, mS1.exit, mS2.enter and then * mS4.enter. The new list of active states is mP0, mP1, mS2 and mS4. So * when the next message is received mS4.processMessage will be invoked. * - * To assist in describing an HSM a simple grammar has been created which - * is informally defined here and a formal EBNF description is at the end - * of the class comment. - * - * An HSM starts with the name and includes a set of hierarchical states. - * A state is preceeded by one or more plus signs (+), to indicate its - * depth and a hash (#) if its the initial state. Child states follow their - * parents and have one more plus sign then their parent. Inside a state - * are a series of messages, the actions they perform and if the processing - * is complete ends with a period (.). If processing isn't complete and - * the parent should process the message it ends with a caret (^). The - * actions include send a message ($MESSAGE), defer a message (%MESSAGE), - * transition to a new state (>MESSAGE) and an if statement - * (if ( expression ) { list of actions }.) - * - * The Hsm HelloWorld could documented as: - * - * HelloWorld { - * + # mState1. - * } - * - * and interpreted as HSM HelloWorld: - * - * mState1 a root state (single +) and initial state (#) which - * processes all messages completely, the period (.). - * - * The implementation is: + * Now for some concrete examples, here is the canonical HelloWorld as an HSM. + * It responds with "Hello World" being printed to the log for every message. <code> class HelloWorld extends HierarchicalStateMachine { Hsm1(String name) { @@ -164,7 +139,7 @@ class HelloWorld extends HierarchicalStateMachine { class State1 extends HierarchicalState { @Override public boolean processMessage(Message message) { Log.d(TAG, "Hello World"); - return true; + return HANDLED; } } State1 mState1 = new State1(); @@ -176,7 +151,7 @@ void testHelloWorld() { } </code> * - * A more interesting state machine is one of four states + * A more interesting state machine is one with four states * with two independent parent states. <code> mP1 mP2 @@ -184,45 +159,68 @@ void testHelloWorld() { mS2 mS1 </code> * - * documented as: + * Here is a description of this state machine using pseudo code. * - * Hsm1 { - * + mP1 { - * CMD_2 { - * $CMD_3 - * %CMD_2 - * >mS2 - * }. - * } - * ++ # mS1 { CMD_1{ >mS1 }^ } - * ++ mS2 { - * CMD_2{$CMD_4}. - * CMD_3{%CMD_3 ; >mP2}. - * } * - * + mP2 e($CMD_5) { - * CMD_3, CMD_4. - * CMD_5{>HALT}. - * } + * state mP1 { + * enter { log("mP1.enter"); } + * exit { log("mP1.exit"); } + * on msg { + * CMD_2 { + * send(CMD_3); + * defer(msg); + * transitonTo(mS2); + * return HANDLED; + * } + * return NOT_HANDLED; + * } * } * - * and interpreted as HierarchicalStateMachine Hsm1: - * - * mP1 a root state. - * processes message CMD_2 which sends CMD_3, defers CMD_2, and transitions to mS2 - * - * mS1 a child of mP1 is the initial state: - * processes message CMD_1 which transitions to itself and returns false to let mP1 handle it. + * INITIAL + * state mS1 parent mP1 { + * enter { log("mS1.enter"); } + * exit { log("mS1.exit"); } + * on msg { + * CMD_1 { + * transitionTo(mS1); + * return HANDLED; + * } + * return NOT_HANDLED; + * } + * } * - * mS2 a child of mP1: - * processes message CMD_2 which send CMD_4 - * processes message CMD_3 which defers CMD_3 and transitions to mP2 + * state mS2 parent mP1 { + * enter { log("mS2.enter"); } + * exit { log("mS2.exit"); } + * on msg { + * CMD_2 { + * send(CMD_4); + * return HANDLED; + * } + * CMD_3 { + * defer(msg); + * transitionTo(mP2); + * return HANDLED; + * } + * return NOT_HANDLED; + * } + * } * - * mP2 a root state. - * on enter it sends CMD_5 - * processes message CMD_3 - * processes message CMD_4 - * processes message CMD_5 which transitions to halt state + * state mP2 { + * enter { + * log("mP2.enter"); + * send(CMD_5); + * } + * exit { log("mP2.exit"); } + * on msg { + * CMD_3, CMD_4 { return HANDLED; } + * CMD_5 { + * transitionTo(HaltingState); + * return HANDLED; + * } + * return NOT_HANDLED; + * } + * } * * The implementation is below and also in HierarchicalStateMachineTest: <code> @@ -271,11 +269,11 @@ class Hsm1 extends HierarchicalStateMachine { sendMessage(obtainMessage(CMD_3)); deferMessage(message); transitionTo(mS2); - retVal = true; + retVal = HANDLED; break; default: // Any message we don't understand in this state invokes unhandledMessage - retVal = false; + retVal = NOT_HANDLED; break; } return retVal; @@ -294,10 +292,10 @@ class Hsm1 extends HierarchicalStateMachine { if (message.what == CMD_1) { // Transition to ourself to show that enter/exit is called transitionTo(mS1); - return true; + return HANDLED; } else { // Let parent process all other messages - return false; + return NOT_HANDLED; } } @Override public void exit() { @@ -315,15 +313,15 @@ class Hsm1 extends HierarchicalStateMachine { switch(message.what) { case(CMD_2): sendMessage(obtainMessage(CMD_4)); - retVal = true; + retVal = HANDLED; break; case(CMD_3): deferMessage(message); transitionTo(mP2); - retVal = true; + retVal = HANDLED; break; default: - retVal = false; + retVal = NOT_HANDLED; break; } return retVal; @@ -349,7 +347,7 @@ class Hsm1 extends HierarchicalStateMachine { transitionToHaltingState(); break; } - return true; + return HANDLED; } @Override public void exit() { Log.d(TAG, "mP2.exit"); @@ -357,7 +355,7 @@ class Hsm1 extends HierarchicalStateMachine { } @Override - protected void halting() { + void halting() { Log.d(TAG, "halting"); synchronized (this) { this.notifyAll(); @@ -413,53 +411,32 @@ class Hsm1 extends HierarchicalStateMachine { * D/hsm1 ( 1999): mP2.exit * D/hsm1 ( 1999): halting * - * Here is the HSM a BNF grammar, this is a first stab at creating an - * HSM description language, suggestions corrections or alternatives - * would be much appreciated. - * - * Legend: - * {} ::= zero or more - * {}+ ::= one or more - * [] ::= zero or one - * () ::= define a group with "or" semantics. - * - * HSM EBNF: - * HSM = HSM_NAME "{" { STATE }+ "}" ; - * HSM_NAME = alpha_numeric_name ; - * STATE = INTRODUCE_STATE [ ENTER | [ ENTER EXIT ] "{" [ MESSAGES ] "}" [ EXIT ] ; - * INTRODUCE_STATE = { STATE_DEPTH }+ [ INITIAL_STATE_INDICATOR ] STATE_NAME ; - * STATE_DEPTH = "+" ; - * INITIAL_STATE_INDICATOR = "#" - * ENTER = "e(" SEND_ACTION | TRANSITION_ACTION | HALT_ACTION ")" ; - * MESSAGES = { MSG_LIST MESSAGE_ACTIONS } ; - * MSG_LIST = { MSG_NAME { "," MSG_NAME } }; - * EXIT = "x(" SEND_ACTION | TRANSITION_ACTION | HALT_ACTION ")" ; - * PROCESS_COMPLETION = PROCESS_IN_PARENT_OR_COMPLETE | PROCESS_COMPLETE ; - * SEND_ACTION = "$" MSG_NAME ; - * DEFER_ACTION = "%" MSG_NAME ; - * TRANSITION_ACTION = ">" STATE_NAME ; - * HALT_ACTION = ">" HALT ; - * MESSAGE_ACTIONS = { "{" ACTION_LIST "}" } [ PROCESS_COMPLETION ] ; - * ACTION_LIST = ACTION { (";" | "\n") ACTION } ; - * ACTION = IF_ACTION | SEND_ACTION | DEFER_ACTION | TRANSITION_ACTION | HALT_ACTION ; - * IF_ACTION = "if(" boolean_expression ")" "{" ACTION_LIST "}" - * PROCESS_IN_PARENT_OR_COMPLETE = "^" ; - * PROCESS_COMPLETE = "." ; - * STATE_NAME = alpha_numeric_name ; - * MSG_NAME = alpha_numeric_name | ALL_OTHER_MESSAGES ; - * ALL_OTHER_MESSAGES = "*" ; - * EXP = boolean_expression ; - * - * Idioms: - * * { %* }. ::= All other messages will be deferred. */ public class HierarchicalStateMachine { private static final String TAG = "HierarchicalStateMachine"; private String mName; + /** Message.what value when quitting */ public static final int HSM_QUIT_CMD = -1; + /** Message.what value when initializing */ + public static final int HSM_INIT_CMD = -1; + + /** + * Convenience constant that maybe returned by processMessage + * to indicate the the message was processed and is not to be + * processed by parent states + */ + public static final boolean HANDLED = true; + + /** + * Convenience constant that maybe returned by processMessage + * to indicate the the message was NOT processed and is to be + * processed by parent states + */ + public static final boolean NOT_HANDLED = false; + private static class HsmHandler extends Handler { /** The debug flag */ @@ -468,6 +445,12 @@ public class HierarchicalStateMachine { /** The quit object */ private static final Object mQuitObj = new Object(); + /** The initialization message */ + private static final Message mInitMsg = null; + + /** The current message */ + private Message mMsg; + /** A list of messages that this state machine has processed */ private ProcessedMessages mProcessedMessages = new ProcessedMessages(); @@ -550,8 +533,7 @@ public class HierarchicalStateMachine { private class QuittingState extends HierarchicalState { @Override public boolean processMessage(Message msg) { - // Ignore - return false; + return NOT_HANDLED; } } @@ -565,6 +547,9 @@ public class HierarchicalStateMachine { public final void handleMessage(Message msg) { if (mDbg) Log.d(TAG, "handleMessage: E msg.what=" + msg.what); + /** Save the current message */ + mMsg = msg; + /** * Check that construction was completed */ @@ -679,6 +664,7 @@ public class HierarchicalStateMachine { * starting at the first entry. */ mIsConstructionCompleted = true; + mMsg = obtainMessage(HSM_INIT_CMD); invokeEnterMethods(0); /** @@ -855,6 +841,13 @@ public class HierarchicalStateMachine { } /** + * @return current message + */ + private final Message getCurrentMessage() { + return mMsg; + } + + /** * @return current state */ private final HierarchicalState getCurrentState() { @@ -1025,6 +1018,14 @@ public class HierarchicalStateMachine { protected final void addState(HierarchicalState state, HierarchicalState parent) { mHsmHandler.addState(state, parent); } + + /** + * @return current message + */ + protected final Message getCurrentMessage() { + return mHsmHandler.getCurrentMessage(); + } + /** * @return current state */ @@ -1032,7 +1033,6 @@ public class HierarchicalStateMachine { return mHsmHandler.getCurrentState(); } - /** * Add a new state to the state machine, parent will be null * @param state to add diff --git a/core/java/com/android/internal/view/BaseIWindow.java b/core/java/com/android/internal/view/BaseIWindow.java index b13d656796db..4da74e6e31ca 100644 --- a/core/java/com/android/internal/view/BaseIWindow.java +++ b/core/java/com/android/internal/view/BaseIWindow.java @@ -43,59 +43,6 @@ public class BaseIWindow extends IWindow.Stub { } } - public void dispatchKey(KeyEvent event) { - try { - mSession.finishKey(this); - } catch (RemoteException ex) { - } - } - - public boolean onDispatchPointer(MotionEvent event, long eventTime, - boolean callWhenDone) { - event.recycle(); - return false; - } - - public void dispatchPointer(MotionEvent event, long eventTime, - boolean callWhenDone) { - try { - if (event == null) { - event = mSession.getPendingPointerMove(this); - onDispatchPointer(event, eventTime, false); - } else if (callWhenDone) { - if (!onDispatchPointer(event, eventTime, true)) { - mSession.finishKey(this); - } - } else { - onDispatchPointer(event, eventTime, false); - } - } catch (RemoteException ex) { - } - } - - public boolean onDispatchTrackball(MotionEvent event, long eventTime, - boolean callWhenDone) { - event.recycle(); - return false; - } - - public void dispatchTrackball(MotionEvent event, long eventTime, - boolean callWhenDone) { - try { - if (event == null) { - event = mSession.getPendingTrackballMove(this); - onDispatchTrackball(event, eventTime, false); - } else if (callWhenDone) { - if (!onDispatchTrackball(event, eventTime, true)) { - mSession.finishKey(this); - } - } else { - onDispatchTrackball(event, eventTime, false); - } - } catch (RemoteException ex) { - } - } - public void dispatchAppVisibility(boolean visible) { } diff --git a/core/java/com/android/internal/view/BaseInputHandler.java b/core/java/com/android/internal/view/BaseInputHandler.java new file mode 100644 index 000000000000..e943a7dd3c0d --- /dev/null +++ b/core/java/com/android/internal/view/BaseInputHandler.java @@ -0,0 +1,35 @@ +/* + * 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.internal.view; + +import android.view.InputHandler; +import android.view.KeyEvent; +import android.view.MotionEvent; + +/** + * Base do-nothing implementation of an input handler. + * @hide + */ +public abstract class BaseInputHandler implements InputHandler { + public void handleKey(KeyEvent event, Runnable finishedCallback) { + finishedCallback.run(); + } + + public void handleMotion(MotionEvent event, Runnable finishedCallback) { + finishedCallback.run(); + } +} diff --git a/core/java/com/android/internal/view/BaseSurfaceHolder.java b/core/java/com/android/internal/view/BaseSurfaceHolder.java index e0d3a5f87b2e..1e97cd687f40 100644 --- a/core/java/com/android/internal/view/BaseSurfaceHolder.java +++ b/core/java/com/android/internal/view/BaseSurfaceHolder.java @@ -33,13 +33,16 @@ public abstract class BaseSurfaceHolder implements SurfaceHolder { public final ArrayList<SurfaceHolder.Callback> mCallbacks = new ArrayList<SurfaceHolder.Callback>(); - + SurfaceHolder.Callback[] mGottenCallbacks; + boolean mHaveGottenCallbacks; + public final ReentrantLock mSurfaceLock = new ReentrantLock(); - public final Surface mSurface = new Surface(); + public Surface mSurface = new Surface(); int mRequestedWidth = -1; int mRequestedHeight = -1; - int mRequestedFormat = PixelFormat.OPAQUE; + /** @hide */ + protected int mRequestedFormat = PixelFormat.OPAQUE; int mRequestedType = -1; long mLastLockTime = 0; @@ -83,6 +86,31 @@ public abstract class BaseSurfaceHolder implements SurfaceHolder { } } + public SurfaceHolder.Callback[] getCallbacks() { + if (mHaveGottenCallbacks) { + return mGottenCallbacks; + } + + synchronized (mCallbacks) { + final int N = mCallbacks.size(); + if (N > 0) { + if (mGottenCallbacks == null || mGottenCallbacks.length != N) { + mGottenCallbacks = new SurfaceHolder.Callback[N]; + } + mCallbacks.toArray(mGottenCallbacks); + } else { + mGottenCallbacks = null; + } + mHaveGottenCallbacks = true; + } + + return mGottenCallbacks; + } + + public void ungetCallbacks() { + mHaveGottenCallbacks = false; + } + public void setFixedSize(int width, int height) { if (mRequestedWidth != width || mRequestedHeight != height) { mRequestedWidth = width; diff --git a/core/java/com/android/internal/view/IInputConnectionWrapper.java b/core/java/com/android/internal/view/IInputConnectionWrapper.java index a765e384aa5e..986ba3815eb6 100644 --- a/core/java/com/android/internal/view/IInputConnectionWrapper.java +++ b/core/java/com/android/internal/view/IInputConnectionWrapper.java @@ -31,9 +31,10 @@ import java.lang.ref.WeakReference; public class IInputConnectionWrapper extends IInputContext.Stub { static final String TAG = "IInputConnectionWrapper"; - + private static final int DO_GET_TEXT_AFTER_CURSOR = 10; private static final int DO_GET_TEXT_BEFORE_CURSOR = 20; + private static final int DO_GET_SELECTED_TEXT = 25; private static final int DO_GET_CURSOR_CAPS_MODE = 30; private static final int DO_GET_EXTRACTED_TEXT = 40; private static final int DO_COMMIT_TEXT = 50; @@ -42,6 +43,7 @@ public class IInputConnectionWrapper extends IInputContext.Stub { private static final int DO_PERFORM_EDITOR_ACTION = 58; private static final int DO_PERFORM_CONTEXT_MENU_ACTION = 59; private static final int DO_SET_COMPOSING_TEXT = 60; + private static final int DO_SET_COMPOSING_REGION = 63; private static final int DO_FINISH_COMPOSING_TEXT = 65; private static final int DO_SEND_KEY_EVENT = 70; private static final int DO_DELETE_SURROUNDING_TEXT = 80; @@ -50,7 +52,7 @@ public class IInputConnectionWrapper extends IInputContext.Stub { private static final int DO_REPORT_FULLSCREEN_MODE = 100; private static final int DO_PERFORM_PRIVATE_COMMAND = 120; private static final int DO_CLEAR_META_KEY_STATES = 130; - + private WeakReference<InputConnection> mInputConnection; private Looper mMainLooper; @@ -92,6 +94,10 @@ public class IInputConnectionWrapper extends IInputContext.Stub { dispatchMessage(obtainMessageIISC(DO_GET_TEXT_BEFORE_CURSOR, length, flags, seq, callback)); } + public void getSelectedText(int flags, int seq, IInputContextCallback callback) { + dispatchMessage(obtainMessageISC(DO_GET_SELECTED_TEXT, flags, seq, callback)); + } + public void getCursorCapsMode(int reqModes, int seq, IInputContextCallback callback) { dispatchMessage(obtainMessageISC(DO_GET_CURSOR_CAPS_MODE, reqModes, seq, callback)); } @@ -122,6 +128,10 @@ public class IInputConnectionWrapper extends IInputContext.Stub { dispatchMessage(obtainMessageII(DO_PERFORM_CONTEXT_MENU_ACTION, id, 0)); } + public void setComposingRegion(int start, int end) { + dispatchMessage(obtainMessageII(DO_SET_COMPOSING_REGION, start, end)); + } + public void setComposingText(CharSequence text, int newCursorPosition) { dispatchMessage(obtainMessageIO(DO_SET_COMPOSING_TEXT, newCursorPosition, text)); } @@ -206,6 +216,22 @@ public class IInputConnectionWrapper extends IInputContext.Stub { } return; } + case DO_GET_SELECTED_TEXT: { + SomeArgs args = (SomeArgs)msg.obj; + try { + InputConnection ic = mInputConnection.get(); + if (ic == null || !isActive()) { + Log.w(TAG, "getSelectedText on inactive InputConnection"); + args.callback.setSelectedText(null, args.seq); + return; + } + args.callback.setSelectedText(ic.getSelectedText( + msg.arg1), args.seq); + } catch (RemoteException e) { + Log.w(TAG, "Got RemoteException calling setSelectedText", e); + } + return; + } case DO_GET_CURSOR_CAPS_MODE: { SomeArgs args = (SomeArgs)msg.obj; try { @@ -292,6 +318,15 @@ public class IInputConnectionWrapper extends IInputContext.Stub { ic.setComposingText((CharSequence)msg.obj, msg.arg1); return; } + case DO_SET_COMPOSING_REGION: { + InputConnection ic = mInputConnection.get(); + if (ic == null || !isActive()) { + Log.w(TAG, "setComposingRegion on inactive InputConnection"); + return; + } + ic.setComposingRegion(msg.arg1, msg.arg2); + return; + } case DO_FINISH_COMPOSING_TEXT: { InputConnection ic = mInputConnection.get(); // Note we do NOT check isActive() here, because this is safe diff --git a/core/java/com/android/internal/view/IInputContext.aidl b/core/java/com/android/internal/view/IInputContext.aidl index 02cb9e4df5e3..333fc82c964a 100644 --- a/core/java/com/android/internal/view/IInputContext.aidl +++ b/core/java/com/android/internal/view/IInputContext.aidl @@ -65,4 +65,8 @@ import com.android.internal.view.IInputContextCallback; void clearMetaKeyStates(int states); void performPrivateCommand(String action, in Bundle data); + + void setComposingRegion(int start, int end); + + void getSelectedText(int flags, int seq, IInputContextCallback callback); } diff --git a/core/java/com/android/internal/view/IInputContextCallback.aidl b/core/java/com/android/internal/view/IInputContextCallback.aidl index 9b8c43c6ff72..661066baa1d9 100644 --- a/core/java/com/android/internal/view/IInputContextCallback.aidl +++ b/core/java/com/android/internal/view/IInputContextCallback.aidl @@ -26,4 +26,5 @@ oneway interface IInputContextCallback { void setTextAfterCursor(CharSequence textAfterCursor, int seq); void setCursorCapsMode(int capsMode, int seq); void setExtractedText(in ExtractedText extractedText, int seq); + void setSelectedText(CharSequence selectedText, int seq); } diff --git a/core/java/com/android/internal/view/IInputMethodSession.aidl b/core/java/com/android/internal/view/IInputMethodSession.aidl index a05ff14bcccd..338dcaa7db96 100644 --- a/core/java/com/android/internal/view/IInputMethodSession.aidl +++ b/core/java/com/android/internal/view/IInputMethodSession.aidl @@ -48,4 +48,6 @@ oneway interface IInputMethodSession { void appPrivateCommand(String action, in Bundle data); void toggleSoftInput(int showFlags, int hideFlags); + + void finishSession(); } diff --git a/core/java/com/android/internal/view/InputConnectionWrapper.java b/core/java/com/android/internal/view/InputConnectionWrapper.java index 3c44e58a9467..08c302636a21 100644 --- a/core/java/com/android/internal/view/InputConnectionWrapper.java +++ b/core/java/com/android/internal/view/InputConnectionWrapper.java @@ -16,8 +16,6 @@ package com.android.internal.view; -import com.android.internal.view.IInputContext; - import android.os.Bundle; import android.os.RemoteException; import android.os.SystemClock; @@ -38,6 +36,7 @@ public class InputConnectionWrapper implements InputConnection { public boolean mHaveValue; public CharSequence mTextBeforeCursor; public CharSequence mTextAfterCursor; + public CharSequence mSelectedText; public ExtractedText mExtractedText; public int mCursorCapsMode; @@ -114,6 +113,19 @@ public class InputConnectionWrapper implements InputConnection { } } + public void setSelectedText(CharSequence selectedText, int seq) { + synchronized (this) { + if (seq == mSeq) { + mSelectedText = selectedText; + mHaveValue = true; + notifyAll(); + } else { + Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq + + ") in setSelectedText, ignoring."); + } + } + } + public void setCursorCapsMode(int capsMode, int seq) { synchronized (this) { if (seq == mSeq) { @@ -203,6 +215,24 @@ public class InputConnectionWrapper implements InputConnection { return value; } + public CharSequence getSelectedText(int flags) { + CharSequence value = null; + try { + InputContextCallback callback = InputContextCallback.getInstance(); + mIInputContext.getSelectedText(flags, callback.mSeq, callback); + synchronized (callback) { + callback.waitForResultLocked(); + if (callback.mHaveValue) { + value = callback.mSelectedText; + } + } + callback.dispose(); + } catch (RemoteException e) { + return null; + } + return value; + } + public int getCursorCapsMode(int reqModes) { int value = 0; try { @@ -283,7 +313,16 @@ public class InputConnectionWrapper implements InputConnection { return false; } } - + + public boolean setComposingRegion(int start, int end) { + try { + mIInputContext.setComposingRegion(start, end); + return true; + } catch (RemoteException e) { + return false; + } + } + public boolean setComposingText(CharSequence text, int newCursorPosition) { try { mIInputContext.setComposingText(text, newCursorPosition); diff --git a/core/java/com/android/internal/view/RootViewSurfaceTaker.java b/core/java/com/android/internal/view/RootViewSurfaceTaker.java new file mode 100644 index 000000000000..9c1b558c740b --- /dev/null +++ b/core/java/com/android/internal/view/RootViewSurfaceTaker.java @@ -0,0 +1,13 @@ +package com.android.internal.view; + +import android.view.InputQueue; +import android.view.SurfaceHolder; + +/** hahahah */ +public interface RootViewSurfaceTaker { + SurfaceHolder.Callback2 willYouTakeTheSurface(); + void setSurfaceType(int type); + void setSurfaceFormat(int format); + void setSurfaceKeepScreenOn(boolean keepOn); + InputQueue.Callback willYouTakeTheInputQueue(); +} diff --git a/core/java/com/android/internal/widget/DigitalClock.java b/core/java/com/android/internal/widget/DigitalClock.java index 23e2277379a9..fa47ff638cd5 100644 --- a/core/java/com/android/internal/widget/DigitalClock.java +++ b/core/java/com/android/internal/widget/DigitalClock.java @@ -30,7 +30,7 @@ import android.provider.Settings; import android.text.format.DateFormat; import android.util.AttributeSet; import android.view.View; -import android.widget.RelativeLayout; +import android.widget.LinearLayout; import android.widget.TextView; import java.text.DateFormatSymbols; @@ -39,7 +39,7 @@ import java.util.Calendar; /** * Displays the time */ -public class DigitalClock extends RelativeLayout { +public class DigitalClock extends LinearLayout { private final static String M12 = "h:mm"; private final static String M24 = "kk:mm"; diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java index dbbd2860a5c0..c7886055444d 100644 --- a/core/java/com/android/internal/widget/LockPatternUtils.java +++ b/core/java/com/android/internal/widget/LockPatternUtils.java @@ -19,6 +19,7 @@ package com.android.internal.widget; import android.app.admin.DevicePolicyManager; import android.content.ContentResolver; import android.content.Context; +import android.os.FileObserver; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; @@ -33,6 +34,7 @@ import com.android.internal.R; import com.android.internal.telephony.ITelephony; import com.google.android.collect.Lists; +import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.RandomAccessFile; @@ -40,6 +42,7 @@ import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.Arrays; import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; /** * Utilities for the lock patten and its settings. @@ -48,8 +51,9 @@ public class LockPatternUtils { private static final String TAG = "LockPatternUtils"; - private static final String LOCK_PATTERN_FILE = "/system/gesture.key"; - private static final String LOCK_PASSWORD_FILE = "/system/password.key"; + private static final String SYSTEM_DIRECTORY = "/system/"; + private static final String LOCK_PATTERN_FILE = "gesture.key"; + private static final String LOCK_PASSWORD_FILE = "password.key"; /** * The maximum number of incorrect attempts before the user is prevented @@ -98,6 +102,10 @@ public class LockPatternUtils { private static String sLockPatternFilename; private static String sLockPasswordFilename; + private static final AtomicBoolean sHaveNonZeroPatternFile = new AtomicBoolean(false); + private static final AtomicBoolean sHaveNonZeroPasswordFile = new AtomicBoolean(false); + private static FileObserver sPasswordObserver; + public DevicePolicyManager getDevicePolicyManager() { if (mDevicePolicyManager == null) { mDevicePolicyManager = @@ -115,14 +123,31 @@ public class LockPatternUtils { public LockPatternUtils(Context context) { mContext = context; mContentResolver = context.getContentResolver(); - // Initialize the location of gesture lock file + + // Initialize the location of gesture & PIN lock files if (sLockPatternFilename == null) { - sLockPatternFilename = android.os.Environment.getDataDirectory() - .getAbsolutePath() + LOCK_PATTERN_FILE; - sLockPasswordFilename = android.os.Environment.getDataDirectory() - .getAbsolutePath() + LOCK_PASSWORD_FILE; + String dataSystemDirectory = + android.os.Environment.getDataDirectory().getAbsolutePath() + + SYSTEM_DIRECTORY; + sLockPatternFilename = dataSystemDirectory + LOCK_PATTERN_FILE; + sLockPasswordFilename = dataSystemDirectory + LOCK_PASSWORD_FILE; + sHaveNonZeroPatternFile.set(new File(sLockPatternFilename).length() > 0); + sHaveNonZeroPasswordFile.set(new File(sLockPasswordFilename).length() > 0); + int fileObserverMask = FileObserver.CLOSE_WRITE | FileObserver.DELETE | + FileObserver.MOVED_TO | FileObserver.CREATE; + sPasswordObserver = new FileObserver(dataSystemDirectory, fileObserverMask) { + public void onEvent(int event, String path) { + if (LOCK_PATTERN_FILE.equals(path)) { + Log.d(TAG, "lock pattern file changed"); + sHaveNonZeroPatternFile.set(new File(sLockPatternFilename).length() > 0); + } else if (LOCK_PASSWORD_FILE.equals(path)) { + Log.d(TAG, "lock password file changed"); + sHaveNonZeroPasswordFile.set(new File(sLockPasswordFilename).length() > 0); + } + } + }; + sPasswordObserver.startWatching(); } - } public int getRequestedMinimumPasswordLength() { @@ -202,31 +227,11 @@ public class LockPatternUtils { } /** - * Checks to see if the given file exists and contains any data. Returns true if it does, - * false otherwise. - * @param filename - * @return true if file exists and is non-empty. - */ - private boolean nonEmptyFileExists(String filename) { - try { - // Check if we can read a byte from the file - RandomAccessFile raf = new RandomAccessFile(filename, "r"); - raf.readByte(); - raf.close(); - return true; - } catch (FileNotFoundException fnfe) { - return false; - } catch (IOException ioe) { - return false; - } - } - - /** * Check to see if the user has stored a lock pattern. * @return Whether a saved pattern exists. */ public boolean savedPatternExists() { - return nonEmptyFileExists(sLockPatternFilename); + return sHaveNonZeroPatternFile.get(); } /** @@ -234,7 +239,7 @@ public class LockPatternUtils { * @return Whether a saved pattern exists. */ public boolean savedPasswordExists() { - return nonEmptyFileExists(sLockPasswordFilename); + return sHaveNonZeroPasswordFile.get(); } /** diff --git a/core/java/com/android/internal/widget/PointerLocationView.java b/core/java/com/android/internal/widget/PointerLocationView.java index f487a1637dbc..12cf8536f52d 100644 --- a/core/java/com/android/internal/widget/PointerLocationView.java +++ b/core/java/com/android/internal/widget/PointerLocationView.java @@ -19,8 +19,10 @@ package com.android.internal.widget; import android.content.Context; import android.graphics.Canvas; import android.graphics.Paint; +import android.graphics.RectF; import android.graphics.Paint.FontMetricsInt; import android.util.Log; +import android.view.InputDevice; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.View; @@ -29,16 +31,45 @@ import android.view.ViewConfiguration; import java.util.ArrayList; public class PointerLocationView extends View { + private static final String TAG = "Pointer"; + public static class PointerState { - private final ArrayList<Float> mXs = new ArrayList<Float>(); - private final ArrayList<Float> mYs = new ArrayList<Float>(); + // Trace of previous points. + private float[] mTraceX = new float[32]; + private float[] mTraceY = new float[32]; + private int mTraceCount; + + // True if the pointer is down. private boolean mCurDown; - private int mCurX; - private int mCurY; - private float mCurPressure; - private float mCurSize; - private int mCurWidth; - private VelocityTracker mVelocity; + + // Most recent coordinates. + private MotionEvent.PointerCoords mCoords = new MotionEvent.PointerCoords(); + + // Most recent velocity. + private float mXVelocity; + private float mYVelocity; + + public void clearTrace() { + mTraceCount = 0; + } + + public void addTrace(float x, float y) { + int traceCapacity = mTraceX.length; + if (mTraceCount == traceCapacity) { + traceCapacity *= 2; + float[] newTraceX = new float[traceCapacity]; + System.arraycopy(mTraceX, 0, newTraceX, 0, mTraceCount); + mTraceX = newTraceX; + + float[] newTraceY = new float[traceCapacity]; + System.arraycopy(mTraceY, 0, newTraceY, 0, mTraceCount); + mTraceY = newTraceY; + } + + mTraceX[mTraceCount] = x; + mTraceY[mTraceCount] = y; + mTraceCount += 1; + } } private final ViewConfiguration mVC; @@ -53,8 +84,12 @@ public class PointerLocationView extends View { private boolean mCurDown; private int mCurNumPointers; private int mMaxNumPointers; - private final ArrayList<PointerState> mPointers - = new ArrayList<PointerState>(); + private int mActivePointerId; + private final ArrayList<PointerState> mPointers = new ArrayList<PointerState>(); + + private final VelocityTracker mVelocity; + + private final FasterStringBuilder mText = new FasterStringBuilder(); private boolean mPrintCoords = true; @@ -88,8 +123,22 @@ public class PointerLocationView extends View { mPaint.setStrokeWidth(1); PointerState ps = new PointerState(); - ps.mVelocity = VelocityTracker.obtain(); mPointers.add(ps); + mActivePointerId = 0; + + mVelocity = VelocityTracker.obtain(); + + logInputDeviceCapabilities(); + } + + private void logInputDeviceCapabilities() { + int[] deviceIds = InputDevice.getDeviceIds(); + for (int i = 0; i < deviceIds.length; i++) { + InputDevice device = InputDevice.getDevice(deviceIds[i]); + if (device != null) { + Log.i(TAG, device.toString()); + } + } } public void setPrintCoords(boolean state) { @@ -109,6 +158,21 @@ public class PointerLocationView extends View { + " bottom=" + mTextMetrics.bottom); } } + + // Draw an oval. When angle is 0 radians, orients the major axis vertically, + // angles less than or greater than 0 radians rotate the major axis left or right. + private RectF mReusableOvalRect = new RectF(); + private void drawOval(Canvas canvas, float x, float y, float major, float minor, + float angle, Paint paint) { + canvas.save(Canvas.MATRIX_SAVE_FLAG); + canvas.rotate((float) (angle * 180 / Math.PI), x, y); + mReusableOvalRect.left = x - minor / 2; + mReusableOvalRect.right = x + minor / 2; + mReusableOvalRect.top = y - major / 2; + mReusableOvalRect.bottom = y + major / 2; + canvas.drawOval(mReusableOvalRect, paint); + canvas.restore(); + } @Override protected void onDraw(Canvas canvas) { @@ -120,76 +184,81 @@ public class PointerLocationView extends View { final int NP = mPointers.size(); - if (NP > 0) { - final PointerState ps = mPointers.get(0); - canvas.drawRect(0, 0, itemW-1, bottom,mTextBackgroundPaint); - canvas.drawText("P: " + mCurNumPointers + " / " + mMaxNumPointers, - 1, base, mTextPaint); + // Labels + if (mActivePointerId >= 0) { + final PointerState ps = mPointers.get(mActivePointerId); - final int N = ps.mXs.size(); + canvas.drawRect(0, 0, itemW-1, bottom,mTextBackgroundPaint); + canvas.drawText(mText.clear() + .append("P: ").append(mCurNumPointers) + .append(" / ").append(mMaxNumPointers) + .toString(), 1, base, mTextPaint); + + final int N = ps.mTraceCount; if ((mCurDown && ps.mCurDown) || N == 0) { canvas.drawRect(itemW, 0, (itemW * 2) - 1, bottom, mTextBackgroundPaint); - canvas.drawText("X: " + ps.mCurX, 1 + itemW, base, mTextPaint); + canvas.drawText(mText.clear() + .append("X: ").append(ps.mCoords.x, 1) + .toString(), 1 + itemW, base, mTextPaint); canvas.drawRect(itemW * 2, 0, (itemW * 3) - 1, bottom, mTextBackgroundPaint); - canvas.drawText("Y: " + ps.mCurY, 1 + itemW * 2, base, mTextPaint); + canvas.drawText(mText.clear() + .append("Y: ").append(ps.mCoords.y, 1) + .toString(), 1 + itemW * 2, base, mTextPaint); } else { - float dx = ps.mXs.get(N-1) - ps.mXs.get(0); - float dy = ps.mYs.get(N-1) - ps.mYs.get(0); + float dx = ps.mTraceX[N - 1] - ps.mTraceX[0]; + float dy = ps.mTraceY[N - 1] - ps.mTraceY[0]; canvas.drawRect(itemW, 0, (itemW * 2) - 1, bottom, Math.abs(dx) < mVC.getScaledTouchSlop() ? mTextBackgroundPaint : mTextLevelPaint); - canvas.drawText("dX: " + String.format("%.1f", dx), 1 + itemW, base, mTextPaint); + canvas.drawText(mText.clear() + .append("dX: ").append(dx, 1) + .toString(), 1 + itemW, base, mTextPaint); canvas.drawRect(itemW * 2, 0, (itemW * 3) - 1, bottom, Math.abs(dy) < mVC.getScaledTouchSlop() ? mTextBackgroundPaint : mTextLevelPaint); - canvas.drawText("dY: " + String.format("%.1f", dy), 1 + itemW * 2, base, mTextPaint); + canvas.drawText(mText.clear() + .append("dY: ").append(dy, 1) + .toString(), 1 + itemW * 2, base, mTextPaint); } canvas.drawRect(itemW * 3, 0, (itemW * 4) - 1, bottom, mTextBackgroundPaint); - int velocity = ps.mVelocity == null ? 0 : (int) (ps.mVelocity.getXVelocity() * 1000); - canvas.drawText("Xv: " + velocity, 1 + itemW * 3, base, mTextPaint); + canvas.drawText(mText.clear() + .append("Xv: ").append(ps.mXVelocity, 3) + .toString(), 1 + itemW * 3, base, mTextPaint); canvas.drawRect(itemW * 4, 0, (itemW * 5) - 1, bottom, mTextBackgroundPaint); - velocity = ps.mVelocity == null ? 0 : (int) (ps.mVelocity.getYVelocity() * 1000); - canvas.drawText("Yv: " + velocity, 1 + itemW * 4, base, mTextPaint); + canvas.drawText(mText.clear() + .append("Yv: ").append(ps.mYVelocity, 3) + .toString(), 1 + itemW * 4, base, mTextPaint); canvas.drawRect(itemW * 5, 0, (itemW * 6) - 1, bottom, mTextBackgroundPaint); - canvas.drawRect(itemW * 5, 0, (itemW * 5) + (ps.mCurPressure * itemW) - 1, + canvas.drawRect(itemW * 5, 0, (itemW * 5) + (ps.mCoords.pressure * itemW) - 1, bottom, mTextLevelPaint); - canvas.drawText("Prs: " + String.format("%.2f", ps.mCurPressure), 1 + itemW * 5, - base, mTextPaint); + canvas.drawText(mText.clear() + .append("Prs: ").append(ps.mCoords.pressure, 2) + .toString(), 1 + itemW * 5, base, mTextPaint); canvas.drawRect(itemW * 6, 0, w, bottom, mTextBackgroundPaint); - canvas.drawRect(itemW * 6, 0, (itemW * 6) + (ps.mCurSize * itemW) - 1, + canvas.drawRect(itemW * 6, 0, (itemW * 6) + (ps.mCoords.size * itemW) - 1, bottom, mTextLevelPaint); - canvas.drawText("Size: " + String.format("%.2f", ps.mCurSize), 1 + itemW * 6, - base, mTextPaint); + canvas.drawText(mText.clear() + .append("Size: ").append(ps.mCoords.size, 2) + .toString(), 1 + itemW * 6, base, mTextPaint); } - for (int p=0; p<NP; p++) { + // Pointer trace. + for (int p = 0; p < NP; p++) { final PointerState ps = mPointers.get(p); - if (mCurDown && ps.mCurDown) { - canvas.drawLine(0, (int)ps.mCurY, getWidth(), (int)ps.mCurY, mTargetPaint); - canvas.drawLine((int)ps.mCurX, 0, (int)ps.mCurX, getHeight(), mTargetPaint); - int pressureLevel = (int)(ps.mCurPressure*255); - mPaint.setARGB(255, pressureLevel, 128, 255-pressureLevel); - canvas.drawPoint(ps.mCurX, ps.mCurY, mPaint); - canvas.drawCircle(ps.mCurX, ps.mCurY, ps.mCurWidth, mPaint); - } - } - - for (int p=0; p<NP; p++) { - final PointerState ps = mPointers.get(p); - - final int N = ps.mXs.size(); - float lastX=0, lastY=0; + // Draw path. + final int N = ps.mTraceCount; + float lastX = 0, lastY = 0; boolean haveLast = false; boolean drawn = false; mPaint.setARGB(255, 128, 255, 255); - for (int i=0; i<N; i++) { - float x = ps.mXs.get(i); - float y = ps.mYs.get(i); + for (int i=0; i < N; i++) { + float x = ps.mTraceX[i]; + float y = ps.mTraceY[i]; if (Float.isNaN(x)) { haveLast = false; continue; @@ -204,25 +273,57 @@ public class PointerLocationView extends View { haveLast = true; } + // Draw velocity vector. if (drawn) { - if (ps.mVelocity != null) { - mPaint.setARGB(255, 255, 64, 128); - float xVel = ps.mVelocity.getXVelocity() * (1000/60); - float yVel = ps.mVelocity.getYVelocity() * (1000/60); - canvas.drawLine(lastX, lastY, lastX+xVel, lastY+yVel, mPaint); - } else { - canvas.drawPoint(lastX, lastY, mPaint); - } + mPaint.setARGB(255, 255, 64, 128); + float xVel = ps.mXVelocity * (1000 / 60); + float yVel = ps.mYVelocity * (1000 / 60); + canvas.drawLine(lastX, lastY, lastX + xVel, lastY + yVel, mPaint); + } + + if (mCurDown && ps.mCurDown) { + // Draw crosshairs. + canvas.drawLine(0, ps.mCoords.y, getWidth(), ps.mCoords.y, mTargetPaint); + canvas.drawLine(ps.mCoords.x, 0, ps.mCoords.x, getHeight(), mTargetPaint); + + // Draw current point. + int pressureLevel = (int)(ps.mCoords.pressure * 255); + mPaint.setARGB(255, pressureLevel, 255, 255 - pressureLevel); + canvas.drawPoint(ps.mCoords.x, ps.mCoords.y, mPaint); + + // Draw current touch ellipse. + mPaint.setARGB(255, pressureLevel, 255 - pressureLevel, 128); + drawOval(canvas, ps.mCoords.x, ps.mCoords.y, ps.mCoords.touchMajor, + ps.mCoords.touchMinor, ps.mCoords.orientation, mPaint); + + // Draw current tool ellipse. + mPaint.setARGB(255, pressureLevel, 128, 255 - pressureLevel); + drawOval(canvas, ps.mCoords.x, ps.mCoords.y, ps.mCoords.toolMajor, + ps.mCoords.toolMinor, ps.mCoords.orientation, mPaint); } } } } + + private void logPointerCoords(MotionEvent.PointerCoords coords, int id) { + Log.i(TAG, mText.clear() + .append("Pointer ").append(id + 1) + .append(": (").append(coords.x, 3).append(", ").append(coords.y, 3) + .append(") Pressure=").append(coords.pressure, 3) + .append(" Size=").append(coords.size, 3) + .append(" TouchMajor=").append(coords.touchMajor, 3) + .append(" TouchMinor=").append(coords.touchMinor, 3) + .append(" ToolMajor=").append(coords.toolMajor, 3) + .append(" ToolMinor=").append(coords.toolMinor, 3) + .append(" Orientation=").append((float)(coords.orientation * 180 / Math.PI), 1) + .append("deg").toString()); + } public void addTouchEvent(MotionEvent event) { synchronized (mPointers) { int action = event.getAction(); - //Log.i("Pointer", "Motion: action=0x" + Integer.toHexString(action) + //Log.i(TAG, "Motion: action=0x" + Integer.toHexString(action) // + " pointers=" + event.getPointerCount()); int NP = mPointers.size(); @@ -235,36 +336,38 @@ public class PointerLocationView extends View { //} else { // mRect.setEmpty(); //} - if (action == MotionEvent.ACTION_DOWN) { - for (int p=0; p<NP; p++) { - final PointerState ps = mPointers.get(p); - ps.mXs.clear(); - ps.mYs.clear(); - ps.mVelocity = VelocityTracker.obtain(); - ps.mCurDown = false; - } - mPointers.get(0).mCurDown = true; - mMaxNumPointers = 0; - if (mPrintCoords) { - Log.i("Pointer", "Pointer 1: DOWN"); + if (action == MotionEvent.ACTION_DOWN + || (action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_DOWN) { + final int index = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) + >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; // will be 0 for down + if (action == MotionEvent.ACTION_DOWN) { + for (int p=0; p<NP; p++) { + final PointerState ps = mPointers.get(p); + ps.clearTrace(); + ps.mCurDown = false; + } + mCurDown = true; + mMaxNumPointers = 0; + mVelocity.clear(); } - } - - if ((action&MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_DOWN) { - final int index = (action&MotionEvent.ACTION_POINTER_INDEX_MASK) - >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; + final int id = event.getPointerId(index); while (NP <= id) { PointerState ps = new PointerState(); - ps.mVelocity = VelocityTracker.obtain(); mPointers.add(ps); NP++; } + + if (mActivePointerId < 0 || + ! mPointers.get(mActivePointerId).mCurDown) { + mActivePointerId = id; + } + final PointerState ps = mPointers.get(id); - ps.mVelocity = VelocityTracker.obtain(); ps.mCurDown = true; if (mPrintCoords) { - Log.i("Pointer", "Pointer " + (id+1) + ": DOWN"); + Log.i(TAG, mText.clear().append("Pointer ") + .append(id + 1).append(": DOWN").toString()); } } @@ -276,64 +379,52 @@ public class PointerLocationView extends View { if (mMaxNumPointers < mCurNumPointers) { mMaxNumPointers = mCurNumPointers; } + + mVelocity.addMovement(event); + mVelocity.computeCurrentVelocity(1); for (int i=0; i<NI; i++) { final int id = event.getPointerId(i); final PointerState ps = mPointers.get(id); - ps.mVelocity.addMovement(event); - ps.mVelocity.computeCurrentVelocity(1); final int N = event.getHistorySize(); for (int j=0; j<N; j++) { + event.getHistoricalPointerCoords(i, j, ps.mCoords); if (mPrintCoords) { - Log.i("Pointer", "Pointer " + (id+1) + ": (" - + event.getHistoricalX(i, j) - + ", " + event.getHistoricalY(i, j) + ")" - + " Prs=" + event.getHistoricalPressure(i, j) - + " Size=" + event.getHistoricalSize(i, j)); + logPointerCoords(ps.mCoords, id); } - ps.mXs.add(event.getHistoricalX(i, j)); - ps.mYs.add(event.getHistoricalY(i, j)); + ps.addTrace(event.getHistoricalX(i, j), event.getHistoricalY(i, j)); } + event.getPointerCoords(i, ps.mCoords); if (mPrintCoords) { - Log.i("Pointer", "Pointer " + (id+1) + ": (" - + event.getX(i) + ", " + event.getY(i) + ")" - + " Prs=" + event.getPressure(i) - + " Size=" + event.getSize(i)); + logPointerCoords(ps.mCoords, id); } - ps.mXs.add(event.getX(i)); - ps.mYs.add(event.getY(i)); - ps.mCurX = (int)event.getX(i); - ps.mCurY = (int)event.getY(i); - //Log.i("Pointer", "Pointer #" + p + ": (" + ps.mCurX - // + "," + ps.mCurY + ")"); - ps.mCurPressure = event.getPressure(i); - ps.mCurSize = event.getSize(i); - ps.mCurWidth = (int)(ps.mCurSize*(getWidth()/3)); + ps.addTrace(ps.mCoords.x, ps.mCoords.y); + ps.mXVelocity = mVelocity.getXVelocity(id); + ps.mYVelocity = mVelocity.getYVelocity(id); } - if ((action&MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_UP) { - final int index = (action&MotionEvent.ACTION_POINTER_INDEX_MASK) - >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; + if (action == MotionEvent.ACTION_UP + || action == MotionEvent.ACTION_CANCEL + || (action & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_UP) { + final int index = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) + >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; // will be 0 for UP + final int id = event.getPointerId(index); final PointerState ps = mPointers.get(id); - ps.mXs.add(Float.NaN); - ps.mYs.add(Float.NaN); ps.mCurDown = false; if (mPrintCoords) { - Log.i("Pointer", "Pointer " + (id+1) + ": UP"); + Log.i(TAG, mText.clear().append("Pointer ") + .append(id + 1).append(": UP").toString()); } - } - - if (action == MotionEvent.ACTION_UP) { - for (int i=0; i<NI; i++) { - final int id = event.getPointerId(i); - final PointerState ps = mPointers.get(id); - if (ps.mCurDown) { - ps.mCurDown = false; - if (mPrintCoords) { - Log.i("Pointer", "Pointer " + (id+1) + ": UP"); - } + + if (action == MotionEvent.ACTION_UP + || action == MotionEvent.ACTION_CANCEL) { + mCurDown = false; + } else { + if (mActivePointerId == id) { + mActivePointerId = event.getPointerId(index == 0 ? 1 : 0); } + ps.addTrace(Float.NaN, Float.NaN); } } @@ -354,8 +445,120 @@ public class PointerLocationView extends View { @Override public boolean onTrackballEvent(MotionEvent event) { - Log.i("Pointer", "Trackball: " + event); + Log.i(TAG, "Trackball: " + event); return super.onTrackballEvent(event); } + // HACK + // A quick and dirty string builder implementation optimized for GC. + // Using String.format causes the application grind to a halt when + // more than a couple of pointers are down due to the number of + // temporary objects allocated while formatting strings for drawing or logging. + private static final class FasterStringBuilder { + private char[] mChars; + private int mLength; + + public FasterStringBuilder() { + mChars = new char[64]; + } + + public FasterStringBuilder clear() { + mLength = 0; + return this; + } + + public FasterStringBuilder append(String value) { + final int valueLength = value.length(); + final int index = reserve(valueLength); + value.getChars(0, valueLength, mChars, index); + mLength += valueLength; + return this; + } + + public FasterStringBuilder append(int value) { + return append(value, 0); + } + + public FasterStringBuilder append(int value, int zeroPadWidth) { + final boolean negative = value < 0; + if (negative) { + value = - value; + if (value < 0) { + append("-2147483648"); + return this; + } + } + + int index = reserve(11); + final char[] chars = mChars; + + if (value == 0) { + chars[index++] = '0'; + mLength += 1; + return this; + } + + if (negative) { + chars[index++] = '-'; + } + + int divisor = 1000000000; + int numberWidth = 10; + while (value < divisor) { + divisor /= 10; + numberWidth -= 1; + if (numberWidth < zeroPadWidth) { + chars[index++] = '0'; + } + } + + do { + int digit = value / divisor; + value -= digit * divisor; + divisor /= 10; + chars[index++] = (char) (digit + '0'); + } while (divisor != 0); + + mLength = index; + return this; + } + + public FasterStringBuilder append(float value, int precision) { + int scale = 1; + for (int i = 0; i < precision; i++) { + scale *= 10; + } + value = (float) (Math.rint(value * scale) / scale); + + append((int) value); + + if (precision != 0) { + append("."); + value = Math.abs(value); + value -= Math.floor(value); + append((int) (value * scale), precision); + } + + return this; + } + + @Override + public String toString() { + return new String(mChars, 0, mLength); + } + + private int reserve(int length) { + final int oldLength = mLength; + final int newLength = mLength + length; + final char[] oldChars = mChars; + final int oldCapacity = oldChars.length; + if (newLength > oldCapacity) { + final int newCapacity = oldCapacity * 2; + final char[] newChars = new char[newCapacity]; + System.arraycopy(oldChars, 0, newChars, 0, oldLength); + mChars = newChars; + } + return oldLength; + } + } } diff --git a/core/java/com/google/android/mms/pdu/PduParser.java b/core/java/com/google/android/mms/pdu/PduParser.java index 131ac51707bb..21f0c93df3dc 100644 --- a/core/java/com/google/android/mms/pdu/PduParser.java +++ b/core/java/com/google/android/mms/pdu/PduParser.java @@ -202,7 +202,18 @@ public class PduParser { PduHeaders headers = new PduHeaders(); while (keepParsing && (pduDataStream.available() > 0)) { + pduDataStream.mark(1); int headerField = extractByteValue(pduDataStream); + /* parse custom text header */ + if ((headerField >= TEXT_MIN) && (headerField <= TEXT_MAX)) { + pduDataStream.reset(); + byte [] bVal = parseWapString(pduDataStream, TYPE_TEXT_STRING); + if (LOCAL_LOGV) { + Log.v(LOG_TAG, "TextHeader: " + new String(bVal)); + } + /* we should ignore it at the moment */ + continue; + } switch (headerField) { case PduHeaders.MESSAGE_TYPE: { @@ -780,26 +791,34 @@ public class PduParser { /* get part's data */ if (dataLength > 0) { byte[] partData = new byte[dataLength]; + String partContentType = new String(part.getContentType()); pduDataStream.read(partData, 0, dataLength); - // Check Content-Transfer-Encoding. - byte[] partDataEncoding = part.getContentTransferEncoding(); - if (null != partDataEncoding) { - String encoding = new String(partDataEncoding); - if (encoding.equalsIgnoreCase(PduPart.P_BASE64)) { - // Decode "base64" into "binary". - partData = Base64.decodeBase64(partData); - } else if (encoding.equalsIgnoreCase(PduPart.P_QUOTED_PRINTABLE)) { - // Decode "quoted-printable" into "binary". - partData = QuotedPrintable.decodeQuotedPrintable(partData); - } else { - // "binary" is the default encoding. + if (partContentType.equalsIgnoreCase(ContentType.MULTIPART_ALTERNATIVE)) { + // parse "multipart/vnd.wap.multipart.alternative". + PduBody childBody = parseParts(new ByteArrayInputStream(partData)); + // take the first part of children. + part = childBody.getPart(0); + } else { + // Check Content-Transfer-Encoding. + byte[] partDataEncoding = part.getContentTransferEncoding(); + if (null != partDataEncoding) { + String encoding = new String(partDataEncoding); + if (encoding.equalsIgnoreCase(PduPart.P_BASE64)) { + // Decode "base64" into "binary". + partData = Base64.decodeBase64(partData); + } else if (encoding.equalsIgnoreCase(PduPart.P_QUOTED_PRINTABLE)) { + // Decode "quoted-printable" into "binary". + partData = QuotedPrintable.decodeQuotedPrintable(partData); + } else { + // "binary" is the default encoding. + } } + if (null == partData) { + log("Decode part data error!"); + return null; + } + part.setData(partData); } - if (null == partData) { - log("Decode part data error!"); - return null; - } - part.setData(partData); } /* add this part to body */ diff --git a/core/jni/ActivityManager.cpp b/core/jni/ActivityManager.cpp index 8950dfb0ce18..0bd14faa4224 100644 --- a/core/jni/ActivityManager.cpp +++ b/core/jni/ActivityManager.cpp @@ -39,7 +39,7 @@ int openContentProviderFile(const String16& uri) data.writeString16(uri); status_t ret = am->transact(OPEN_CONTENT_URI_TRANSACTION, data, &reply); if (ret == NO_ERROR) { - int32_t exceptionCode = reply.readInt32(); + int32_t exceptionCode = reply.readExceptionCode(); if (!exceptionCode) { // Success is indicated here by a nonzero int followed by the fd; // failure by a zero int with no data following. diff --git a/core/jni/Android.mk b/core/jni/Android.mk index df1ab9ee44bb..a038cc5a47ab 100644 --- a/core/jni/Android.mk +++ b/core/jni/Android.mk @@ -28,6 +28,7 @@ LOCAL_SRC_FILES:= \ Time.cpp \ com_google_android_gles_jni_EGLImpl.cpp \ com_google_android_gles_jni_GLImpl.cpp.arm \ + android_app_NativeActivity.cpp \ android_opengl_GLES10.cpp \ android_opengl_GLES10Ext.cpp \ android_opengl_GLES11.cpp \ @@ -44,12 +45,17 @@ LOCAL_SRC_FILES:= \ android_view_Display.cpp \ android_view_Surface.cpp \ android_view_ViewRoot.cpp \ + android_view_InputChannel.cpp \ + android_view_InputQueue.cpp \ + android_view_KeyEvent.cpp \ + android_view_MotionEvent.cpp \ android_text_AndroidCharacter.cpp \ android_text_AndroidBidi.cpp \ android_text_KeyCharacterMap.cpp \ android_os_Debug.cpp \ android_os_FileUtils.cpp \ android_os_MemoryFile.cpp \ + android_os_MessageQueue.cpp \ android_os_ParcelFileDescriptor.cpp \ android_os_Power.cpp \ android_os_StatFs.cpp \ @@ -72,6 +78,7 @@ LOCAL_SRC_FILES:= \ android_util_Process.cpp \ android_util_StringBlock.cpp \ android_util_XmlBlock.cpp \ + android/graphics/AutoDecodeCancel.cpp \ android/graphics/Bitmap.cpp \ android/graphics/BitmapFactory.cpp \ android/graphics/Camera.cpp \ @@ -85,7 +92,6 @@ LOCAL_SRC_FILES:= \ android/graphics/MaskFilter.cpp \ android/graphics/Matrix.cpp \ android/graphics/Movie.cpp \ - android/graphics/NIOBuffer.cpp \ android/graphics/NinePatch.cpp \ android/graphics/NinePatchImpl.cpp \ android/graphics/Paint.cpp \ @@ -95,6 +101,7 @@ LOCAL_SRC_FILES:= \ android_graphics_PixelFormat.cpp \ android/graphics/Picture.cpp \ android/graphics/PorterDuff.cpp \ + android/graphics/LargeBitmap.cpp \ android/graphics/Rasterizer.cpp \ android/graphics/Region.cpp \ android/graphics/Shader.cpp \ @@ -123,13 +130,14 @@ LOCAL_SRC_FILES:= \ android_server_Watchdog.cpp \ android_message_digest_sha1.cpp \ android_ddm_DdmHandleNativeHeap.cpp \ - android_location_GpsLocationProvider.cpp \ com_android_internal_os_ZygoteInit.cpp \ com_android_internal_graphics_NativeUtils.cpp \ android_backup_BackupDataInput.cpp \ android_backup_BackupDataOutput.cpp \ android_backup_FileBackupHelperBase.cpp \ - android_backup_BackupHelperDispatcher.cpp + android_backup_BackupHelperDispatcher.cpp \ + android_content_res_ObbScanner.cpp \ + android_content_res_Configuration.cpp LOCAL_C_INCLUDES += \ $(JNI_H_INCLUDE) \ @@ -162,6 +170,7 @@ LOCAL_SHARED_LIBRARIES := \ libbinder \ libnetutils \ libui \ + libgui \ libsurfaceflinger_client \ libcamera_client \ libskiagl \ @@ -179,7 +188,6 @@ LOCAL_SHARED_LIBRARIES := \ libssl \ libicuuc \ libicui18n \ - libicudata \ libmedia \ libwpa_client \ libjpeg diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index d38d748a3e32..2751a8228f6c 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -53,6 +53,7 @@ extern int register_android_os_Binder(JNIEnv* env); extern int register_android_os_Process(JNIEnv* env); extern int register_android_graphics_Bitmap(JNIEnv*); extern int register_android_graphics_BitmapFactory(JNIEnv*); +extern int register_android_graphics_LargeBitmap(JNIEnv*); extern int register_android_graphics_Camera(JNIEnv* env); extern int register_android_graphics_Graphics(JNIEnv* env); extern int register_android_graphics_Interpolator(JNIEnv* env); @@ -128,6 +129,7 @@ extern int register_android_nio_utils(JNIEnv* env); extern int register_android_pim_EventRecurrence(JNIEnv* env); extern int register_android_text_format_Time(JNIEnv* env); extern int register_android_os_Debug(JNIEnv* env); +extern int register_android_os_MessageQueue(JNIEnv* env); extern int register_android_os_ParcelFileDescriptor(JNIEnv *env); extern int register_android_os_Power(JNIEnv *env); extern int register_android_os_StatFs(JNIEnv *env); @@ -156,11 +158,17 @@ extern int register_android_server_BluetoothA2dpService(JNIEnv* env); extern int register_android_server_Watchdog(JNIEnv* env); extern int register_android_ddm_DdmHandleNativeHeap(JNIEnv *env); extern int register_com_android_internal_os_ZygoteInit(JNIEnv* env); -extern int register_android_location_GpsLocationProvider(JNIEnv* env); extern int register_android_backup_BackupDataInput(JNIEnv *env); extern int register_android_backup_BackupDataOutput(JNIEnv *env); extern int register_android_backup_FileBackupHelperBase(JNIEnv *env); extern int register_android_backup_BackupHelperDispatcher(JNIEnv *env); +extern int register_android_app_NativeActivity(JNIEnv *env); +extern int register_android_view_InputChannel(JNIEnv* env); +extern int register_android_view_InputQueue(JNIEnv* env); +extern int register_android_view_KeyEvent(JNIEnv* env); +extern int register_android_view_MotionEvent(JNIEnv* env); +extern int register_android_content_res_ObbScanner(JNIEnv* env); +extern int register_android_content_res_Configuration(JNIEnv* env); static AndroidRuntime* gCurRuntime = NULL; @@ -300,6 +308,8 @@ status_t AndroidRuntime::callMain( jclass clazz; jmethodID methodId; + LOGD("Calling main entry %s", className); + env = getJNIEnv(); if (env == NULL) return UNKNOWN_ERROR; @@ -498,7 +508,7 @@ static void blockSigpipe() static void readLocale(char* language, char* region) { char propLang[PROPERTY_VALUE_MAX], propRegn[PROPERTY_VALUE_MAX]; - + property_get("persist.sys.language", propLang, ""); property_get("persist.sys.country", propRegn, ""); if (*propLang == 0 && *propRegn == 0) { @@ -512,6 +522,40 @@ static void readLocale(char* language, char* region) } /* + * Parse a property containing space-separated options that should be + * passed directly to the VM, e.g. "-Xmx32m -verbose:gc -Xregenmap". + * + * This will cut up "extraOptsBuf" as we chop it into individual options. + * + * Adds the strings, if any, to mOptions. + */ +void AndroidRuntime::parseExtraOpts(char* extraOptsBuf) +{ + JavaVMOption opt; + char* start; + char* end; + + memset(&opt, 0, sizeof(opt)); + start = extraOptsBuf; + while (*start != '\0') { + while (*start == ' ') /* skip leading whitespace */ + start++; + if (*start == '\0') /* was trailing ws, bail */ + break; + + end = start+1; + while (*end != ' ' && *end != '\0') /* find end of token */ + end++; + if (*end == ' ') + *end++ = '\0'; /* mark end, advance to indicate more */ + + opt.optionString = start; + mOptions.add(opt); + start = end; + } +} + +/* * Start the Dalvik Virtual Machine. * * Various arguments, most determined by system properties, are passed in. @@ -530,6 +574,7 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv) char enableAssertBuf[sizeof("-ea:")-1 + PROPERTY_VALUE_MAX]; char jniOptsBuf[sizeof("-Xjniopts:")-1 + PROPERTY_VALUE_MAX]; char heapsizeOptsBuf[sizeof("-Xmx")-1 + PROPERTY_VALUE_MAX]; + char extraOptsBuf[PROPERTY_VALUE_MAX]; char* stackTraceFile = NULL; bool checkJni = false; bool checkDexSum = false; @@ -667,6 +712,33 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv) LOGW("dalvik.vm.gc.overwritefree should be 'true' or 'false'"); } + /* enable heap verification before each gc */ + property_get("dalvik.vm.gc.preverify", propBuf, "false"); + if (strcmp(propBuf, "true") == 0) { + opt.optionString = "-Xgc:preverify"; + mOptions.add(opt); + } else if (strcmp(propBuf, "false") != 0) { + LOGW("dalvik.vm.gc.preverify should be 'true' or 'false'"); + } + + /* enable heap verification after each gc */ + property_get("dalvik.vm.gc.postverify", propBuf, "false"); + if (strcmp(propBuf, "true") == 0) { + opt.optionString = "-Xgc:postverify"; + mOptions.add(opt); + } else if (strcmp(propBuf, "false") != 0) { + LOGW("dalvik.vm.gc.postverify should be 'true' or 'false'"); + } + + /* enable card table verification for partial gc */ + property_get("dalvik.vm.gc.verifycardtable", propBuf, "false"); + if (strcmp(propBuf, "true") == 0) { + opt.optionString = "-Xgc:verifycardtable"; + mOptions.add(opt); + } else if (strcmp(propBuf, "false") != 0) { + LOGW("dalvik.vm.gc.verifycardtable should be 'true' or 'false'"); + } + /* enable debugging; set suspend=y to pause during VM init */ #ifdef HAVE_ANDROID_OS /* use android ADB transport */ @@ -714,16 +786,6 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv) } #if defined(WITH_JIT) - /* Minimal profile threshold to trigger JIT compilation */ - char jitThresholdBuf[sizeof("-Xjitthreshold:") + PROPERTY_VALUE_MAX]; - property_get("dalvik.vm.jit.threshold", propBuf, ""); - if (strlen(propBuf) > 0) { - strcpy(jitThresholdBuf, "-Xjitthreshold:"); - strcat(jitThresholdBuf, propBuf); - opt.optionString = jitThresholdBuf; - mOptions.add(opt); - } - /* Force interpreter-only mode for selected opcodes. Eg "1-0a,3c,f1-ff" */ char jitOpBuf[sizeof("-Xjitop:") + PROPERTY_VALUE_MAX]; property_get("dalvik.vm.jit.op", propBuf, ""); @@ -734,16 +796,6 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv) mOptions.add(opt); } - /* - * Reverse the polarity of dalvik.vm.jit.op and force interpreter-only - * for non-selected opcodes. - */ - property_get("dalvik.vm.jit.includeop", propBuf, ""); - if (strlen(propBuf) > 0) { - opt.optionString = "-Xincludeselectedop"; - mOptions.add(opt); - } - /* Force interpreter-only mode for selected methods */ char jitMethodBuf[sizeof("-Xjitmethod:") + PROPERTY_VALUE_MAX]; property_get("dalvik.vm.jit.method", propBuf, ""); @@ -753,37 +805,6 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv) opt.optionString = jitMethodBuf; mOptions.add(opt); } - - /* - * Reverse the polarity of dalvik.vm.jit.method and force interpreter-only - * for non-selected methods. - */ - property_get("dalvik.vm.jit.includemethod", propBuf, ""); - if (strlen(propBuf) > 0) { - opt.optionString = "-Xincludeselectedmethod"; - mOptions.add(opt); - } - - /* - * Enable profile collection on JIT'ed code. - */ - property_get("dalvik.vm.jit.profile", propBuf, ""); - if (strlen(propBuf) > 0) { - opt.optionString = "-Xjitprofile"; - mOptions.add(opt); - } - - /* - * Disable optimizations by setting the corresponding bit to 1. - */ - char jitOptBuf[sizeof("-Xjitdisableopt:") + PROPERTY_VALUE_MAX]; - property_get("dalvik.vm.jit.disableopt", propBuf, ""); - if (strlen(propBuf) > 0) { - strcpy(jitOptBuf, "-Xjitdisableopt:"); - strcat(jitOptBuf, propBuf); - opt.optionString = jitOptBuf; - mOptions.add(opt); - } #endif if (executionMode == kEMIntPortable) { @@ -838,7 +859,11 @@ int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv) opt.optionString = stackTraceFile; mOptions.add(opt); } - + + /* extra options; parse this late so it overrides others */ + property_get("dalvik.vm.extra-opts", extraOptsBuf, ""); + parseExtraOpts(extraOptsBuf); + /* Set the properties for locale */ { char langOption[sizeof("-Duser.language=") + 3]; @@ -891,7 +916,8 @@ bail: */ void AndroidRuntime::start(const char* className, const bool startSystemServer) { - LOGD("\n>>>>>>>>>>>>>> AndroidRuntime START <<<<<<<<<<<<<<\n"); + LOGD("\n>>>>>> AndroidRuntime START %s <<<<<<\n", + className != NULL ? className : "(unknown)"); char* slashClassName = NULL; char* cp; @@ -1006,7 +1032,7 @@ void AndroidRuntime::start() void AndroidRuntime::onExit(int code) { - LOGI("AndroidRuntime onExit calling exit(%d)", code); + LOGV("AndroidRuntime onExit calling exit(%d)", code); exit(code); } @@ -1130,11 +1156,13 @@ static int javaDetachThread(void) * * This is called from elsewhere in the library. */ -/*static*/ void AndroidRuntime::createJavaThread(const char* name, +/*static*/ android_thread_id_t AndroidRuntime::createJavaThread(const char* name, void (*start)(void *), void* arg) { + android_thread_id_t threadId = 0; javaCreateThreadEtc((android_thread_func_t) start, arg, name, - ANDROID_PRIORITY_DEFAULT, 0, NULL); + ANDROID_PRIORITY_DEFAULT, 0, &threadId); + return threadId; } #if 0 @@ -1216,6 +1244,7 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_graphics_Bitmap), REG_JNI(register_android_graphics_BitmapFactory), + REG_JNI(register_android_graphics_LargeBitmap), REG_JNI(register_android_graphics_Camera), REG_JNI(register_android_graphics_Canvas), REG_JNI(register_android_graphics_ColorFilter), @@ -1250,6 +1279,7 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_os_Debug), REG_JNI(register_android_os_FileObserver), REG_JNI(register_android_os_FileUtils), + REG_JNI(register_android_os_MessageQueue), REG_JNI(register_android_os_ParcelFileDescriptor), REG_JNI(register_android_os_Power), REG_JNI(register_android_os_StatFs), @@ -1280,11 +1310,19 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_server_Watchdog), REG_JNI(register_android_message_digest_sha1), REG_JNI(register_android_ddm_DdmHandleNativeHeap), - REG_JNI(register_android_location_GpsLocationProvider), REG_JNI(register_android_backup_BackupDataInput), REG_JNI(register_android_backup_BackupDataOutput), REG_JNI(register_android_backup_FileBackupHelperBase), REG_JNI(register_android_backup_BackupHelperDispatcher), + + REG_JNI(register_android_app_NativeActivity), + REG_JNI(register_android_view_InputChannel), + REG_JNI(register_android_view_InputQueue), + REG_JNI(register_android_view_KeyEvent), + REG_JNI(register_android_view_MotionEvent), + + REG_JNI(register_android_content_res_ObbScanner), + REG_JNI(register_android_content_res_Configuration), }; /* @@ -1299,7 +1337,7 @@ static const RegJNIRec gRegJNI[] = { */ androidSetCreateThreadFunc((android_create_thread_fn) javaCreateThreadEtc); - LOGD("--- registering native functions ---\n"); + LOGV("--- registering native functions ---\n"); /* * Every "register" function calls one or more things that return diff --git a/core/jni/android/graphics/AutoDecodeCancel.cpp b/core/jni/android/graphics/AutoDecodeCancel.cpp new file mode 100644 index 000000000000..f0739ea84911 --- /dev/null +++ b/core/jni/android/graphics/AutoDecodeCancel.cpp @@ -0,0 +1,100 @@ +#include "AutoDecodeCancel.h" + +static SkMutex gAutoDecoderCancelMutex; +static AutoDecoderCancel* gAutoDecoderCancel; +#ifdef SK_DEBUG +static int gAutoDecoderCancelCount; +#endif + +AutoDecoderCancel::AutoDecoderCancel(jobject joptions, + SkImageDecoder* decoder) { + fJOptions = joptions; + fDecoder = decoder; + + if (NULL != joptions) { + SkAutoMutexAcquire ac(gAutoDecoderCancelMutex); + + // Add us as the head of the list + fPrev = NULL; + fNext = gAutoDecoderCancel; + if (gAutoDecoderCancel) { + gAutoDecoderCancel->fPrev = this; + } + gAutoDecoderCancel = this; + + SkDEBUGCODE(gAutoDecoderCancelCount += 1;) + Validate(); + } +} + +AutoDecoderCancel::~AutoDecoderCancel() { + if (NULL != fJOptions) { + SkAutoMutexAcquire ac(gAutoDecoderCancelMutex); + + // take us out of the dllist + AutoDecoderCancel* prev = fPrev; + AutoDecoderCancel* next = fNext; + + if (prev) { + SkASSERT(prev->fNext == this); + prev->fNext = next; + } else { + SkASSERT(gAutoDecoderCancel == this); + gAutoDecoderCancel = next; + } + if (next) { + SkASSERT(next->fPrev == this); + next->fPrev = prev; + } + + SkDEBUGCODE(gAutoDecoderCancelCount -= 1;) + Validate(); + } +} + +bool AutoDecoderCancel::RequestCancel(jobject joptions) { + SkAutoMutexAcquire ac(gAutoDecoderCancelMutex); + + Validate(); + + AutoDecoderCancel* pair = gAutoDecoderCancel; + while (pair != NULL) { + if (pair->fJOptions == joptions) { + pair->fDecoder->cancelDecode(); + return true; + } + pair = pair->fNext; + } + return false; +} + +#ifdef SK_DEBUG +// can only call this inside a lock on gAutoDecoderCancelMutex +void AutoDecoderCancel::Validate() { + const int gCount = gAutoDecoderCancelCount; + + if (gCount == 0) { + SkASSERT(gAutoDecoderCancel == NULL); + } else { + SkASSERT(gCount > 0); + + AutoDecoderCancel* curr = gAutoDecoderCancel; + SkASSERT(curr); + SkASSERT(curr->fPrev == NULL); + + int count = 0; + while (curr) { + count += 1; + SkASSERT(count <= gCount); + if (curr->fPrev) { + SkASSERT(curr->fPrev->fNext == curr); + } + if (curr->fNext) { + SkASSERT(curr->fNext->fPrev == curr); + } + curr = curr->fNext; + } + SkASSERT(count == gCount); + } +} +#endif diff --git a/core/jni/android/graphics/AutoDecodeCancel.h b/core/jni/android/graphics/AutoDecodeCancel.h new file mode 100644 index 000000000000..37b86f92f990 --- /dev/null +++ b/core/jni/android/graphics/AutoDecodeCancel.h @@ -0,0 +1,27 @@ +#ifndef AutoDecodeCancel_DEFINED +#define AutoDecodeCancel_DEFINED + +#include <jni.h> +#include "SkImageDecoder.h" + +class AutoDecoderCancel { +public: + AutoDecoderCancel(jobject options, SkImageDecoder* decoder); + ~AutoDecoderCancel(); + + static bool RequestCancel(jobject options); + +private: + AutoDecoderCancel* fNext; + AutoDecoderCancel* fPrev; + jobject fJOptions; // java options object + SkImageDecoder* fDecoder; + +#ifdef SK_DEBUG + static void Validate(); +#else + static void Validate() {} +#endif +}; + +#endif diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp index b41bad0d66e8..6745b24169c5 100644 --- a/core/jni/android/graphics/BitmapFactory.cpp +++ b/core/jni/android/graphics/BitmapFactory.cpp @@ -1,33 +1,35 @@ #define LOG_TAG "BitmapFactory" +#include "BitmapFactory.h" #include "SkImageDecoder.h" #include "SkImageRef_ashmem.h" #include "SkImageRef_GlobalPool.h" #include "SkPixelRef.h" #include "SkStream.h" -#include "GraphicsJNI.h" #include "SkTemplates.h" #include "SkUtils.h" #include "CreateJavaOutputStreamAdaptor.h" +#include "AutoDecodeCancel.h" #include <android_runtime/AndroidRuntime.h> #include <utils/Asset.h> #include <utils/ResourceTypes.h> #include <netinet/in.h> #include <sys/mman.h> - -static jclass gOptions_class; -static jfieldID gOptions_justBoundsFieldID; -static jfieldID gOptions_sampleSizeFieldID; -static jfieldID gOptions_configFieldID; -static jfieldID gOptions_ditherFieldID; -static jfieldID gOptions_purgeableFieldID; -static jfieldID gOptions_shareableFieldID; -static jfieldID gOptions_nativeAllocFieldID; -static jfieldID gOptions_widthFieldID; -static jfieldID gOptions_heightFieldID; -static jfieldID gOptions_mimeFieldID; -static jfieldID gOptions_mCancelID; +#include <sys/stat.h> + +jclass gOptions_class; +jfieldID gOptions_justBoundsFieldID; +jfieldID gOptions_sampleSizeFieldID; +jfieldID gOptions_configFieldID; +jfieldID gOptions_ditherFieldID; +jfieldID gOptions_purgeableFieldID; +jfieldID gOptions_shareableFieldID; +jfieldID gOptions_nativeAllocFieldID; +jfieldID gOptions_widthFieldID; +jfieldID gOptions_heightFieldID; +jfieldID gOptions_mimeFieldID; +jfieldID gOptions_mCancelID; static jclass gFileDescriptor_class; static jfieldID gFileDescriptor_descriptor; @@ -38,129 +40,6 @@ static jfieldID gFileDescriptor_descriptor; #define TRACE_BITMAP(code) #endif -/////////////////////////////////////////////////////////////////////////////// - -class AutoDecoderCancel { -public: - AutoDecoderCancel(jobject options, SkImageDecoder* decoder); - ~AutoDecoderCancel(); - - static bool RequestCancel(jobject options); - -private: - AutoDecoderCancel* fNext; - AutoDecoderCancel* fPrev; - jobject fJOptions; // java options object - SkImageDecoder* fDecoder; - -#ifdef SK_DEBUG - static void Validate(); -#else - static void Validate() {} -#endif -}; - -static SkMutex gAutoDecoderCancelMutex; -static AutoDecoderCancel* gAutoDecoderCancel; -#ifdef SK_DEBUG - static int gAutoDecoderCancelCount; -#endif - -AutoDecoderCancel::AutoDecoderCancel(jobject joptions, - SkImageDecoder* decoder) { - fJOptions = joptions; - fDecoder = decoder; - - if (NULL != joptions) { - SkAutoMutexAcquire ac(gAutoDecoderCancelMutex); - - // Add us as the head of the list - fPrev = NULL; - fNext = gAutoDecoderCancel; - if (gAutoDecoderCancel) { - gAutoDecoderCancel->fPrev = this; - } - gAutoDecoderCancel = this; - - SkDEBUGCODE(gAutoDecoderCancelCount += 1;) - Validate(); - } -} - -AutoDecoderCancel::~AutoDecoderCancel() { - if (NULL != fJOptions) { - SkAutoMutexAcquire ac(gAutoDecoderCancelMutex); - - // take us out of the dllist - AutoDecoderCancel* prev = fPrev; - AutoDecoderCancel* next = fNext; - - if (prev) { - SkASSERT(prev->fNext == this); - prev->fNext = next; - } else { - SkASSERT(gAutoDecoderCancel == this); - gAutoDecoderCancel = next; - } - if (next) { - SkASSERT(next->fPrev == this); - next->fPrev = prev; - } - - SkDEBUGCODE(gAutoDecoderCancelCount -= 1;) - Validate(); - } -} - -bool AutoDecoderCancel::RequestCancel(jobject joptions) { - SkAutoMutexAcquire ac(gAutoDecoderCancelMutex); - - Validate(); - - AutoDecoderCancel* pair = gAutoDecoderCancel; - while (pair != NULL) { - if (pair->fJOptions == joptions) { - pair->fDecoder->cancelDecode(); - return true; - } - pair = pair->fNext; - } - return false; -} - -#ifdef SK_DEBUG -// can only call this inside a lock on gAutoDecoderCancelMutex -void AutoDecoderCancel::Validate() { - const int gCount = gAutoDecoderCancelCount; - - if (gCount == 0) { - SkASSERT(gAutoDecoderCancel == NULL); - } else { - SkASSERT(gCount > 0); - - AutoDecoderCancel* curr = gAutoDecoderCancel; - SkASSERT(curr); - SkASSERT(curr->fPrev == NULL); - - int count = 0; - while (curr) { - count += 1; - SkASSERT(count <= gCount); - if (curr->fPrev) { - SkASSERT(curr->fPrev->fNext == curr); - } - if (curr->fNext) { - SkASSERT(curr->fNext->fPrev == curr); - } - curr = curr->fNext; - } - SkASSERT(count == gCount); - } -} -#endif - -/////////////////////////////////////////////////////////////////////////////// - using namespace android; class NinePatchPeeker : public SkImageDecoder::Peeker { @@ -279,7 +158,7 @@ static inline int32_t validOrNeg1(bool isValid, int32_t value) { return ((int32_t)isValid - 1) | value; } -static jstring getMimeTypeString(JNIEnv* env, SkImageDecoder::Format format) { +jstring getMimeTypeString(JNIEnv* env, SkImageDecoder::Format format) { static const struct { SkImageDecoder::Format fFormat; const char* fMimeType; @@ -340,6 +219,7 @@ static SkPixelRef* installPixelRef(SkBitmap* bitmap, SkStream* stream, } pr->setDitherImage(ditherImage); bitmap->setPixelRef(pr)->unref(); + pr->isOpaque(bitmap); return pr; } @@ -477,7 +357,7 @@ static jobject nativeDecodeStream(JNIEnv* env, jobject clazz, jobject padding, jobject options) { // BitmapFactory$Options jobject bitmap = NULL; - SkStream* stream = CreateJavaInputStreamAdaptor(env, is, storage); + SkStream* stream = CreateJavaInputStreamAdaptor(env, is, storage, 0); if (stream) { // for now we don't allow purgeable with java inputstreams @@ -586,10 +466,8 @@ static jobject nativeDecodeAsset(JNIEnv* env, jobject clazz, jobject options) { // BitmapFactory$Options SkStream* stream; Asset* asset = reinterpret_cast<Asset*>(native_asset); - // assets can always be rebuilt, so force this - bool forcePurgeable = true; - - if (forcePurgeable || optionsPurgeable(env, options)) { + bool forcePurgeable = optionsPurgeable(env, options); + if (forcePurgeable) { // if we could "ref/reopen" the asset, we may not need to copy it here // and we could assume optionsShareable, since assets are always RO stream = copyAssetToStream(asset); @@ -682,6 +560,134 @@ static void nativeSetDefaultConfig(JNIEnv* env, jobject, int nativeConfig) { } } +static SkMemoryStream* buildSkMemoryStream(SkStream *stream) { + size_t bufferSize = 4096; + size_t streamLen = 0; + size_t len; + char* data = (char*)sk_malloc_throw(bufferSize); + + while ((len = stream->read(data + streamLen, + bufferSize - streamLen)) != 0) { + streamLen += len; + if (streamLen == bufferSize) { + bufferSize *= 2; + data = (char*)sk_realloc_throw(data, bufferSize); + } + } + data = (char*)sk_realloc_throw(data, streamLen); + SkMemoryStream* streamMem = new SkMemoryStream(); + streamMem->setMemoryOwned(data, streamLen); + return streamMem; +} + +static jobject doBuildTileIndex(JNIEnv* env, SkStream* stream) { + SkImageDecoder* decoder = SkImageDecoder::Factory(stream); + int width, height; + if (NULL == decoder) { + doThrowIOE(env, "Image format not supported"); + return nullObjectReturn("SkImageDecoder::Factory returned null"); + } + + JavaPixelAllocator *javaAllocator = new JavaPixelAllocator(env, true); + decoder->setAllocator(javaAllocator); + JavaMemoryUsageReporter *javaMemoryReporter = new JavaMemoryUsageReporter(env); + decoder->setReporter(javaMemoryReporter); + javaAllocator->unref(); + javaMemoryReporter->unref(); + + if (!decoder->buildTileIndex(stream, &width, &height)) { + char msg[100]; + snprintf(msg, sizeof(msg), "Image failed to decode using %s decoder", + decoder->getFormatName()); + doThrowIOE(env, msg); + return nullObjectReturn("decoder->buildTileIndex returned false"); + } + + SkLargeBitmap *bm = new SkLargeBitmap(decoder, width, height); + + return GraphicsJNI::createLargeBitmap(env, bm); +} + +static jobject nativeCreateLargeBitmapFromByteArray(JNIEnv* env, jobject, jbyteArray byteArray, + int offset, int length, jboolean isShareable) { + /* If isShareable we could decide to just wrap the java array and + share it, but that means adding a globalref to the java array object + For now we just always copy the array's data if isShareable. + */ + AutoJavaByteArray ar(env, byteArray); + SkStream* stream = new SkMemoryStream(ar.ptr() + offset, length, true); + return doBuildTileIndex(env, stream); +} + +static jobject nativeCreateLargeBitmapFromFileDescriptor(JNIEnv* env, jobject clazz, + jobject fileDescriptor, jboolean isShareable) { + NPE_CHECK_RETURN_ZERO(env, fileDescriptor); + + jint descriptor = env->GetIntField(fileDescriptor, + gFileDescriptor_descriptor); + SkStream *stream = NULL; + struct stat fdStat; + int newFD; + if (fstat(descriptor, &fdStat) == -1) { + doThrowIOE(env, "broken file descriptor"); + return nullObjectReturn("fstat return -1"); + } + + if (isShareable && + S_ISREG(fdStat.st_mode) && + (newFD = ::dup(descriptor)) != -1) { + SkFDStream* fdStream = new SkFDStream(newFD, true); + if (!fdStream->isValid()) { + fdStream->unref(); + return NULL; + } + stream = fdStream; + } else { + SkFDStream* fdStream = new SkFDStream(descriptor, false); + if (!fdStream->isValid()) { + fdStream->unref(); + return NULL; + } + stream = buildSkMemoryStream(fdStream); + fdStream->unref(); + } + + /* Restore our offset when we leave, so we can be called more than once + with the same descriptor. This is only required if we didn't dup the + file descriptor, but it is OK to do it all the time. + */ + AutoFDSeek as(descriptor); + + return doBuildTileIndex(env, stream); +} + +static jobject nativeCreateLargeBitmapFromStream(JNIEnv* env, jobject clazz, + jobject is, // InputStream + jbyteArray storage, // byte[] + jboolean isShareable) { + jobject largeBitmap = NULL; + SkStream* stream = CreateJavaInputStreamAdaptor(env, is, storage, 1024); + + if (stream) { + // for now we don't allow shareable with java inputstreams + SkMemoryStream *mStream = buildSkMemoryStream(stream); + largeBitmap = doBuildTileIndex(env, mStream); + stream->unref(); + } + return largeBitmap; +} + +static jobject nativeCreateLargeBitmapFromAsset(JNIEnv* env, jobject clazz, + jint native_asset, // Asset + jboolean isShareable) { + SkStream* stream, *assStream; + Asset* asset = reinterpret_cast<Asset*>(native_asset); + assStream = new AssetStreamAdaptor(asset); + stream = buildSkMemoryStream(assStream); + assStream->unref(); + return doBuildTileIndex(env, stream); +} + /////////////////////////////////////////////////////////////////////////////// static JNINativeMethod gMethods[] = { @@ -711,6 +717,26 @@ static JNINativeMethod gMethods[] = { }, { "nativeSetDefaultConfig", "(I)V", (void*)nativeSetDefaultConfig }, + + { "nativeCreateLargeBitmap", + "([BIIZ)Landroid/graphics/LargeBitmap;", + (void*)nativeCreateLargeBitmapFromByteArray + }, + + { "nativeCreateLargeBitmap", + "(Ljava/io/InputStream;[BZ)Landroid/graphics/LargeBitmap;", + (void*)nativeCreateLargeBitmapFromStream + }, + + { "nativeCreateLargeBitmap", + "(Ljava/io/FileDescriptor;Z)Landroid/graphics/LargeBitmap;", + (void*)nativeCreateLargeBitmapFromFileDescriptor + }, + + { "nativeCreateLargeBitmap", + "(IZ)Landroid/graphics/LargeBitmap;", + (void*)nativeCreateLargeBitmapFromAsset + }, }; static JNINativeMethod gOptionsMethods[] = { diff --git a/core/jni/android/graphics/BitmapFactory.h b/core/jni/android/graphics/BitmapFactory.h new file mode 100644 index 000000000000..f868434620f1 --- /dev/null +++ b/core/jni/android/graphics/BitmapFactory.h @@ -0,0 +1,21 @@ +#ifndef BitmapFactory_DEFINE +#define BitmapFactory_DEFINE + +#include "GraphicsJNI.h" + +extern jclass gOptions_class; +extern jfieldID gOptions_justBoundsFieldID; +extern jfieldID gOptions_sampleSizeFieldID; +extern jfieldID gOptions_configFieldID; +extern jfieldID gOptions_ditherFieldID; +extern jfieldID gOptions_purgeableFieldID; +extern jfieldID gOptions_shareableFieldID; +extern jfieldID gOptions_nativeAllocFieldID; +extern jfieldID gOptions_widthFieldID; +extern jfieldID gOptions_heightFieldID; +extern jfieldID gOptions_mimeFieldID; +extern jfieldID gOptions_mCancelID; + +jstring getMimeTypeString(JNIEnv* env, SkImageDecoder::Format format); + +#endif diff --git a/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.cpp b/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.cpp index a285def804a6..137acc612cbf 100644 --- a/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.cpp +++ b/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.cpp @@ -5,6 +5,7 @@ static jclass gInputStream_Clazz; static jmethodID gInputStream_resetMethodID; +static jmethodID gInputStream_markMethodID; static jmethodID gInputStream_availableMethodID; static jmethodID gInputStream_readMethodID; static jmethodID gInputStream_skipMethodID; @@ -52,7 +53,7 @@ public: return 0; } - if (n <= 0) { + if (n < 0) { // n == 0 should not be possible, see InputStream read() specifications. break; // eof } @@ -76,17 +77,19 @@ public: size_t doSkip(size_t size) { JNIEnv* env = fEnv; + jlong skipped = env->CallLongMethod(fJavaInputStream, gInputStream_skipMethodID, (jlong)size); if (env->ExceptionCheck()) { env->ExceptionDescribe(); env->ExceptionClear(); - SkDebugf("------- available threw an exception\n"); + SkDebugf("------- skip threw an exception\n"); return 0; } if (skipped < 0) { skipped = 0; } + return (size_t)skipped; } @@ -115,7 +118,7 @@ public: */ size_t amountSkipped = 0; do { - size_t amount = this->doSkip(size); + size_t amount = this->doSkip(size - amountSkipped); if (0 == amount) { char tmp; amount = this->doRead(&tmp, 1); @@ -141,7 +144,7 @@ private: }; SkStream* CreateJavaInputStreamAdaptor(JNIEnv* env, jobject stream, - jbyteArray storage) { + jbyteArray storage, int markSize) { static bool gInited; if (!gInited) { @@ -151,6 +154,8 @@ SkStream* CreateJavaInputStreamAdaptor(JNIEnv* env, jobject stream, gInputStream_resetMethodID = env->GetMethodID(gInputStream_Clazz, "reset", "()V"); + gInputStream_markMethodID = env->GetMethodID(gInputStream_Clazz, + "mark", "(I)V"); gInputStream_availableMethodID = env->GetMethodID(gInputStream_Clazz, "available", "()I"); gInputStream_readMethodID = env->GetMethodID(gInputStream_Clazz, @@ -159,6 +164,7 @@ SkStream* CreateJavaInputStreamAdaptor(JNIEnv* env, jobject stream, "skip", "(J)J"); RETURN_NULL_IF_NULL(gInputStream_resetMethodID); + RETURN_NULL_IF_NULL(gInputStream_markMethodID); RETURN_NULL_IF_NULL(gInputStream_availableMethodID); RETURN_NULL_IF_NULL(gInputStream_availableMethodID); RETURN_NULL_IF_NULL(gInputStream_skipMethodID); @@ -166,6 +172,10 @@ SkStream* CreateJavaInputStreamAdaptor(JNIEnv* env, jobject stream, gInited = true; } + if (markSize) { + env->CallVoidMethod(stream, gInputStream_markMethodID, markSize); + } + return new JavaInputStreamAdaptor(env, stream, storage); } diff --git a/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.h b/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.h index cf21dde12301..c34c96ae54f1 100644 --- a/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.h +++ b/core/jni/android/graphics/CreateJavaOutputStreamAdaptor.h @@ -6,7 +6,7 @@ #include "SkStream.h" SkStream* CreateJavaInputStreamAdaptor(JNIEnv* env, jobject stream, - jbyteArray storage); + jbyteArray storage, int markSize = 0); SkWStream* CreateJavaOutputStreamAdaptor(JNIEnv* env, jobject stream, jbyteArray storage); diff --git a/core/jni/android/graphics/Graphics.cpp b/core/jni/android/graphics/Graphics.cpp index 5659ba2f353f..72cea65c26b0 100644 --- a/core/jni/android/graphics/Graphics.cpp +++ b/core/jni/android/graphics/Graphics.cpp @@ -2,7 +2,6 @@ #include "jni.h" #include "GraphicsJNI.h" -#include "NIOBuffer.h" #include "SkPicture.h" #include "SkRegion.h" #include <android_runtime/AndroidRuntime.h> @@ -46,6 +45,10 @@ void doThrowOOME(JNIEnv* env, const char* msg) { doThrow(env, "java/lang/OutOfMemoryError", msg); } +void doThrowIOE(JNIEnv* env, const char* msg) { + doThrow(env, "java/lang/IOException", msg); +} + bool GraphicsJNI::hasException(JNIEnv *env) { if (env->ExceptionCheck() != 0) { LOGE("*** Uncaught exception returned from Java call!\n"); @@ -165,6 +168,9 @@ static jmethodID gBitmap_allocBufferMethodID; static jclass gBitmapConfig_class; static jfieldID gBitmapConfig_nativeInstanceID; +static jclass gLargeBitmap_class; +static jmethodID gLargeBitmap_constructorMethodID; + static jclass gCanvas_class; static jfieldID gCanvas_nativeInstanceID; @@ -370,6 +376,23 @@ jobject GraphicsJNI::createBitmap(JNIEnv* env, SkBitmap* bitmap, bool isMutable, } return obj; } +jobject GraphicsJNI::createLargeBitmap(JNIEnv* env, SkLargeBitmap* bitmap) +{ + SkASSERT(bitmap != NULL); + + jobject obj = env->AllocObject(gLargeBitmap_class); + if (hasException(env)) { + obj = NULL; + return obj; + } + if (obj) { + env->CallVoidMethod(obj, gLargeBitmap_constructorMethodID, (jint)bitmap); + if (hasException(env)) { + obj = NULL; + } + } + return obj; +} jobject GraphicsJNI::createRegion(JNIEnv* env, SkRegion* region) { @@ -494,10 +517,52 @@ bool GraphicsJNI::setJavaPixelRef(JNIEnv* env, SkBitmap* bitmap, /////////////////////////////////////////////////////////////////////////////// JavaPixelAllocator::JavaPixelAllocator(JNIEnv* env, bool reportSizeToVM) - : fEnv(env), fReportSizeToVM(reportSizeToVM) {} + : fReportSizeToVM(reportSizeToVM) { + if (env->GetJavaVM(&fVM) != JNI_OK) { + SkDebugf("------ [%p] env->GetJavaVM failed\n", env); + sk_throw(); + } +} bool JavaPixelAllocator::allocPixelRef(SkBitmap* bitmap, SkColorTable* ctable) { - return GraphicsJNI::setJavaPixelRef(fEnv, bitmap, ctable, fReportSizeToVM); + JNIEnv* env = vm2env(fVM); + return GraphicsJNI::setJavaPixelRef(env, bitmap, ctable, fReportSizeToVM); +} + +//////////////////////////////////////////////////////////////////////////////// + +JavaMemoryUsageReporter::JavaMemoryUsageReporter(JNIEnv* env) + : fTotalSize(0) { + if (env->GetJavaVM(&fVM) != JNI_OK) { + SkDebugf("------ [%p] env->GetJavaVM failed\n", env); + sk_throw(); + } +} + +JavaMemoryUsageReporter::~JavaMemoryUsageReporter() { + JNIEnv* env = vm2env(fVM); + jlong jtotalSize = fTotalSize; + env->CallVoidMethod(gVMRuntime_singleton, + gVMRuntime_trackExternalFreeMethodID, + jtotalSize); +} + +bool JavaMemoryUsageReporter::reportMemory(size_t memorySize) { + jlong jsize = memorySize; // the VM wants longs for the size + JNIEnv* env = vm2env(fVM); + bool r = env->CallBooleanMethod(gVMRuntime_singleton, + gVMRuntime_trackExternalAllocationMethodID, + jsize); + if (GraphicsJNI::hasException(env)) { + return false; + } + if (!r) { + LOGE("VM won't let us allocate %zd bytes\n", memorySize); + doThrowOOME(env, "bitmap size exceeds VM budget"); + return false; + } + fTotalSize += memorySize; + return true; } //////////////////////////////////////////////////////////////////////////////// @@ -547,6 +612,9 @@ int register_android_graphics_Graphics(JNIEnv* env) gBitmap_constructorMethodID = env->GetMethodID(gBitmap_class, "<init>", "(IZ[BI)V"); + gLargeBitmap_class = make_globalref(env, "android/graphics/LargeBitmap"); + gLargeBitmap_constructorMethodID = env->GetMethodID(gLargeBitmap_class, "<init>", "(I)V"); + gBitmapConfig_class = make_globalref(env, "android/graphics/Bitmap$Config"); gBitmapConfig_nativeInstanceID = getFieldIDCheck(env, gBitmapConfig_class, "nativeInt", "I"); @@ -581,8 +649,6 @@ int register_android_graphics_Graphics(JNIEnv* env) gVMRuntime_trackExternalFreeMethodID = env->GetMethodID(c, "trackExternalFree", "(J)V"); - NIOBuffer::RegisterJNI(env); - return 0; } diff --git a/core/jni/android/graphics/GraphicsJNI.h b/core/jni/android/graphics/GraphicsJNI.h index fe24b055577e..1a43a3e622d1 100644 --- a/core/jni/android/graphics/GraphicsJNI.h +++ b/core/jni/android/graphics/GraphicsJNI.h @@ -4,6 +4,8 @@ #include "SkPoint.h" #include "SkRect.h" #include "SkBitmap.h" +#include "../images/SkLargeBitmap.h" +#include "../images/SkImageDecoder.h" #include <jni.h> class SkCanvas; @@ -54,6 +56,8 @@ public: static jobject createRegion(JNIEnv* env, SkRegion* region); + static jobject createLargeBitmap(JNIEnv* env, SkLargeBitmap* bitmap); + /** Set a pixelref for the bitmap (needs setConfig to already be called) Returns true on success. If it returns false, then it failed, and the appropriate exception will have been raised. @@ -76,10 +80,22 @@ public: virtual bool allocPixelRef(SkBitmap* bitmap, SkColorTable* ctable); private: - JNIEnv* fEnv; + JavaVM* fVM; bool fReportSizeToVM; }; +class JavaMemoryUsageReporter : public SkVMMemoryReporter { +public: + JavaMemoryUsageReporter(JNIEnv* env); + virtual ~JavaMemoryUsageReporter(); + // overrides + virtual bool reportMemory(size_t memorySize); + +private: + JavaVM* fVM; + size_t fTotalSize; +}; + enum JNIAccess { kRO_JNIAccess, kRW_JNIAccess @@ -156,6 +172,7 @@ void doThrowIAE(JNIEnv* env, const char* msg = NULL); // Illegal Argument void doThrowRE(JNIEnv* env, const char* msg = NULL); // Runtime void doThrowISE(JNIEnv* env, const char* msg = NULL); // Illegal State void doThrowOOME(JNIEnv* env, const char* msg = NULL); // Out of memory +void doThrowIOE(JNIEnv* env, const char* msg = NULL); // IO Exception #define NPE_CHECK_RETURN_ZERO(env, object) \ do { if (NULL == (object)) { doThrowNPE(env); return 0; } } while (0) diff --git a/core/jni/android/graphics/LargeBitmap.cpp b/core/jni/android/graphics/LargeBitmap.cpp new file mode 100644 index 000000000000..4cf5dfa49da0 --- /dev/null +++ b/core/jni/android/graphics/LargeBitmap.cpp @@ -0,0 +1,138 @@ +#define LOG_TAG "LargeBitmap" + +#include "SkBitmap.h" +#include "SkImageEncoder.h" +#include "SkColorPriv.h" +#include "GraphicsJNI.h" +#include "SkDither.h" +#include "SkUnPreMultiply.h" +#include "SkUtils.h" +#include "SkTemplates.h" +#include "SkPixelRef.h" +#include "BitmapFactory.h" +#include "AutoDecodeCancel.h" +#include "SkLargeBitmap.h" + +#include <binder/Parcel.h> +#include "android_util_Binder.h" +#include "android_nio_utils.h" +#include "CreateJavaOutputStreamAdaptor.h" + +#include <jni.h> + +#if 0 + #define TRACE_BITMAP(code) code +#else + #define TRACE_BITMAP(code) +#endif + +static jobject nullObjectReturn(const char msg[]) { + if (msg) { + SkDebugf("--- %s\n", msg); + } + return NULL; +} + +/* + * nine patch not supported + * + * purgeable not supported + * reportSizeToVM not supported + */ +static jobject nativeDecodeRegion(JNIEnv* env, jobject, SkLargeBitmap *bm, + int start_x, int start_y, int width, int height, jobject options) { + SkImageDecoder *decoder = bm->getDecoder(); + int sampleSize = 1; + SkBitmap::Config prefConfig = SkBitmap::kNo_Config; + bool doDither = true; + + if (NULL != options) { + sampleSize = env->GetIntField(options, gOptions_sampleSizeFieldID); + // initialize these, in case we fail later on + env->SetIntField(options, gOptions_widthFieldID, -1); + env->SetIntField(options, gOptions_heightFieldID, -1); + env->SetObjectField(options, gOptions_mimeFieldID, 0); + + jobject jconfig = env->GetObjectField(options, gOptions_configFieldID); + prefConfig = GraphicsJNI::getNativeBitmapConfig(env, jconfig); + doDither = env->GetBooleanField(options, gOptions_ditherFieldID); + } + + decoder->setDitherImage(doDither); + SkBitmap* bitmap = new SkBitmap; + SkAutoTDelete<SkBitmap> adb(bitmap); + AutoDecoderCancel adc(options, decoder); + + // To fix the race condition in case "requestCancelDecode" + // happens earlier than AutoDecoderCancel object is added + // to the gAutoDecoderCancelMutex linked list. + if (NULL != options && env->GetBooleanField(options, gOptions_mCancelID)) { + return nullObjectReturn("gOptions_mCancelID");; + } + + SkIRect region; + region.fLeft = start_x; + region.fTop = start_y; + region.fRight = start_x + width; + region.fBottom = start_y + height; + + if (!bm->decodeRegion(bitmap, region, prefConfig, sampleSize)) { + return nullObjectReturn("decoder->decodeRegion returned false"); + } + + // update options (if any) + if (NULL != options) { + env->SetIntField(options, gOptions_widthFieldID, bitmap->width()); + env->SetIntField(options, gOptions_heightFieldID, bitmap->height()); + // TODO: set the mimeType field with the data from the codec. + // but how to reuse a set of strings, rather than allocating new one + // each time? + env->SetObjectField(options, gOptions_mimeFieldID, + getMimeTypeString(env, decoder->getFormat())); + } + + // detach bitmap from its autotdeleter, since we want to own it now + adb.detach(); + + SkPixelRef* pr; + pr = bitmap->pixelRef(); + // promise we will never change our pixels (great for sharing and pictures) + pr->setImmutable(); + // now create the java bitmap + return GraphicsJNI::createBitmap(env, bitmap, false, NULL); +} + +static int nativeGetHeight(JNIEnv* env, jobject, SkLargeBitmap *bm) { + return bm->getHeight(); +} + +static int nativeGetWidth(JNIEnv* env, jobject, SkLargeBitmap *bm) { + return bm->getWidth(); +} + +static void nativeClean(JNIEnv* env, jobject, SkLargeBitmap *bm) { + delete bm; +} + +/////////////////////////////////////////////////////////////////////////////// + +#include <android_runtime/AndroidRuntime.h> + +static JNINativeMethod gLargeBitmapMethods[] = { + { "nativeDecodeRegion", + "(IIIIILandroid/graphics/BitmapFactory$Options;)Landroid/graphics/Bitmap;", + (void*)nativeDecodeRegion}, + { "nativeGetHeight", "(I)I", (void*)nativeGetHeight}, + { "nativeGetWidth", "(I)I", (void*)nativeGetWidth}, + { "nativeClean", "(I)V", (void*)nativeClean}, +}; + +#define kClassPathName "android/graphics/LargeBitmap" + +int register_android_graphics_LargeBitmap(JNIEnv* env); +int register_android_graphics_LargeBitmap(JNIEnv* env) +{ + return android::AndroidRuntime::registerNativeMethods(env, kClassPathName, + gLargeBitmapMethods, SK_ARRAY_COUNT(gLargeBitmapMethods)); +} + diff --git a/core/jni/android/graphics/NIOBuffer.cpp b/core/jni/android/graphics/NIOBuffer.cpp deleted file mode 100644 index cb937a36ed9b..000000000000 --- a/core/jni/android/graphics/NIOBuffer.cpp +++ /dev/null @@ -1,143 +0,0 @@ -#include "NIOBuffer.h" -#include "GraphicsJNI.h" - -// enable this to dump each time we ref/unref a global java object (buffer) -// -//#define TRACE_GLOBAL_REFS - -//#define TRACE_ARRAY_LOCKS - -static jclass gNIOAccess_classID; -static jmethodID gNIOAccess_getBasePointer; -static jmethodID gNIOAccess_getBaseArray; -static jmethodID gNIOAccess_getBaseArrayOffset; -static jmethodID gNIOAccess_getRemainingBytes; - -void NIOBuffer::RegisterJNI(JNIEnv* env) { - if (0 != gNIOAccess_classID) { - return; // already called - } - - jclass c = env->FindClass("java/nio/NIOAccess"); - gNIOAccess_classID = (jclass)env->NewGlobalRef(c); - - gNIOAccess_getBasePointer = env->GetStaticMethodID(gNIOAccess_classID, - "getBasePointer", "(Ljava/nio/Buffer;)J"); - gNIOAccess_getBaseArray = env->GetStaticMethodID(gNIOAccess_classID, - "getBaseArray", "(Ljava/nio/Buffer;)Ljava/lang/Object;"); - gNIOAccess_getBaseArrayOffset = env->GetStaticMethodID(gNIOAccess_classID, - "getBaseArrayOffset", "(Ljava/nio/Buffer;)I"); - gNIOAccess_getRemainingBytes = env->GetStaticMethodID(gNIOAccess_classID, - "getRemainingBytes", "(Ljava/nio/Buffer;)I"); -} - -/////////////////////////////////////////////////////////////////////////////// - -#ifdef TRACE_GLOBAL_REFS - static int gGlobalRefs; -#endif - -#ifdef TRACE_ARRAY_LOCKS - static int gLockCount; -#endif - -NIOBuffer::NIOBuffer(JNIEnv* env, jobject buffer) { - fBuffer = env->NewGlobalRef(buffer); -#ifdef TRACE_GLOBAL_REFS - SkDebugf("------------ newglobalref bbuffer %X %d\n", buffer, gGlobalRefs++); -#endif - fLockedPtr = NULL; - fLockedArray = NULL; -} - -NIOBuffer::~NIOBuffer() { - // free() needs to have already been called - if (NULL != fBuffer) { - SkDebugf("----- leaked fBuffer in NIOBuffer"); - sk_throw(); - } -} - -void NIOBuffer::free(JNIEnv* env) { - - if (NULL != fLockedPtr) { - SkDebugf("======= free: array still locked %x %p\n", fLockedArray, fLockedPtr); - } - - - if (NULL != fBuffer) { -#ifdef TRACE_GLOBAL_REFS - SkDebugf("----------- deleteglobalref buffer %X %d\n", fBuffer, --gGlobalRefs); -#endif - env->DeleteGlobalRef(fBuffer); - fBuffer = NULL; - } -} - -void* NIOBuffer::lock(JNIEnv* env, int* remaining) { - if (NULL != fLockedPtr) { - SkDebugf("======= lock: array still locked %x %p\n", fLockedArray, fLockedPtr); - } - - fLockedPtr = NULL; - fLockedArray = NULL; - - if (NULL != remaining) { - *remaining = env->CallStaticIntMethod(gNIOAccess_classID, - gNIOAccess_getRemainingBytes, - fBuffer); - if (GraphicsJNI::hasException(env)) { - return NULL; - } - } - - jlong pointer = env->CallStaticLongMethod(gNIOAccess_classID, - gNIOAccess_getBasePointer, - fBuffer); - if (GraphicsJNI::hasException(env)) { - return NULL; - } - if (0 != pointer) { - return reinterpret_cast<void*>(pointer); - } - - fLockedArray = (jbyteArray)env->CallStaticObjectMethod(gNIOAccess_classID, - gNIOAccess_getBaseArray, - fBuffer); - if (GraphicsJNI::hasException(env) || NULL == fLockedArray) { - return NULL; - } - jint offset = env->CallStaticIntMethod(gNIOAccess_classID, - gNIOAccess_getBaseArrayOffset, - fBuffer); - fLockedPtr = env->GetByteArrayElements(fLockedArray, NULL); - if (GraphicsJNI::hasException(env)) { - SkDebugf("------------ failed to lockarray %x\n", fLockedArray); - return NULL; - } -#ifdef TRACE_ARRAY_LOCKS - SkDebugf("------------ lockarray %x %p %d\n", - fLockedArray, fLockedPtr, gLockCount++); -#endif - if (NULL == fLockedPtr) { - offset = 0; - } - return (char*)fLockedPtr + offset; -} - -void NIOBuffer::unlock(JNIEnv* env, bool dataChanged) { - if (NULL != fLockedPtr) { -#ifdef TRACE_ARRAY_LOCKS - SkDebugf("------------ unlockarray %x %p %d\n", - fLockedArray, fLockedPtr, --gLockCount); -#endif - env->ReleaseByteArrayElements(fLockedArray, (jbyte*)fLockedPtr, - dataChanged ? 0 : JNI_ABORT); - - fLockedPtr = NULL; - fLockedArray = NULL; - } else { - SkDebugf("============= unlock called with null ptr %x\n", fLockedArray); - } -} - diff --git a/core/jni/android/graphics/NIOBuffer.h b/core/jni/android/graphics/NIOBuffer.h deleted file mode 100644 index 36b5554a5dc7..000000000000 --- a/core/jni/android/graphics/NIOBuffer.h +++ /dev/null @@ -1,27 +0,0 @@ -#ifndef NIOBuffer_DEFINED -#define NIOBuffer_DEFINED - -#include <jni.h> -#include "SkBitmap.h" - -class NIOBuffer { -public: - NIOBuffer(JNIEnv* env, jobject buffer); - // this checks to ensure that free() was called - ~NIOBuffer(); - - void* lock(JNIEnv* env, int* remaining); - void unlock(JNIEnv* env, bool dataChanged); - // must be called before destructor - void free(JNIEnv* env); - - // call once on boot, to setup JNI globals - static void RegisterJNI(JNIEnv*); - -private: - jobject fBuffer; - void* fLockedPtr; - jbyteArray fLockedArray; -}; - -#endif diff --git a/core/jni/android_app_NativeActivity.cpp b/core/jni/android_app_NativeActivity.cpp new file mode 100644 index 000000000000..2517a8a4adaf --- /dev/null +++ b/core/jni/android_app_NativeActivity.cpp @@ -0,0 +1,1039 @@ +/* + * 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. + */ + +#define LOG_TAG "NativeActivity" +#include <utils/Log.h> + +#include <poll.h> +#include <dlfcn.h> +#include <fcntl.h> + +#include <android_runtime/AndroidRuntime.h> +#include <android_runtime/android_view_Surface.h> +#include <android_runtime/android_app_NativeActivity.h> +#include <android_runtime/android_util_AssetManager.h> +#include <surfaceflinger/Surface.h> +#include <ui/egl/android_natives.h> +#include <ui/InputTransport.h> +#include <utils/PollLoop.h> + +#include "JNIHelp.h" +#include "android_os_MessageQueue.h" +#include "android_view_InputChannel.h" +#include "android_view_KeyEvent.h" + +//#define LOG_TRACE(...) +#define LOG_TRACE(...) LOG(LOG_DEBUG, LOG_TAG, __VA_ARGS__) + +namespace android +{ + +static struct { + jclass clazz; + + jmethodID dispatchUnhandledKeyEvent; + jmethodID preDispatchKeyEvent; + jmethodID setWindowFlags; + jmethodID setWindowFormat; + jmethodID showIme; + jmethodID hideIme; +} gNativeActivityClassInfo; + +// ------------------------------------------------------------------------ + +struct ActivityWork { + int32_t cmd; + int32_t arg1; + int32_t arg2; +}; + +enum { + CMD_DEF_KEY = 1, + CMD_SET_WINDOW_FORMAT, + CMD_SET_WINDOW_FLAGS, + CMD_SHOW_SOFT_INPUT, + CMD_HIDE_SOFT_INPUT, +}; + +static void write_work(int fd, int32_t cmd, int32_t arg1=0, int32_t arg2=0) { + ActivityWork work; + work.cmd = cmd; + work.arg1 = arg1; + work.arg2 = arg2; + + LOG_TRACE("write_work: cmd=%d", cmd); + +restart: + int res = write(fd, &work, sizeof(work)); + if (res < 0 && errno == EINTR) { + goto restart; + } + + if (res == sizeof(work)) return; + + if (res < 0) LOGW("Failed writing to work fd: %s", strerror(errno)); + else LOGW("Truncated writing to work fd: %d", res); +} + +static bool read_work(int fd, ActivityWork* outWork) { + int res = read(fd, outWork, sizeof(ActivityWork)); + // no need to worry about EINTR, poll loop will just come back again. + if (res == sizeof(ActivityWork)) return true; + + if (res < 0) LOGW("Failed reading work fd: %s", strerror(errno)); + else LOGW("Truncated reading work fd: %d", res); + return false; +} + +// ------------------------------------------------------------------------ + +} // namespace android + +using namespace android; + +AInputQueue::AInputQueue(const sp<InputChannel>& channel, int workWrite) : + mWorkWrite(workWrite), mConsumer(channel), mSeq(0) { + int msgpipe[2]; + if (pipe(msgpipe)) { + LOGW("could not create pipe: %s", strerror(errno)); + mDispatchKeyRead = mDispatchKeyWrite = -1; + } else { + mDispatchKeyRead = msgpipe[0]; + mDispatchKeyWrite = msgpipe[1]; + int result = fcntl(mDispatchKeyRead, F_SETFL, O_NONBLOCK); + SLOGW_IF(result != 0, "Could not make AInputQueue read pipe " + "non-blocking: %s", strerror(errno)); + result = fcntl(mDispatchKeyWrite, F_SETFL, O_NONBLOCK); + SLOGW_IF(result != 0, "Could not make AInputQueue write pipe " + "non-blocking: %s", strerror(errno)); + } +} + +AInputQueue::~AInputQueue() { + close(mDispatchKeyRead); + close(mDispatchKeyWrite); +} + +void AInputQueue::attachLooper(ALooper* looper, int ident, + ALooper_callbackFunc* callback, void* data) { + mPollLoop = static_cast<android::PollLoop*>(looper); + mPollLoop->setLooperCallback(mConsumer.getChannel()->getReceivePipeFd(), + ident, POLLIN, callback, data); + mPollLoop->setLooperCallback(mDispatchKeyRead, + ident, POLLIN, callback, data); +} + +void AInputQueue::detachLooper() { + mPollLoop->removeCallback(mConsumer.getChannel()->getReceivePipeFd()); + mPollLoop->removeCallback(mDispatchKeyRead); +} + +int32_t AInputQueue::hasEvents() { + struct pollfd pfd[2]; + + pfd[0].fd = mConsumer.getChannel()->getReceivePipeFd(); + pfd[0].events = POLLIN; + pfd[0].revents = 0; + pfd[1].fd = mDispatchKeyRead; + pfd[0].events = POLLIN; + pfd[0].revents = 0; + + int nfd = poll(pfd, 2, 0); + if (nfd <= 0) return 0; + return (pfd[0].revents == POLLIN || pfd[1].revents == POLLIN) ? 1 : -1; +} + +int32_t AInputQueue::getEvent(AInputEvent** outEvent) { + *outEvent = NULL; + + bool finishNow = false; + + char byteread; + ssize_t nRead = read(mDispatchKeyRead, &byteread, 1); + if (nRead == 1) { + mLock.lock(); + if (mDispatchingKeys.size() > 0) { + KeyEvent* kevent = mDispatchingKeys[0]; + *outEvent = kevent; + mDispatchingKeys.removeAt(0); + in_flight_event inflight; + inflight.event = kevent; + inflight.seq = -1; + inflight.doFinish = false; + mInFlightEvents.push(inflight); + } + if (mFinishPreDispatches.size() > 0) { + finish_pre_dispatch finish(mFinishPreDispatches[0]); + mFinishPreDispatches.removeAt(0); + const size_t N = mInFlightEvents.size(); + for (size_t i=0; i<N; i++) { + const in_flight_event& inflight(mInFlightEvents[i]); + if (inflight.seq == finish.seq) { + *outEvent = inflight.event; + finishNow = finish.handled; + } + } + if (*outEvent == NULL) { + LOGW("getEvent couldn't find inflight for seq %d", finish.seq); + } + } + mLock.unlock(); + + if (finishNow) { + finishEvent(*outEvent, true); + *outEvent = NULL; + return -1; + } else if (*outEvent != NULL) { + return 0; + } + } + + int32_t res = mConsumer.receiveDispatchSignal(); + if (res != android::OK) { + LOGE("channel '%s' ~ Failed to receive dispatch signal. status=%d", + mConsumer.getChannel()->getName().string(), res); + return -1; + } + + InputEvent* myEvent = NULL; + res = mConsumer.consume(this, &myEvent); + if (res != android::OK) { + LOGW("channel '%s' ~ Failed to consume input event. status=%d", + mConsumer.getChannel()->getName().string(), res); + mConsumer.sendFinishedSignal(); + return -1; + } + + in_flight_event inflight; + inflight.event = myEvent; + inflight.seq = -1; + inflight.doFinish = true; + mInFlightEvents.push(inflight); + + *outEvent = myEvent; + return 0; +} + +bool AInputQueue::preDispatchEvent(AInputEvent* event) { + if (((InputEvent*)event)->getType() != AINPUT_EVENT_TYPE_KEY) { + // The IME only cares about key events. + return false; + } + + // For now we only send system keys to the IME... this avoids having + // critical keys like DPAD go through this path. We really need to have + // the IME report which keys it wants. + if (!((KeyEvent*)event)->isSystemKey()) { + return false; + } + + return preDispatchKey((KeyEvent*)event); +} + +void AInputQueue::finishEvent(AInputEvent* event, bool handled) { + LOG_TRACE("finishEvent: %p handled=%d", event, handled ? 1 : 0); + + if (!handled && ((InputEvent*)event)->getType() == AINPUT_EVENT_TYPE_KEY + && ((KeyEvent*)event)->hasDefaultAction()) { + // The app didn't handle this, but it may have a default action + // associated with it. We need to hand this back to Java to be + // executed. + doUnhandledKey((KeyEvent*)event); + return; + } + + mLock.lock(); + const size_t N = mInFlightEvents.size(); + for (size_t i=0; i<N; i++) { + const in_flight_event& inflight(mInFlightEvents[i]); + if (inflight.event == event) { + if (inflight.doFinish) { + int32_t res = mConsumer.sendFinishedSignal(); + if (res != android::OK) { + LOGW("Failed to send finished signal on channel '%s'. status=%d", + mConsumer.getChannel()->getName().string(), res); + } + } + if (static_cast<InputEvent*>(event)->getType() == AINPUT_EVENT_TYPE_KEY) { + mAvailKeyEvents.push(static_cast<KeyEvent*>(event)); + } else { + mAvailMotionEvents.push(static_cast<MotionEvent*>(event)); + } + mInFlightEvents.removeAt(i); + mLock.unlock(); + return; + } + } + mLock.unlock(); + + LOGW("finishEvent called for unknown event: %p", event); +} + +void AInputQueue::dispatchEvent(android::KeyEvent* event) { + mLock.lock(); + LOG_TRACE("dispatchEvent: dispatching=%d write=%d\n", mDispatchingKeys.size(), + mDispatchKeyWrite); + mDispatchingKeys.add(event); + wakeupDispatch(); + mLock.unlock(); +} + +void AInputQueue::finishPreDispatch(int seq, bool handled) { + mLock.lock(); + LOG_TRACE("finishPreDispatch: seq=%d handled=%d\n", seq, handled ? 1 : 0); + finish_pre_dispatch finish; + finish.seq = seq; + finish.handled = handled; + mFinishPreDispatches.add(finish); + wakeupDispatch(); + mLock.unlock(); +} + +KeyEvent* AInputQueue::consumeUnhandledEvent() { + KeyEvent* event = NULL; + + mLock.lock(); + if (mUnhandledKeys.size() > 0) { + event = mUnhandledKeys[0]; + mUnhandledKeys.removeAt(0); + } + mLock.unlock(); + + LOG_TRACE("consumeUnhandledEvent: KeyEvent=%p", event); + + return event; +} + +KeyEvent* AInputQueue::consumePreDispatchingEvent(int* outSeq) { + KeyEvent* event = NULL; + + mLock.lock(); + if (mPreDispatchingKeys.size() > 0) { + const in_flight_event& inflight(mPreDispatchingKeys[0]); + event = static_cast<KeyEvent*>(inflight.event); + *outSeq = inflight.seq; + mPreDispatchingKeys.removeAt(0); + } + mLock.unlock(); + + LOG_TRACE("consumePreDispatchingEvent: KeyEvent=%p", event); + + return event; +} + +KeyEvent* AInputQueue::createKeyEvent() { + mLock.lock(); + KeyEvent* event; + if (mAvailKeyEvents.size() <= 0) { + event = new KeyEvent(); + } else { + event = mAvailKeyEvents.top(); + mAvailKeyEvents.pop(); + } + mLock.unlock(); + return event; +} + +MotionEvent* AInputQueue::createMotionEvent() { + mLock.lock(); + MotionEvent* event; + if (mAvailMotionEvents.size() <= 0) { + event = new MotionEvent(); + } else { + event = mAvailMotionEvents.top(); + mAvailMotionEvents.pop(); + } + mLock.unlock(); + return event; +} + +void AInputQueue::doUnhandledKey(KeyEvent* keyEvent) { + mLock.lock(); + LOG_TRACE("Unhandled key: pending=%d write=%d\n", mUnhandledKeys.size(), mWorkWrite); + if (mUnhandledKeys.size() <= 0 && mWorkWrite >= 0) { + write_work(mWorkWrite, CMD_DEF_KEY); + } + mUnhandledKeys.add(keyEvent); + mLock.unlock(); +} + +bool AInputQueue::preDispatchKey(KeyEvent* keyEvent) { + mLock.lock(); + LOG_TRACE("preDispatch key: pending=%d write=%d\n", mPreDispatchingKeys.size(), mWorkWrite); + const size_t N = mInFlightEvents.size(); + for (size_t i=0; i<N; i++) { + in_flight_event& inflight(mInFlightEvents.editItemAt(i)); + if (inflight.event == keyEvent) { + if (inflight.seq >= 0) { + // This event has already been pre-dispatched! + LOG_TRACE("Event already pre-dispatched!"); + mLock.unlock(); + return false; + } + mSeq++; + if (mSeq < 0) mSeq = 1; + inflight.seq = mSeq; + + if (mPreDispatchingKeys.size() <= 0 && mWorkWrite >= 0) { + write_work(mWorkWrite, CMD_DEF_KEY); + } + mPreDispatchingKeys.add(inflight); + mLock.unlock(); + return true; + } + } + + LOGW("preDispatchKey called for unknown event: %p", keyEvent); + return false; +} + +void AInputQueue::wakeupDispatch() { +restart: + char dummy = 0; + int res = write(mDispatchKeyWrite, &dummy, sizeof(dummy)); + if (res < 0 && errno == EINTR) { + goto restart; + } + + if (res == sizeof(dummy)) return; + + if (res < 0) LOGW("Failed writing to dispatch fd: %s", strerror(errno)); + else LOGW("Truncated writing to dispatch fd: %d", res); +} + +namespace android { + +// ------------------------------------------------------------------------ + +/* + * Native state for interacting with the NativeActivity class. + */ +struct NativeCode : public ANativeActivity { + NativeCode(void* _dlhandle, ANativeActivity_createFunc* _createFunc) { + memset((ANativeActivity*)this, 0, sizeof(ANativeActivity)); + memset(&callbacks, 0, sizeof(callbacks)); + dlhandle = _dlhandle; + createActivityFunc = _createFunc; + nativeWindow = NULL; + inputChannel = NULL; + nativeInputQueue = NULL; + mainWorkRead = mainWorkWrite = -1; + } + + ~NativeCode() { + if (callbacks.onDestroy != NULL) { + callbacks.onDestroy(this); + } + if (env != NULL && clazz != NULL) { + env->DeleteGlobalRef(clazz); + } + if (pollLoop != NULL && mainWorkRead >= 0) { + pollLoop->removeCallback(mainWorkRead); + } + if (nativeInputQueue != NULL) { + nativeInputQueue->mWorkWrite = -1; + } + setSurface(NULL); + setInputChannel(NULL); + if (mainWorkRead >= 0) close(mainWorkRead); + if (mainWorkWrite >= 0) close(mainWorkWrite); + if (dlhandle != NULL) { + // for now don't unload... we probably should clean this + // up and only keep one open dlhandle per proc, since there + // is really no benefit to unloading the code. + //dlclose(dlhandle); + } + } + + void setSurface(jobject _surface) { + if (_surface != NULL) { + nativeWindow = android_Surface_getNativeWindow(env, _surface); + } else { + nativeWindow = NULL; + } + } + + status_t setInputChannel(jobject _channel) { + if (inputChannel != NULL) { + delete nativeInputQueue; + env->DeleteGlobalRef(inputChannel); + } + inputChannel = NULL; + nativeInputQueue = NULL; + if (_channel != NULL) { + inputChannel = env->NewGlobalRef(_channel); + sp<InputChannel> ic = + android_view_InputChannel_getInputChannel(env, _channel); + if (ic != NULL) { + nativeInputQueue = new AInputQueue(ic, mainWorkWrite); + if (nativeInputQueue->getConsumer().initialize() != android::OK) { + delete nativeInputQueue; + nativeInputQueue = NULL; + return UNKNOWN_ERROR; + } + } else { + return UNKNOWN_ERROR; + } + } + return OK; + } + + ANativeActivityCallbacks callbacks; + + void* dlhandle; + ANativeActivity_createFunc* createActivityFunc; + + String8 internalDataPath; + String8 externalDataPath; + + sp<ANativeWindow> nativeWindow; + int32_t lastWindowWidth; + int32_t lastWindowHeight; + + jobject inputChannel; + struct AInputQueue* nativeInputQueue; + + // These are used to wake up the main thread to process work. + int mainWorkRead; + int mainWorkWrite; + sp<PollLoop> pollLoop; +}; + +void android_NativeActivity_setWindowFormat( + ANativeActivity* activity, int32_t format) { + NativeCode* code = static_cast<NativeCode*>(activity); + write_work(code->mainWorkWrite, CMD_SET_WINDOW_FORMAT, format); +} + +void android_NativeActivity_setWindowFlags( + ANativeActivity* activity, int32_t values, int32_t mask) { + NativeCode* code = static_cast<NativeCode*>(activity); + write_work(code->mainWorkWrite, CMD_SET_WINDOW_FLAGS, values, mask); +} + +void android_NativeActivity_showSoftInput( + ANativeActivity* activity, int32_t flags) { + NativeCode* code = static_cast<NativeCode*>(activity); + write_work(code->mainWorkWrite, CMD_SHOW_SOFT_INPUT, flags); +} + +void android_NativeActivity_hideSoftInput( + ANativeActivity* activity, int32_t flags) { + NativeCode* code = static_cast<NativeCode*>(activity); + write_work(code->mainWorkWrite, CMD_HIDE_SOFT_INPUT, flags); +} + +// ------------------------------------------------------------------------ + +/* + * Callback for handling native events on the application's main thread. + */ +static bool mainWorkCallback(int fd, int events, void* data) { + NativeCode* code = (NativeCode*)data; + if ((events & POLLIN) == 0) { + return true; + } + + ActivityWork work; + if (!read_work(code->mainWorkRead, &work)) { + return true; + } + + LOG_TRACE("mainWorkCallback: cmd=%d", work.cmd); + + switch (work.cmd) { + case CMD_DEF_KEY: { + KeyEvent* keyEvent; + while ((keyEvent=code->nativeInputQueue->consumeUnhandledEvent()) != NULL) { + jobject inputEventObj = android_view_KeyEvent_fromNative( + code->env, keyEvent); + code->env->CallVoidMethod(code->clazz, + gNativeActivityClassInfo.dispatchUnhandledKeyEvent, inputEventObj); + code->nativeInputQueue->finishEvent(keyEvent, true); + } + int seq; + while ((keyEvent=code->nativeInputQueue->consumePreDispatchingEvent(&seq)) != NULL) { + jobject inputEventObj = android_view_KeyEvent_fromNative( + code->env, keyEvent); + code->env->CallVoidMethod(code->clazz, + gNativeActivityClassInfo.preDispatchKeyEvent, inputEventObj, seq); + } + } break; + case CMD_SET_WINDOW_FORMAT: { + code->env->CallVoidMethod(code->clazz, + gNativeActivityClassInfo.setWindowFormat, work.arg1); + } break; + case CMD_SET_WINDOW_FLAGS: { + code->env->CallVoidMethod(code->clazz, + gNativeActivityClassInfo.setWindowFlags, work.arg1, work.arg2); + } break; + case CMD_SHOW_SOFT_INPUT: { + code->env->CallVoidMethod(code->clazz, + gNativeActivityClassInfo.showIme, work.arg1); + } break; + case CMD_HIDE_SOFT_INPUT: { + code->env->CallVoidMethod(code->clazz, + gNativeActivityClassInfo.hideIme, work.arg1); + } break; + default: + LOGW("Unknown work command: %d", work.cmd); + break; + } + + return true; +} + +// ------------------------------------------------------------------------ + +static jint +loadNativeCode_native(JNIEnv* env, jobject clazz, jstring path, jobject messageQueue, + jstring internalDataDir, jstring externalDataDir, int sdkVersion, + jobject jAssetMgr, jbyteArray savedState) +{ + LOG_TRACE("loadNativeCode_native"); + + const char* pathStr = env->GetStringUTFChars(path, NULL); + NativeCode* code = NULL; + + void* handle = dlopen(pathStr, RTLD_LAZY); + + env->ReleaseStringUTFChars(path, pathStr); + + if (handle != NULL) { + code = new NativeCode(handle, (ANativeActivity_createFunc*) + dlsym(handle, "ANativeActivity_onCreate")); + if (code->createActivityFunc == NULL) { + LOGW("ANativeActivity_onCreate not found"); + delete code; + return 0; + } + + code->pollLoop = android_os_MessageQueue_getPollLoop(env, messageQueue); + if (code->pollLoop == NULL) { + LOGW("Unable to retrieve MessageQueue's PollLoop"); + delete code; + return 0; + } + + int msgpipe[2]; + if (pipe(msgpipe)) { + LOGW("could not create pipe: %s", strerror(errno)); + delete code; + return 0; + } + code->mainWorkRead = msgpipe[0]; + code->mainWorkWrite = msgpipe[1]; + int result = fcntl(code->mainWorkRead, F_SETFL, O_NONBLOCK); + SLOGW_IF(result != 0, "Could not make main work read pipe " + "non-blocking: %s", strerror(errno)); + result = fcntl(code->mainWorkWrite, F_SETFL, O_NONBLOCK); + SLOGW_IF(result != 0, "Could not make main work write pipe " + "non-blocking: %s", strerror(errno)); + code->pollLoop->setCallback(code->mainWorkRead, POLLIN, mainWorkCallback, code); + + code->ANativeActivity::callbacks = &code->callbacks; + if (env->GetJavaVM(&code->vm) < 0) { + LOGW("NativeActivity GetJavaVM failed"); + delete code; + return 0; + } + code->env = env; + code->clazz = env->NewGlobalRef(clazz); + + const char* dirStr = env->GetStringUTFChars(internalDataDir, NULL); + code->internalDataPath = dirStr; + code->internalDataPath = code->internalDataPath.string(); + env->ReleaseStringUTFChars(path, dirStr); + + dirStr = env->GetStringUTFChars(externalDataDir, NULL); + code->externalDataPath = dirStr; + code->externalDataPath = code->externalDataPath.string(); + env->ReleaseStringUTFChars(path, dirStr); + + code->sdkVersion = sdkVersion; + + code->assetManager = assetManagerForJavaObject(env, jAssetMgr); + + jbyte* rawSavedState = NULL; + jsize rawSavedSize = 0; + if (savedState != NULL) { + rawSavedState = env->GetByteArrayElements(savedState, NULL); + rawSavedSize = env->GetArrayLength(savedState); + } + + code->createActivityFunc(code, rawSavedState, rawSavedSize); + + if (rawSavedState != NULL) { + env->ReleaseByteArrayElements(savedState, rawSavedState, 0); + } + } + + return (jint)code; +} + +static void +unloadNativeCode_native(JNIEnv* env, jobject clazz, jint handle) +{ + LOG_TRACE("unloadNativeCode_native"); + if (handle != 0) { + NativeCode* code = (NativeCode*)handle; + delete code; + } +} + +static void +onStart_native(JNIEnv* env, jobject clazz, jint handle) +{ + LOG_TRACE("onStart_native"); + if (handle != 0) { + NativeCode* code = (NativeCode*)handle; + if (code->callbacks.onStart != NULL) { + code->callbacks.onStart(code); + } + } +} + +static void +onResume_native(JNIEnv* env, jobject clazz, jint handle) +{ + LOG_TRACE("onResume_native"); + if (handle != 0) { + NativeCode* code = (NativeCode*)handle; + if (code->callbacks.onResume != NULL) { + code->callbacks.onResume(code); + } + } +} + +static jbyteArray +onSaveInstanceState_native(JNIEnv* env, jobject clazz, jint handle) +{ + LOG_TRACE("onSaveInstanceState_native"); + + jbyteArray array = NULL; + + if (handle != 0) { + NativeCode* code = (NativeCode*)handle; + if (code->callbacks.onSaveInstanceState != NULL) { + size_t len = 0; + jbyte* state = (jbyte*)code->callbacks.onSaveInstanceState(code, &len); + if (len > 0) { + array = env->NewByteArray(len); + if (array != NULL) { + env->SetByteArrayRegion(array, 0, len, state); + } + } + if (state != NULL) { + free(state); + } + } + } + + return array; +} + +static void +onPause_native(JNIEnv* env, jobject clazz, jint handle) +{ + LOG_TRACE("onPause_native"); + if (handle != 0) { + NativeCode* code = (NativeCode*)handle; + if (code->callbacks.onPause != NULL) { + code->callbacks.onPause(code); + } + } +} + +static void +onStop_native(JNIEnv* env, jobject clazz, jint handle) +{ + LOG_TRACE("onStop_native"); + if (handle != 0) { + NativeCode* code = (NativeCode*)handle; + if (code->callbacks.onStop != NULL) { + code->callbacks.onStop(code); + } + } +} + +static void +onConfigurationChanged_native(JNIEnv* env, jobject clazz, jint handle) +{ + LOG_TRACE("onConfigurationChanged_native"); + if (handle != 0) { + NativeCode* code = (NativeCode*)handle; + if (code->callbacks.onConfigurationChanged != NULL) { + code->callbacks.onConfigurationChanged(code); + } + } +} + +static void +onLowMemory_native(JNIEnv* env, jobject clazz, jint handle) +{ + LOG_TRACE("onLowMemory_native"); + if (handle != 0) { + NativeCode* code = (NativeCode*)handle; + if (code->callbacks.onLowMemory != NULL) { + code->callbacks.onLowMemory(code); + } + } +} + +static void +onWindowFocusChanged_native(JNIEnv* env, jobject clazz, jint handle, jboolean focused) +{ + LOG_TRACE("onWindowFocusChanged_native"); + if (handle != 0) { + NativeCode* code = (NativeCode*)handle; + if (code->callbacks.onWindowFocusChanged != NULL) { + code->callbacks.onWindowFocusChanged(code, focused ? 1 : 0); + } + } +} + +static void +onSurfaceCreated_native(JNIEnv* env, jobject clazz, jint handle, jobject surface) +{ + LOG_TRACE("onSurfaceCreated_native"); + if (handle != 0) { + NativeCode* code = (NativeCode*)handle; + code->setSurface(surface); + if (code->nativeWindow != NULL && code->callbacks.onNativeWindowCreated != NULL) { + code->callbacks.onNativeWindowCreated(code, + code->nativeWindow.get()); + } + } +} + +static int32_t getWindowProp(ANativeWindow* window, int what) { + int value; + int res = window->query(window, what, &value); + return res < 0 ? res : value; +} + +static void +onSurfaceChanged_native(JNIEnv* env, jobject clazz, jint handle, jobject surface, + jint format, jint width, jint height) +{ + LOG_TRACE("onSurfaceChanged_native"); + if (handle != 0) { + NativeCode* code = (NativeCode*)handle; + sp<ANativeWindow> oldNativeWindow = code->nativeWindow; + code->setSurface(surface); + if (oldNativeWindow != code->nativeWindow) { + if (oldNativeWindow != NULL && code->callbacks.onNativeWindowDestroyed != NULL) { + code->callbacks.onNativeWindowDestroyed(code, + oldNativeWindow.get()); + } + if (code->nativeWindow != NULL) { + if (code->callbacks.onNativeWindowCreated != NULL) { + code->callbacks.onNativeWindowCreated(code, + code->nativeWindow.get()); + } + code->lastWindowWidth = getWindowProp(code->nativeWindow.get(), + NATIVE_WINDOW_WIDTH); + code->lastWindowHeight = getWindowProp(code->nativeWindow.get(), + NATIVE_WINDOW_HEIGHT); + } + } else { + // Maybe it resized? + int32_t newWidth = getWindowProp(code->nativeWindow.get(), + NATIVE_WINDOW_WIDTH); + int32_t newHeight = getWindowProp(code->nativeWindow.get(), + NATIVE_WINDOW_HEIGHT); + if (newWidth != code->lastWindowWidth + || newHeight != code->lastWindowHeight) { + if (code->callbacks.onNativeWindowResized != NULL) { + code->callbacks.onNativeWindowResized(code, + code->nativeWindow.get()); + } + } + } + } +} + +static void +onSurfaceRedrawNeeded_native(JNIEnv* env, jobject clazz, jint handle) +{ + LOG_TRACE("onSurfaceRedrawNeeded_native"); + if (handle != 0) { + NativeCode* code = (NativeCode*)handle; + if (code->nativeWindow != NULL && code->callbacks.onNativeWindowRedrawNeeded != NULL) { + code->callbacks.onNativeWindowRedrawNeeded(code, code->nativeWindow.get()); + } + } +} + +static void +onSurfaceDestroyed_native(JNIEnv* env, jobject clazz, jint handle, jobject surface) +{ + LOG_TRACE("onSurfaceDestroyed_native"); + if (handle != 0) { + NativeCode* code = (NativeCode*)handle; + if (code->nativeWindow != NULL && code->callbacks.onNativeWindowDestroyed != NULL) { + code->callbacks.onNativeWindowDestroyed(code, + code->nativeWindow.get()); + } + code->setSurface(NULL); + } +} + +static void +onInputChannelCreated_native(JNIEnv* env, jobject clazz, jint handle, jobject channel) +{ + LOG_TRACE("onInputChannelCreated_native"); + if (handle != 0) { + NativeCode* code = (NativeCode*)handle; + status_t err = code->setInputChannel(channel); + if (err != OK) { + jniThrowException(env, "java/lang/IllegalStateException", + "Error setting input channel"); + return; + } + if (code->callbacks.onInputQueueCreated != NULL) { + code->callbacks.onInputQueueCreated(code, + code->nativeInputQueue); + } + } +} + +static void +onInputChannelDestroyed_native(JNIEnv* env, jobject clazz, jint handle, jobject channel) +{ + LOG_TRACE("onInputChannelDestroyed_native"); + if (handle != 0) { + NativeCode* code = (NativeCode*)handle; + if (code->nativeInputQueue != NULL + && code->callbacks.onInputQueueDestroyed != NULL) { + code->callbacks.onInputQueueDestroyed(code, + code->nativeInputQueue); + } + code->setInputChannel(NULL); + } +} + +static void +onContentRectChanged_native(JNIEnv* env, jobject clazz, jint handle, + jint x, jint y, jint w, jint h) +{ + LOG_TRACE("onContentRectChanged_native"); + if (handle != 0) { + NativeCode* code = (NativeCode*)handle; + if (code->callbacks.onContentRectChanged != NULL) { + ARect rect; + rect.left = x; + rect.top = y; + rect.right = x+w; + rect.bottom = y+h; + code->callbacks.onContentRectChanged(code, &rect); + } + } +} + +static void +dispatchKeyEvent_native(JNIEnv* env, jobject clazz, jint handle, jobject eventObj) +{ + LOG_TRACE("dispatchKeyEvent_native"); + if (handle != 0) { + NativeCode* code = (NativeCode*)handle; + if (code->nativeInputQueue != NULL) { + KeyEvent* event = code->nativeInputQueue->createKeyEvent(); + android_view_KeyEvent_toNative(env, eventObj, event); + code->nativeInputQueue->dispatchEvent(event); + } + } +} + +static void +finishPreDispatchKeyEvent_native(JNIEnv* env, jobject clazz, jint handle, + jint seq, jboolean handled) +{ + LOG_TRACE("finishPreDispatchKeyEvent_native"); + if (handle != 0) { + NativeCode* code = (NativeCode*)handle; + if (code->nativeInputQueue != NULL) { + code->nativeInputQueue->finishPreDispatch(seq, handled ? true : false); + } + } +} + +static const JNINativeMethod g_methods[] = { + { "loadNativeCode", "(Ljava/lang/String;Landroid/os/MessageQueue;Ljava/lang/String;Ljava/lang/String;ILandroid/content/res/AssetManager;[B)I", + (void*)loadNativeCode_native }, + { "unloadNativeCode", "(I)V", (void*)unloadNativeCode_native }, + { "onStartNative", "(I)V", (void*)onStart_native }, + { "onResumeNative", "(I)V", (void*)onResume_native }, + { "onSaveInstanceStateNative", "(I)[B", (void*)onSaveInstanceState_native }, + { "onPauseNative", "(I)V", (void*)onPause_native }, + { "onStopNative", "(I)V", (void*)onStop_native }, + { "onConfigurationChangedNative", "(I)V", (void*)onConfigurationChanged_native }, + { "onLowMemoryNative", "(I)V", (void*)onLowMemory_native }, + { "onWindowFocusChangedNative", "(IZ)V", (void*)onWindowFocusChanged_native }, + { "onSurfaceCreatedNative", "(ILandroid/view/Surface;)V", (void*)onSurfaceCreated_native }, + { "onSurfaceChangedNative", "(ILandroid/view/Surface;III)V", (void*)onSurfaceChanged_native }, + { "onSurfaceRedrawNeededNative", "(ILandroid/view/Surface;)V", (void*)onSurfaceRedrawNeeded_native }, + { "onSurfaceDestroyedNative", "(I)V", (void*)onSurfaceDestroyed_native }, + { "onInputChannelCreatedNative", "(ILandroid/view/InputChannel;)V", (void*)onInputChannelCreated_native }, + { "onInputChannelDestroyedNative", "(ILandroid/view/InputChannel;)V", (void*)onInputChannelDestroyed_native }, + { "onContentRectChangedNative", "(IIIII)V", (void*)onContentRectChanged_native }, + { "dispatchKeyEventNative", "(ILandroid/view/KeyEvent;)V", (void*)dispatchKeyEvent_native }, + { "finishPreDispatchKeyEventNative", "(IIZ)V", (void*)finishPreDispatchKeyEvent_native }, +}; + +static const char* const kNativeActivityPathName = "android/app/NativeActivity"; + +#define FIND_CLASS(var, className) \ + var = env->FindClass(className); \ + LOG_FATAL_IF(! var, "Unable to find class " className); \ + var = jclass(env->NewGlobalRef(var)); + +#define GET_METHOD_ID(var, clazz, methodName, fieldDescriptor) \ + var = env->GetMethodID(clazz, methodName, fieldDescriptor); \ + LOG_FATAL_IF(! var, "Unable to find method" methodName); + +int register_android_app_NativeActivity(JNIEnv* env) +{ + //LOGD("register_android_app_NativeActivity"); + + FIND_CLASS(gNativeActivityClassInfo.clazz, kNativeActivityPathName); + + GET_METHOD_ID(gNativeActivityClassInfo.dispatchUnhandledKeyEvent, + gNativeActivityClassInfo.clazz, + "dispatchUnhandledKeyEvent", "(Landroid/view/KeyEvent;)V"); + GET_METHOD_ID(gNativeActivityClassInfo.preDispatchKeyEvent, + gNativeActivityClassInfo.clazz, + "preDispatchKeyEvent", "(Landroid/view/KeyEvent;I)V"); + + GET_METHOD_ID(gNativeActivityClassInfo.setWindowFlags, + gNativeActivityClassInfo.clazz, + "setWindowFlags", "(II)V"); + GET_METHOD_ID(gNativeActivityClassInfo.setWindowFormat, + gNativeActivityClassInfo.clazz, + "setWindowFormat", "(I)V"); + GET_METHOD_ID(gNativeActivityClassInfo.showIme, + gNativeActivityClassInfo.clazz, + "showIme", "(I)V"); + GET_METHOD_ID(gNativeActivityClassInfo.hideIme, + gNativeActivityClassInfo.clazz, + "hideIme", "(I)V"); + + return AndroidRuntime::registerNativeMethods( + env, kNativeActivityPathName, + g_methods, NELEM(g_methods)); +} + +} // namespace android diff --git a/core/jni/android_bluetooth_HeadsetBase.cpp b/core/jni/android_bluetooth_HeadsetBase.cpp index 3f14c3a6e276..4e9fbaf1bd62 100644 --- a/core/jni/android_bluetooth_HeadsetBase.cpp +++ b/core/jni/android_bluetooth_HeadsetBase.cpp @@ -169,7 +169,7 @@ again: // never receive non-ASCII UTF-8). // This was added because of the BMW 2005 E46 which sends binary junk. if (is_ascii(buf)) { - IF_LOGV() LOG(LOG_VERBOSE, "Bluetooth AT recv", buf); + IF_LOGV() LOG(LOG_VERBOSE, "Bluetooth AT recv", "%s", buf); } else { LOGW("Ignoring invalid AT command: %s", buf); buf[0] = NULL; @@ -494,7 +494,7 @@ static void pretty_log_urc(const char *urc) { } } } - IF_LOGV() LOG(LOG_VERBOSE, "Bluetooth AT sent", buf); + IF_LOGV() LOG(LOG_VERBOSE, "Bluetooth AT sent", "%s", buf); free(buf); } diff --git a/core/jni/android_bluetooth_common.cpp b/core/jni/android_bluetooth_common.cpp index 343fa53904af..43c3a9518ef7 100644 --- a/core/jni/android_bluetooth_common.cpp +++ b/core/jni/android_bluetooth_common.cpp @@ -46,6 +46,7 @@ static Properties remote_device_properties[] = { {"Paired", DBUS_TYPE_BOOLEAN}, {"Connected", DBUS_TYPE_BOOLEAN}, {"Trusted", DBUS_TYPE_BOOLEAN}, + {"Blocked", DBUS_TYPE_BOOLEAN}, {"Alias", DBUS_TYPE_STRING}, {"Nodes", DBUS_TYPE_ARRAY}, {"Adapter", DBUS_TYPE_OBJECT_PATH}, @@ -65,6 +66,7 @@ static Properties adapter_properties[] = { {"PairableTimeout", DBUS_TYPE_UINT32}, {"Discovering", DBUS_TYPE_BOOLEAN}, {"Devices", DBUS_TYPE_ARRAY}, + {"UUIDs", DBUS_TYPE_ARRAY}, }; typedef union { diff --git a/core/jni/android_content_res_Configuration.cpp b/core/jni/android_content_res_Configuration.cpp new file mode 100644 index 000000000000..28a43ab09f76 --- /dev/null +++ b/core/jni/android_content_res_Configuration.cpp @@ -0,0 +1,118 @@ +/* + * Copyright 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. + */ + +#define LOG_TAG "Configuration" + +#include <utils/Log.h> +#include "utils/misc.h" + +#include "jni.h" +#include <android_runtime/android_content_res_Configuration.h> +#include "android_runtime/AndroidRuntime.h" + +namespace android { + +static struct { + jclass clazz; + + jfieldID mcc; + jfieldID mnc; + jfieldID locale; + jfieldID screenLayout; + jfieldID touchscreen; + jfieldID keyboard; + jfieldID keyboardHidden; + jfieldID hardKeyboardHidden; + jfieldID navigation; + jfieldID navigationHidden; + jfieldID orientation; + jfieldID uiMode; +} gConfigurationClassInfo; + +void android_Configuration_getFromJava( + JNIEnv* env, jobject clazz, struct AConfiguration* out) { + out->mcc = env->GetIntField(clazz, gConfigurationClassInfo.mcc); + out->mnc = env->GetIntField(clazz, gConfigurationClassInfo.mnc); + out->screenLayout = env->GetIntField(clazz, gConfigurationClassInfo.screenLayout); + out->touchscreen = env->GetIntField(clazz, gConfigurationClassInfo.touchscreen); + out->keyboard = env->GetIntField(clazz, gConfigurationClassInfo.keyboard); + out->navigation = env->GetIntField(clazz, gConfigurationClassInfo.navigation); + + out->inputFlags = env->GetIntField(clazz, gConfigurationClassInfo.keyboardHidden); + int hardKeyboardHidden = env->GetIntField(clazz, gConfigurationClassInfo.hardKeyboardHidden); + if (out->inputFlags == ACONFIGURATION_KEYSHIDDEN_NO + && hardKeyboardHidden == 2) { + out->inputFlags = ACONFIGURATION_KEYSHIDDEN_SOFT; + } + out->inputFlags |= env->GetIntField(clazz, gConfigurationClassInfo.navigationHidden) + << ResTable_config::SHIFT_NAVHIDDEN; + + out->orientation = env->GetIntField(clazz, gConfigurationClassInfo.orientation); + out->uiMode = env->GetIntField(clazz, gConfigurationClassInfo.uiMode); +} + +/* + * JNI registration. + */ +static JNINativeMethod gMethods[] = { + /* name, signature, funcPtr */ + //{ "getObbInfo_native", "(Ljava/lang/String;Landroid/content/res/ObbInfo;)Z", + // (void*) android_content_res_ObbScanner_getObbInfo }, +}; + +#define FIND_CLASS(var, className) \ + var = env->FindClass(className); \ + LOG_FATAL_IF(! var, "Unable to find class " className); \ + var = jclass(env->NewGlobalRef(var)); + +#define GET_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \ + var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \ + LOG_FATAL_IF(! var, "Unable to find field " fieldName); + +int register_android_content_res_Configuration(JNIEnv* env) +{ + FIND_CLASS(gConfigurationClassInfo.clazz, "android/content/res/Configuration"); + + GET_FIELD_ID(gConfigurationClassInfo.mcc, gConfigurationClassInfo.clazz, + "mcc", "I"); + GET_FIELD_ID(gConfigurationClassInfo.mnc, gConfigurationClassInfo.clazz, + "mnc", "I"); + GET_FIELD_ID(gConfigurationClassInfo.locale, gConfigurationClassInfo.clazz, + "locale", "Ljava/util/Locale;"); + GET_FIELD_ID(gConfigurationClassInfo.screenLayout, gConfigurationClassInfo.clazz, + "screenLayout", "I"); + GET_FIELD_ID(gConfigurationClassInfo.touchscreen, gConfigurationClassInfo.clazz, + "touchscreen", "I"); + GET_FIELD_ID(gConfigurationClassInfo.keyboard, gConfigurationClassInfo.clazz, + "keyboard", "I"); + GET_FIELD_ID(gConfigurationClassInfo.keyboardHidden, gConfigurationClassInfo.clazz, + "keyboardHidden", "I"); + GET_FIELD_ID(gConfigurationClassInfo.hardKeyboardHidden, gConfigurationClassInfo.clazz, + "hardKeyboardHidden", "I"); + GET_FIELD_ID(gConfigurationClassInfo.navigation, gConfigurationClassInfo.clazz, + "navigation", "I"); + GET_FIELD_ID(gConfigurationClassInfo.navigationHidden, gConfigurationClassInfo.clazz, + "navigationHidden", "I"); + GET_FIELD_ID(gConfigurationClassInfo.orientation, gConfigurationClassInfo.clazz, + "orientation", "I"); + GET_FIELD_ID(gConfigurationClassInfo.uiMode, gConfigurationClassInfo.clazz, + "uiMode", "I"); + + return AndroidRuntime::registerNativeMethods(env, "android/content/res/Configuration", gMethods, + NELEM(gMethods)); +} + +}; // namespace android diff --git a/core/jni/android_content_res_ObbScanner.cpp b/core/jni/android_content_res_ObbScanner.cpp new file mode 100644 index 000000000000..62c89fc72958 --- /dev/null +++ b/core/jni/android_content_res_ObbScanner.cpp @@ -0,0 +1,97 @@ +/* + * Copyright 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. + */ + +#define LOG_TAG "ObbScanner" + +#include <utils/Log.h> +#include <utils/String8.h> +#include <utils/ObbFile.h> + +#include "jni.h" +#include "utils/misc.h" +#include "android_runtime/AndroidRuntime.h" + +namespace android { + +static struct { + jclass clazz; + + jfieldID packageName; + jfieldID version; + jfieldID flags; +} gObbInfoClassInfo; + +static jboolean android_content_res_ObbScanner_getObbInfo(JNIEnv* env, jobject clazz, jstring file, + jobject obbInfo) +{ + const char* filePath = env->GetStringUTFChars(file, JNI_FALSE); + + sp<ObbFile> obb = new ObbFile(); + if (!obb->readFrom(filePath)) { + env->ReleaseStringUTFChars(file, filePath); + return JNI_FALSE; + } + + env->ReleaseStringUTFChars(file, filePath); + + const char* packageNameStr = obb->getPackageName().string(); + + jstring packageName = env->NewStringUTF(packageNameStr); + if (packageName == NULL) { + return JNI_FALSE; + } + + env->SetObjectField(obbInfo, gObbInfoClassInfo.packageName, packageName); + env->SetIntField(obbInfo, gObbInfoClassInfo.version, obb->getVersion()); + + return JNI_TRUE; +} + +/* + * JNI registration. + */ +static JNINativeMethod gMethods[] = { + /* name, signature, funcPtr */ + { "getObbInfo_native", "(Ljava/lang/String;Landroid/content/res/ObbInfo;)Z", + (void*) android_content_res_ObbScanner_getObbInfo }, +}; + +#define FIND_CLASS(var, className) \ + var = env->FindClass(className); \ + LOG_FATAL_IF(! var, "Unable to find class " className); \ + var = jclass(env->NewGlobalRef(var)); + +#define GET_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \ + var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \ + LOG_FATAL_IF(! var, "Unable to find field " fieldName); + +int register_android_content_res_ObbScanner(JNIEnv* env) +{ + FIND_CLASS(gObbInfoClassInfo.clazz, "android/content/res/ObbInfo"); + + GET_FIELD_ID(gObbInfoClassInfo.packageName, gObbInfoClassInfo.clazz, + "packageName", "Ljava/lang/String;"); + GET_FIELD_ID(gObbInfoClassInfo.version, gObbInfoClassInfo.clazz, + "version", "I"); + GET_FIELD_ID(gObbInfoClassInfo.flags, gObbInfoClassInfo.clazz, + "flags", "I"); + + return AndroidRuntime::registerNativeMethods(env, "android/content/res/ObbScanner", gMethods, + NELEM(gMethods)); +} + +}; // namespace android + diff --git a/core/jni/android_hardware_Camera.cpp b/core/jni/android_hardware_Camera.cpp index b85466b5925d..c784974619b8 100644 --- a/core/jni/android_hardware_Camera.cpp +++ b/core/jni/android_hardware_Camera.cpp @@ -34,6 +34,8 @@ using namespace android; struct fields_t { jfieldID context; jfieldID surface; + jfieldID facing; + jfieldID orientation; jmethodID post_event; }; @@ -288,10 +290,30 @@ void JNICameraContext::clearCallbackBuffers_l(JNIEnv *env) } } +static jint android_hardware_Camera_getNumberOfCameras(JNIEnv *env, jobject thiz) +{ + return Camera::getNumberOfCameras(); +} + +static void android_hardware_Camera_getCameraInfo(JNIEnv *env, jobject thiz, + jint cameraId, jobject info_obj) +{ + CameraInfo cameraInfo; + status_t rc = Camera::getCameraInfo(cameraId, &cameraInfo); + if (rc != NO_ERROR) { + jniThrowException(env, "java/lang/RuntimeException", + "Fail to get camera info"); + return; + } + env->SetIntField(info_obj, fields.facing, cameraInfo.facing); + env->SetIntField(info_obj, fields.orientation, cameraInfo.orientation); +} + // connect to camera service -static void android_hardware_Camera_native_setup(JNIEnv *env, jobject thiz, jobject weak_this) +static void android_hardware_Camera_native_setup(JNIEnv *env, jobject thiz, + jobject weak_this, jint cameraId) { - sp<Camera> camera = Camera::connect(); + sp<Camera> camera = Camera::connect(cameraId); if (camera == NULL) { jniThrowException(env, "java/lang/RuntimeException", @@ -566,8 +588,14 @@ static void android_hardware_Camera_setDisplayOrientation(JNIEnv *env, jobject t //------------------------------------------------- static JNINativeMethod camMethods[] = { + { "getNumberOfCameras", + "()I", + (void *)android_hardware_Camera_getNumberOfCameras }, + { "getCameraInfo", + "(ILandroid/hardware/Camera$CameraInfo;)V", + (void*)android_hardware_Camera_getCameraInfo }, { "native_setup", - "(Ljava/lang/Object;)V", + "(Ljava/lang/Object;I)V", (void*)android_hardware_Camera_native_setup }, { "native_release", "()V", @@ -659,7 +687,9 @@ int register_android_hardware_Camera(JNIEnv *env) { field fields_to_find[] = { { "android/hardware/Camera", "mNativeContext", "I", &fields.context }, - { "android/view/Surface", "mSurface", "I", &fields.surface } + { "android/view/Surface", ANDROID_VIEW_SURFACE_JNI_ID, "I", &fields.surface }, + { "android/hardware/Camera$CameraInfo", "mFacing", "I", &fields.facing }, + { "android/hardware/Camera$CameraInfo", "mOrientation", "I", &fields.orientation }, }; if (find_fields(env, fields_to_find, NELEM(fields_to_find)) < 0) diff --git a/core/jni/android_hardware_SensorManager.cpp b/core/jni/android_hardware_SensorManager.cpp index 9a90b72fe50a..e29495cf0543 100644 --- a/core/jni/android_hardware_SensorManager.cpp +++ b/core/jni/android_hardware_SensorManager.cpp @@ -18,8 +18,9 @@ #include "utils/Log.h" -#include <hardware/sensors.h> -#include <cutils/native_handle.h> +#include <gui/Sensor.h> +#include <gui/SensorManager.h> +#include <gui/SensorEventQueue.h> #include "jni.h" #include "JNIHelp.h" @@ -37,50 +38,44 @@ struct SensorOffsets jfieldID range; jfieldID resolution; jfieldID power; + jfieldID minDelay; } gSensorOffsets; /* * The method below are not thread-safe and not intended to be */ -static sensors_module_t* sSensorModule = 0; -static sensors_data_device_t* sSensorDevice = 0; static jint sensors_module_init(JNIEnv *env, jclass clazz) { - int err = 0; - sensors_module_t const* module; - err = hw_get_module(SENSORS_HARDWARE_MODULE_ID, (const hw_module_t **)&module); - if (err == 0) - sSensorModule = (sensors_module_t*)module; - return err; + SensorManager::getInstance(); + return 0; } static jint sensors_module_get_next_sensor(JNIEnv *env, jobject clazz, jobject sensor, jint next) { - if (sSensorModule == NULL) - return 0; + SensorManager& mgr(SensorManager::getInstance()); - SensorOffsets& sensorOffsets = gSensorOffsets; - const struct sensor_t* list; - int count = sSensorModule->get_sensors_list(sSensorModule, &list); + Sensor const* const* sensorList; + size_t count = mgr.getSensorList(&sensorList); if (next >= count) return -1; - list += next; - - jstring name = env->NewStringUTF(list->name); - jstring vendor = env->NewStringUTF(list->vendor); + Sensor const* const list = sensorList[next]; + const SensorOffsets& sensorOffsets(gSensorOffsets); + jstring name = env->NewStringUTF(list->getName().string()); + jstring vendor = env->NewStringUTF(list->getVendor().string()); env->SetObjectField(sensor, sensorOffsets.name, name); env->SetObjectField(sensor, sensorOffsets.vendor, vendor); - env->SetIntField(sensor, sensorOffsets.version, list->version); - env->SetIntField(sensor, sensorOffsets.handle, list->handle); - env->SetIntField(sensor, sensorOffsets.type, list->type); - env->SetFloatField(sensor, sensorOffsets.range, list->maxRange); - env->SetFloatField(sensor, sensorOffsets.resolution, list->resolution); - env->SetFloatField(sensor, sensorOffsets.power, list->power); + env->SetIntField(sensor, sensorOffsets.version, 1); + env->SetIntField(sensor, sensorOffsets.handle, list->getHandle()); + env->SetIntField(sensor, sensorOffsets.type, list->getType()); + env->SetFloatField(sensor, sensorOffsets.range, list->getMaxValue()); + env->SetFloatField(sensor, sensorOffsets.resolution, list->getResolution()); + env->SetFloatField(sensor, sensorOffsets.power, list->getPowerUsage()); + env->SetIntField(sensor, sensorOffsets.minDelay, list->getMinDelay()); next++; return next<count ? next : 0; @@ -88,75 +83,64 @@ sensors_module_get_next_sensor(JNIEnv *env, jobject clazz, jobject sensor, jint //---------------------------------------------------------------------------- static jint -sensors_data_init(JNIEnv *env, jclass clazz) +sensors_create_queue(JNIEnv *env, jclass clazz) { - if (sSensorModule == NULL) - return -1; - int err = sensors_data_open(&sSensorModule->common, &sSensorDevice); - return err; + SensorManager& mgr(SensorManager::getInstance()); + sp<SensorEventQueue> queue(mgr.createEventQueue()); + queue->incStrong(clazz); + return reinterpret_cast<int>(queue.get()); } -static jint -sensors_data_uninit(JNIEnv *env, jclass clazz) +static void +sensors_destroy_queue(JNIEnv *env, jclass clazz, jint nativeQueue) { - int err = 0; - if (sSensorDevice) { - err = sensors_data_close(sSensorDevice); - if (err == 0) - sSensorDevice = 0; + sp<SensorEventQueue> queue(reinterpret_cast<SensorEventQueue *>(nativeQueue)); + if (queue != 0) { + queue->decStrong(clazz); } - return err; } -static jint -sensors_data_open(JNIEnv *env, jclass clazz, jobjectArray fdArray, jintArray intArray) +static jboolean +sensors_enable_sensor(JNIEnv *env, jclass clazz, + jint nativeQueue, jstring name, jint sensor, jint delay) { - jclass FileDescriptor = env->FindClass("java/io/FileDescriptor"); - jfieldID fieldOffset = env->GetFieldID(FileDescriptor, "descriptor", "I"); - int numFds = (fdArray ? env->GetArrayLength(fdArray) : 0); - int numInts = (intArray ? env->GetArrayLength(intArray) : 0); - native_handle_t* handle = native_handle_create(numFds, numInts); - int offset = 0; - - for (int i = 0; i < numFds; i++) { - jobject fdo = env->GetObjectArrayElement(fdArray, i); - if (fdo) { - handle->data[offset++] = env->GetIntField(fdo, fieldOffset); - } else { - handle->data[offset++] = -1; - } - } - if (numInts > 0) { - jint* ints = env->GetIntArrayElements(intArray, 0); - for (int i = 0; i < numInts; i++) { - handle->data[offset++] = ints[i]; - } - env->ReleaseIntArrayElements(intArray, ints, 0); + sp<SensorEventQueue> queue(reinterpret_cast<SensorEventQueue *>(nativeQueue)); + if (queue == 0) return JNI_FALSE; + status_t res; + if (delay >= 0) { + res = queue->enableSensor(sensor, delay); + } else { + res = queue->disableSensor(sensor); } - - // doesn't take ownership of the native handle - return sSensorDevice->data_open(sSensorDevice, handle); + return res == NO_ERROR ? true : false; } static jint -sensors_data_close(JNIEnv *env, jclass clazz) -{ - return sSensorDevice->data_close(sSensorDevice); -} - -static jint -sensors_data_poll(JNIEnv *env, jclass clazz, +sensors_data_poll(JNIEnv *env, jclass clazz, jint nativeQueue, jfloatArray values, jintArray status, jlongArray timestamp) { - sensors_data_t data; - int res = sSensorDevice->poll(sSensorDevice, &data); - if (res >= 0) { - jint accuracy = data.vector.status; - env->SetFloatArrayRegion(values, 0, 3, data.vector.v); - env->SetIntArrayRegion(status, 0, 1, &accuracy); - env->SetLongArrayRegion(timestamp, 0, 1, &data.time); + sp<SensorEventQueue> queue(reinterpret_cast<SensorEventQueue *>(nativeQueue)); + if (queue == 0) return -1; + + status_t res; + ASensorEvent event; + + res = queue->read(&event, 1); + if (res == -EAGAIN) { + res = queue->waitForEvent(); + if (res != NO_ERROR) + return -1; + res = queue->read(&event, 1); } - return res; + if (res < 0) + return -1; + + jint accuracy = event.vector.status; + env->SetFloatArrayRegion(values, 0, 3, event.vector.v); + env->SetIntArrayRegion(status, 0, 1, &accuracy); + env->SetLongArrayRegion(timestamp, 0, 1, &event.timestamp); + + return event.sensor; } static void @@ -172,6 +156,7 @@ nativeClassInit (JNIEnv *_env, jclass _this) sensorOffsets.range = _env->GetFieldID(sensorClass, "mMaxRange", "F"); sensorOffsets.resolution = _env->GetFieldID(sensorClass, "mResolution","F"); sensorOffsets.power = _env->GetFieldID(sensorClass, "mPower", "F"); + sensorOffsets.minDelay = _env->GetFieldID(sensorClass, "mMinDelay", "I"); } static JNINativeMethod gMethods[] = { @@ -179,11 +164,13 @@ static JNINativeMethod gMethods[] = { {"sensors_module_init","()I", (void*)sensors_module_init }, {"sensors_module_get_next_sensor","(Landroid/hardware/Sensor;I)I", (void*)sensors_module_get_next_sensor }, - {"sensors_data_init", "()I", (void*)sensors_data_init }, - {"sensors_data_uninit", "()I", (void*)sensors_data_uninit }, - {"sensors_data_open", "([Ljava/io/FileDescriptor;[I)I", (void*)sensors_data_open }, - {"sensors_data_close", "()I", (void*)sensors_data_close }, - {"sensors_data_poll", "([F[I[J)I", (void*)sensors_data_poll }, + + {"sensors_create_queue", "()I", (void*)sensors_create_queue }, + {"sensors_destroy_queue", "(I)V", (void*)sensors_destroy_queue }, + {"sensors_enable_sensor", "(ILjava/lang/String;II)Z", + (void*)sensors_enable_sensor }, + + {"sensors_data_poll", "(I[F[I[J)I", (void*)sensors_data_poll }, }; }; // namespace android diff --git a/core/jni/android_location_GpsLocationProvider.cpp b/core/jni/android_location_GpsLocationProvider.cpp deleted file mode 100755 index f60fe6da070a..000000000000 --- a/core/jni/android_location_GpsLocationProvider.cpp +++ /dev/null @@ -1,542 +0,0 @@ -/* - * Copyright (C) 2008 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. - */ - -#define LOG_TAG "GpsLocationProvider" - -//#define LOG_NDDEBUG 0 - -#include "JNIHelp.h" -#include "jni.h" -#include "hardware_legacy/gps.h" -#include "hardware_legacy/gps_ni.h" -#include "utils/Log.h" -#include "utils/misc.h" - -#include <string.h> -#include <pthread.h> - -static pthread_mutex_t sEventMutex = PTHREAD_MUTEX_INITIALIZER; -static pthread_cond_t sEventCond = PTHREAD_COND_INITIALIZER; -static jmethodID method_reportLocation; -static jmethodID method_reportStatus; -static jmethodID method_reportSvStatus; -static jmethodID method_reportAGpsStatus; -static jmethodID method_reportNmea; -static jmethodID method_xtraDownloadRequest; -static jmethodID method_reportNiNotification; - -static const GpsInterface* sGpsInterface = NULL; -static const GpsXtraInterface* sGpsXtraInterface = NULL; -static const AGpsInterface* sAGpsInterface = NULL; -static const GpsPrivacyInterface* sGpsPrivacyInterface = NULL; -static const GpsNiInterface* sGpsNiInterface = NULL; -static const GpsDebugInterface* sGpsDebugInterface = NULL; - -// data written to by GPS callbacks -static GpsLocation sGpsLocation; -static GpsStatus sGpsStatus; -static GpsSvStatus sGpsSvStatus; -static AGpsStatus sAGpsStatus; -static GpsNiNotification sGpsNiNotification; - -// buffer for NMEA data -#define NMEA_SENTENCE_LENGTH 100 -#define NMEA_SENTENCE_COUNT 40 -struct NmeaSentence { - GpsUtcTime timestamp; - char nmea[NMEA_SENTENCE_LENGTH]; -}; -static NmeaSentence sNmeaBuffer[NMEA_SENTENCE_COUNT]; -static int mNmeaSentenceCount = 0; - -// a copy of the data shared by android_location_GpsLocationProvider_wait_for_event -// and android_location_GpsLocationProvider_read_status -static GpsLocation sGpsLocationCopy; -static GpsStatus sGpsStatusCopy; -static GpsSvStatus sGpsSvStatusCopy; -static AGpsStatus sAGpsStatusCopy; -static NmeaSentence sNmeaBufferCopy[NMEA_SENTENCE_COUNT]; -static GpsNiNotification sGpsNiNotificationCopy; - -enum CallbackType { - kLocation = 1, - kStatus = 2, - kSvStatus = 4, - kAGpsStatus = 8, - kXtraDownloadRequest = 16, - kDisableRequest = 32, - kNmeaAvailable = 64, - kNiNotification = 128, -}; -static int sPendingCallbacks; - -namespace android { - -static void location_callback(GpsLocation* location) -{ - pthread_mutex_lock(&sEventMutex); - - sPendingCallbacks |= kLocation; - memcpy(&sGpsLocation, location, sizeof(sGpsLocation)); - - pthread_cond_signal(&sEventCond); - pthread_mutex_unlock(&sEventMutex); -} - -static void status_callback(GpsStatus* status) -{ - pthread_mutex_lock(&sEventMutex); - - sPendingCallbacks |= kStatus; - memcpy(&sGpsStatus, status, sizeof(sGpsStatus)); - - pthread_cond_signal(&sEventCond); - pthread_mutex_unlock(&sEventMutex); -} - -static void sv_status_callback(GpsSvStatus* sv_status) -{ - pthread_mutex_lock(&sEventMutex); - - sPendingCallbacks |= kSvStatus; - memcpy(&sGpsSvStatus, sv_status, sizeof(GpsSvStatus)); - - pthread_cond_signal(&sEventCond); - pthread_mutex_unlock(&sEventMutex); -} - -static void nmea_callback(GpsUtcTime timestamp, const char* nmea, int length) -{ - pthread_mutex_lock(&sEventMutex); - - if (length >= NMEA_SENTENCE_LENGTH) { - LOGE("NMEA data too long in nmea_callback (length = %d)\n", length); - length = NMEA_SENTENCE_LENGTH - 1; - } - if (mNmeaSentenceCount >= NMEA_SENTENCE_COUNT) { - LOGE("NMEA data overflowed buffer\n"); - pthread_mutex_unlock(&sEventMutex); - return; - } - - sPendingCallbacks |= kNmeaAvailable; - sNmeaBuffer[mNmeaSentenceCount].timestamp = timestamp; - memcpy(sNmeaBuffer[mNmeaSentenceCount].nmea, nmea, length); - sNmeaBuffer[mNmeaSentenceCount].nmea[length] = 0; - mNmeaSentenceCount++; - - pthread_cond_signal(&sEventCond); - pthread_mutex_unlock(&sEventMutex); -} - -static void agps_status_callback(AGpsStatus* agps_status) -{ - pthread_mutex_lock(&sEventMutex); - - sPendingCallbacks |= kAGpsStatus; - memcpy(&sAGpsStatus, agps_status, sizeof(AGpsStatus)); - - pthread_cond_signal(&sEventCond); - pthread_mutex_unlock(&sEventMutex); -} - -GpsCallbacks sGpsCallbacks = { - location_callback, - status_callback, - sv_status_callback, - nmea_callback -}; - -static void -download_request_callback() -{ - pthread_mutex_lock(&sEventMutex); - sPendingCallbacks |= kXtraDownloadRequest; - pthread_cond_signal(&sEventCond); - pthread_mutex_unlock(&sEventMutex); -} - -static void -gps_ni_notify_callback(GpsNiNotification *notification) -{ - LOGD("gps_ni_notify_callback: notif=%d", notification->notification_id); - - pthread_mutex_lock(&sEventMutex); - - sPendingCallbacks |= kNiNotification; - memcpy(&sGpsNiNotification, notification, sizeof(GpsNiNotification)); - - pthread_cond_signal(&sEventCond); - pthread_mutex_unlock(&sEventMutex); -} - -GpsXtraCallbacks sGpsXtraCallbacks = { - download_request_callback, -}; - -AGpsCallbacks sAGpsCallbacks = { - agps_status_callback, -}; - -GpsNiCallbacks sGpsNiCallbacks = { - gps_ni_notify_callback, -}; - -static void android_location_GpsLocationProvider_class_init_native(JNIEnv* env, jclass clazz) { - method_reportLocation = env->GetMethodID(clazz, "reportLocation", "(IDDDFFFJ)V"); - method_reportStatus = env->GetMethodID(clazz, "reportStatus", "(I)V"); - method_reportSvStatus = env->GetMethodID(clazz, "reportSvStatus", "()V"); - method_reportAGpsStatus = env->GetMethodID(clazz, "reportAGpsStatus", "(II)V"); - method_reportNmea = env->GetMethodID(clazz, "reportNmea", "(IJ)V"); - method_xtraDownloadRequest = env->GetMethodID(clazz, "xtraDownloadRequest", "()V"); - method_reportNiNotification = env->GetMethodID(clazz, "reportNiNotification", "(IIIIILjava/lang/String;Ljava/lang/String;IILjava/lang/String;)V"); -} - -static jboolean android_location_GpsLocationProvider_is_supported(JNIEnv* env, jclass clazz) { - if (!sGpsInterface) - sGpsInterface = gps_get_interface(); - return (sGpsInterface != NULL); -} - -static jboolean android_location_GpsLocationProvider_init(JNIEnv* env, jobject obj) -{ - if (!sGpsInterface) - sGpsInterface = gps_get_interface(); - if (!sGpsInterface || sGpsInterface->init(&sGpsCallbacks) != 0) - return false; - - if (!sAGpsInterface) - sAGpsInterface = (const AGpsInterface*)sGpsInterface->get_extension(AGPS_INTERFACE); - if (sAGpsInterface) - sAGpsInterface->init(&sAGpsCallbacks); - - if (!sGpsNiInterface) - sGpsNiInterface = (const GpsNiInterface*)sGpsInterface->get_extension(GPS_NI_INTERFACE); - if (sGpsNiInterface) - sGpsNiInterface->init(&sGpsNiCallbacks); - - // Clear privacy lock while enabled - if (!sGpsPrivacyInterface) - sGpsPrivacyInterface = (const GpsPrivacyInterface*)sGpsInterface->get_extension(GPS_PRIVACY_INTERFACE); - if (sGpsPrivacyInterface) - sGpsPrivacyInterface->set_privacy_lock(0); - - if (!sGpsDebugInterface) - sGpsDebugInterface = (const GpsDebugInterface*)sGpsInterface->get_extension(GPS_DEBUG_INTERFACE); - - return true; -} - -static void android_location_GpsLocationProvider_disable(JNIEnv* env, jobject obj) -{ - // Enable privacy lock while disabled - if (!sGpsPrivacyInterface) - sGpsPrivacyInterface = (const GpsPrivacyInterface*)sGpsInterface->get_extension(GPS_PRIVACY_INTERFACE); - if (sGpsPrivacyInterface) - sGpsPrivacyInterface->set_privacy_lock(1); - - pthread_mutex_lock(&sEventMutex); - sPendingCallbacks |= kDisableRequest; - pthread_cond_signal(&sEventCond); - pthread_mutex_unlock(&sEventMutex); -} - -static void android_location_GpsLocationProvider_cleanup(JNIEnv* env, jobject obj) -{ - sGpsInterface->cleanup(); -} - -static jboolean android_location_GpsLocationProvider_start(JNIEnv* env, jobject obj, jint positionMode, - jboolean singleFix, jint fixFrequency) -{ - int result = sGpsInterface->set_position_mode(positionMode, (singleFix ? 0 : fixFrequency)); - if (result) { - return false; - } - - return (sGpsInterface->start() == 0); -} - -static jboolean android_location_GpsLocationProvider_stop(JNIEnv* env, jobject obj) -{ - return (sGpsInterface->stop() == 0); -} - -static void android_location_GpsLocationProvider_delete_aiding_data(JNIEnv* env, jobject obj, jint flags) -{ - sGpsInterface->delete_aiding_data(flags); -} - -static void android_location_GpsLocationProvider_wait_for_event(JNIEnv* env, jobject obj) -{ - pthread_mutex_lock(&sEventMutex); - while (sPendingCallbacks == 0) { - pthread_cond_wait(&sEventCond, &sEventMutex); - } - - // copy and clear the callback flags - int pendingCallbacks = sPendingCallbacks; - sPendingCallbacks = 0; - int nmeaSentenceCount = mNmeaSentenceCount; - mNmeaSentenceCount = 0; - - // copy everything and unlock the mutex before calling into Java code to avoid the possibility - // of timeouts in the GPS engine. - if (pendingCallbacks & kLocation) - memcpy(&sGpsLocationCopy, &sGpsLocation, sizeof(sGpsLocationCopy)); - if (pendingCallbacks & kStatus) - memcpy(&sGpsStatusCopy, &sGpsStatus, sizeof(sGpsStatusCopy)); - if (pendingCallbacks & kSvStatus) - memcpy(&sGpsSvStatusCopy, &sGpsSvStatus, sizeof(sGpsSvStatusCopy)); - if (pendingCallbacks & kAGpsStatus) - memcpy(&sAGpsStatusCopy, &sAGpsStatus, sizeof(sAGpsStatusCopy)); - if (pendingCallbacks & kNmeaAvailable) - memcpy(&sNmeaBufferCopy, &sNmeaBuffer, nmeaSentenceCount * sizeof(sNmeaBuffer[0])); - if (pendingCallbacks & kNiNotification) - memcpy(&sGpsNiNotificationCopy, &sGpsNiNotification, sizeof(sGpsNiNotificationCopy)); - pthread_mutex_unlock(&sEventMutex); - - if (pendingCallbacks & kLocation) { - env->CallVoidMethod(obj, method_reportLocation, sGpsLocationCopy.flags, - (jdouble)sGpsLocationCopy.latitude, (jdouble)sGpsLocationCopy.longitude, - (jdouble)sGpsLocationCopy.altitude, - (jfloat)sGpsLocationCopy.speed, (jfloat)sGpsLocationCopy.bearing, - (jfloat)sGpsLocationCopy.accuracy, (jlong)sGpsLocationCopy.timestamp); - } - if (pendingCallbacks & kStatus) { - env->CallVoidMethod(obj, method_reportStatus, sGpsStatusCopy.status); - } - if (pendingCallbacks & kSvStatus) { - env->CallVoidMethod(obj, method_reportSvStatus); - } - if (pendingCallbacks & kAGpsStatus) { - env->CallVoidMethod(obj, method_reportAGpsStatus, sAGpsStatusCopy.type, sAGpsStatusCopy.status); - } - if (pendingCallbacks & kNmeaAvailable) { - for (int i = 0; i < nmeaSentenceCount; i++) { - env->CallVoidMethod(obj, method_reportNmea, i, sNmeaBuffer[i].timestamp); - } - } - if (pendingCallbacks & kXtraDownloadRequest) { - env->CallVoidMethod(obj, method_xtraDownloadRequest); - } - if (pendingCallbacks & kDisableRequest) { - // don't need to do anything - we are just poking so wait_for_event will return. - } - if (pendingCallbacks & kNiNotification) { - LOGD("android_location_GpsLocationProvider_wait_for_event: sent notification callback."); - jstring reqId = env->NewStringUTF(sGpsNiNotificationCopy.requestor_id); - jstring text = env->NewStringUTF(sGpsNiNotificationCopy.text); - jstring extras = env->NewStringUTF(sGpsNiNotificationCopy.extras); - env->CallVoidMethod(obj, method_reportNiNotification, - sGpsNiNotificationCopy.notification_id, - sGpsNiNotificationCopy.ni_type, - sGpsNiNotificationCopy.notify_flags, - sGpsNiNotificationCopy.timeout, - sGpsNiNotificationCopy.default_response, - reqId, - text, - sGpsNiNotificationCopy.requestor_id_encoding, - sGpsNiNotificationCopy.text_encoding, - extras - ); - } -} - -static jint android_location_GpsLocationProvider_read_sv_status(JNIEnv* env, jobject obj, - jintArray prnArray, jfloatArray snrArray, jfloatArray elevArray, jfloatArray azumArray, - jintArray maskArray) -{ - // this should only be called from within a call to reportStatus, so we don't need to lock here - - jint* prns = env->GetIntArrayElements(prnArray, 0); - jfloat* snrs = env->GetFloatArrayElements(snrArray, 0); - jfloat* elev = env->GetFloatArrayElements(elevArray, 0); - jfloat* azim = env->GetFloatArrayElements(azumArray, 0); - jint* mask = env->GetIntArrayElements(maskArray, 0); - - int num_svs = sGpsSvStatusCopy.num_svs; - for (int i = 0; i < num_svs; i++) { - prns[i] = sGpsSvStatusCopy.sv_list[i].prn; - snrs[i] = sGpsSvStatusCopy.sv_list[i].snr; - elev[i] = sGpsSvStatusCopy.sv_list[i].elevation; - azim[i] = sGpsSvStatusCopy.sv_list[i].azimuth; - } - mask[0] = sGpsSvStatusCopy.ephemeris_mask; - mask[1] = sGpsSvStatusCopy.almanac_mask; - mask[2] = sGpsSvStatusCopy.used_in_fix_mask; - - env->ReleaseIntArrayElements(prnArray, prns, 0); - env->ReleaseFloatArrayElements(snrArray, snrs, 0); - env->ReleaseFloatArrayElements(elevArray, elev, 0); - env->ReleaseFloatArrayElements(azumArray, azim, 0); - env->ReleaseIntArrayElements(maskArray, mask, 0); - return num_svs; -} - -static jint android_location_GpsLocationProvider_read_nmea(JNIEnv* env, jobject obj, jint index, jbyteArray nmeaArray, jint buffer_size) -{ - // this should only be called from within a call to reportNmea, so we don't need to lock here - - jbyte* nmea = env->GetByteArrayElements(nmeaArray, 0); - - int length = strlen(sNmeaBufferCopy[index].nmea); - if (length > buffer_size) - length = buffer_size; - memcpy(nmea, sNmeaBufferCopy[index].nmea, length); - - env->ReleaseByteArrayElements(nmeaArray, nmea, 0); - return length; -} - -static void android_location_GpsLocationProvider_inject_time(JNIEnv* env, jobject obj, jlong time, - jlong timeReference, jint uncertainty) -{ - sGpsInterface->inject_time(time, timeReference, uncertainty); -} - -static void android_location_GpsLocationProvider_inject_location(JNIEnv* env, jobject obj, - jdouble latitude, jdouble longitude, jfloat accuracy) -{ - sGpsInterface->inject_location(latitude, longitude, accuracy); -} - -static jboolean android_location_GpsLocationProvider_supports_xtra(JNIEnv* env, jobject obj) -{ - if (!sGpsXtraInterface) { - sGpsXtraInterface = (const GpsXtraInterface*)sGpsInterface->get_extension(GPS_XTRA_INTERFACE); - if (sGpsXtraInterface) { - int result = sGpsXtraInterface->init(&sGpsXtraCallbacks); - if (result) { - sGpsXtraInterface = NULL; - } - } - } - - return (sGpsXtraInterface != NULL); -} - -static void android_location_GpsLocationProvider_inject_xtra_data(JNIEnv* env, jobject obj, - jbyteArray data, jint length) -{ - jbyte* bytes = env->GetByteArrayElements(data, 0); - sGpsXtraInterface->inject_xtra_data((char *)bytes, length); - env->ReleaseByteArrayElements(data, bytes, 0); -} - -static void android_location_GpsLocationProvider_agps_data_conn_open(JNIEnv* env, jobject obj, jstring apn) -{ - if (!sAGpsInterface) { - sAGpsInterface = (const AGpsInterface*)sGpsInterface->get_extension(AGPS_INTERFACE); - } - if (sAGpsInterface) { - if (apn == NULL) { - jniThrowException(env, "java/lang/IllegalArgumentException", NULL); - return; - } - const char *apnStr = env->GetStringUTFChars(apn, NULL); - sAGpsInterface->data_conn_open(apnStr); - env->ReleaseStringUTFChars(apn, apnStr); - } -} - -static void android_location_GpsLocationProvider_agps_data_conn_closed(JNIEnv* env, jobject obj) -{ - if (!sAGpsInterface) { - sAGpsInterface = (const AGpsInterface*)sGpsInterface->get_extension(AGPS_INTERFACE); - } - if (sAGpsInterface) { - sAGpsInterface->data_conn_closed(); - } -} - -static void android_location_GpsLocationProvider_agps_data_conn_failed(JNIEnv* env, jobject obj) -{ - if (!sAGpsInterface) { - sAGpsInterface = (const AGpsInterface*)sGpsInterface->get_extension(AGPS_INTERFACE); - } - if (sAGpsInterface) { - sAGpsInterface->data_conn_failed(); - } -} - -static void android_location_GpsLocationProvider_set_agps_server(JNIEnv* env, jobject obj, - jint type, jstring hostname, jint port) -{ - if (!sAGpsInterface) { - sAGpsInterface = (const AGpsInterface*)sGpsInterface->get_extension(AGPS_INTERFACE); - } - if (sAGpsInterface) { - const char *c_hostname = env->GetStringUTFChars(hostname, NULL); - sAGpsInterface->set_server(type, c_hostname, port); - env->ReleaseStringUTFChars(hostname, c_hostname); - } -} - -static void android_location_GpsLocationProvider_send_ni_response(JNIEnv* env, jobject obj, - jint notifId, jint response) -{ - if (!sGpsNiInterface) - sGpsNiInterface = (const GpsNiInterface*)sGpsInterface->get_extension(GPS_NI_INTERFACE); - if (sGpsNiInterface) - sGpsNiInterface->respond(notifId, response); -} - -static jstring android_location_GpsLocationProvider_get_internal_state(JNIEnv* env, jobject obj) -{ - jstring result = NULL; - if (sGpsDebugInterface) { - const size_t maxLength = 2047; - char buffer[maxLength+1]; - size_t length = sGpsDebugInterface->get_internal_state(buffer, maxLength); - if (length > maxLength) length = maxLength; - buffer[length] = 0; - result = env->NewStringUTF(buffer); - } - return result; -} - -static JNINativeMethod sMethods[] = { - /* name, signature, funcPtr */ - {"class_init_native", "()V", (void *)android_location_GpsLocationProvider_class_init_native}, - {"native_is_supported", "()Z", (void*)android_location_GpsLocationProvider_is_supported}, - {"native_init", "()Z", (void*)android_location_GpsLocationProvider_init}, - {"native_disable", "()V", (void*)android_location_GpsLocationProvider_disable}, - {"native_cleanup", "()V", (void*)android_location_GpsLocationProvider_cleanup}, - {"native_start", "(IZI)Z", (void*)android_location_GpsLocationProvider_start}, - {"native_stop", "()Z", (void*)android_location_GpsLocationProvider_stop}, - {"native_delete_aiding_data", "(I)V", (void*)android_location_GpsLocationProvider_delete_aiding_data}, - {"native_wait_for_event", "()V", (void*)android_location_GpsLocationProvider_wait_for_event}, - {"native_read_sv_status", "([I[F[F[F[I)I", (void*)android_location_GpsLocationProvider_read_sv_status}, - {"native_read_nmea", "(I[BI)I", (void*)android_location_GpsLocationProvider_read_nmea}, - {"native_inject_time", "(JJI)V", (void*)android_location_GpsLocationProvider_inject_time}, - {"native_inject_location", "(DDF)V", (void*)android_location_GpsLocationProvider_inject_location}, - {"native_supports_xtra", "()Z", (void*)android_location_GpsLocationProvider_supports_xtra}, - {"native_inject_xtra_data", "([BI)V", (void*)android_location_GpsLocationProvider_inject_xtra_data}, - {"native_agps_data_conn_open", "(Ljava/lang/String;)V", (void*)android_location_GpsLocationProvider_agps_data_conn_open}, - {"native_agps_data_conn_closed", "()V", (void*)android_location_GpsLocationProvider_agps_data_conn_closed}, - {"native_agps_data_conn_failed", "()V", (void*)android_location_GpsLocationProvider_agps_data_conn_failed}, - {"native_set_agps_server", "(ILjava/lang/String;I)V", (void*)android_location_GpsLocationProvider_set_agps_server}, - {"native_send_ni_response", "(II)V", (void*)android_location_GpsLocationProvider_send_ni_response}, - {"native_get_internal_state", "()Ljava/lang/String;", (void*)android_location_GpsLocationProvider_get_internal_state}, -}; - -int register_android_location_GpsLocationProvider(JNIEnv* env) -{ - return jniRegisterNativeMethods(env, "com/android/internal/location/GpsLocationProvider", sMethods, NELEM(sMethods)); -} - -} /* namespace android */ diff --git a/core/jni/android_media_AudioRecord.cpp b/core/jni/android_media_AudioRecord.cpp index 17f5daf426c2..f78f83c2a6d9 100644 --- a/core/jni/android_media_AudioRecord.cpp +++ b/core/jni/android_media_AudioRecord.cpp @@ -453,30 +453,23 @@ static jint android_media_AudioRecord_get_pos_update_period(JNIEnv *env, jobjec // return -1 if there was an error querying the buffer size. static jint android_media_AudioRecord_get_min_buff_size(JNIEnv *env, jobject thiz, jint sampleRateInHertz, jint nbChannels, jint audioFormat) { - - size_t inputBuffSize = 0; + LOGV(">> android_media_AudioRecord_get_min_buff_size(%d, %d, %d)", sampleRateInHertz, nbChannels, audioFormat); - - status_t result = AudioSystem::getInputBufferSize( - sampleRateInHertz, - (audioFormat == javaAudioRecordFields.PCM16 ? - AudioSystem::PCM_16_BIT : AudioSystem::PCM_8_BIT), - nbChannels, &inputBuffSize); - switch(result) { - case(NO_ERROR): - if(inputBuffSize == 0) { - LOGV("Recording parameters are not supported: %dHz, %d channel(s), (java) format %d", - sampleRateInHertz, nbChannels, audioFormat); - return 0; - } else { - // the minimum buffer size is twice the hardware input buffer size - return 2*inputBuffSize; - } - break; - case(PERMISSION_DENIED): - default: - return -1; + + int frameCount = 0; + status_t result = AudioRecord::getMinFrameCount(&frameCount, + sampleRateInHertz, + (audioFormat == javaAudioRecordFields.PCM16 ? + AudioSystem::PCM_16_BIT : AudioSystem::PCM_8_BIT), + nbChannels); + + if (result == BAD_VALUE) { + return 0; + } + if (result != NO_ERROR) { + return -1; } + return frameCount * nbChannels * (audioFormat == javaAudioRecordFields.PCM16 ? 2 : 1); } diff --git a/core/jni/android_media_AudioTrack.cpp b/core/jni/android_media_AudioTrack.cpp index 65c04357adfd..9d215b72d8be 100644 --- a/core/jni/android_media_AudioTrack.cpp +++ b/core/jni/android_media_AudioTrack.cpp @@ -166,7 +166,7 @@ static void audioCallback(int event, void* user, void *info) { static int android_media_AudioTrack_native_setup(JNIEnv *env, jobject thiz, jobject weak_this, jint streamType, jint sampleRateInHertz, jint channels, - jint audioFormat, jint buffSizeInBytes, jint memoryMode) + jint audioFormat, jint buffSizeInBytes, jint memoryMode, jintArray jSession) { LOGV("sampleRate=%d, audioFormat(from Java)=%d, channels=%x, buffSize=%d", sampleRateInHertz, audioFormat, channels, buffSizeInBytes); @@ -253,6 +253,20 @@ android_media_AudioTrack_native_setup(JNIEnv *env, jobject thiz, jobject weak_th lpJniStorage->mStreamType = atStreamType; + jint* nSession = NULL; + if (jSession) { + nSession = (jint *) env->GetPrimitiveArrayCritical(jSession, NULL); + if (nSession == NULL) { + LOGE("Error creating AudioTrack: Error retrieving session id pointer"); + delete lpJniStorage; + return AUDIOTRACK_ERROR; + } + } else { + LOGE("Error creating AudioTrack: invalid session ID pointer"); + delete lpJniStorage; + return AUDIOTRACK_ERROR; + } + // create the native AudioTrack object AudioTrack* lpTrack = new AudioTrack(); if (lpTrack == NULL) { @@ -273,7 +287,8 @@ android_media_AudioTrack_native_setup(JNIEnv *env, jobject thiz, jobject weak_th audioCallback, &(lpJniStorage->mCallbackData),//callback, callback data (user) 0,// notificationFrames == 0 since not using EVENT_MORE_DATA to feed the AudioTrack 0,// shared mem - true);// thread can call Java + true,// thread can call Java + nSession[0]);// audio session ID } else if (memoryMode == javaAudioTrackFields.MODE_STATIC) { // AudioTrack is using shared memory @@ -293,7 +308,8 @@ android_media_AudioTrack_native_setup(JNIEnv *env, jobject thiz, jobject weak_th audioCallback, &(lpJniStorage->mCallbackData),//callback, callback data (user)); 0,// notificationFrames == 0 since not using EVENT_MORE_DATA to feed the AudioTrack lpJniStorage->mMemBase,// shared mem - true);// thread can call Java + true,// thread can call Java + nSession[0]);// audio session ID } if (lpTrack->initCheck() != NO_ERROR) { @@ -301,6 +317,12 @@ android_media_AudioTrack_native_setup(JNIEnv *env, jobject thiz, jobject weak_th goto native_init_failure; } + // read the audio session ID back from AudioTrack in case we create a new session + nSession[0] = lpTrack->getSessionId(); + + env->ReleasePrimitiveArrayCritical(jSession, nSession, 0); + nSession = NULL; + // save our newly created C++ AudioTrack in the "nativeTrackInJavaObj" field // of the Java object (in mNativeTrackInJavaObj) env->SetIntField(thiz, javaAudioTrackFields.nativeTrackInJavaObj, (int)lpTrack); @@ -317,6 +339,9 @@ native_init_failure: env->SetIntField(thiz, javaAudioTrackFields.nativeTrackInJavaObj, 0); native_track_failure: + if (nSession != NULL) { + env->ReleasePrimitiveArrayCritical(jSession, nSession, 0); + } env->DeleteGlobalRef(lpJniStorage->mCallbackData.audioTrack_class); env->DeleteGlobalRef(lpJniStorage->mCallbackData.audioTrack_ref); delete lpJniStorage; @@ -335,6 +360,7 @@ android_media_AudioTrack_start(JNIEnv *env, jobject thiz) if (lpTrack == NULL ) { jniThrowException(env, "java/lang/IllegalStateException", "Unable to retrieve AudioTrack pointer for start()"); + return; } lpTrack->start(); @@ -350,6 +376,7 @@ android_media_AudioTrack_stop(JNIEnv *env, jobject thiz) if (lpTrack == NULL ) { jniThrowException(env, "java/lang/IllegalStateException", "Unable to retrieve AudioTrack pointer for stop()"); + return; } lpTrack->stop(); @@ -365,6 +392,7 @@ android_media_AudioTrack_pause(JNIEnv *env, jobject thiz) if (lpTrack == NULL ) { jniThrowException(env, "java/lang/IllegalStateException", "Unable to retrieve AudioTrack pointer for pause()"); + return; } lpTrack->pause(); @@ -380,6 +408,7 @@ android_media_AudioTrack_flush(JNIEnv *env, jobject thiz) if (lpTrack == NULL ) { jniThrowException(env, "java/lang/IllegalStateException", "Unable to retrieve AudioTrack pointer for flush()"); + return; } lpTrack->flush(); @@ -394,6 +423,7 @@ android_media_AudioTrack_set_volume(JNIEnv *env, jobject thiz, jfloat leftVol, j if (lpTrack == NULL ) { jniThrowException(env, "java/lang/IllegalStateException", "Unable to retrieve AudioTrack pointer for setVolume()"); + return; } lpTrack->setVolume(leftVol, rightVol); @@ -490,6 +520,7 @@ static jint android_media_AudioTrack_native_write(JNIEnv *env, jobject thiz, if (lpTrack == NULL) { jniThrowException(env, "java/lang/IllegalStateException", "Unable to retrieve AudioTrack pointer for write()"); + return 0; } // get the pointer for the audio data from the java array @@ -751,31 +782,45 @@ static jint android_media_AudioTrack_get_output_sample_rate(JNIEnv *env, jobjec // returns -1 if there was an error querying the hardware. static jint android_media_AudioTrack_get_min_buff_size(JNIEnv *env, jobject thiz, jint sampleRateInHertz, jint nbChannels, jint audioFormat) { - int afSamplingRate; - int afFrameCount; - uint32_t afLatency; - - if (AudioSystem::getOutputSamplingRate(&afSamplingRate) != NO_ERROR) { - return -1; - } - if (AudioSystem::getOutputFrameCount(&afFrameCount) != NO_ERROR) { + + int frameCount = 0; + if (AudioTrack::getMinFrameCount(&frameCount, AudioSystem::DEFAULT, + sampleRateInHertz) != NO_ERROR) { return -1; } - - if (AudioSystem::getOutputLatency(&afLatency) != NO_ERROR) { - return -1; + return frameCount * nbChannels * (audioFormat == javaAudioTrackFields.PCM16 ? 2 : 1); +} + +// ---------------------------------------------------------------------------- +static void +android_media_AudioTrack_setAuxEffectSendLevel(JNIEnv *env, jobject thiz, jfloat level ) +{ + AudioTrack *lpTrack = (AudioTrack *)env->GetIntField( + thiz, javaAudioTrackFields.nativeTrackInJavaObj); + if (lpTrack == NULL ) { + jniThrowException(env, "java/lang/IllegalStateException", + "Unable to retrieve AudioTrack pointer for setAuxEffectSendLevel()"); + return; } - - // Ensure that buffer depth covers at least audio hardware latency - uint32_t minBufCount = afLatency / ((1000 * afFrameCount)/afSamplingRate); - if (minBufCount < 2) minBufCount = 2; - uint32_t minFrameCount = (afFrameCount*sampleRateInHertz*minBufCount)/afSamplingRate; - int minBuffSize = minFrameCount - * (audioFormat == javaAudioTrackFields.PCM16 ? 2 : 1) - * nbChannels; - return minBuffSize; + + lpTrack->setAuxEffectSendLevel(level); } +// ---------------------------------------------------------------------------- +static jint android_media_AudioTrack_attachAuxEffect(JNIEnv *env, jobject thiz, + jint effectId) { + + AudioTrack *lpTrack = (AudioTrack *)env->GetIntField( + thiz, javaAudioTrackFields.nativeTrackInJavaObj); + + if (lpTrack) { + return android_media_translateErrorCode( lpTrack->attachAuxEffect(effectId) ); + } else { + jniThrowException(env, "java/lang/IllegalStateException", + "Unable to retrieve AudioTrack pointer for attachAuxEffect()"); + return AUDIOTRACK_ERROR; + } +} // ---------------------------------------------------------------------------- // ---------------------------------------------------------------------------- @@ -785,7 +830,7 @@ static JNINativeMethod gMethods[] = { {"native_stop", "()V", (void *)android_media_AudioTrack_stop}, {"native_pause", "()V", (void *)android_media_AudioTrack_pause}, {"native_flush", "()V", (void *)android_media_AudioTrack_flush}, - {"native_setup", "(Ljava/lang/Object;IIIIII)I", + {"native_setup", "(Ljava/lang/Object;IIIIII[I)I", (void *)android_media_AudioTrack_native_setup}, {"native_finalize", "()V", (void *)android_media_AudioTrack_native_finalize}, {"native_release", "()V", (void *)android_media_AudioTrack_native_release}, @@ -812,6 +857,10 @@ static JNINativeMethod gMethods[] = { "(I)I", (void *)android_media_AudioTrack_get_output_sample_rate}, {"native_get_min_buff_size", "(III)I", (void *)android_media_AudioTrack_get_min_buff_size}, + {"native_setAuxEffectSendLevel", + "(F)V", (void *)android_media_AudioTrack_setAuxEffectSendLevel}, + {"native_attachAuxEffect", + "(I)I", (void *)android_media_AudioTrack_attachAuxEffect}, }; diff --git a/core/jni/android_net_wifi_Wifi.cpp b/core/jni/android_net_wifi_Wifi.cpp index 46000c971715..fb029e6e1713 100644 --- a/core/jni/android_net_wifi_Wifi.cpp +++ b/core/jni/android_net_wifi_Wifi.cpp @@ -392,6 +392,20 @@ static jboolean android_net_wifi_setPowerModeCommand(JNIEnv* env, jobject clazz, return (jboolean)!cmdTooLong && doBooleanCommand(cmdstr, "OK"); } +static jint android_net_wifi_getPowerModeCommand(JNIEnv* env, jobject clazz) +{ + char reply[256]; + int power; + + if (doCommand("DRIVER GETPOWER", reply, sizeof(reply)) != 0) { + return (jint)-1; + } + // reply comes back in the form "powermode = XX" where XX is the + // number we're interested in. + sscanf(reply, "%*s = %u", &power); + return (jint)power; +} + static jboolean android_net_wifi_setNumAllowedChannelsCommand(JNIEnv* env, jobject clazz, jint numChannels) { char cmdstr[256]; @@ -479,6 +493,15 @@ static jboolean android_net_wifi_clearBlacklistCommand(JNIEnv* env, jobject claz return doBooleanCommand("BLACKLIST clear", "OK"); } +static jboolean android_net_wifi_setSuspendOptimizationsCommand(JNIEnv* env, jobject clazz, jboolean enabled) +{ + char cmdstr[25]; + + snprintf(cmdstr, sizeof(cmdstr), "DRIVER SETSUSPENDOPT %d", enabled ? 0 : 1); + return doBooleanCommand(cmdstr, "OK"); +} + + static jboolean android_net_wifi_doDhcpRequest(JNIEnv* env, jobject clazz, jobject info) { jint ipaddr, gateway, mask, dns1, dns2, server, lease; @@ -540,6 +563,7 @@ static JNINativeMethod gWifiMethods[] = { { "startPacketFiltering", "()Z", (void*) android_net_wifi_startPacketFiltering }, { "stopPacketFiltering", "()Z", (void*) android_net_wifi_stopPacketFiltering }, { "setPowerModeCommand", "(I)Z", (void*) android_net_wifi_setPowerModeCommand }, + { "getPowerModeCommand", "()I", (void*) android_net_wifi_getPowerModeCommand }, { "setNumAllowedChannelsCommand", "(I)Z", (void*) android_net_wifi_setNumAllowedChannelsCommand }, { "getNumAllowedChannelsCommand", "()I", (void*) android_net_wifi_getNumAllowedChannelsCommand }, { "setBluetoothCoexistenceModeCommand", "(I)Z", @@ -556,6 +580,7 @@ static JNINativeMethod gWifiMethods[] = { { "setScanResultHandlingCommand", "(I)Z", (void*) android_net_wifi_setScanResultHandlingCommand }, { "addToBlacklistCommand", "(Ljava/lang/String;)Z", (void*) android_net_wifi_addToBlacklistCommand }, { "clearBlacklistCommand", "()Z", (void*) android_net_wifi_clearBlacklistCommand }, + { "setSuspendOptimizationsCommand", "(Z)Z", (void*) android_net_wifi_setSuspendOptimizationsCommand}, { "doDhcpRequest", "(Landroid/net/DhcpInfo;)Z", (void*) android_net_wifi_doDhcpRequest }, { "getDhcpError", "()Ljava/lang/String;", (void*) android_net_wifi_getDhcpError }, diff --git a/core/jni/android_opengl_GLES20.cpp b/core/jni/android_opengl_GLES20.cpp index fd1c8fdaffa1..ef25319b7600 100644 --- a/core/jni/android_opengl_GLES20.cpp +++ b/core/jni/android_opengl_GLES20.cpp @@ -823,6 +823,18 @@ android_glDrawArrays__III ); } +/* void glDrawElements ( GLenum mode, GLsizei count, GLenum type, GLint offset ) */ +static void +android_glDrawElements__IIII + (JNIEnv *_env, jobject _this, jint mode, jint count, jint type, jint offset) { + glDrawElements( + (GLenum)mode, + (GLsizei)count, + (GLenum)type, + (const GLvoid *)offset + ); +} + /* void glDrawElements ( GLenum mode, GLsizei count, GLenum type, const GLvoid *indices ) */ static void android_glDrawElements__IIILjava_nio_Buffer_2 @@ -4786,6 +4798,20 @@ android_glVertexAttrib4fv__ILjava_nio_FloatBuffer_2 } } +/* void glVertexAttribPointer ( GLuint indx, GLint size, GLenum type, GLboolean normalized, GLsizei stride, GLint offset ) */ +static void +android_glVertexAttribPointer__IIIZII + (JNIEnv *_env, jobject _this, jint indx, jint size, jint type, jboolean normalized, jint stride, jint offset) { + glVertexAttribPointer( + (GLuint)indx, + (GLint)size, + (GLenum)type, + (GLboolean)normalized, + (GLsizei)stride, + (const GLvoid *)offset + ); +} + /* void glVertexAttribPointer ( GLuint indx, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid *ptr ) */ static void android_glVertexAttribPointerBounds__IIIZILjava_nio_Buffer_2I @@ -4872,6 +4898,7 @@ static JNINativeMethod methods[] = { {"glDisable", "(I)V", (void *) android_glDisable__I }, {"glDisableVertexAttribArray", "(I)V", (void *) android_glDisableVertexAttribArray__I }, {"glDrawArrays", "(III)V", (void *) android_glDrawArrays__III }, +{"glDrawElements", "(IIII)V", (void *) android_glDrawElements__IIII }, {"glDrawElements", "(IIILjava/nio/Buffer;)V", (void *) android_glDrawElements__IIILjava_nio_Buffer_2 }, {"glEnable", "(I)V", (void *) android_glEnable__I }, {"glEnableVertexAttribArray", "(I)V", (void *) android_glEnableVertexAttribArray__I }, @@ -5011,6 +5038,7 @@ static JNINativeMethod methods[] = { {"glVertexAttrib4f", "(IFFFF)V", (void *) android_glVertexAttrib4f__IFFFF }, {"glVertexAttrib4fv", "(I[FI)V", (void *) android_glVertexAttrib4fv__I_3FI }, {"glVertexAttrib4fv", "(ILjava/nio/FloatBuffer;)V", (void *) android_glVertexAttrib4fv__ILjava_nio_FloatBuffer_2 }, +{"glVertexAttribPointer", "(IIIZII)V", (void *) android_glVertexAttribPointer__IIIZII }, {"glVertexAttribPointerBounds", "(IIIZILjava/nio/Buffer;I)V", (void *) android_glVertexAttribPointerBounds__IIIZILjava_nio_Buffer_2I }, {"glViewport", "(IIII)V", (void *) android_glViewport__IIII }, }; diff --git a/core/jni/android_os_MessageQueue.cpp b/core/jni/android_os_MessageQueue.cpp new file mode 100644 index 000000000000..847b5a5354e1 --- /dev/null +++ b/core/jni/android_os_MessageQueue.cpp @@ -0,0 +1,168 @@ +/* + * 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. + */ + +#define LOG_TAG "MessageQueue-JNI" + +#include "JNIHelp.h" + +#include <utils/PollLoop.h> +#include <utils/Log.h> +#include "android_os_MessageQueue.h" + +namespace android { + +// ---------------------------------------------------------------------------- + +static struct { + jclass clazz; + + jfieldID mPtr; // native object attached to the DVM MessageQueue +} gMessageQueueClassInfo; + +// ---------------------------------------------------------------------------- + +class NativeMessageQueue { +public: + NativeMessageQueue(); + ~NativeMessageQueue(); + + inline sp<PollLoop> getPollLoop() { return mPollLoop; } + + bool pollOnce(int timeoutMillis); + void wake(); + +private: + sp<PollLoop> mPollLoop; +}; + +// ---------------------------------------------------------------------------- + +NativeMessageQueue::NativeMessageQueue() { + mPollLoop = PollLoop::getForThread(); + if (mPollLoop == NULL) { + mPollLoop = new PollLoop(false); + PollLoop::setForThread(mPollLoop); + } +} + +NativeMessageQueue::~NativeMessageQueue() { +} + +bool NativeMessageQueue::pollOnce(int timeoutMillis) { + return mPollLoop->pollOnce(timeoutMillis) != PollLoop::POLL_TIMEOUT; +} + +void NativeMessageQueue::wake() { + mPollLoop->wake(); +} + +// ---------------------------------------------------------------------------- + +static NativeMessageQueue* android_os_MessageQueue_getNativeMessageQueue(JNIEnv* env, + jobject messageQueueObj) { + jint intPtr = env->GetIntField(messageQueueObj, gMessageQueueClassInfo.mPtr); + return reinterpret_cast<NativeMessageQueue*>(intPtr); +} + +static void android_os_MessageQueue_setNativeMessageQueue(JNIEnv* env, jobject messageQueueObj, + NativeMessageQueue* nativeMessageQueue) { + env->SetIntField(messageQueueObj, gMessageQueueClassInfo.mPtr, + reinterpret_cast<jint>(nativeMessageQueue)); +} + +sp<PollLoop> android_os_MessageQueue_getPollLoop(JNIEnv* env, jobject messageQueueObj) { + NativeMessageQueue* nativeMessageQueue = + android_os_MessageQueue_getNativeMessageQueue(env, messageQueueObj); + return nativeMessageQueue != NULL ? nativeMessageQueue->getPollLoop() : NULL; +} + +static void android_os_MessageQueue_nativeInit(JNIEnv* env, jobject obj) { + NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue(); + if (! nativeMessageQueue) { + jniThrowRuntimeException(env, "Unable to allocate native queue"); + return; + } + + android_os_MessageQueue_setNativeMessageQueue(env, obj, nativeMessageQueue); +} + +static void android_os_MessageQueue_nativeDestroy(JNIEnv* env, jobject obj) { + NativeMessageQueue* nativeMessageQueue = + android_os_MessageQueue_getNativeMessageQueue(env, obj); + if (nativeMessageQueue) { + android_os_MessageQueue_setNativeMessageQueue(env, obj, NULL); + delete nativeMessageQueue; + } +} + +static void throwQueueNotInitialized(JNIEnv* env) { + jniThrowException(env, "java/lang/IllegalStateException", "Message queue not initialized"); +} + +static jboolean android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj, + jint timeoutMillis) { + NativeMessageQueue* nativeMessageQueue = + android_os_MessageQueue_getNativeMessageQueue(env, obj); + if (! nativeMessageQueue) { + throwQueueNotInitialized(env); + return false; + } + return nativeMessageQueue->pollOnce(timeoutMillis); +} + +static void android_os_MessageQueue_nativeWake(JNIEnv* env, jobject obj) { + NativeMessageQueue* nativeMessageQueue = + android_os_MessageQueue_getNativeMessageQueue(env, obj); + if (! nativeMessageQueue) { + throwQueueNotInitialized(env); + return; + } + return nativeMessageQueue->wake(); +} + +// ---------------------------------------------------------------------------- + +static JNINativeMethod gMessageQueueMethods[] = { + /* name, signature, funcPtr */ + { "nativeInit", "()V", (void*)android_os_MessageQueue_nativeInit }, + { "nativeDestroy", "()V", (void*)android_os_MessageQueue_nativeDestroy }, + { "nativePollOnce", "(I)Z", (void*)android_os_MessageQueue_nativePollOnce }, + { "nativeWake", "()V", (void*)android_os_MessageQueue_nativeWake } +}; + +#define FIND_CLASS(var, className) \ + var = env->FindClass(className); \ + LOG_FATAL_IF(! var, "Unable to find class " className); \ + var = jclass(env->NewGlobalRef(var)); + +#define GET_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \ + var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \ + LOG_FATAL_IF(! var, "Unable to find field " fieldName); + +int register_android_os_MessageQueue(JNIEnv* env) { + int res = jniRegisterNativeMethods(env, "android/os/MessageQueue", + gMessageQueueMethods, NELEM(gMessageQueueMethods)); + LOG_FATAL_IF(res < 0, "Unable to register native methods."); + + FIND_CLASS(gMessageQueueClassInfo.clazz, "android/os/MessageQueue"); + + GET_FIELD_ID(gMessageQueueClassInfo.mPtr, gMessageQueueClassInfo.clazz, + "mPtr", "I"); + + return 0; +} + +} // namespace android diff --git a/core/jni/android_os_MessageQueue.h b/core/jni/android_os_MessageQueue.h new file mode 100644 index 000000000000..5c742e23b0a9 --- /dev/null +++ b/core/jni/android_os_MessageQueue.h @@ -0,0 +1,30 @@ +/* + * 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. + */ + +#ifndef _ANDROID_OS_MESSAGEQUEUE_H +#define _ANDROID_OS_MESSAGEQUEUE_H + +#include "jni.h" + +namespace android { + +class PollLoop; + +extern sp<PollLoop> android_os_MessageQueue_getPollLoop(JNIEnv* env, jobject messageQueueObj); + +} // namespace android + +#endif // _ANDROID_OS_MESSAGEQUEUE_H diff --git a/core/jni/android_server_BluetoothA2dpService.cpp b/core/jni/android_server_BluetoothA2dpService.cpp index cf53a066e523..8c795afe3ee4 100644 --- a/core/jni/android_server_BluetoothA2dpService.cpp +++ b/core/jni/android_server_BluetoothA2dpService.cpp @@ -256,10 +256,12 @@ DBusHandlerResult a2dp_event_filter(DBusMessage *msg, JNIEnv *env) { parse_property_change(env, msg, (Properties *)&sink_properties, sizeof(sink_properties) / sizeof(Properties)); const char *c_path = dbus_message_get_path(msg); + jstring path = env->NewStringUTF(c_path); env->CallVoidMethod(nat->me, method_onSinkPropertyChanged, - env->NewStringUTF(c_path), + path, str_array); + env->DeleteLocalRef(path); result = DBUS_HANDLER_RESULT_HANDLED; return result; } else { @@ -292,10 +294,13 @@ void onConnectSinkResult(DBusMessage *msg, void *user, void *n) { result = JNI_FALSE; } LOGV("... Device Path = %s, result = %d", path, result); + + jstring jPath = env->NewStringUTF(path); env->CallVoidMethod(nat->me, method_onConnectSinkResult, - env->NewStringUTF(path), + jPath, result); + env->DeleteLocalRef(jPath); free(user); } diff --git a/core/jni/android_server_BluetoothEventLoop.cpp b/core/jni/android_server_BluetoothEventLoop.cpp index 259cc01baf1c..d8e049dd3be2 100644 --- a/core/jni/android_server_BluetoothEventLoop.cpp +++ b/core/jni/android_server_BluetoothEventLoop.cpp @@ -61,6 +61,8 @@ static jmethodID method_onRequestPasskey; static jmethodID method_onRequestPasskeyConfirmation; static jmethodID method_onRequestPairingConsent; static jmethodID method_onDisplayPasskey; +static jmethodID method_onRequestOobData; +static jmethodID method_onAgentOutOfBandDataAvailable; static jmethodID method_onAgentAuthorize; static jmethodID method_onAgentCancel; @@ -105,6 +107,8 @@ static void classInitNative(JNIEnv* env, jclass clazz) { method_onAgentAuthorize = env->GetMethodID(clazz, "onAgentAuthorize", "(Ljava/lang/String;Ljava/lang/String;)Z"); + method_onAgentOutOfBandDataAvailable = env->GetMethodID(clazz, "onAgentOutOfBandDataAvailable", + "(Ljava/lang/String;)Z"); method_onAgentCancel = env->GetMethodID(clazz, "onAgentCancel", "()V"); method_onRequestPinCode = env->GetMethodID(clazz, "onRequestPinCode", "(Ljava/lang/String;I)V"); @@ -116,6 +120,8 @@ static void classInitNative(JNIEnv* env, jclass clazz) { "(Ljava/lang/String;I)V"); method_onDisplayPasskey = env->GetMethodID(clazz, "onDisplayPasskey", "(Ljava/lang/String;II)V"); + method_onRequestOobData = env->GetMethodID(clazz, "onRequestOobData", + "(Ljava/lang/String;I)V"); field_mNativeData = env->GetFieldID(clazz, "mNativeData", "I"); #endif @@ -305,6 +311,7 @@ static int register_agent(native_data_t *nat, { DBusMessage *msg, *reply; DBusError err; + bool oob = TRUE; if (!dbus_connection_register_object_path(nat->conn, agent_path, &agent_vtable, nat)) { @@ -326,6 +333,7 @@ static int register_agent(native_data_t *nat, } dbus_message_append_args(msg, DBUS_TYPE_OBJECT_PATH, &agent_path, DBUS_TYPE_STRING, &capabilities, + DBUS_TYPE_BOOLEAN, &oob, DBUS_TYPE_INVALID); dbus_error_init(&err); @@ -388,7 +396,7 @@ static void tearDownEventLoop(native_data_t *nat) { LOG_AND_FREE_DBUS_ERROR(&err); } dbus_bus_remove_match(nat->conn, - "type='signal',interface='org.bluez.audio.Device'", + "type='signal',interface='org.bluez.Device'", &err); if (dbus_error_is_set(&err)) { LOG_AND_FREE_DBUS_ERROR(&err); @@ -934,6 +942,43 @@ DBusHandlerResult agent_event_filter(DBusConnection *conn, } goto success; } else if (dbus_message_is_method_call(msg, + "org.bluez.Agent", "OutOfBandAvailable")) { + char *object_path; + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_OBJECT_PATH, &object_path, + DBUS_TYPE_INVALID)) { + LOGE("%s: Invalid arguments for OutOfBandData available() method", __FUNCTION__); + goto failure; + } + + LOGV("... object_path = %s", object_path); + + bool available = + env->CallBooleanMethod(nat->me, method_onAgentOutOfBandDataAvailable, + env->NewStringUTF(object_path)); + + + // reply + if (available) { + DBusMessage *reply = dbus_message_new_method_return(msg); + if (!reply) { + LOGE("%s: Cannot create message reply\n", __FUNCTION__); + goto failure; + } + dbus_connection_send(nat->conn, reply, NULL); + dbus_message_unref(reply); + } else { + DBusMessage *reply = dbus_message_new_error(msg, + "org.bluez.Error.DoesNotExist", "OutofBand data not available"); + if (!reply) { + LOGE("%s: Cannot create message reply\n", __FUNCTION__); + goto failure; + } + dbus_connection_send(nat->conn, reply, NULL); + dbus_message_unref(reply); + } + goto success; + } else if (dbus_message_is_method_call(msg, "org.bluez.Agent", "RequestPinCode")) { char *object_path; if (!dbus_message_get_args(msg, NULL, @@ -964,6 +1009,21 @@ DBusHandlerResult agent_event_filter(DBusConnection *conn, int(msg)); goto success; } else if (dbus_message_is_method_call(msg, + "org.bluez.Agent", "RequestOobData")) { + char *object_path; + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_OBJECT_PATH, &object_path, + DBUS_TYPE_INVALID)) { + LOGE("%s: Invalid arguments for RequestOobData() method", __FUNCTION__); + goto failure; + } + + dbus_message_ref(msg); // increment refcount because we pass to java + env->CallVoidMethod(nat->me, method_onRequestOobData, + env->NewStringUTF(object_path), + int(msg)); + goto success; + } else if (dbus_message_is_method_call(msg, "org.bluez.Agent", "DisplayPasskey")) { char *object_path; uint32_t passkey; @@ -1061,6 +1121,8 @@ void onCreatePairedDeviceResult(DBusMessage *msg, void *user, void *n) { DBusError err; dbus_error_init(&err); JNIEnv *env; + jstring addr; + nat->vm->GetEnv((void**)&env, nat->envVer); LOGV("... address = %s", address); @@ -1109,10 +1171,12 @@ void onCreatePairedDeviceResult(DBusMessage *msg, void *user, void *n) { } } + addr = env->NewStringUTF(address); env->CallVoidMethod(nat->me, method_onCreatePairedDeviceResult, - env->NewStringUTF(address), + addr, result); + env->DeleteLocalRef(addr); done: dbus_error_free(&err); free(user); @@ -1139,10 +1203,12 @@ void onCreateDeviceResult(DBusMessage *msg, void *user, void *n) { } LOG_AND_FREE_DBUS_ERROR(&err); } + jstring addr = env->NewStringUTF(address); env->CallVoidMethod(nat->me, method_onCreateDeviceResult, - env->NewStringUTF(address), + addr, result); + env->DeleteLocalRef(addr); free(user); } @@ -1163,10 +1229,12 @@ void onDiscoverServicesResult(DBusMessage *msg, void *user, void *n) { LOG_AND_FREE_DBUS_ERROR(&err); result = JNI_FALSE; } + jstring jPath = env->NewStringUTF(path); env->CallVoidMethod(nat->me, method_onDiscoverServicesResult, - env->NewStringUTF(path), + jPath, result); + env->DeleteLocalRef(jPath); free(user); } @@ -1194,10 +1262,12 @@ void onGetDeviceServiceChannelResult(DBusMessage *msg, void *user, void *n) { } done: + jstring addr = env->NewStringUTF(address); env->CallVoidMethod(nat->me, method_onGetDeviceServiceChannelResult, - env->NewStringUTF(address), + addr, channel); + env->DeleteLocalRef(addr); free(user); } #endif diff --git a/core/jni/android_server_BluetoothService.cpp b/core/jni/android_server_BluetoothService.cpp index 4420acab7c60..daa59a636314 100644 --- a/core/jni/android_server_BluetoothService.cpp +++ b/core/jni/android_server_BluetoothService.cpp @@ -288,6 +288,46 @@ done: #endif } +static jbyteArray readAdapterOutOfBandDataNative(JNIEnv *env, jobject object) { + LOGV(__FUNCTION__); +#ifdef HAVE_BLUETOOTH + native_data_t *nat = get_native_data(env, object); + DBusError err; + jbyte *hash, *randomizer; + jbyteArray byteArray = NULL; + int hash_len, r_len; + if (nat) { + DBusMessage *reply = dbus_func_args(env, nat->conn, + get_adapter_path(env, object), + DBUS_ADAPTER_IFACE, "ReadLocalOutOfBandData", + DBUS_TYPE_INVALID); + if (!reply) return NULL; + + dbus_error_init(&err); + if (dbus_message_get_args(reply, &err, + DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &hash, &hash_len, + DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &randomizer, &r_len, + DBUS_TYPE_INVALID)) { + if (hash_len == 16 && r_len == 16) { + byteArray = env->NewByteArray(32); + if (byteArray) { + env->SetByteArrayRegion(byteArray, 0, 16, hash); + env->SetByteArrayRegion(byteArray, 16, 16, randomizer); + } + } else { + LOGE("readAdapterOutOfBandDataNative: Hash len = %d, R len = %d", + hash_len, r_len); + } + } else { + LOG_AND_FREE_DBUS_ERROR(&err); + } + dbus_message_unref(reply); + return byteArray; + } +#endif + return NULL; +} + static jboolean createPairedDeviceNative(JNIEnv *env, jobject object, jstring address, jint timeout_ms) { LOGV(__FUNCTION__); @@ -324,6 +364,41 @@ static jboolean createPairedDeviceNative(JNIEnv *env, jobject object, return JNI_FALSE; } +static jboolean createPairedDeviceOutOfBandNative(JNIEnv *env, jobject object, + jstring address, jint timeout_ms) { + LOGV(__FUNCTION__); +#ifdef HAVE_BLUETOOTH + native_data_t *nat = get_native_data(env, object); + jobject eventLoop = env->GetObjectField(object, field_mEventLoop); + struct event_loop_native_data_t *eventLoopNat = + get_EventLoop_native_data(env, eventLoop); + + if (nat && eventLoopNat) { + const char *c_address = env->GetStringUTFChars(address, NULL); + LOGV("... address = %s", c_address); + char *context_address = (char *)calloc(BTADDR_SIZE, sizeof(char)); + const char *capabilities = "DisplayYesNo"; + const char *agent_path = "/android/bluetooth/remote_device_agent"; + + strlcpy(context_address, c_address, BTADDR_SIZE); // for callback + bool ret = dbus_func_args_async(env, nat->conn, (int)timeout_ms, + onCreatePairedDeviceResult, // callback + context_address, + eventLoopNat, + get_adapter_path(env, object), + DBUS_ADAPTER_IFACE, + "CreatePairedDeviceOutOfBand", + DBUS_TYPE_STRING, &c_address, + DBUS_TYPE_OBJECT_PATH, &agent_path, + DBUS_TYPE_STRING, &capabilities, + DBUS_TYPE_INVALID); + env->ReleaseStringUTFChars(address, c_address); + return ret ? JNI_TRUE : JNI_FALSE; + } +#endif + return JNI_FALSE; +} + static jint getDeviceServiceChannelNative(JNIEnv *env, jobject object, jstring path, jstring pattern, jint attr_id) { @@ -490,6 +565,40 @@ static jboolean setPasskeyNative(JNIEnv *env, jobject object, jstring address, return JNI_FALSE; } +static jboolean setRemoteOutOfBandDataNative(JNIEnv *env, jobject object, jstring address, + jbyteArray hash, jbyteArray randomizer, int nativeData) { +#ifdef HAVE_BLUETOOTH + LOGV(__FUNCTION__); + native_data_t *nat = get_native_data(env, object); + if (nat) { + DBusMessage *msg = (DBusMessage *)nativeData; + DBusMessage *reply = dbus_message_new_method_return(msg); + jbyte *h_ptr = env->GetByteArrayElements(hash, NULL); + jbyte *r_ptr = env->GetByteArrayElements(randomizer, NULL); + if (!reply) { + LOGE("%s: Cannot create message reply to return remote OOB data to " + "D-Bus\n", __FUNCTION__); + dbus_message_unref(msg); + return JNI_FALSE; + } + + dbus_message_append_args(reply, + DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &h_ptr, 16, + DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &r_ptr, 16, + DBUS_TYPE_INVALID); + + env->ReleaseByteArrayElements(hash, h_ptr, 0); + env->ReleaseByteArrayElements(randomizer, r_ptr, 0); + + dbus_connection_send(nat->conn, reply, NULL); + dbus_message_unref(msg); + dbus_message_unref(reply); + return JNI_TRUE; + } +#endif + return JNI_FALSE; +} + static jboolean setPinNative(JNIEnv *env, jobject object, jstring address, jstring pin, int nativeData) { #ifdef HAVE_BLUETOOTH @@ -907,7 +1016,10 @@ static JNINativeMethod sMethods[] = { {"startDiscoveryNative", "()Z", (void*)startDiscoveryNative}, {"stopDiscoveryNative", "()Z", (void *)stopDiscoveryNative}, + {"readAdapterOutOfBandDataNative", "()[B", (void *)readAdapterOutOfBandDataNative}, {"createPairedDeviceNative", "(Ljava/lang/String;I)Z", (void *)createPairedDeviceNative}, + {"createPairedDeviceOutOfBandNative", "(Ljava/lang/String;I)Z", + (void *)createPairedDeviceOutOfBandNative}, {"cancelDeviceCreationNative", "(Ljava/lang/String;)Z", (void *)cancelDeviceCreationNative}, {"removeDeviceNative", "(Ljava/lang/String;)Z", (void *)removeDeviceNative}, {"getDeviceServiceChannelNative", "(Ljava/lang/String;Ljava/lang/String;I)I", @@ -916,6 +1028,7 @@ static JNINativeMethod sMethods[] = { {"setPairingConfirmationNative", "(Ljava/lang/String;ZI)Z", (void *)setPairingConfirmationNative}, {"setPasskeyNative", "(Ljava/lang/String;II)Z", (void *)setPasskeyNative}, + {"setRemoteOutOfBandDataNative", "(Ljava/lang/String;[B[BI)Z", (void *)setRemoteOutOfBandDataNative}, {"setPinNative", "(Ljava/lang/String;Ljava/lang/String;I)Z", (void *)setPinNative}, {"cancelPairingUserInputNative", "(Ljava/lang/String;I)Z", (void *)cancelPairingUserInputNative}, diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp index a2b7cc4d1104..f3b935733b28 100644 --- a/core/jni/android_util_AssetManager.cpp +++ b/core/jni/android_util_AssetManager.cpp @@ -66,16 +66,6 @@ jclass g_stringClass = NULL; // ---------------------------------------------------------------------------- -static void doThrow(JNIEnv* env, const char* exc, const char* msg = NULL) -{ - jclass npeClazz; - - npeClazz = env->FindClass(exc); - LOG_FATAL_IF(npeClazz == NULL, "Unable to find class %s", exc); - - env->ThrowNew(npeClazz, msg); -} - enum { STYLE_NUM_ENTRIES = 6, STYLE_TYPE = 0, @@ -131,14 +121,14 @@ static jint android_content_AssetManager_openAsset(JNIEnv* env, jobject clazz, LOGV("openAsset in %p (Java object %p)\n", am, clazz); - if (fileName == NULL || am == NULL) { - doThrow(env, "java/lang/NullPointerException"); + if (fileName == NULL) { + jniThrowException(env, "java/lang/NullPointerException", "fileName"); return -1; } if (mode != Asset::ACCESS_UNKNOWN && mode != Asset::ACCESS_RANDOM && mode != Asset::ACCESS_STREAMING && mode != Asset::ACCESS_BUFFER) { - doThrow(env, "java/lang/IllegalArgumentException"); + jniThrowException(env, "java/lang/IllegalArgumentException", "Bad access mode"); return -1; } @@ -146,7 +136,7 @@ static jint android_content_AssetManager_openAsset(JNIEnv* env, jobject clazz, Asset* a = am->open(fileName8, (Asset::AccessMode)mode); if (a == NULL) { - doThrow(env, "java/io/FileNotFoundException", fileName8); + jniThrowException(env, "java/io/FileNotFoundException", fileName8); env->ReleaseStringUTFChars(fileName, fileName8); return -1; } @@ -164,7 +154,7 @@ static jobject returnParcelFileDescriptor(JNIEnv* env, Asset* a, jlongArray outO delete a; if (fd < 0) { - doThrow(env, "java/io/FileNotFoundException", + jniThrowException(env, "java/io/FileNotFoundException", "This file can not be opened as a file descriptor; it is probably compressed"); return NULL; } @@ -199,8 +189,8 @@ static jobject android_content_AssetManager_openAssetFd(JNIEnv* env, jobject cla LOGV("openAssetFd in %p (Java object %p)\n", am, clazz); - if (fileName == NULL || am == NULL) { - doThrow(env, "java/lang/NullPointerException"); + if (fileName == NULL) { + jniThrowException(env, "java/lang/NullPointerException", "fileName"); return NULL; } @@ -208,7 +198,7 @@ static jobject android_content_AssetManager_openAssetFd(JNIEnv* env, jobject cla Asset* a = am->open(fileName8, Asset::ACCESS_RANDOM); if (a == NULL) { - doThrow(env, "java/io/FileNotFoundException", fileName8); + jniThrowException(env, "java/io/FileNotFoundException", fileName8); env->ReleaseStringUTFChars(fileName, fileName8); return NULL; } @@ -231,14 +221,14 @@ static jint android_content_AssetManager_openNonAssetNative(JNIEnv* env, jobject LOGV("openNonAssetNative in %p (Java object %p)\n", am, clazz); - if (fileName == NULL || am == NULL) { - doThrow(env, "java/lang/NullPointerException"); + if (fileName == NULL) { + jniThrowException(env, "java/lang/NullPointerException", "fileName"); return -1; } if (mode != Asset::ACCESS_UNKNOWN && mode != Asset::ACCESS_RANDOM && mode != Asset::ACCESS_STREAMING && mode != Asset::ACCESS_BUFFER) { - doThrow(env, "java/lang/IllegalArgumentException"); + jniThrowException(env, "java/lang/IllegalArgumentException", "Bad access mode"); return -1; } @@ -248,7 +238,7 @@ static jint android_content_AssetManager_openNonAssetNative(JNIEnv* env, jobject : am->openNonAsset(fileName8, (Asset::AccessMode)mode); if (a == NULL) { - doThrow(env, "java/io/FileNotFoundException", fileName8); + jniThrowException(env, "java/io/FileNotFoundException", fileName8); env->ReleaseStringUTFChars(fileName, fileName8); return -1; } @@ -271,8 +261,8 @@ static jobject android_content_AssetManager_openNonAssetFdNative(JNIEnv* env, jo LOGV("openNonAssetFd in %p (Java object %p)\n", am, clazz); - if (fileName == NULL || am == NULL) { - doThrow(env, "java/lang/NullPointerException"); + if (fileName == NULL ) { + jniThrowException(env, "java/lang/NullPointerException", "fileName"); return NULL; } @@ -282,7 +272,7 @@ static jobject android_content_AssetManager_openNonAssetFdNative(JNIEnv* env, jo : am->openNonAsset(fileName8, Asset::ACCESS_RANDOM); if (a == NULL) { - doThrow(env, "java/io/FileNotFoundException", fileName8); + jniThrowException(env, "java/io/FileNotFoundException", fileName8); env->ReleaseStringUTFChars(fileName, fileName8); return NULL; } @@ -301,8 +291,8 @@ static jobjectArray android_content_AssetManager_list(JNIEnv* env, jobject clazz return NULL; } - if (fileName == NULL || am == NULL) { - doThrow(env, "java/lang/NullPointerException"); + if (fileName == NULL) { + jniThrowException(env, "java/lang/NullPointerException", "fileName"); return NULL; } @@ -313,7 +303,7 @@ static jobjectArray android_content_AssetManager_list(JNIEnv* env, jobject clazz env->ReleaseStringUTFChars(fileName, fileName8); if (dir == NULL) { - doThrow(env, "java/io/FileNotFoundException", fileName8); + jniThrowException(env, "java/io/FileNotFoundException", fileName8); return NULL; } @@ -329,7 +319,6 @@ static jobjectArray android_content_AssetManager_list(JNIEnv* env, jobject clazz jobjectArray array = env->NewObjectArray(dir->getFileCount(), cls, NULL); if (array == NULL) { - doThrow(env, "java/lang/OutOfMemoryError"); delete dir; return NULL; } @@ -338,11 +327,11 @@ static jobjectArray android_content_AssetManager_list(JNIEnv* env, jobject clazz const String8& name = dir->getFileName(i); jstring str = env->NewStringUTF(name.string()); if (str == NULL) { - doThrow(env, "java/lang/OutOfMemoryError"); delete dir; return NULL; } env->SetObjectArrayElement(array, i, str); + env->DeleteLocalRef(str); } delete dir; @@ -358,7 +347,7 @@ static void android_content_AssetManager_destroyAsset(JNIEnv* env, jobject clazz //printf("Destroying Asset Stream: %p\n", a); if (a == NULL) { - doThrow(env, "java/lang/NullPointerException"); + jniThrowException(env, "java/lang/NullPointerException", "asset"); return; } @@ -371,7 +360,7 @@ static jint android_content_AssetManager_readAssetChar(JNIEnv* env, jobject claz Asset* a = (Asset*)asset; if (a == NULL) { - doThrow(env, "java/lang/NullPointerException"); + jniThrowException(env, "java/lang/NullPointerException", "asset"); return -1; } @@ -387,7 +376,7 @@ static jint android_content_AssetManager_readAsset(JNIEnv* env, jobject clazz, Asset* a = (Asset*)asset; if (a == NULL || bArray == NULL) { - doThrow(env, "java/lang/NullPointerException"); + jniThrowException(env, "java/lang/NullPointerException", "asset"); return -1; } @@ -397,7 +386,7 @@ static jint android_content_AssetManager_readAsset(JNIEnv* env, jobject clazz, jsize bLen = env->GetArrayLength(bArray); if (off < 0 || off >= bLen || len < 0 || len > bLen || (off+len) > bLen) { - doThrow(env, "java/lang/IndexOutOfBoundsException"); + jniThrowException(env, "java/lang/IndexOutOfBoundsException", ""); return -1; } @@ -408,7 +397,7 @@ static jint android_content_AssetManager_readAsset(JNIEnv* env, jobject clazz, if (res > 0) return res; if (res < 0) { - doThrow(env, "java/io/IOException"); + jniThrowException(env, "java/io/IOException", ""); } return -1; } @@ -420,7 +409,7 @@ static jlong android_content_AssetManager_seekAsset(JNIEnv* env, jobject clazz, Asset* a = (Asset*)asset; if (a == NULL) { - doThrow(env, "java/lang/NullPointerException"); + jniThrowException(env, "java/lang/NullPointerException", "asset"); return -1; } @@ -434,7 +423,7 @@ static jlong android_content_AssetManager_getAssetLength(JNIEnv* env, jobject cl Asset* a = (Asset*)asset; if (a == NULL) { - doThrow(env, "java/lang/NullPointerException"); + jniThrowException(env, "java/lang/NullPointerException", "asset"); return -1; } @@ -447,7 +436,7 @@ static jlong android_content_AssetManager_getAssetRemainingLength(JNIEnv* env, j Asset* a = (Asset*)asset; if (a == NULL) { - doThrow(env, "java/lang/NullPointerException"); + jniThrowException(env, "java/lang/NullPointerException", "asset"); return -1; } @@ -458,7 +447,7 @@ static jint android_content_AssetManager_addAssetPath(JNIEnv* env, jobject clazz jstring path) { if (path == NULL) { - doThrow(env, "java/lang/NullPointerException"); + jniThrowException(env, "java/lang/NullPointerException", "path"); return JNI_FALSE; } @@ -490,7 +479,7 @@ static void android_content_AssetManager_setLocale(JNIEnv* env, jobject clazz, jstring locale) { if (locale == NULL) { - doThrow(env, "java/lang/NullPointerException"); + jniThrowException(env, "java/lang/NullPointerException", "locale"); return; } @@ -525,7 +514,12 @@ static jobjectArray android_content_AssetManager_getLocales(JNIEnv* env, jobject } for (int i=0; i<N; i++) { - env->SetObjectArrayElement(result, i, env->NewStringUTF(locales[i].string())); + jstring str = env->NewStringUTF(locales[i].string()); + if (str == NULL) { + return NULL; + } + env->SetObjectArrayElement(result, i, str); + env->DeleteLocalRef(str); } return result; @@ -576,7 +570,7 @@ static jint android_content_AssetManager_getResourceIdentifier(JNIEnv* env, jobj jstring defPackage) { if (name == NULL) { - doThrow(env, "java/lang/NullPointerException"); + jniThrowException(env, "java/lang/NullPointerException", "name"); return 0; } @@ -814,14 +808,10 @@ static jstring android_content_AssetManager_getCookieName(JNIEnv* env, jobject c } String8 name(am->getAssetPath((void*)cookie)); if (name.length() == 0) { - doThrow(env, "java/lang/IndexOutOfBoundsException"); + jniThrowException(env, "java/lang/IndexOutOfBoundsException", "Empty cookie name"); return NULL; } jstring str = env->NewStringUTF(name.string()); - if (str == NULL) { - doThrow(env, "java/lang/OutOfMemoryError"); - return NULL; - } return str; } @@ -889,7 +879,7 @@ static void android_content_AssetManager_dumpTheme(JNIEnv* env, jobject clazz, const ResTable& res(theme->getResTable()); if (tag == NULL) { - doThrow(env, "java/lang/NullPointerException"); + jniThrowException(env, "java/lang/NullPointerException", "tag"); return; } @@ -917,8 +907,16 @@ static jboolean android_content_AssetManager_applyStyle(JNIEnv* env, jobject cla jintArray outValues, jintArray outIndices) { - if (themeToken == 0 || attrs == NULL || outValues == NULL) { - doThrow(env, "java/lang/NullPointerException"); + if (themeToken == 0) { + jniThrowException(env, "java/lang/NullPointerException", "theme token"); + return JNI_FALSE; + } + if (attrs == NULL) { + jniThrowException(env, "java/lang/NullPointerException", "attrs"); + return JNI_FALSE; + } + if (outValues == NULL) { + jniThrowException(env, "java/lang/NullPointerException", "out values"); return JNI_FALSE; } @@ -934,13 +932,13 @@ static jboolean android_content_AssetManager_applyStyle(JNIEnv* env, jobject cla const jsize NI = env->GetArrayLength(attrs); const jsize NV = env->GetArrayLength(outValues); if (NV < (NI*STYLE_NUM_ENTRIES)) { - doThrow(env, "java/lang/IndexOutOfBoundsException"); + jniThrowException(env, "java/lang/IndexOutOfBoundsException", "out values too small"); return JNI_FALSE; } jint* src = (jint*)env->GetPrimitiveArrayCritical(attrs, 0); if (src == NULL) { - doThrow(env, "java/lang/OutOfMemoryError"); + jniThrowException(env, "java/lang/OutOfMemoryError", ""); return JNI_FALSE; } @@ -948,7 +946,7 @@ static jboolean android_content_AssetManager_applyStyle(JNIEnv* env, jobject cla jint* dest = baseDest; if (dest == NULL) { env->ReleasePrimitiveArrayCritical(attrs, src, 0); - doThrow(env, "java/lang/OutOfMemoryError"); + jniThrowException(env, "java/lang/OutOfMemoryError", ""); return JNI_FALSE; } @@ -1152,8 +1150,16 @@ static jboolean android_content_AssetManager_retrieveAttributes(JNIEnv* env, job jintArray outValues, jintArray outIndices) { - if (xmlParserToken == 0 || attrs == NULL || outValues == NULL) { - doThrow(env, "java/lang/NullPointerException"); + if (xmlParserToken == 0) { + jniThrowException(env, "java/lang/NullPointerException", "xmlParserToken"); + return JNI_FALSE; + } + if (attrs == NULL) { + jniThrowException(env, "java/lang/NullPointerException", "attrs"); + return JNI_FALSE; + } + if (outValues == NULL) { + jniThrowException(env, "java/lang/NullPointerException", "out values"); return JNI_FALSE; } @@ -1169,13 +1175,13 @@ static jboolean android_content_AssetManager_retrieveAttributes(JNIEnv* env, job const jsize NI = env->GetArrayLength(attrs); const jsize NV = env->GetArrayLength(outValues); if (NV < (NI*STYLE_NUM_ENTRIES)) { - doThrow(env, "java/lang/IndexOutOfBoundsException"); + jniThrowException(env, "java/lang/IndexOutOfBoundsException", "out values too small"); return JNI_FALSE; } jint* src = (jint*)env->GetPrimitiveArrayCritical(attrs, 0); if (src == NULL) { - doThrow(env, "java/lang/OutOfMemoryError"); + jniThrowException(env, "java/lang/OutOfMemoryError", ""); return JNI_FALSE; } @@ -1183,7 +1189,7 @@ static jboolean android_content_AssetManager_retrieveAttributes(JNIEnv* env, job jint* dest = baseDest; if (dest == NULL) { env->ReleasePrimitiveArrayCritical(attrs, src, 0); - doThrow(env, "java/lang/OutOfMemoryError"); + jniThrowException(env, "java/lang/OutOfMemoryError", ""); return JNI_FALSE; } @@ -1306,7 +1312,7 @@ static jint android_content_AssetManager_retrieveArray(JNIEnv* env, jobject claz jintArray outValues) { if (outValues == NULL) { - doThrow(env, "java/lang/NullPointerException"); + jniThrowException(env, "java/lang/NullPointerException", "out values"); return JNI_FALSE; } @@ -1324,7 +1330,7 @@ static jint android_content_AssetManager_retrieveArray(JNIEnv* env, jobject claz jint* baseDest = (jint*)env->GetPrimitiveArrayCritical(outValues, 0); jint* dest = baseDest; if (dest == NULL) { - doThrow(env, "java/lang/OutOfMemoryError"); + jniThrowException(env, "java/lang/OutOfMemoryError", ""); return JNI_FALSE; } @@ -1399,8 +1405,8 @@ static jint android_content_AssetManager_openXmlAssetNative(JNIEnv* env, jobject LOGV("openXmlAsset in %p (Java object %p)\n", am, clazz); - if (fileName == NULL || am == NULL) { - doThrow(env, "java/lang/NullPointerException"); + if (fileName == NULL) { + jniThrowException(env, "java/lang/NullPointerException", "fileName"); return 0; } @@ -1410,7 +1416,7 @@ static jint android_content_AssetManager_openXmlAssetNative(JNIEnv* env, jobject : am->openNonAsset(fileName8, Asset::ACCESS_BUFFER); if (a == NULL) { - doThrow(env, "java/io/FileNotFoundException", fileName8); + jniThrowException(env, "java/io/FileNotFoundException", fileName8); env->ReleaseStringUTFChars(fileName, fileName8); return 0; } @@ -1422,7 +1428,7 @@ static jint android_content_AssetManager_openXmlAssetNative(JNIEnv* env, jobject delete a; if (err != NO_ERROR) { - doThrow(env, "java/io/FileNotFoundException", "Corrupt XML binary file"); + jniThrowException(env, "java/io/FileNotFoundException", "Corrupt XML binary file"); return 0; } @@ -1446,7 +1452,7 @@ static jintArray android_content_AssetManager_getArrayStringInfo(JNIEnv* env, jo jintArray array = env->NewIntArray(N * 2); if (array == NULL) { - doThrow(env, "java/lang/OutOfMemoryError"); + jniThrowException(env, "java/lang/OutOfMemoryError", ""); res.unlockBag(startOfBag); return NULL; } @@ -1504,8 +1510,7 @@ static jobjectArray android_content_AssetManager_getArrayStringResource(JNIEnv* } jobjectArray array = env->NewObjectArray(N, cls, NULL); - if (array == NULL) { - doThrow(env, "java/lang/OutOfMemoryError"); + if (env->ExceptionCheck()) { res.unlockBag(startOfBag); return NULL; } @@ -1533,15 +1538,22 @@ static jobjectArray android_content_AssetManager_getArrayStringResource(JNIEnv* } else { const char16_t* str16 = pool->stringAt(value.data, &strLen); str = env->NewString(str16, strLen); - if (str == NULL) { - doThrow(env, "java/lang/OutOfMemoryError"); - res.unlockBag(startOfBag); - return NULL; - } } - } - env->SetObjectArrayElement(array, i, str); + // If one of our NewString{UTF} calls failed due to memory, an + // exception will be pending. + if (env->ExceptionCheck()) { + res.unlockBag(startOfBag); + return NULL; + } + + env->SetObjectArrayElement(array, i, str); + + // str is not NULL at that point, otherwise ExceptionCheck would have been true. + // If we have a large amount of strings in our array, we might + // overflow the local reference table of the VM. + env->DeleteLocalRef(str); + } } res.unlockBag(startOfBag); return array; @@ -1564,7 +1576,7 @@ static jintArray android_content_AssetManager_getArrayIntResource(JNIEnv* env, j jintArray array = env->NewIntArray(N); if (array == NULL) { - doThrow(env, "java/lang/OutOfMemoryError"); + jniThrowException(env, "java/lang/OutOfMemoryError", ""); res.unlockBag(startOfBag); return NULL; } @@ -1596,7 +1608,7 @@ static void android_content_AssetManager_init(JNIEnv* env, jobject clazz) { AssetManager* am = new AssetManager(); if (am == NULL) { - doThrow(env, "java/lang/OutOfMemoryError"); + jniThrowException(env, "java/lang/OutOfMemoryError", ""); return; } @@ -1630,11 +1642,6 @@ static jobject android_content_AssetManager_getAssetAllocations(JNIEnv* env, job } jstring str = env->NewStringUTF(alloc.string()); - if (str == NULL) { - doThrow(env, "java/lang/OutOfMemoryError"); - return NULL; - } - return str; } diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp index d26cd28f962d..7a53874c6983 100644 --- a/core/jni/android_util_Binder.cpp +++ b/core/jni/android_util_Binder.cpp @@ -134,6 +134,12 @@ static struct parcel_file_descriptor_offsets_t jmethodID mConstructor; } gParcelFileDescriptorOffsets; +static struct strict_mode_callback_offsets_t +{ + jclass mClass; + jmethodID mCallback; +} gStrictModeCallbackOffsets; + // **************************************************************************** // **************************************************************************** // **************************************************************************** @@ -214,6 +220,15 @@ bail: env->DeleteLocalRef(msgstr); } +static void set_dalvik_blockguard_policy(JNIEnv* env, jint strict_policy) +{ + // Call back into android.os.StrictMode#onBinderStrictModePolicyChange + // to sync our state back to it. See the comments in StrictMode.java. + env->CallStaticVoidMethod(gStrictModeCallbackOffsets.mClass, + gStrictModeCallbackOffsets.mCallback, + strict_policy); +} + class JavaBBinderHolder; class JavaBBinder : public BBinder @@ -253,12 +268,29 @@ protected: LOGV("onTransact() on %p calling object %p in env %p vm %p\n", this, mObject, env, mVM); + IPCThreadState* thread_state = IPCThreadState::self(); + const int strict_policy_before = thread_state->getStrictModePolicy(); + thread_state->setLastTransactionBinderFlags(flags); + //printf("Transact from %p to Java code sending: ", this); //data.print(); //printf("\n"); jboolean res = env->CallBooleanMethod(mObject, gBinderOffsets.mExecTransact, code, (int32_t)&data, (int32_t)reply, flags); jthrowable excep = env->ExceptionOccurred(); + + // Restore the Java binder thread's state if it changed while + // processing a call (as it would if the Parcel's header had a + // new policy mask and Parcel.enforceInterface() changed + // it...) + const int strict_policy_after = thread_state->getStrictModePolicy(); + if (strict_policy_after != strict_policy_before) { + // Our thread-local... + thread_state->setStrictModePolicy(strict_policy_before); + // And the Java-level thread-local... + set_dalvik_blockguard_policy(env, strict_policy_before); + } + if (excep) { report_exception(env, excep, "*** Uncaught remote exception! " @@ -574,6 +606,16 @@ static void android_os_Binder_restoreCallingIdentity(JNIEnv* env, jobject clazz, IPCThreadState::self()->restoreCallingIdentity(token); } +static void android_os_Binder_setThreadStrictModePolicy(JNIEnv* env, jobject clazz, jint policyMask) +{ + IPCThreadState::self()->setStrictModePolicy(policyMask); +} + +static jint android_os_Binder_getThreadStrictModePolicy(JNIEnv* env, jobject clazz) +{ + return IPCThreadState::self()->getStrictModePolicy(); +} + static void android_os_Binder_flushPendingCommands(JNIEnv* env, jobject clazz) { IPCThreadState::self()->flushCommands(); @@ -618,6 +660,8 @@ static const JNINativeMethod gBinderMethods[] = { { "getCallingUid", "()I", (void*)android_os_Binder_getCallingUid }, { "clearCallingIdentity", "()J", (void*)android_os_Binder_clearCallingIdentity }, { "restoreCallingIdentity", "(J)V", (void*)android_os_Binder_restoreCallingIdentity }, + { "setThreadStrictModePolicy", "(I)V", (void*)android_os_Binder_setThreadStrictModePolicy }, + { "getThreadStrictModePolicy", "()I", (void*)android_os_Binder_getThreadStrictModePolicy }, { "flushPendingCommands", "()V", (void*)android_os_Binder_flushPendingCommands }, { "init", "()V", (void*)android_os_Binder_init }, { "destroy", "()V", (void*)android_os_Binder_destroy } @@ -1523,9 +1567,24 @@ static void android_os_Parcel_enforceInterface(JNIEnv* env, jobject clazz, jstri if (parcel != NULL) { const jchar* str = env->GetStringCritical(name, 0); if (str) { - bool isValid = parcel->enforceInterface(String16(str, env->GetStringLength(name))); + IPCThreadState* threadState = IPCThreadState::self(); + const int32_t oldPolicy = threadState->getStrictModePolicy(); + const bool isValid = parcel->enforceInterface( + String16(str, env->GetStringLength(name)), + threadState); env->ReleaseStringCritical(name, str); if (isValid) { + const int32_t newPolicy = threadState->getStrictModePolicy(); + if (oldPolicy != newPolicy) { + // Need to keep the Java-level thread-local strict + // mode policy in sync for the libcore + // enforcements, which involves an upcall back + // into Java. (We can't modify the + // Parcel.enforceInterface signature, as it's + // pseudo-public, and used via AIDL + // auto-generation...) + set_dalvik_blockguard_policy(env, newPolicy); + } return; // everything was correct -> return silently } } @@ -1611,6 +1670,14 @@ static int int_register_android_os_Parcel(JNIEnv* env) gParcelOffsets.mOwnObject = env->GetFieldID(clazz, "mOwnObject", "I"); + clazz = env->FindClass("android/os/StrictMode"); + LOG_FATAL_IF(clazz == NULL, "Unable to find class android.os.StrictMode"); + gStrictModeCallbackOffsets.mClass = (jclass) env->NewGlobalRef(clazz); + gStrictModeCallbackOffsets.mCallback = env->GetStaticMethodID( + clazz, "onBinderStrictModePolicyChange", "(I)V"); + LOG_FATAL_IF(gStrictModeCallbackOffsets.mCallback == NULL, + "Unable to find strict mode callback."); + return AndroidRuntime::registerNativeMethods( env, kParcelPathName, gParcelMethods, NELEM(gParcelMethods)); diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp index 68be741b8372..7dfb71693d9c 100644 --- a/core/jni/android_util_Process.cpp +++ b/core/jni/android_util_Process.cpp @@ -52,9 +52,15 @@ pid_t gettid() { return syscall(__NR_gettid);} #endif #define POLICY_DEBUG 0 +#define GUARD_THREAD_PRIORITY 0 using namespace android; +#if GUARD_THREAD_PRIORITY +Mutex gKeyCreateMutex; +static pthread_key_t gBgKey = -1; +#endif + static void signalExceptionForPriorityError(JNIEnv* env, jobject obj, int err) { switch (err) { @@ -255,8 +261,7 @@ void android_os_Process_setProcessGroup(JNIEnv* env, jobject clazz, int pid, jin continue; } - if (set_sched_policy(t_pid, (grp == ANDROID_TGROUP_BG_NONINTERACT) ? - SP_BACKGROUND : SP_FOREGROUND)) { + if (androidSetThreadSchedulingGroup(t_pid, grp) != NO_ERROR) { signalExceptionForGroupError(env, clazz, errno); break; } @@ -264,9 +269,41 @@ void android_os_Process_setProcessGroup(JNIEnv* env, jobject clazz, int pid, jin closedir(d); } +static void android_os_Process_setCanSelfBackground(JNIEnv* env, jobject clazz, jboolean bgOk) { + // Establishes the calling thread as illegal to put into the background. + // Typically used only for the system process's main looper. +#if GUARD_THREAD_PRIORITY + LOGV("Process.setCanSelfBackground(%d) : tid=%d", bgOk, androidGetTid()); + { + Mutex::Autolock _l(gKeyCreateMutex); + if (gBgKey == -1) { + pthread_key_create(&gBgKey, NULL); + } + } + + // inverted: not-okay, we set a sentinel value + pthread_setspecific(gBgKey, (void*)(bgOk ? 0 : 0xbaad)); +#endif +} + void android_os_Process_setThreadPriority(JNIEnv* env, jobject clazz, jint pid, jint pri) { +#if GUARD_THREAD_PRIORITY + // if we're putting the current thread into the background, check the TLS + // to make sure this thread isn't guarded. If it is, raise an exception. + if (pri >= ANDROID_PRIORITY_BACKGROUND) { + if (pid == androidGetTid()) { + void* bgOk = pthread_getspecific(gBgKey); + if (bgOk == ((void*)0xbaad)) { + LOGE("Thread marked fg-only put self in background!"); + jniThrowException(env, "java/lang/SecurityException", "May not put this thread into background"); + return; + } + } + } +#endif + int rc = androidSetThreadPriority(pid, pri); if (rc != 0) { if (rc == INVALID_OPERATION) { @@ -852,6 +889,7 @@ static const JNINativeMethod methods[] = { {"getUidForName", "(Ljava/lang/String;)I", (void*)android_os_Process_getUidForName}, {"getGidForName", "(Ljava/lang/String;)I", (void*)android_os_Process_getGidForName}, {"setThreadPriority", "(II)V", (void*)android_os_Process_setThreadPriority}, + {"setCanSelfBackground", "(Z)V", (void*)android_os_Process_setCanSelfBackground}, {"setThreadPriority", "(I)V", (void*)android_os_Process_setCallingThreadPriority}, {"getThreadPriority", "(I)I", (void*)android_os_Process_getThreadPriority}, {"setThreadGroup", "(II)V", (void*)android_os_Process_setThreadGroup}, diff --git a/core/jni/android_view_InputChannel.cpp b/core/jni/android_view_InputChannel.cpp new file mode 100644 index 000000000000..4a4393ae2dd1 --- /dev/null +++ b/core/jni/android_view_InputChannel.cpp @@ -0,0 +1,288 @@ +/* + * 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. + */ + +#define LOG_TAG "InputChannel-JNI" + +#include "JNIHelp.h" + +#include <android_runtime/AndroidRuntime.h> +#include <binder/Parcel.h> +#include <utils/Log.h> +#include <ui/InputTransport.h> +#include "android_view_InputChannel.h" +#include "android_util_Binder.h" + +namespace android { + +// ---------------------------------------------------------------------------- + +static struct { + jclass clazz; + + jfieldID mPtr; // native object attached to the DVM InputChannel + jmethodID ctor; +} gInputChannelClassInfo; + +// ---------------------------------------------------------------------------- + +class NativeInputChannel { +public: + NativeInputChannel(const sp<InputChannel>& inputChannel); + ~NativeInputChannel(); + + inline sp<InputChannel> getInputChannel() { return mInputChannel; } + + void setDisposeCallback(InputChannelObjDisposeCallback callback, void* data); + void invokeAndRemoveDisposeCallback(JNIEnv* env, jobject obj); + +private: + sp<InputChannel> mInputChannel; + InputChannelObjDisposeCallback mDisposeCallback; + void* mDisposeData; +}; + +// ---------------------------------------------------------------------------- + +NativeInputChannel::NativeInputChannel(const sp<InputChannel>& inputChannel) : + mInputChannel(inputChannel), mDisposeCallback(NULL) { +} + +NativeInputChannel::~NativeInputChannel() { +} + +void NativeInputChannel::setDisposeCallback(InputChannelObjDisposeCallback callback, void* data) { + mDisposeCallback = callback; + mDisposeData = data; +} + +void NativeInputChannel::invokeAndRemoveDisposeCallback(JNIEnv* env, jobject obj) { + if (mDisposeCallback) { + mDisposeCallback(env, obj, mInputChannel, mDisposeData); + mDisposeCallback = NULL; + mDisposeData = NULL; + } +} + +// ---------------------------------------------------------------------------- + +static NativeInputChannel* android_view_InputChannel_getNativeInputChannel(JNIEnv* env, + jobject inputChannelObj) { + jint intPtr = env->GetIntField(inputChannelObj, gInputChannelClassInfo.mPtr); + return reinterpret_cast<NativeInputChannel*>(intPtr); +} + +static void android_view_InputChannel_setNativeInputChannel(JNIEnv* env, jobject inputChannelObj, + NativeInputChannel* nativeInputChannel) { + env->SetIntField(inputChannelObj, gInputChannelClassInfo.mPtr, + reinterpret_cast<jint>(nativeInputChannel)); +} + +sp<InputChannel> android_view_InputChannel_getInputChannel(JNIEnv* env, jobject inputChannelObj) { + NativeInputChannel* nativeInputChannel = + android_view_InputChannel_getNativeInputChannel(env, inputChannelObj); + return nativeInputChannel != NULL ? nativeInputChannel->getInputChannel() : NULL; +} + +void android_view_InputChannel_setDisposeCallback(JNIEnv* env, jobject inputChannelObj, + InputChannelObjDisposeCallback callback, void* data) { + NativeInputChannel* nativeInputChannel = + android_view_InputChannel_getNativeInputChannel(env, inputChannelObj); + if (nativeInputChannel == NULL) { + LOGW("Cannot set dispose callback because input channel object has not been initialized."); + } else { + nativeInputChannel->setDisposeCallback(callback, data); + } +} + +static jobject android_view_InputChannel_createInputChannel(JNIEnv* env, + NativeInputChannel* nativeInputChannel) { + jobject inputChannelObj = env->NewObject(gInputChannelClassInfo.clazz, + gInputChannelClassInfo.ctor); + android_view_InputChannel_setNativeInputChannel(env, inputChannelObj, nativeInputChannel); + return inputChannelObj; +} + +static jobjectArray android_view_InputChannel_nativeOpenInputChannelPair(JNIEnv* env, + jclass clazz, jstring nameObj) { + const char* nameChars = env->GetStringUTFChars(nameObj, NULL); + String8 name(nameChars); + env->ReleaseStringUTFChars(nameObj, nameChars); + + sp<InputChannel> serverChannel; + sp<InputChannel> clientChannel; + status_t result = InputChannel::openInputChannelPair(name, serverChannel, clientChannel); + + if (result) { + LOGE("Could not open input channel pair. status=%d", result); + jniThrowRuntimeException(env, "Could not open input channel pair."); + return NULL; + } + + // TODO more robust error checking + jobject serverChannelObj = android_view_InputChannel_createInputChannel(env, + new NativeInputChannel(serverChannel)); + jobject clientChannelObj = android_view_InputChannel_createInputChannel(env, + new NativeInputChannel(clientChannel)); + + jobjectArray channelPair = env->NewObjectArray(2, gInputChannelClassInfo.clazz, NULL); + env->SetObjectArrayElement(channelPair, 0, serverChannelObj); + env->SetObjectArrayElement(channelPair, 1, clientChannelObj); + return channelPair; +} + +static void android_view_InputChannel_nativeDispose(JNIEnv* env, jobject obj, jboolean finalized) { + NativeInputChannel* nativeInputChannel = + android_view_InputChannel_getNativeInputChannel(env, obj); + if (nativeInputChannel) { + if (finalized) { + LOGW("Input channel object '%s' was finalized without being disposed!", + nativeInputChannel->getInputChannel()->getName().string()); + } + + nativeInputChannel->invokeAndRemoveDisposeCallback(env, obj); + + android_view_InputChannel_setNativeInputChannel(env, obj, NULL); + delete nativeInputChannel; + } +} + +static void android_view_InputChannel_nativeTransferTo(JNIEnv* env, jobject obj, + jobject otherObj) { + if (android_view_InputChannel_getInputChannel(env, otherObj) != NULL) { + jniThrowException(env, "java/lang/IllegalStateException", + "Other object already has a native input channel."); + return; + } + + NativeInputChannel* nativeInputChannel = + android_view_InputChannel_getNativeInputChannel(env, obj); + android_view_InputChannel_setNativeInputChannel(env, otherObj, nativeInputChannel); + android_view_InputChannel_setNativeInputChannel(env, obj, NULL); +} + +static void android_view_InputChannel_nativeReadFromParcel(JNIEnv* env, jobject obj, + jobject parcelObj) { + if (android_view_InputChannel_getInputChannel(env, obj) != NULL) { + jniThrowException(env, "java/lang/IllegalStateException", + "This object already has a native input channel."); + return; + } + + Parcel* parcel = parcelForJavaObject(env, parcelObj); + if (parcel) { + bool isInitialized = parcel->readInt32(); + if (isInitialized) { + String8 name = parcel->readString8(); + int32_t ashmemFd = dup(parcel->readFileDescriptor()); + int32_t receivePipeFd = dup(parcel->readFileDescriptor()); + int32_t sendPipeFd = dup(parcel->readFileDescriptor()); + if (ashmemFd < 0 || receivePipeFd < 0 || sendPipeFd < 0) { + if (ashmemFd >= 0) ::close(ashmemFd); + if (receivePipeFd >= 0) ::close(receivePipeFd); + if (sendPipeFd >= 0) ::close(sendPipeFd); + jniThrowRuntimeException(env, + "Could not read input channel file descriptors from parcel."); + return; + } + + InputChannel* inputChannel = new InputChannel(name, ashmemFd, + receivePipeFd, sendPipeFd); + NativeInputChannel* nativeInputChannel = new NativeInputChannel(inputChannel); + + android_view_InputChannel_setNativeInputChannel(env, obj, nativeInputChannel); + } + } +} + +static void android_view_InputChannel_nativeWriteToParcel(JNIEnv* env, jobject obj, + jobject parcelObj) { + Parcel* parcel = parcelForJavaObject(env, parcelObj); + if (parcel) { + NativeInputChannel* nativeInputChannel = + android_view_InputChannel_getNativeInputChannel(env, obj); + if (nativeInputChannel) { + sp<InputChannel> inputChannel = nativeInputChannel->getInputChannel(); + + parcel->writeInt32(1); + parcel->writeString8(inputChannel->getName()); + parcel->writeDupFileDescriptor(inputChannel->getAshmemFd()); + parcel->writeDupFileDescriptor(inputChannel->getReceivePipeFd()); + parcel->writeDupFileDescriptor(inputChannel->getSendPipeFd()); + } else { + parcel->writeInt32(0); + } + } +} + +static jstring android_view_InputChannel_nativeGetName(JNIEnv* env, jobject obj) { + NativeInputChannel* nativeInputChannel = + android_view_InputChannel_getNativeInputChannel(env, obj); + if (! nativeInputChannel) { + return NULL; + } + + jstring name = env->NewStringUTF(nativeInputChannel->getInputChannel()->getName().string()); + return name; +} + +// ---------------------------------------------------------------------------- + +static JNINativeMethod gInputChannelMethods[] = { + /* name, signature, funcPtr */ + { "nativeOpenInputChannelPair", "(Ljava/lang/String;)[Landroid/view/InputChannel;", + (void*)android_view_InputChannel_nativeOpenInputChannelPair }, + { "nativeDispose", "(Z)V", + (void*)android_view_InputChannel_nativeDispose }, + { "nativeTransferTo", "(Landroid/view/InputChannel;)V", + (void*)android_view_InputChannel_nativeTransferTo }, + { "nativeReadFromParcel", "(Landroid/os/Parcel;)V", + (void*)android_view_InputChannel_nativeReadFromParcel }, + { "nativeWriteToParcel", "(Landroid/os/Parcel;)V", + (void*)android_view_InputChannel_nativeWriteToParcel }, + { "nativeGetName", "()Ljava/lang/String;", + (void*)android_view_InputChannel_nativeGetName }, +}; + +#define FIND_CLASS(var, className) \ + var = env->FindClass(className); \ + LOG_FATAL_IF(! var, "Unable to find class " className); \ + var = jclass(env->NewGlobalRef(var)); + +#define GET_METHOD_ID(var, clazz, methodName, methodDescriptor) \ + var = env->GetMethodID(clazz, methodName, methodDescriptor); \ + LOG_FATAL_IF(! var, "Unable to find method " methodName); + +#define GET_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \ + var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \ + LOG_FATAL_IF(! var, "Unable to find field " fieldName); + +int register_android_view_InputChannel(JNIEnv* env) { + int res = jniRegisterNativeMethods(env, "android/view/InputChannel", + gInputChannelMethods, NELEM(gInputChannelMethods)); + LOG_FATAL_IF(res < 0, "Unable to register native methods."); + + FIND_CLASS(gInputChannelClassInfo.clazz, "android/view/InputChannel"); + + GET_FIELD_ID(gInputChannelClassInfo.mPtr, gInputChannelClassInfo.clazz, + "mPtr", "I"); + + GET_METHOD_ID(gInputChannelClassInfo.ctor, gInputChannelClassInfo.clazz, + "<init>", "()V"); + + return 0; +} + +} // namespace android diff --git a/core/jni/android_view_InputChannel.h b/core/jni/android_view_InputChannel.h new file mode 100644 index 000000000000..fa2d28288923 --- /dev/null +++ b/core/jni/android_view_InputChannel.h @@ -0,0 +1,40 @@ +/* + * 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. + */ + +#ifndef _ANDROID_VIEW_INPUTCHANNEL_H +#define _ANDROID_VIEW_INPUTCHANNEL_H + +#include "jni.h" + +#include <ui/InputTransport.h> + +namespace android { + +typedef void (*InputChannelObjDisposeCallback)(JNIEnv* env, jobject inputChannelObj, + const sp<InputChannel>& inputChannel, void* data); + +extern sp<InputChannel> android_view_InputChannel_getInputChannel(JNIEnv* env, + jobject inputChannelObj); + +/* Sets a callback that is invoked when the InputChannel DVM object is disposed (or finalized). + * This is used to automatically dispose of other native objects in the input dispatcher + * and input queue to prevent memory leaks. */ +extern void android_view_InputChannel_setDisposeCallback(JNIEnv* env, jobject inputChannelObj, + InputChannelObjDisposeCallback callback, void* data = NULL); + +} // namespace android + +#endif // _ANDROID_OS_INPUTCHANNEL_H diff --git a/core/jni/android_view_InputQueue.cpp b/core/jni/android_view_InputQueue.cpp new file mode 100644 index 000000000000..42f35d185d65 --- /dev/null +++ b/core/jni/android_view_InputQueue.cpp @@ -0,0 +1,526 @@ +/* + * 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. + */ + +#define LOG_TAG "InputQueue-JNI" + +//#define LOG_NDEBUG 0 + +// Log debug messages about the dispatch cycle. +#define DEBUG_DISPATCH_CYCLE 0 + +// Log debug messages about registrations. +#define DEBUG_REGISTRATION 0 + + +#include "JNIHelp.h" + +#include <android_runtime/AndroidRuntime.h> +#include <utils/Log.h> +#include <utils/PollLoop.h> +#include <utils/KeyedVector.h> +#include <utils/threads.h> +#include <ui/InputTransport.h> +#include "android_os_MessageQueue.h" +#include "android_view_InputChannel.h" +#include "android_view_KeyEvent.h" +#include "android_view_MotionEvent.h" + +namespace android { + +// ---------------------------------------------------------------------------- + +static struct { + jclass clazz; + + jmethodID dispatchKeyEvent; + jmethodID dispatchMotionEvent; +} gInputQueueClassInfo; + +// ---------------------------------------------------------------------------- + +class NativeInputQueue { +public: + NativeInputQueue(); + ~NativeInputQueue(); + + status_t registerInputChannel(JNIEnv* env, jobject inputChannelObj, + jobject inputHandlerObj, jobject messageQueueObj); + + status_t unregisterInputChannel(JNIEnv* env, jobject inputChannelObj); + + status_t finished(JNIEnv* env, jlong finishedToken, bool ignoreSpuriousFinish); + +private: + class Connection : public RefBase { + protected: + virtual ~Connection(); + + public: + enum Status { + // Everything is peachy. + STATUS_NORMAL, + // The input channel has been unregistered. + STATUS_ZOMBIE + }; + + Connection(uint16_t id, + const sp<InputChannel>& inputChannel, const sp<PollLoop>& pollLoop); + + inline const char* getInputChannelName() const { return inputChannel->getName().string(); } + + // A unique id for this connection. + uint16_t id; + + Status status; + + sp<InputChannel> inputChannel; + InputConsumer inputConsumer; + sp<PollLoop> pollLoop; + jobject inputHandlerObjGlobal; + PreallocatedInputEventFactory inputEventFactory; + + // The sequence number of the current event being dispatched. + // This is used as part of the finished token as a way to determine whether the finished + // token is still valid before sending a finished signal back to the publisher. + uint16_t messageSeqNum; + + // True if a message has been received from the publisher but not yet finished. + bool messageInProgress; + }; + + Mutex mLock; + uint16_t mNextConnectionId; + KeyedVector<int32_t, sp<Connection> > mConnectionsByReceiveFd; + + ssize_t getConnectionIndex(const sp<InputChannel>& inputChannel); + + static void handleInputChannelDisposed(JNIEnv* env, + jobject inputChannelObj, const sp<InputChannel>& inputChannel, void* data); + + static bool handleReceiveCallback(int receiveFd, int events, void* data); + + static jlong generateFinishedToken(int32_t receiveFd, + uint16_t connectionId, uint16_t messageSeqNum); + + static void parseFinishedToken(jlong finishedToken, + int32_t* outReceiveFd, uint16_t* outConnectionId, uint16_t* outMessageIndex); +}; + +// ---------------------------------------------------------------------------- + +NativeInputQueue::NativeInputQueue() : + mNextConnectionId(0) { +} + +NativeInputQueue::~NativeInputQueue() { +} + +status_t NativeInputQueue::registerInputChannel(JNIEnv* env, jobject inputChannelObj, + jobject inputHandlerObj, jobject messageQueueObj) { + sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env, + inputChannelObj); + if (inputChannel == NULL) { + LOGW("Input channel is not initialized."); + return BAD_VALUE; + } + +#if DEBUG_REGISTRATION + LOGD("channel '%s' - Registered", inputChannel->getName().string()); +#endif + + sp<PollLoop> pollLoop = android_os_MessageQueue_getPollLoop(env, messageQueueObj); + + { // acquire lock + AutoMutex _l(mLock); + + if (getConnectionIndex(inputChannel) >= 0) { + LOGW("Attempted to register already registered input channel '%s'", + inputChannel->getName().string()); + return BAD_VALUE; + } + + uint16_t connectionId = mNextConnectionId++; + sp<Connection> connection = new Connection(connectionId, inputChannel, pollLoop); + status_t result = connection->inputConsumer.initialize(); + if (result) { + LOGW("Failed to initialize input consumer for input channel '%s', status=%d", + inputChannel->getName().string(), result); + return result; + } + + connection->inputHandlerObjGlobal = env->NewGlobalRef(inputHandlerObj); + + int32_t receiveFd = inputChannel->getReceivePipeFd(); + mConnectionsByReceiveFd.add(receiveFd, connection); + + pollLoop->setCallback(receiveFd, POLLIN, handleReceiveCallback, this); + } // release lock + + android_view_InputChannel_setDisposeCallback(env, inputChannelObj, + handleInputChannelDisposed, this); + return OK; +} + +status_t NativeInputQueue::unregisterInputChannel(JNIEnv* env, jobject inputChannelObj) { + sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env, + inputChannelObj); + if (inputChannel == NULL) { + LOGW("Input channel is not initialized."); + return BAD_VALUE; + } + +#if DEBUG_REGISTRATION + LOGD("channel '%s' - Unregistered", inputChannel->getName().string()); +#endif + + { // acquire lock + AutoMutex _l(mLock); + + ssize_t connectionIndex = getConnectionIndex(inputChannel); + if (connectionIndex < 0) { + LOGW("Attempted to unregister already unregistered input channel '%s'", + inputChannel->getName().string()); + return BAD_VALUE; + } + + sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex); + mConnectionsByReceiveFd.removeItemsAt(connectionIndex); + + connection->status = Connection::STATUS_ZOMBIE; + + connection->pollLoop->removeCallback(inputChannel->getReceivePipeFd()); + + env->DeleteGlobalRef(connection->inputHandlerObjGlobal); + connection->inputHandlerObjGlobal = NULL; + + if (connection->messageInProgress) { + LOGI("Sending finished signal for input channel '%s' since it is being unregistered " + "while an input message is still in progress.", + connection->getInputChannelName()); + connection->messageInProgress = false; + connection->inputConsumer.sendFinishedSignal(); // ignoring result + } + } // release lock + + android_view_InputChannel_setDisposeCallback(env, inputChannelObj, NULL, NULL); + return OK; +} + +ssize_t NativeInputQueue::getConnectionIndex(const sp<InputChannel>& inputChannel) { + ssize_t connectionIndex = mConnectionsByReceiveFd.indexOfKey(inputChannel->getReceivePipeFd()); + if (connectionIndex >= 0) { + sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex); + if (connection->inputChannel.get() == inputChannel.get()) { + return connectionIndex; + } + } + + return -1; +} + +status_t NativeInputQueue::finished(JNIEnv* env, jlong finishedToken, bool ignoreSpuriousFinish) { + int32_t receiveFd; + uint16_t connectionId; + uint16_t messageSeqNum; + parseFinishedToken(finishedToken, &receiveFd, &connectionId, &messageSeqNum); + + { // acquire lock + AutoMutex _l(mLock); + + ssize_t connectionIndex = mConnectionsByReceiveFd.indexOfKey(receiveFd); + if (connectionIndex < 0) { + if (! ignoreSpuriousFinish) { + LOGI("Ignoring finish signal on channel that is no longer registered."); + } + return DEAD_OBJECT; + } + + sp<Connection> connection = mConnectionsByReceiveFd.valueAt(connectionIndex); + if (connectionId != connection->id) { + if (! ignoreSpuriousFinish) { + LOGI("Ignoring finish signal on channel that is no longer registered."); + } + return DEAD_OBJECT; + } + + if (messageSeqNum != connection->messageSeqNum || ! connection->messageInProgress) { + if (! ignoreSpuriousFinish) { + LOGW("Attempted to finish input twice on channel '%s'. " + "finished messageSeqNum=%d, current messageSeqNum=%d, messageInProgress=%d", + connection->getInputChannelName(), + messageSeqNum, connection->messageSeqNum, connection->messageInProgress); + } + return INVALID_OPERATION; + } + + connection->messageInProgress = false; + + status_t status = connection->inputConsumer.sendFinishedSignal(); + if (status) { + LOGW("Failed to send finished signal on channel '%s'. status=%d", + connection->getInputChannelName(), status); + return status; + } + +#if DEBUG_DISPATCH_CYCLE + LOGD("channel '%s' ~ Finished event.", + connection->getInputChannelName()); +#endif + } // release lock + + return OK; +} + +void NativeInputQueue::handleInputChannelDisposed(JNIEnv* env, + jobject inputChannelObj, const sp<InputChannel>& inputChannel, void* data) { + LOGW("Input channel object '%s' was disposed without first being unregistered with " + "the input queue!", inputChannel->getName().string()); + + NativeInputQueue* q = static_cast<NativeInputQueue*>(data); + q->unregisterInputChannel(env, inputChannelObj); +} + +bool NativeInputQueue::handleReceiveCallback(int receiveFd, int events, void* data) { + NativeInputQueue* q = static_cast<NativeInputQueue*>(data); + JNIEnv* env = AndroidRuntime::getJNIEnv(); + + sp<Connection> connection; + InputEvent* inputEvent; + jobject inputHandlerObjLocal; + jlong finishedToken; + { // acquire lock + AutoMutex _l(q->mLock); + + ssize_t connectionIndex = q->mConnectionsByReceiveFd.indexOfKey(receiveFd); + if (connectionIndex < 0) { + LOGE("Received spurious receive callback for unknown input channel. " + "fd=%d, events=0x%x", receiveFd, events); + return false; // remove the callback + } + + connection = q->mConnectionsByReceiveFd.valueAt(connectionIndex); + if (events & (POLLERR | POLLHUP | POLLNVAL)) { + LOGE("channel '%s' ~ Publisher closed input channel or an error occurred. " + "events=0x%x", connection->getInputChannelName(), events); + return false; // remove the callback + } + + if (! (events & POLLIN)) { + LOGW("channel '%s' ~ Received spurious callback for unhandled poll event. " + "events=0x%x", connection->getInputChannelName(), events); + return true; + } + + status_t status = connection->inputConsumer.receiveDispatchSignal(); + if (status) { + LOGE("channel '%s' ~ Failed to receive dispatch signal. status=%d", + connection->getInputChannelName(), status); + return false; // remove the callback + } + + if (connection->messageInProgress) { + LOGW("channel '%s' ~ Publisher sent spurious dispatch signal.", + connection->getInputChannelName()); + return true; + } + + status = connection->inputConsumer.consume(& connection->inputEventFactory, & inputEvent); + if (status) { + LOGW("channel '%s' ~ Failed to consume input event. status=%d", + connection->getInputChannelName(), status); + connection->inputConsumer.sendFinishedSignal(); + return true; + } + + connection->messageInProgress = true; + connection->messageSeqNum += 1; + + finishedToken = generateFinishedToken(receiveFd, connection->id, connection->messageSeqNum); + + inputHandlerObjLocal = env->NewLocalRef(connection->inputHandlerObjGlobal); + } // release lock + + // Invoke the handler outside of the lock. + // + // Note: inputEvent is stored in a field of the connection object which could potentially + // become disposed due to the input channel being unregistered concurrently. + // For this reason, we explicitly keep the connection object alive by holding + // a strong pointer to it within this scope. We also grabbed a local reference to + // the input handler object itself for the same reason. + + int32_t inputEventType = inputEvent->getType(); + + jobject inputEventObj; + jmethodID dispatchMethodId; + switch (inputEventType) { + case AINPUT_EVENT_TYPE_KEY: +#if DEBUG_DISPATCH_CYCLE + LOGD("channel '%s' ~ Received key event.", connection->getInputChannelName()); +#endif + inputEventObj = android_view_KeyEvent_fromNative(env, + static_cast<KeyEvent*>(inputEvent)); + dispatchMethodId = gInputQueueClassInfo.dispatchKeyEvent; + break; + + case AINPUT_EVENT_TYPE_MOTION: +#if DEBUG_DISPATCH_CYCLE + LOGD("channel '%s' ~ Received motion event.", connection->getInputChannelName()); +#endif + inputEventObj = android_view_MotionEvent_fromNative(env, + static_cast<MotionEvent*>(inputEvent)); + dispatchMethodId = gInputQueueClassInfo.dispatchMotionEvent; + break; + + default: + assert(false); // InputConsumer should prevent this from ever happening + inputEventObj = NULL; + } + + if (! inputEventObj) { + LOGW("channel '%s' ~ Failed to obtain DVM event object.", + connection->getInputChannelName()); + env->DeleteLocalRef(inputHandlerObjLocal); + q->finished(env, finishedToken, false); + return true; + } + +#if DEBUG_DISPATCH_CYCLE + LOGD("Invoking input handler."); +#endif + env->CallStaticVoidMethod(gInputQueueClassInfo.clazz, + dispatchMethodId, inputHandlerObjLocal, inputEventObj, + jlong(finishedToken)); +#if DEBUG_DISPATCH_CYCLE + LOGD("Returned from input handler."); +#endif + + if (env->ExceptionCheck()) { + LOGE("An exception occurred while invoking the input handler for an event."); + LOGE_EX(env); + env->ExceptionClear(); + + q->finished(env, finishedToken, true /*ignoreSpuriousFinish*/); + } + + env->DeleteLocalRef(inputEventObj); + env->DeleteLocalRef(inputHandlerObjLocal); + return true; +} + +jlong NativeInputQueue::generateFinishedToken(int32_t receiveFd, uint16_t connectionId, + uint16_t messageSeqNum) { + return (jlong(receiveFd) << 32) | (jlong(connectionId) << 16) | jlong(messageSeqNum); +} + +void NativeInputQueue::parseFinishedToken(jlong finishedToken, + int32_t* outReceiveFd, uint16_t* outConnectionId, uint16_t* outMessageIndex) { + *outReceiveFd = int32_t(finishedToken >> 32); + *outConnectionId = uint16_t(finishedToken >> 16); + *outMessageIndex = uint16_t(finishedToken); +} + +// ---------------------------------------------------------------------------- + +NativeInputQueue::Connection::Connection(uint16_t id, + const sp<InputChannel>& inputChannel, const sp<PollLoop>& pollLoop) : + id(id), status(STATUS_NORMAL), inputChannel(inputChannel), inputConsumer(inputChannel), + pollLoop(pollLoop), inputHandlerObjGlobal(NULL), + messageSeqNum(0), messageInProgress(false) { +} + +NativeInputQueue::Connection::~Connection() { +} + +// ---------------------------------------------------------------------------- + +static NativeInputQueue gNativeInputQueue; + +static void android_view_InputQueue_nativeRegisterInputChannel(JNIEnv* env, jclass clazz, + jobject inputChannelObj, jobject inputHandlerObj, jobject messageQueueObj) { + status_t status = gNativeInputQueue.registerInputChannel( + env, inputChannelObj, inputHandlerObj, messageQueueObj); + + if (status) { + jniThrowRuntimeException(env, "Failed to register input channel. " + "Check logs for details."); + } +} + +static void android_view_InputQueue_nativeUnregisterInputChannel(JNIEnv* env, jclass clazz, + jobject inputChannelObj) { + status_t status = gNativeInputQueue.unregisterInputChannel(env, inputChannelObj); + + if (status) { + jniThrowRuntimeException(env, "Failed to unregister input channel. " + "Check logs for details."); + } +} + +static void android_view_InputQueue_nativeFinished(JNIEnv* env, jclass clazz, + jlong finishedToken) { + status_t status = gNativeInputQueue.finished( + env, finishedToken, false /*ignoreSpuriousFinish*/); + + // We ignore the case where an event could not be finished because the input channel + // was no longer registered (DEAD_OBJECT) since it is a common race that can occur + // during application shutdown. The input dispatcher recovers gracefully anyways. + if (status != OK && status != DEAD_OBJECT) { + jniThrowRuntimeException(env, "Failed to finish input event. " + "Check logs for details."); + } +} + +// ---------------------------------------------------------------------------- + +static JNINativeMethod gInputQueueMethods[] = { + /* name, signature, funcPtr */ + { "nativeRegisterInputChannel", + "(Landroid/view/InputChannel;Landroid/view/InputHandler;Landroid/os/MessageQueue;)V", + (void*)android_view_InputQueue_nativeRegisterInputChannel }, + { "nativeUnregisterInputChannel", + "(Landroid/view/InputChannel;)V", + (void*)android_view_InputQueue_nativeUnregisterInputChannel }, + { "nativeFinished", "(J)V", + (void*)android_view_InputQueue_nativeFinished } +}; + +#define FIND_CLASS(var, className) \ + var = env->FindClass(className); \ + LOG_FATAL_IF(! var, "Unable to find class " className); \ + var = jclass(env->NewGlobalRef(var)); + +#define GET_STATIC_METHOD_ID(var, clazz, methodName, methodDescriptor) \ + var = env->GetStaticMethodID(clazz, methodName, methodDescriptor); \ + LOG_FATAL_IF(! var, "Unable to find static method " methodName); + +int register_android_view_InputQueue(JNIEnv* env) { + int res = jniRegisterNativeMethods(env, "android/view/InputQueue", + gInputQueueMethods, NELEM(gInputQueueMethods)); + LOG_FATAL_IF(res < 0, "Unable to register native methods."); + + FIND_CLASS(gInputQueueClassInfo.clazz, "android/view/InputQueue"); + + GET_STATIC_METHOD_ID(gInputQueueClassInfo.dispatchKeyEvent, gInputQueueClassInfo.clazz, + "dispatchKeyEvent", + "(Landroid/view/InputHandler;Landroid/view/KeyEvent;J)V"); + + GET_STATIC_METHOD_ID(gInputQueueClassInfo.dispatchMotionEvent, gInputQueueClassInfo.clazz, + "dispatchMotionEvent", + "(Landroid/view/InputHandler;Landroid/view/MotionEvent;J)V"); + return 0; +} + +} // namespace android diff --git a/core/jni/android_view_KeyEvent.cpp b/core/jni/android_view_KeyEvent.cpp new file mode 100644 index 000000000000..7e7583cac782 --- /dev/null +++ b/core/jni/android_view_KeyEvent.cpp @@ -0,0 +1,143 @@ +/* + * 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. + */ + +#define LOG_TAG "KeyEvent-JNI" + +#include "JNIHelp.h" + +#include <android_runtime/AndroidRuntime.h> +#include <utils/Log.h> +#include <ui/Input.h> +#include "android_view_KeyEvent.h" + +namespace android { + +// ---------------------------------------------------------------------------- + +static struct { + jclass clazz; + + jmethodID ctor; + + jfieldID mDeviceId; + jfieldID mSource; + jfieldID mMetaState; + jfieldID mAction; + jfieldID mKeyCode; + jfieldID mScanCode; + jfieldID mRepeatCount; + jfieldID mFlags; + jfieldID mDownTime; + jfieldID mEventTime; + jfieldID mCharacters; +} gKeyEventClassInfo; + +// ---------------------------------------------------------------------------- + +jobject android_view_KeyEvent_fromNative(JNIEnv* env, const KeyEvent* event) { + return env->NewObject(gKeyEventClassInfo.clazz, gKeyEventClassInfo.ctor, + nanoseconds_to_milliseconds(event->getDownTime()), + nanoseconds_to_milliseconds(event->getEventTime()), + event->getAction(), + event->getKeyCode(), + event->getRepeatCount(), + event->getMetaState(), + event->getDeviceId(), + event->getScanCode(), + event->getFlags(), + event->getSource()); +} + +void android_view_KeyEvent_toNative(JNIEnv* env, jobject eventObj, + KeyEvent* event) { + jint deviceId = env->GetIntField(eventObj, gKeyEventClassInfo.mDeviceId); + jint source = env->GetIntField(eventObj, gKeyEventClassInfo.mSource); + jint metaState = env->GetIntField(eventObj, gKeyEventClassInfo.mMetaState); + jint action = env->GetIntField(eventObj, gKeyEventClassInfo.mAction); + jint keyCode = env->GetIntField(eventObj, gKeyEventClassInfo.mKeyCode); + jint scanCode = env->GetIntField(eventObj, gKeyEventClassInfo.mScanCode); + jint repeatCount = env->GetIntField(eventObj, gKeyEventClassInfo.mRepeatCount); + jint flags = env->GetIntField(eventObj, gKeyEventClassInfo.mFlags); + jlong downTime = env->GetLongField(eventObj, gKeyEventClassInfo.mDownTime); + jlong eventTime = env->GetLongField(eventObj, gKeyEventClassInfo.mEventTime); + + event->initialize(deviceId, source, action, flags, keyCode, scanCode, metaState, repeatCount, + milliseconds_to_nanoseconds(downTime), + milliseconds_to_nanoseconds(eventTime)); +} + +static jboolean native_isSystemKey(JNIEnv* env, jobject clazz, jint keyCode) { + return KeyEvent::isSystemKey(keyCode); +} + +static jboolean native_hasDefaultAction(JNIEnv* env, jobject clazz, jint keyCode) { + return KeyEvent::hasDefaultAction(keyCode); +} + +// ---------------------------------------------------------------------------- + +static const JNINativeMethod g_methods[] = { + { "native_isSystemKey", "(I)Z", (void*)native_isSystemKey }, + { "native_hasDefaultAction", "(I)Z", (void*)native_hasDefaultAction }, +}; + +#define FIND_CLASS(var, className) \ + var = env->FindClass(className); \ + LOG_FATAL_IF(! var, "Unable to find class " className); \ + var = jclass(env->NewGlobalRef(var)); + +#define GET_METHOD_ID(var, clazz, methodName, fieldDescriptor) \ + var = env->GetMethodID(clazz, methodName, fieldDescriptor); \ + LOG_FATAL_IF(! var, "Unable to find method" methodName); + +#define GET_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \ + var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \ + LOG_FATAL_IF(! var, "Unable to find field " fieldName); + +int register_android_view_KeyEvent(JNIEnv* env) { + FIND_CLASS(gKeyEventClassInfo.clazz, "android/view/KeyEvent"); + + GET_METHOD_ID(gKeyEventClassInfo.ctor, gKeyEventClassInfo.clazz, + "<init>", "(JJIIIIIIII)V"); + + GET_FIELD_ID(gKeyEventClassInfo.mDeviceId, gKeyEventClassInfo.clazz, + "mDeviceId", "I"); + GET_FIELD_ID(gKeyEventClassInfo.mSource, gKeyEventClassInfo.clazz, + "mSource", "I"); + GET_FIELD_ID(gKeyEventClassInfo.mMetaState, gKeyEventClassInfo.clazz, + "mMetaState", "I"); + GET_FIELD_ID(gKeyEventClassInfo.mAction, gKeyEventClassInfo.clazz, + "mAction", "I"); + GET_FIELD_ID(gKeyEventClassInfo.mKeyCode, gKeyEventClassInfo.clazz, + "mKeyCode", "I"); + GET_FIELD_ID(gKeyEventClassInfo.mScanCode, gKeyEventClassInfo.clazz, + "mScanCode", "I"); + GET_FIELD_ID(gKeyEventClassInfo.mRepeatCount, gKeyEventClassInfo.clazz, + "mRepeatCount", "I"); + GET_FIELD_ID(gKeyEventClassInfo.mFlags, gKeyEventClassInfo.clazz, + "mFlags", "I"); + GET_FIELD_ID(gKeyEventClassInfo.mDownTime, gKeyEventClassInfo.clazz, + "mDownTime", "J"); + GET_FIELD_ID(gKeyEventClassInfo.mEventTime, gKeyEventClassInfo.clazz, + "mEventTime", "J"); + GET_FIELD_ID(gKeyEventClassInfo.mCharacters, gKeyEventClassInfo.clazz, + "mCharacters", "Ljava/lang/String;"); + + return AndroidRuntime::registerNativeMethods( + env, "android/view/KeyEvent", g_methods, NELEM(g_methods)); +} + +} // namespace android diff --git a/core/jni/android_view_KeyEvent.h b/core/jni/android_view_KeyEvent.h new file mode 100644 index 000000000000..0bd410cbd601 --- /dev/null +++ b/core/jni/android_view_KeyEvent.h @@ -0,0 +1,35 @@ +/* + * 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. + */ + +#ifndef _ANDROID_VIEW_KEYEVENT_H +#define _ANDROID_VIEW_KEYEVENT_H + +#include "jni.h" + +namespace android { + +class KeyEvent; + +/* Obtains an instance of a DVM KeyEvent object as a copy of a native KeyEvent instance. */ +extern jobject android_view_KeyEvent_fromNative(JNIEnv* env, const KeyEvent* event); + +/* Copies the contents of a DVM KeyEvent object to a native KeyEvent instance. */ +extern void android_view_KeyEvent_toNative(JNIEnv* env, jobject eventObj, + KeyEvent* event); + +} // namespace android + +#endif // _ANDROID_OS_KEYEVENT_H diff --git a/core/jni/android_view_MotionEvent.cpp b/core/jni/android_view_MotionEvent.cpp new file mode 100644 index 000000000000..93fd54feddda --- /dev/null +++ b/core/jni/android_view_MotionEvent.cpp @@ -0,0 +1,308 @@ +/* + * 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. + */ + +#define LOG_TAG "MotionEvent-JNI" + +#include "JNIHelp.h" + +#include <android_runtime/AndroidRuntime.h> +#include <utils/Log.h> +#include <ui/Input.h> +#include "android_view_MotionEvent.h" + +// Number of float items per entry in a DVM sample data array +#define NUM_SAMPLE_DATA 9 + +namespace android { + +// ---------------------------------------------------------------------------- + +static struct { + jclass clazz; + + jmethodID obtain; + jmethodID recycle; + + jfieldID mDeviceId; + jfieldID mSource; + jfieldID mDownTimeNano; + jfieldID mAction; + jfieldID mXOffset; + jfieldID mYOffset; + jfieldID mXPrecision; + jfieldID mYPrecision; + jfieldID mEdgeFlags; + jfieldID mMetaState; + jfieldID mFlags; + jfieldID mNumPointers; + jfieldID mNumSamples; + jfieldID mPointerIdentifiers; + jfieldID mDataSamples; + jfieldID mEventTimeNanoSamples; + jfieldID mLastDataSampleIndex; + jfieldID mLastEventTimeNanoSampleIndex; +} gMotionEventClassInfo; + +// ---------------------------------------------------------------------------- + +jobject android_view_MotionEvent_fromNative(JNIEnv* env, const MotionEvent* event) { + jint numPointers = jint(event->getPointerCount()); + jint numHistoricalSamples = jint(event->getHistorySize()); + jint numSamples = numHistoricalSamples + 1; + + jobject eventObj = env->CallStaticObjectMethod(gMotionEventClassInfo.clazz, + gMotionEventClassInfo.obtain, numPointers, numSamples); + if (env->ExceptionCheck()) { + LOGE("An exception occurred while obtaining a motion event."); + LOGE_EX(env); + env->ExceptionClear(); + return NULL; + } + + env->SetIntField(eventObj, gMotionEventClassInfo.mDeviceId, + event->getDeviceId()); + env->SetIntField(eventObj, gMotionEventClassInfo.mSource, + event->getSource()); + env->SetLongField(eventObj, gMotionEventClassInfo.mDownTimeNano, + event->getDownTime()); + env->SetIntField(eventObj, gMotionEventClassInfo.mAction, + event->getAction()); + env->SetFloatField(eventObj, gMotionEventClassInfo.mXOffset, + event->getXOffset()); + env->SetFloatField(eventObj, gMotionEventClassInfo.mYOffset, + event->getYOffset()); + env->SetFloatField(eventObj, gMotionEventClassInfo.mXPrecision, + event->getXPrecision()); + env->SetFloatField(eventObj, gMotionEventClassInfo.mYPrecision, + event->getYPrecision()); + env->SetIntField(eventObj, gMotionEventClassInfo.mEdgeFlags, + event->getEdgeFlags()); + env->SetIntField(eventObj, gMotionEventClassInfo.mMetaState, + event->getMetaState()); + env->SetIntField(eventObj, gMotionEventClassInfo.mFlags, + event->getFlags()); + env->SetIntField(eventObj, gMotionEventClassInfo.mNumPointers, + numPointers); + env->SetIntField(eventObj, gMotionEventClassInfo.mNumSamples, + numSamples); + env->SetIntField(eventObj, gMotionEventClassInfo.mLastDataSampleIndex, + (numSamples - 1) * numPointers * NUM_SAMPLE_DATA); + env->SetIntField(eventObj, gMotionEventClassInfo.mLastEventTimeNanoSampleIndex, + numSamples - 1); + + jintArray pointerIdentifierArray = jintArray(env->GetObjectField(eventObj, + gMotionEventClassInfo.mPointerIdentifiers)); + jfloatArray dataSampleArray = jfloatArray(env->GetObjectField(eventObj, + gMotionEventClassInfo.mDataSamples)); + jlongArray eventTimeNanoSampleArray = jlongArray(env->GetObjectField(eventObj, + gMotionEventClassInfo.mEventTimeNanoSamples)); + + jint* pointerIdentifiers = (jint*)env->GetPrimitiveArrayCritical(pointerIdentifierArray, NULL); + jfloat* dataSamples = (jfloat*)env->GetPrimitiveArrayCritical(dataSampleArray, NULL); + jlong* eventTimeNanoSamples = (jlong*)env->GetPrimitiveArrayCritical( + eventTimeNanoSampleArray, NULL); + + const int32_t* srcPointerIdentifiers = event->getPointerIds(); + jint* destPointerIdentifiers = pointerIdentifiers; + for (jint i = 0; i < numPointers; i++) { + *(destPointerIdentifiers++) = *(srcPointerIdentifiers++); + } + + const nsecs_t* srcSampleEventTimes = event->getSampleEventTimes(); + jlong* destEventTimeNanoSamples = eventTimeNanoSamples; + for (jint i = 0; i < numSamples; i++) { + *(destEventTimeNanoSamples++) = *(srcSampleEventTimes++); + } + + const PointerCoords* srcSamplePointerCoords = event->getSamplePointerCoords(); + jfloat* destDataSamples = dataSamples; + jint numItems = numSamples * numPointers; + for (jint i = 0; i < numItems; i++) { + *(destDataSamples++) = srcSamplePointerCoords->x; + *(destDataSamples++) = srcSamplePointerCoords->y; + *(destDataSamples++) = srcSamplePointerCoords->pressure; + *(destDataSamples++) = srcSamplePointerCoords->size; + *(destDataSamples++) = srcSamplePointerCoords->touchMajor; + *(destDataSamples++) = srcSamplePointerCoords->touchMinor; + *(destDataSamples++) = srcSamplePointerCoords->toolMajor; + *(destDataSamples++) = srcSamplePointerCoords->toolMinor; + *(destDataSamples++) = srcSamplePointerCoords->orientation; + srcSamplePointerCoords += 1; + } + + env->ReleasePrimitiveArrayCritical(pointerIdentifierArray, pointerIdentifiers, 0); + env->ReleasePrimitiveArrayCritical(dataSampleArray, dataSamples, 0); + env->ReleasePrimitiveArrayCritical(eventTimeNanoSampleArray, eventTimeNanoSamples, 0); + + env->DeleteLocalRef(pointerIdentifierArray); + env->DeleteLocalRef(dataSampleArray); + env->DeleteLocalRef(eventTimeNanoSampleArray); + return eventObj; +} + +void android_view_MotionEvent_toNative(JNIEnv* env, jobject eventObj, + MotionEvent* event) { + jint deviceId = env->GetIntField(eventObj, gMotionEventClassInfo.mDeviceId); + jint source = env->GetIntField(eventObj, gMotionEventClassInfo.mSource); + jlong downTimeNano = env->GetLongField(eventObj, gMotionEventClassInfo.mDownTimeNano); + jint action = env->GetIntField(eventObj, gMotionEventClassInfo.mAction); + jfloat xOffset = env->GetFloatField(eventObj, gMotionEventClassInfo.mXOffset); + jfloat yOffset = env->GetFloatField(eventObj, gMotionEventClassInfo.mYOffset); + jfloat xPrecision = env->GetFloatField(eventObj, gMotionEventClassInfo.mXPrecision); + jfloat yPrecision = env->GetFloatField(eventObj, gMotionEventClassInfo.mYPrecision); + jint edgeFlags = env->GetIntField(eventObj, gMotionEventClassInfo.mEdgeFlags); + jint metaState = env->GetIntField(eventObj, gMotionEventClassInfo.mMetaState); + jint flags = env->GetIntField(eventObj, gMotionEventClassInfo.mFlags); + jint numPointers = env->GetIntField(eventObj, gMotionEventClassInfo.mNumPointers); + jint numSamples = env->GetIntField(eventObj, gMotionEventClassInfo.mNumSamples); + jintArray pointerIdentifierArray = jintArray(env->GetObjectField(eventObj, + gMotionEventClassInfo.mPointerIdentifiers)); + jfloatArray dataSampleArray = jfloatArray(env->GetObjectField(eventObj, + gMotionEventClassInfo.mDataSamples)); + jlongArray eventTimeNanoSampleArray = jlongArray(env->GetObjectField(eventObj, + gMotionEventClassInfo.mEventTimeNanoSamples)); + + LOG_FATAL_IF(numPointers == 0, "numPointers was zero"); + LOG_FATAL_IF(numSamples == 0, "numSamples was zero"); + + jint* pointerIdentifiers = (jint*)env->GetPrimitiveArrayCritical(pointerIdentifierArray, NULL); + jfloat* dataSamples = (jfloat*)env->GetPrimitiveArrayCritical(dataSampleArray, NULL); + jlong* eventTimeNanoSamples = (jlong*)env->GetPrimitiveArrayCritical( + eventTimeNanoSampleArray, NULL); + + jfloat* srcDataSamples = dataSamples; + jlong* srcEventTimeNanoSamples = eventTimeNanoSamples; + + jlong sampleEventTime = *(srcEventTimeNanoSamples++); + PointerCoords samplePointerCoords[MAX_POINTERS]; + for (jint j = 0; j < numPointers; j++) { + samplePointerCoords[j].x = *(srcDataSamples++); + samplePointerCoords[j].y = *(srcDataSamples++); + samplePointerCoords[j].pressure = *(srcDataSamples++); + samplePointerCoords[j].size = *(srcDataSamples++); + samplePointerCoords[j].touchMajor = *(srcDataSamples++); + samplePointerCoords[j].touchMinor = *(srcDataSamples++); + samplePointerCoords[j].toolMajor = *(srcDataSamples++); + samplePointerCoords[j].toolMinor = *(srcDataSamples++); + samplePointerCoords[j].orientation = *(srcDataSamples++); + } + + event->initialize(deviceId, source, action, flags, edgeFlags, metaState, + xOffset, yOffset, xPrecision, yPrecision, downTimeNano, sampleEventTime, + numPointers, pointerIdentifiers, samplePointerCoords); + + for (jint i = 1; i < numSamples; i++) { + sampleEventTime = *(srcEventTimeNanoSamples++); + for (jint j = 0; j < numPointers; j++) { + samplePointerCoords[j].x = *(srcDataSamples++); + samplePointerCoords[j].y = *(srcDataSamples++); + samplePointerCoords[j].pressure = *(srcDataSamples++); + samplePointerCoords[j].size = *(srcDataSamples++); + samplePointerCoords[j].touchMajor = *(srcDataSamples++); + samplePointerCoords[j].touchMinor = *(srcDataSamples++); + samplePointerCoords[j].toolMajor = *(srcDataSamples++); + samplePointerCoords[j].toolMinor = *(srcDataSamples++); + samplePointerCoords[j].orientation = *(srcDataSamples++); + } + event->addSample(sampleEventTime, samplePointerCoords); + } + + env->ReleasePrimitiveArrayCritical(pointerIdentifierArray, pointerIdentifiers, JNI_ABORT); + env->ReleasePrimitiveArrayCritical(dataSampleArray, dataSamples, JNI_ABORT); + env->ReleasePrimitiveArrayCritical(eventTimeNanoSampleArray, eventTimeNanoSamples, JNI_ABORT); + + env->DeleteLocalRef(pointerIdentifierArray); + env->DeleteLocalRef(dataSampleArray); + env->DeleteLocalRef(eventTimeNanoSampleArray); +} + +void android_view_MotionEvent_recycle(JNIEnv* env, jobject eventObj) { + env->CallVoidMethod(eventObj, gMotionEventClassInfo.recycle); + if (env->ExceptionCheck()) { + LOGW("An exception occurred while recycling a motion event."); + LOGW_EX(env); + env->ExceptionClear(); + } +} + +// ---------------------------------------------------------------------------- + +#define FIND_CLASS(var, className) \ + var = env->FindClass(className); \ + LOG_FATAL_IF(! var, "Unable to find class " className); \ + var = jclass(env->NewGlobalRef(var)); + +#define GET_STATIC_METHOD_ID(var, clazz, methodName, fieldDescriptor) \ + var = env->GetStaticMethodID(clazz, methodName, fieldDescriptor); \ + LOG_FATAL_IF(! var, "Unable to find static method" methodName); + +#define GET_METHOD_ID(var, clazz, methodName, fieldDescriptor) \ + var = env->GetMethodID(clazz, methodName, fieldDescriptor); \ + LOG_FATAL_IF(! var, "Unable to find method" methodName); + +#define GET_FIELD_ID(var, clazz, fieldName, fieldDescriptor) \ + var = env->GetFieldID(clazz, fieldName, fieldDescriptor); \ + LOG_FATAL_IF(! var, "Unable to find field " fieldName); + +int register_android_view_MotionEvent(JNIEnv* env) { + FIND_CLASS(gMotionEventClassInfo.clazz, "android/view/MotionEvent"); + + GET_STATIC_METHOD_ID(gMotionEventClassInfo.obtain, gMotionEventClassInfo.clazz, + "obtain", "(II)Landroid/view/MotionEvent;"); + GET_METHOD_ID(gMotionEventClassInfo.recycle, gMotionEventClassInfo.clazz, + "recycle", "()V"); + + GET_FIELD_ID(gMotionEventClassInfo.mDeviceId, gMotionEventClassInfo.clazz, + "mDeviceId", "I"); + GET_FIELD_ID(gMotionEventClassInfo.mSource, gMotionEventClassInfo.clazz, + "mSource", "I"); + GET_FIELD_ID(gMotionEventClassInfo.mDownTimeNano, gMotionEventClassInfo.clazz, + "mDownTimeNano", "J"); + GET_FIELD_ID(gMotionEventClassInfo.mAction, gMotionEventClassInfo.clazz, + "mAction", "I"); + GET_FIELD_ID(gMotionEventClassInfo.mXOffset, gMotionEventClassInfo.clazz, + "mXOffset", "F"); + GET_FIELD_ID(gMotionEventClassInfo.mYOffset, gMotionEventClassInfo.clazz, + "mYOffset", "F"); + GET_FIELD_ID(gMotionEventClassInfo.mXPrecision, gMotionEventClassInfo.clazz, + "mXPrecision", "F"); + GET_FIELD_ID(gMotionEventClassInfo.mYPrecision, gMotionEventClassInfo.clazz, + "mYPrecision", "F"); + GET_FIELD_ID(gMotionEventClassInfo.mEdgeFlags, gMotionEventClassInfo.clazz, + "mEdgeFlags", "I"); + GET_FIELD_ID(gMotionEventClassInfo.mMetaState, gMotionEventClassInfo.clazz, + "mMetaState", "I"); + GET_FIELD_ID(gMotionEventClassInfo.mFlags, gMotionEventClassInfo.clazz, + "mFlags", "I"); + GET_FIELD_ID(gMotionEventClassInfo.mNumPointers, gMotionEventClassInfo.clazz, + "mNumPointers", "I"); + GET_FIELD_ID(gMotionEventClassInfo.mNumSamples, gMotionEventClassInfo.clazz, + "mNumSamples", "I"); + GET_FIELD_ID(gMotionEventClassInfo.mPointerIdentifiers, gMotionEventClassInfo.clazz, + "mPointerIdentifiers", "[I"); + GET_FIELD_ID(gMotionEventClassInfo.mDataSamples, gMotionEventClassInfo.clazz, + "mDataSamples", "[F"); + GET_FIELD_ID(gMotionEventClassInfo.mEventTimeNanoSamples, gMotionEventClassInfo.clazz, + "mEventTimeNanoSamples", "[J"); + GET_FIELD_ID(gMotionEventClassInfo.mLastDataSampleIndex, gMotionEventClassInfo.clazz, + "mLastDataSampleIndex", "I"); + GET_FIELD_ID(gMotionEventClassInfo.mLastEventTimeNanoSampleIndex, gMotionEventClassInfo.clazz, + "mLastEventTimeNanoSampleIndex", "I"); + + return 0; +} + +} // namespace android diff --git a/core/jni/android_view_MotionEvent.h b/core/jni/android_view_MotionEvent.h new file mode 100644 index 000000000000..86e4bdefc5fe --- /dev/null +++ b/core/jni/android_view_MotionEvent.h @@ -0,0 +1,38 @@ +/* + * 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. + */ + +#ifndef _ANDROID_VIEW_MOTIONEVENT_H +#define _ANDROID_VIEW_MOTIONEVENT_H + +#include "jni.h" + +namespace android { + +class MotionEvent; + +/* Obtains an instance of a DVM MotionEvent object as a copy of a native MotionEvent instance. */ +extern jobject android_view_MotionEvent_fromNative(JNIEnv* env, const MotionEvent* event); + +/* Copies the contents of a DVM MotionEvent object to a native MotionEvent instance. */ +extern void android_view_MotionEvent_toNative(JNIEnv* env, jobject eventObj, + MotionEvent* event); + +/* Recycles a DVM MotionEvent object. */ +extern void android_view_MotionEvent_recycle(JNIEnv* env, jobject eventObj); + +} // namespace android + +#endif // _ANDROID_OS_KEYEVENT_H diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp index 788374b6fa3a..c4d6d1b7ab49 100644 --- a/core/jni/android_view_Surface.cpp +++ b/core/jni/android_view_Surface.cpp @@ -14,20 +14,26 @@ * limitations under the License. */ +#define LOG_TAG "Surface" + #include <stdio.h> #include "android_util_Binder.h" #include <surfaceflinger/SurfaceComposerClient.h> +#include <surfaceflinger/Surface.h> #include <ui/Region.h> #include <ui/Rect.h> +#include <EGL/egl.h> + #include <SkCanvas.h> #include <SkBitmap.h> #include <SkRegion.h> #include "jni.h" #include <android_runtime/AndroidRuntime.h> +#include <android_runtime/android_view_Surface.h> #include <utils/misc.h> @@ -174,6 +180,11 @@ static sp<Surface> getSurface(JNIEnv* env, jobject clazz) return result; } +sp<ANativeWindow> android_Surface_getNativeWindow( + JNIEnv* env, jobject clazz) { + return getSurface(env, clazz).get(); +} + static void setSurface(JNIEnv* env, jobject clazz, const sp<Surface>& surface) { Surface* const p = (Surface*)env->GetIntField(clazz, so.surface); @@ -225,8 +236,9 @@ static void Surface_initParcel(JNIEnv* env, jobject clazz, jobject argParcel) doThrow(env, "java/lang/NullPointerException", NULL); return; } - sp<Surface> rhs = new Surface(*parcel); - setSurface(env, clazz, rhs); + + sp<Surface> sur(Surface::readFromParcel(*parcel)); + setSurface(env, clazz, sur); } static jint Surface_getIdentity(JNIEnv* env, jobject clazz) @@ -332,7 +344,7 @@ static jobject Surface_lockCanvas(JNIEnv* env, jobject clazz, jobject dirtyRect) SkRegion clipReg; if (dirtyRegion.isRect()) { // very common case - const Rect& b(dirtyRegion.getBounds()); + const Rect b(dirtyRegion.getBounds()); clipReg.setRect(b.left, b.top, b.right, b.bottom); } else { size_t count; @@ -585,7 +597,7 @@ static void Surface_copyFrom( * a Surface and is necessary for returning the Surface reference to * the caller. At this point, we should only have a SurfaceControl. */ - + const sp<SurfaceControl>& surface = getSurfaceControl(env, clazz); const sp<SurfaceControl>& rhs = getSurfaceControl(env, other); if (!SurfaceControl::isSameSurface(surface, rhs)) { @@ -604,13 +616,8 @@ static void Surface_readFromParcel( return; } - const sp<Surface>& control(getSurface(env, clazz)); - sp<Surface> rhs = new Surface(*parcel); - if (!Surface::isSameSurface(control, rhs)) { - // we reassign the surface only if it's a different one - // otherwise we would loose our client-side state. - setSurface(env, clazz, rhs); - } + sp<Surface> sur(Surface::readFromParcel(*parcel)); + setSurface(env, clazz, sur); } static void Surface_writeToParcel( @@ -680,7 +687,7 @@ static JNINativeMethod gSurfaceMethods[] = { void nativeClassInit(JNIEnv* env, jclass clazz) { - so.surface = env->GetFieldID(clazz, "mSurface", "I"); + so.surface = env->GetFieldID(clazz, ANDROID_VIEW_SURFACE_JNI_ID, "I"); so.surfaceControl = env->GetFieldID(clazz, "mSurfaceControl", "I"); so.saveCount = env->GetFieldID(clazz, "mSaveCount", "I"); so.canvas = env->GetFieldID(clazz, "mCanvas", "Landroid/graphics/Canvas;"); @@ -721,4 +728,3 @@ int register_android_view_Surface(JNIEnv* env) } }; - diff --git a/core/jni/android_view_ViewRoot.cpp b/core/jni/android_view_ViewRoot.cpp index 9d62d895927e..5173bb89757c 100644 --- a/core/jni/android_view_ViewRoot.cpp +++ b/core/jni/android_view_ViewRoot.cpp @@ -16,6 +16,7 @@ #include <stdio.h> #include <assert.h> +#include <sys/socket.h> #include <core/SkCanvas.h> #include <core/SkDevice.h> @@ -24,6 +25,7 @@ #include "GraphicsJNI.h" #include "jni.h" +#include <nativehelper/JNIHelp.h> #include <android_runtime/AndroidRuntime.h> #include <utils/misc.h> @@ -78,6 +80,7 @@ static void android_view_ViewRoot_abandonGlCaches(JNIEnv* env, jobject) { SkGLCanvas::AbandonAllTextures(); } + // ---------------------------------------------------------------------------- const char* const kClassPathName = "android/view/ViewRoot"; diff --git a/core/jni/com_google_android_gles_jni_EGLImpl.cpp b/core/jni/com_google_android_gles_jni_EGLImpl.cpp index 6a8c4b9281cc..941ed6394714 100644 --- a/core/jni/com_google_android_gles_jni_EGLImpl.cpp +++ b/core/jni/com_google_android_gles_jni_EGLImpl.cpp @@ -16,6 +16,7 @@ */ #include <android_runtime/AndroidRuntime.h> +#include <android_runtime/android_view_Surface.h> #include <utils/misc.h> #include <EGL/egl.h> @@ -95,9 +96,6 @@ static void nativeClassInit(JNIEnv *_env, jclass eglImplClass) gSurface_NativePixelRefFieldID = _env->GetFieldID(gSurface_class, "mNativePixelRef", "I"); gConfig_EGLConfigFieldID = _env->GetFieldID(gConfig_class, "mEGLConfig", "I"); - jclass surface_class = _env->FindClass("android/view/Surface"); - gSurface_SurfaceFieldID = _env->GetFieldID(surface_class, "mSurface", "I"); - jclass bitmap_class = _env->FindClass("android/graphics/Bitmap"); gBitmap_NativeBitmapFieldID = _env->GetFieldID(bitmap_class, "mNativeBitmap", "I"); } @@ -325,19 +323,20 @@ static jint jni_eglCreateWindowSurface(JNIEnv *_env, jobject _this, jobject disp } EGLDisplay dpy = getDisplay(_env, display); EGLContext cnf = getConfig(_env, config); - Surface* window = 0; + sp<ANativeWindow> window; if (native_window == NULL) { not_valid_surface: doThrow(_env, "java/lang/IllegalArgumentException", "Make sure the SurfaceView or associated SurfaceHolder has a valid Surface"); return 0; } - window = (Surface*)_env->GetIntField(native_window, gSurface_SurfaceFieldID); + + window = android_Surface_getNativeWindow(_env, native_window); if (window == NULL) goto not_valid_surface; jint* base = beginNativeAttribList(_env, attrib_list); - EGLSurface sur = eglCreateWindowSurface(dpy, cnf, window, base); + EGLSurface sur = eglCreateWindowSurface(dpy, cnf, window.get(), base); endNativeAttributeList(_env, attrib_list, base); return (jint)sur; } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 2a2208f1ced0..9b9b4be18561 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -50,6 +50,8 @@ <protected-broadcast android:name="android.intent.action.ACTION_SHUTDOWN" /> <protected-broadcast android:name="android.intent.action.DEVICE_STORAGE_LOW" /> <protected-broadcast android:name="android.intent.action.DEVICE_STORAGE_OK" /> + <protected-broadcast android:name="android.intent.action.DEVICE_STORAGE_FULL" /> + <protected-broadcast android:name="android.intent.action.DEVICE_STORAGE_NOT_FULL" /> <protected-broadcast android:name="android.intent.action.NEW_OUTGOING_CALL" /> <protected-broadcast android:name="android.intent.action.REBOOT" /> <protected-broadcast android:name="android.intent.action.DOCK_EVENT" /> @@ -80,6 +82,10 @@ <protected-broadcast android:name="android.bluetooth.device.action.PAIRING_REQUEST" /> <protected-broadcast android:name="android.bluetooth.device.action.PAIRING_CANCEL" /> + <protected-broadcast android:name="android.hardware.action.USB_CONNECTED" /> + <protected-broadcast android:name="android.hardware.action.USB_DISCONNECTED" /> + <protected-broadcast android:name="android.hardware.action.USB_STATE" /> + <!-- ====================================== --> <!-- Permissions for things that cost money --> <!-- ====================================== --> @@ -187,20 +193,6 @@ android:label="@string/permlab_writeContacts" android:description="@string/permdesc_writeContacts" /> - <!-- Allows an application to read the owner's data. --> - <permission android:name="android.permission.READ_OWNER_DATA" - android:permissionGroup="android.permission-group.PERSONAL_INFO" - android:protectionLevel="dangerous" - android:label="@string/permlab_readOwnerData" - android:description="@string/permdesc_readOwnerData" /> - - <!-- Allows an application to write (but not read) the owner's data. --> - <permission android:name="android.permission.WRITE_OWNER_DATA" - android:permissionGroup="android.permission-group.PERSONAL_INFO" - android:protectionLevel="dangerous" - android:label="@string/permlab_writeOwnerData" - android:description="@string/permdesc_writeOwnerData" /> - <!-- Allows an application to read the user's calendar data. --> <permission android:name="android.permission.READ_CALENDAR" android:permissionGroup="android.permission-group.PERSONAL_INFO" @@ -436,6 +428,14 @@ android:label="@string/permlab_flashlight" android:description="@string/permdesc_flashlight" /> + <!-- Allows an application to access USB devices + @hide --> + <permission android:name="android.permission.ACCESS_USB" + android:permissionGroup="android.permission-group.HARDWARE_CONTROLS" + android:protectionLevel="signatureOrSystem" + android:label="@string/permlab_accessUsb" + android:description="@string/permdesc_accessUsb" /> + <!-- Allows access to hardware peripherals. Intended only for hardware testing --> <permission android:name="android.permission.HARDWARE_TEST" android:permissionGroup="android.permission-group.HARDWARE_CONTROLS" @@ -613,7 +613,10 @@ android:label="@string/permlab_setAnimationScale" android:description="@string/permdesc_setAnimationScale" /> - <!-- Allow an application to make its activities persistent. --> + <!-- @deprecated This functionality will be removed in the future; please do + not use. + + Allow an application to make its activities persistent. --> <permission android:name="android.permission.PERSISTENT_ACTIVITY" android:permissionGroup="android.permission-group.SYSTEM_TOOLS" android:protectionLevel="dangerous" @@ -755,7 +758,7 @@ <!-- Allows applications to disable the keyguard --> <permission android:name="android.permission.DISABLE_KEYGUARD" android:permissionGroup="android.permission-group.SYSTEM_TOOLS" - android:protectionLevel="normal" + android:protectionLevel="dangerous" android:description="@string/permdesc_disableKeyguard" android:label="@string/permlab_disableKeyguard" /> @@ -908,6 +911,13 @@ android:description="@string/permdesc_statusBar" android:protectionLevel="signatureOrSystem" /> + <!-- Allows an application to be the status bar. Currently used only by SystemUI.apk + @hide --> + <permission android:name="android.permission.STATUS_BAR_SERVICE" + android:label="@string/permlab_statusBarService" + android:description="@string/permdesc_statusBarService" + android:protectionLevel="signature" /> + <!-- Allows an application to force a BACK operation on whatever is the top activity. --> <permission android:name="android.permission.FORCE_BACK" @@ -920,7 +930,7 @@ <permission android:name="android.permission.UPDATE_DEVICE_STATS" android:label="@string/permlab_batteryStats" android:description="@string/permdesc_batteryStats" - android:protectionLevel="signature" /> + android:protectionLevel="signatureOrSystem" /> <!-- Allows an application to open windows that are for use by parts of the system user interface. Not for use by third party apps. --> @@ -1250,6 +1260,15 @@ <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </activity> + <activity android:name="com.android.internal.app.HeavyWeightSwitcherActivity" + android:theme="@style/Theme.Dialog" + android:label="@string/heavy_weight_switcher_title" + android:finishOnCloseSystemDialogs="true" + android:excludeFromRecents="true"> + </activity> + <activity android:name="com.android.internal.app.PlatLogoActivity" + android:theme="@style/Theme.NoTitleBar.Fullscreen"> + </activity> <activity android:name="com.android.internal.app.DisableCarModeActivity" android:theme="@style/Theme.NoDisplay" android:excludeFromRecents="true"> @@ -1263,9 +1282,6 @@ <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </activity> - <activity android:name="com.android.server.status.UsbStorageActivity" - android:excludeFromRecents="true"> - </activity> <activity android:name="com.android.internal.app.ExternalMediaFormatActivity" android:theme="@style/Theme.Dialog.Alert" android:excludeFromRecents="true"> diff --git a/core/res/res/anim/priority_alert_enter.xml b/core/res/res/anim/priority_alert_enter.xml new file mode 100644 index 000000000000..c8ce23c6b66a --- /dev/null +++ b/core/res/res/anim/priority_alert_enter.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="utf-8"?> +<set xmlns:android="http://schemas.android.com/apk/res/android" + > + <scale + android:interpolator="@android:anim/overshoot_interpolator" + android:fromXScale="0.7" android:toXScale="1.0" + android:fromYScale="0.7" android:toYScale="1.0" + android:pivotX="50%" android:pivotY="50%" + android:duration="@android:integer/config_shortAnimTime" /> + <alpha + android:interpolator="@android:anim/decelerate_interpolator" + android:fromAlpha="0.0" android:toAlpha="1.0" + android:duration="@android:integer/config_shortAnimTime" /> +</set> diff --git a/core/res/res/anim/priority_alert_exit.xml b/core/res/res/anim/priority_alert_exit.xml new file mode 100644 index 000000000000..b538cb28b5c9 --- /dev/null +++ b/core/res/res/anim/priority_alert_exit.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="utf-8"?> +<set xmlns:android="http://schemas.android.com/apk/res/android" + > + <scale + android:interpolator="@android:anim/accelerate_interpolator" + android:fromXScale="1.0" android:toXScale="0.7" + android:fromYScale="1.0" android:toYScale="0.7" + android:pivotX="50%" android:pivotY="50%" + android:duration="@android:integer/config_shortAnimTime" /> + <alpha + android:interpolator="@android:anim/accelerate_interpolator" + android:fromAlpha="1.0" android:toAlpha="0.0" + android:duration="@android:integer/config_shortAnimTime" /> +</set> diff --git a/core/res/res/drawable-hdpi/battery_charge_fill_empty.9.png b/core/res/res/drawable-hdpi/battery_charge_fill_empty.9.png Binary files differdeleted file mode 100644 index c4e70a81754a..000000000000 --- a/core/res/res/drawable-hdpi/battery_charge_fill_empty.9.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/battery_charge_fill_full.9.png b/core/res/res/drawable-hdpi/battery_charge_fill_full.9.png Binary files differdeleted file mode 100644 index ac66f5a8dbc9..000000000000 --- a/core/res/res/drawable-hdpi/battery_charge_fill_full.9.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/battery_charge_fill_warning.9.png b/core/res/res/drawable-hdpi/battery_charge_fill_warning.9.png Binary files differdeleted file mode 100644 index 32d99c686976..000000000000 --- a/core/res/res/drawable-hdpi/battery_charge_fill_warning.9.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/battery_low_battery.png b/core/res/res/drawable-hdpi/battery_low_battery.png Binary files differdeleted file mode 100644 index d894f7bbe894..000000000000 --- a/core/res/res/drawable-hdpi/battery_low_battery.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/divider_horizontal_dark_opaque.9.png b/core/res/res/drawable-hdpi/divider_horizontal_dark_opaque.9.png Binary files differindex 9444f0d18583..d21aad295c29 100644 --- a/core/res/res/drawable-hdpi/divider_horizontal_dark_opaque.9.png +++ b/core/res/res/drawable-hdpi/divider_horizontal_dark_opaque.9.png diff --git a/core/res/res/drawable-hdpi/menu_background.9.png b/core/res/res/drawable-hdpi/menu_background.9.png Binary files differindex cbe62af2e61e..60f073192d1f 100644 --- a/core/res/res/drawable-hdpi/menu_background.9.png +++ b/core/res/res/drawable-hdpi/menu_background.9.png diff --git a/core/res/res/drawable-hdpi/menu_background_fill_parent_width.9.png b/core/res/res/drawable-hdpi/menu_background_fill_parent_width.9.png Binary files differindex 0812487b5b24..09eac9b4895f 100644 --- a/core/res/res/drawable-hdpi/menu_background_fill_parent_width.9.png +++ b/core/res/res/drawable-hdpi/menu_background_fill_parent_width.9.png diff --git a/core/res/res/drawable-hdpi/presence_audio_away.png b/core/res/res/drawable-hdpi/presence_audio_away.png Binary files differnew file mode 100644 index 000000000000..fb5e1232bb65 --- /dev/null +++ b/core/res/res/drawable-hdpi/presence_audio_away.png diff --git a/core/res/res/drawable-hdpi/presence_audio_busy.png b/core/res/res/drawable-hdpi/presence_audio_busy.png Binary files differnew file mode 100644 index 000000000000..81aeb0161556 --- /dev/null +++ b/core/res/res/drawable-hdpi/presence_audio_busy.png diff --git a/core/res/res/drawable-hdpi/presence_audio_online.png b/core/res/res/drawable-hdpi/presence_audio_online.png Binary files differnew file mode 100644 index 000000000000..553acbb4841e --- /dev/null +++ b/core/res/res/drawable-hdpi/presence_audio_online.png diff --git a/core/res/res/drawable-hdpi/presence_away.png b/core/res/res/drawable-hdpi/presence_away.png Binary files differindex d4aa66ba2b15..84b00bbfcc5e 100644 --- a/core/res/res/drawable-hdpi/presence_away.png +++ b/core/res/res/drawable-hdpi/presence_away.png diff --git a/core/res/res/drawable-hdpi/presence_busy.png b/core/res/res/drawable-hdpi/presence_busy.png Binary files differindex 4b278539d527..d77a46396e9f 100644 --- a/core/res/res/drawable-hdpi/presence_busy.png +++ b/core/res/res/drawable-hdpi/presence_busy.png diff --git a/core/res/res/drawable-hdpi/presence_invisible.png b/core/res/res/drawable-hdpi/presence_invisible.png Binary files differindex 0e27fbaa6051..0d83e70a7ff1 100644 --- a/core/res/res/drawable-hdpi/presence_invisible.png +++ b/core/res/res/drawable-hdpi/presence_invisible.png diff --git a/core/res/res/drawable-hdpi/presence_offline.png b/core/res/res/drawable-hdpi/presence_offline.png Binary files differindex e511aa4b95bf..2619e9fcf3a3 100644 --- a/core/res/res/drawable-hdpi/presence_offline.png +++ b/core/res/res/drawable-hdpi/presence_offline.png diff --git a/core/res/res/drawable-hdpi/presence_online.png b/core/res/res/drawable-hdpi/presence_online.png Binary files differindex d787d2f78a35..7e846efe467e 100644 --- a/core/res/res/drawable-hdpi/presence_online.png +++ b/core/res/res/drawable-hdpi/presence_online.png diff --git a/core/res/res/drawable-hdpi/presence_video_away.png b/core/res/res/drawable-hdpi/presence_video_away.png Binary files differnew file mode 100644 index 000000000000..ab5493b743e9 --- /dev/null +++ b/core/res/res/drawable-hdpi/presence_video_away.png diff --git a/core/res/res/drawable-hdpi/presence_video_busy.png b/core/res/res/drawable-hdpi/presence_video_busy.png Binary files differnew file mode 100644 index 000000000000..456cdb0e4bd8 --- /dev/null +++ b/core/res/res/drawable-hdpi/presence_video_busy.png diff --git a/core/res/res/drawable-hdpi/presence_video_online.png b/core/res/res/drawable-hdpi/presence_video_online.png Binary files differnew file mode 100644 index 000000000000..bd9f8a420a19 --- /dev/null +++ b/core/res/res/drawable-hdpi/presence_video_online.png diff --git a/core/res/res/drawable-hdpi/stat_notify_alarm.png b/core/res/res/drawable-hdpi/stat_notify_alarm.png Binary files differdeleted file mode 100644 index 89daee190668..000000000000 --- a/core/res/res/drawable-hdpi/stat_notify_alarm.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/stat_sys_data_bluetooth.png b/core/res/res/drawable-hdpi/stat_sys_data_bluetooth.png Binary files differindex 96dc0855b80c..7a8b78f6e8db 100644 --- a/core/res/res/drawable-hdpi/stat_sys_data_bluetooth.png +++ b/core/res/res/drawable-hdpi/stat_sys_data_bluetooth.png diff --git a/core/res/res/drawable-hdpi/stat_sys_data_bluetooth_connected.png b/core/res/res/drawable-hdpi/stat_sys_data_bluetooth_connected.png Binary files differdeleted file mode 100644 index 1e4bbf52c4f5..000000000000 --- a/core/res/res/drawable-hdpi/stat_sys_data_bluetooth_connected.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/stat_sys_data_connected_1x.png b/core/res/res/drawable-hdpi/stat_sys_data_connected_1x.png Binary files differdeleted file mode 100644 index 12d4ac411e97..000000000000 --- a/core/res/res/drawable-hdpi/stat_sys_data_connected_1x.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/stat_sys_data_connected_3g.png b/core/res/res/drawable-hdpi/stat_sys_data_connected_3g.png Binary files differdeleted file mode 100644 index 4f1f3774d7dc..000000000000 --- a/core/res/res/drawable-hdpi/stat_sys_data_connected_3g.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/stat_sys_data_connected_e.png b/core/res/res/drawable-hdpi/stat_sys_data_connected_e.png Binary files differdeleted file mode 100644 index c65f56ae0179..000000000000 --- a/core/res/res/drawable-hdpi/stat_sys_data_connected_e.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/stat_sys_data_connected_g.png b/core/res/res/drawable-hdpi/stat_sys_data_connected_g.png Binary files differdeleted file mode 100644 index 4aca0c7dda99..000000000000 --- a/core/res/res/drawable-hdpi/stat_sys_data_connected_g.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/stat_sys_data_connected_h.png b/core/res/res/drawable-hdpi/stat_sys_data_connected_h.png Binary files differdeleted file mode 100755 index 24e07aba1dee..000000000000 --- a/core/res/res/drawable-hdpi/stat_sys_data_connected_h.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/stat_sys_data_in_1x.png b/core/res/res/drawable-hdpi/stat_sys_data_in_1x.png Binary files differdeleted file mode 100644 index 511e22a1f93f..000000000000 --- a/core/res/res/drawable-hdpi/stat_sys_data_in_1x.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/stat_sys_data_in_3g.png b/core/res/res/drawable-hdpi/stat_sys_data_in_3g.png Binary files differdeleted file mode 100644 index 64f8087f4e11..000000000000 --- a/core/res/res/drawable-hdpi/stat_sys_data_in_3g.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/stat_sys_data_in_e.png b/core/res/res/drawable-hdpi/stat_sys_data_in_e.png Binary files differdeleted file mode 100644 index 90aaf71a994d..000000000000 --- a/core/res/res/drawable-hdpi/stat_sys_data_in_e.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/stat_sys_data_in_g.png b/core/res/res/drawable-hdpi/stat_sys_data_in_g.png Binary files differdeleted file mode 100644 index c21387e27f63..000000000000 --- a/core/res/res/drawable-hdpi/stat_sys_data_in_g.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/stat_sys_data_in_h.png b/core/res/res/drawable-hdpi/stat_sys_data_in_h.png Binary files differdeleted file mode 100755 index f2f6daa29309..000000000000 --- a/core/res/res/drawable-hdpi/stat_sys_data_in_h.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/stat_sys_data_inandout_1x.png b/core/res/res/drawable-hdpi/stat_sys_data_inandout_1x.png Binary files differdeleted file mode 100644 index 19ea80c5de9b..000000000000 --- a/core/res/res/drawable-hdpi/stat_sys_data_inandout_1x.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/stat_sys_data_inandout_3g.png b/core/res/res/drawable-hdpi/stat_sys_data_inandout_3g.png Binary files differdeleted file mode 100644 index ebfa6fb6ee7f..000000000000 --- a/core/res/res/drawable-hdpi/stat_sys_data_inandout_3g.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/stat_sys_data_inandout_e.png b/core/res/res/drawable-hdpi/stat_sys_data_inandout_e.png Binary files differdeleted file mode 100644 index 7ccd7dea45ad..000000000000 --- a/core/res/res/drawable-hdpi/stat_sys_data_inandout_e.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/stat_sys_data_inandout_g.png b/core/res/res/drawable-hdpi/stat_sys_data_inandout_g.png Binary files differdeleted file mode 100644 index c614d0d84187..000000000000 --- a/core/res/res/drawable-hdpi/stat_sys_data_inandout_g.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/stat_sys_data_inandout_h.png b/core/res/res/drawable-hdpi/stat_sys_data_inandout_h.png Binary files differdeleted file mode 100755 index 5d6ef05c0fb1..000000000000 --- a/core/res/res/drawable-hdpi/stat_sys_data_inandout_h.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/stat_sys_data_out_1x.png b/core/res/res/drawable-hdpi/stat_sys_data_out_1x.png Binary files differdeleted file mode 100644 index adf5f955c493..000000000000 --- a/core/res/res/drawable-hdpi/stat_sys_data_out_1x.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/stat_sys_data_out_3g.png b/core/res/res/drawable-hdpi/stat_sys_data_out_3g.png Binary files differdeleted file mode 100644 index 9936f2ab7377..000000000000 --- a/core/res/res/drawable-hdpi/stat_sys_data_out_3g.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/stat_sys_data_out_e.png b/core/res/res/drawable-hdpi/stat_sys_data_out_e.png Binary files differdeleted file mode 100644 index 904c5650e1a1..000000000000 --- a/core/res/res/drawable-hdpi/stat_sys_data_out_e.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/stat_sys_data_out_g.png b/core/res/res/drawable-hdpi/stat_sys_data_out_g.png Binary files differdeleted file mode 100644 index 1261c152a3ad..000000000000 --- a/core/res/res/drawable-hdpi/stat_sys_data_out_g.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/stat_sys_data_out_h.png b/core/res/res/drawable-hdpi/stat_sys_data_out_h.png Binary files differdeleted file mode 100755 index 5e3122d612d6..000000000000 --- a/core/res/res/drawable-hdpi/stat_sys_data_out_h.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/stat_sys_gps_acquiring.png b/core/res/res/drawable-hdpi/stat_sys_gps_acquiring.png Binary files differdeleted file mode 100644 index 9003d671b164..000000000000 --- a/core/res/res/drawable-hdpi/stat_sys_gps_acquiring.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/stat_sys_no_sim.png b/core/res/res/drawable-hdpi/stat_sys_no_sim.png Binary files differdeleted file mode 100644 index 157491e8e5c7..000000000000 --- a/core/res/res/drawable-hdpi/stat_sys_no_sim.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/stat_sys_phone_call_bluetooth.png b/core/res/res/drawable-hdpi/stat_sys_phone_call_bluetooth.png Binary files differdeleted file mode 100644 index 2d132379af31..000000000000 --- a/core/res/res/drawable-hdpi/stat_sys_phone_call_bluetooth.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/stat_sys_r_signal_0.png b/core/res/res/drawable-hdpi/stat_sys_r_signal_0.png Binary files differdeleted file mode 100644 index 95ba18137f6b..000000000000 --- a/core/res/res/drawable-hdpi/stat_sys_r_signal_0.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/stat_sys_r_signal_1.png b/core/res/res/drawable-hdpi/stat_sys_r_signal_1.png Binary files differdeleted file mode 100644 index adf668d1c507..000000000000 --- a/core/res/res/drawable-hdpi/stat_sys_r_signal_1.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/stat_sys_r_signal_2.png b/core/res/res/drawable-hdpi/stat_sys_r_signal_2.png Binary files differdeleted file mode 100644 index 7bf6b51066fe..000000000000 --- a/core/res/res/drawable-hdpi/stat_sys_r_signal_2.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/stat_sys_r_signal_3.png b/core/res/res/drawable-hdpi/stat_sys_r_signal_3.png Binary files differdeleted file mode 100644 index 78738ac73ec7..000000000000 --- a/core/res/res/drawable-hdpi/stat_sys_r_signal_3.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/stat_sys_r_signal_4.png b/core/res/res/drawable-hdpi/stat_sys_r_signal_4.png Binary files differdeleted file mode 100644 index ac881431aae2..000000000000 --- a/core/res/res/drawable-hdpi/stat_sys_r_signal_4.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/stat_sys_ringer_silent.png b/core/res/res/drawable-hdpi/stat_sys_ringer_silent.png Binary files differdeleted file mode 100644 index bdd37e10d8e3..000000000000 --- a/core/res/res/drawable-hdpi/stat_sys_ringer_silent.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/stat_sys_ringer_vibrate.png b/core/res/res/drawable-hdpi/stat_sys_ringer_vibrate.png Binary files differdeleted file mode 100644 index 21c1c0846946..000000000000 --- a/core/res/res/drawable-hdpi/stat_sys_ringer_vibrate.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/stat_sys_roaming_cdma_0.png b/core/res/res/drawable-hdpi/stat_sys_roaming_cdma_0.png Binary files differdeleted file mode 100644 index adde938b24f1..000000000000 --- a/core/res/res/drawable-hdpi/stat_sys_roaming_cdma_0.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/stat_sys_roaming_cdma_flash_anim0.png b/core/res/res/drawable-hdpi/stat_sys_roaming_cdma_flash_anim0.png Binary files differdeleted file mode 100644 index 245677b6a725..000000000000 --- a/core/res/res/drawable-hdpi/stat_sys_roaming_cdma_flash_anim0.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/stat_sys_roaming_cdma_flash_anim1.png b/core/res/res/drawable-hdpi/stat_sys_roaming_cdma_flash_anim1.png Binary files differdeleted file mode 100644 index adde938b24f1..000000000000 --- a/core/res/res/drawable-hdpi/stat_sys_roaming_cdma_flash_anim1.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/stat_sys_signal_0.png b/core/res/res/drawable-hdpi/stat_sys_signal_0.png Binary files differdeleted file mode 100644 index 3e317ddfa7e4..000000000000 --- a/core/res/res/drawable-hdpi/stat_sys_signal_0.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/stat_sys_signal_1.png b/core/res/res/drawable-hdpi/stat_sys_signal_1.png Binary files differdeleted file mode 100644 index 72329f82a731..000000000000 --- a/core/res/res/drawable-hdpi/stat_sys_signal_1.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/stat_sys_signal_2.png b/core/res/res/drawable-hdpi/stat_sys_signal_2.png Binary files differdeleted file mode 100644 index 558c49cd7caa..000000000000 --- a/core/res/res/drawable-hdpi/stat_sys_signal_2.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/stat_sys_signal_3.png b/core/res/res/drawable-hdpi/stat_sys_signal_3.png Binary files differdeleted file mode 100644 index 6440bddb6927..000000000000 --- a/core/res/res/drawable-hdpi/stat_sys_signal_3.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/stat_sys_signal_4.png b/core/res/res/drawable-hdpi/stat_sys_signal_4.png Binary files differdeleted file mode 100644 index fe204230c736..000000000000 --- a/core/res/res/drawable-hdpi/stat_sys_signal_4.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/stat_sys_signal_flightmode.png b/core/res/res/drawable-hdpi/stat_sys_signal_flightmode.png Binary files differdeleted file mode 100644 index 7a419f1616e0..000000000000 --- a/core/res/res/drawable-hdpi/stat_sys_signal_flightmode.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/stat_sys_signal_null.png b/core/res/res/drawable-hdpi/stat_sys_signal_null.png Binary files differdeleted file mode 100644 index 1adc05aa0831..000000000000 --- a/core/res/res/drawable-hdpi/stat_sys_signal_null.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/stat_sys_tty_mode.png b/core/res/res/drawable-hdpi/stat_sys_tty_mode.png Binary files differdeleted file mode 100644 index 4e161c6f6e4c..000000000000 --- a/core/res/res/drawable-hdpi/stat_sys_tty_mode.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/stat_sys_vp_phone_call_bluetooth.png b/core/res/res/drawable-hdpi/stat_sys_vp_phone_call_bluetooth.png Binary files differdeleted file mode 100644 index 2d132379af31..000000000000 --- a/core/res/res/drawable-hdpi/stat_sys_vp_phone_call_bluetooth.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/stat_sys_wifi_signal_0.png b/core/res/res/drawable-hdpi/stat_sys_wifi_signal_0.png Binary files differdeleted file mode 100644 index 55a2ad82f976..000000000000 --- a/core/res/res/drawable-hdpi/stat_sys_wifi_signal_0.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/stat_sys_wifi_signal_1.png b/core/res/res/drawable-hdpi/stat_sys_wifi_signal_1.png Binary files differdeleted file mode 100644 index d16b3e8d8e78..000000000000 --- a/core/res/res/drawable-hdpi/stat_sys_wifi_signal_1.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/stat_sys_wifi_signal_2.png b/core/res/res/drawable-hdpi/stat_sys_wifi_signal_2.png Binary files differdeleted file mode 100644 index 2511083e9a16..000000000000 --- a/core/res/res/drawable-hdpi/stat_sys_wifi_signal_2.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/stat_sys_wifi_signal_3.png b/core/res/res/drawable-hdpi/stat_sys_wifi_signal_3.png Binary files differdeleted file mode 100644 index e0799a573c89..000000000000 --- a/core/res/res/drawable-hdpi/stat_sys_wifi_signal_3.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/stat_sys_wifi_signal_4.png b/core/res/res/drawable-hdpi/stat_sys_wifi_signal_4.png Binary files differdeleted file mode 100644 index 2385c3a492ed..000000000000 --- a/core/res/res/drawable-hdpi/stat_sys_wifi_signal_4.png +++ /dev/null diff --git a/core/res/res/drawable-hdpi/status_bar_background.png b/core/res/res/drawable-hdpi/status_bar_background.png Binary files differindex e6a865a78688..3d00cd0f9a5e 100644 --- a/core/res/res/drawable-hdpi/status_bar_background.png +++ b/core/res/res/drawable-hdpi/status_bar_background.png diff --git a/core/res/res/drawable-hdpi/status_bar_close_on.9.png b/core/res/res/drawable-hdpi/status_bar_close_on.9.png Binary files differindex 5acf638489a0..f313ffba5769 100644 --- a/core/res/res/drawable-hdpi/status_bar_close_on.9.png +++ b/core/res/res/drawable-hdpi/status_bar_close_on.9.png diff --git a/core/res/res/drawable-hdpi/status_bar_header_background.9.png b/core/res/res/drawable-hdpi/status_bar_header_background.9.png Binary files differindex be36ff26bb8a..37b5fef592d1 100644 --- a/core/res/res/drawable-hdpi/status_bar_header_background.9.png +++ b/core/res/res/drawable-hdpi/status_bar_header_background.9.png diff --git a/core/res/res/drawable-hdpi/status_bar_item_app_background_normal.9.png b/core/res/res/drawable-hdpi/status_bar_item_app_background_normal.9.png Binary files differindex 4fbfa4f0ecc7..bdcb37869e75 100644 --- a/core/res/res/drawable-hdpi/status_bar_item_app_background_normal.9.png +++ b/core/res/res/drawable-hdpi/status_bar_item_app_background_normal.9.png diff --git a/core/res/res/drawable-hdpi/status_bar_item_background_normal.9.png b/core/res/res/drawable-hdpi/status_bar_item_background_normal.9.png Binary files differindex c01c018683f2..fa27ee4d5a6d 100644 --- a/core/res/res/drawable-hdpi/status_bar_item_background_normal.9.png +++ b/core/res/res/drawable-hdpi/status_bar_item_background_normal.9.png diff --git a/core/res/res/drawable-hdpi/statusbar_background.9.png b/core/res/res/drawable-hdpi/statusbar_background.9.png Binary files differindex dcca6951d3ae..a4be29879663 100644 --- a/core/res/res/drawable-hdpi/statusbar_background.9.png +++ b/core/res/res/drawable-hdpi/statusbar_background.9.png diff --git a/core/res/res/drawable-hdpi/text_select_handle.png b/core/res/res/drawable-hdpi/text_select_handle.png Binary files differnew file mode 100644 index 000000000000..80d48ab2ca1f --- /dev/null +++ b/core/res/res/drawable-hdpi/text_select_handle.png diff --git a/core/res/res/drawable-mdpi/battery_charge_fill_empty.9.png b/core/res/res/drawable-mdpi/battery_charge_fill_empty.9.png Binary files differdeleted file mode 100644 index 9ed20ba23bac..000000000000 --- a/core/res/res/drawable-mdpi/battery_charge_fill_empty.9.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/battery_charge_fill_full.9.png b/core/res/res/drawable-mdpi/battery_charge_fill_full.9.png Binary files differdeleted file mode 100644 index 8e6aaca1e7dc..000000000000 --- a/core/res/res/drawable-mdpi/battery_charge_fill_full.9.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/battery_charge_fill_warning.9.png b/core/res/res/drawable-mdpi/battery_charge_fill_warning.9.png Binary files differdeleted file mode 100644 index d3287db975d2..000000000000 --- a/core/res/res/drawable-mdpi/battery_charge_fill_warning.9.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/battery_low_battery.png b/core/res/res/drawable-mdpi/battery_low_battery.png Binary files differdeleted file mode 100644 index 60bbe6c1f85f..000000000000 --- a/core/res/res/drawable-mdpi/battery_low_battery.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/menu_background.9.png b/core/res/res/drawable-mdpi/menu_background.9.png Binary files differindex ee99583594a2..9f16df9aa778 100644 --- a/core/res/res/drawable-mdpi/menu_background.9.png +++ b/core/res/res/drawable-mdpi/menu_background.9.png diff --git a/core/res/res/drawable-mdpi/menu_background_fill_parent_width.9.png b/core/res/res/drawable-mdpi/menu_background_fill_parent_width.9.png Binary files differindex d368983df3e3..da3011b1c3c8 100644 --- a/core/res/res/drawable-mdpi/menu_background_fill_parent_width.9.png +++ b/core/res/res/drawable-mdpi/menu_background_fill_parent_width.9.png diff --git a/core/res/res/drawable-mdpi/presence_audio_away.png b/core/res/res/drawable-mdpi/presence_audio_away.png Binary files differnew file mode 100644 index 000000000000..db96754ae915 --- /dev/null +++ b/core/res/res/drawable-mdpi/presence_audio_away.png diff --git a/core/res/res/drawable-mdpi/presence_audio_busy.png b/core/res/res/drawable-mdpi/presence_audio_busy.png Binary files differnew file mode 100644 index 000000000000..0a299481bd53 --- /dev/null +++ b/core/res/res/drawable-mdpi/presence_audio_busy.png diff --git a/core/res/res/drawable-mdpi/presence_audio_online.png b/core/res/res/drawable-mdpi/presence_audio_online.png Binary files differnew file mode 100644 index 000000000000..df415ba1ea7b --- /dev/null +++ b/core/res/res/drawable-mdpi/presence_audio_online.png diff --git a/core/res/res/drawable-mdpi/presence_away.png b/core/res/res/drawable-mdpi/presence_away.png Binary files differindex f8120df43bfe..f98e908f1323 100644 --- a/core/res/res/drawable-mdpi/presence_away.png +++ b/core/res/res/drawable-mdpi/presence_away.png diff --git a/core/res/res/drawable-mdpi/presence_busy.png b/core/res/res/drawable-mdpi/presence_busy.png Binary files differindex 9d7620b1d0c8..933261f48904 100644 --- a/core/res/res/drawable-mdpi/presence_busy.png +++ b/core/res/res/drawable-mdpi/presence_busy.png diff --git a/core/res/res/drawable-mdpi/presence_invisible.png b/core/res/res/drawable-mdpi/presence_invisible.png Binary files differindex 21399a4f3618..8d7275632164 100644 --- a/core/res/res/drawable-mdpi/presence_invisible.png +++ b/core/res/res/drawable-mdpi/presence_invisible.png diff --git a/core/res/res/drawable-mdpi/presence_offline.png b/core/res/res/drawable-mdpi/presence_offline.png Binary files differindex 3941b8205251..fc783962206f 100644 --- a/core/res/res/drawable-mdpi/presence_offline.png +++ b/core/res/res/drawable-mdpi/presence_offline.png diff --git a/core/res/res/drawable-mdpi/presence_online.png b/core/res/res/drawable-mdpi/presence_online.png Binary files differindex 22d5683e2f59..943eee0ad78e 100644 --- a/core/res/res/drawable-mdpi/presence_online.png +++ b/core/res/res/drawable-mdpi/presence_online.png diff --git a/core/res/res/drawable-mdpi/presence_video_away.png b/core/res/res/drawable-mdpi/presence_video_away.png Binary files differnew file mode 100644 index 000000000000..f3ec5d47be20 --- /dev/null +++ b/core/res/res/drawable-mdpi/presence_video_away.png diff --git a/core/res/res/drawable-mdpi/presence_video_busy.png b/core/res/res/drawable-mdpi/presence_video_busy.png Binary files differnew file mode 100644 index 000000000000..7379e004031b --- /dev/null +++ b/core/res/res/drawable-mdpi/presence_video_busy.png diff --git a/core/res/res/drawable-mdpi/presence_video_online.png b/core/res/res/drawable-mdpi/presence_video_online.png Binary files differnew file mode 100644 index 000000000000..24ceb39a8641 --- /dev/null +++ b/core/res/res/drawable-mdpi/presence_video_online.png diff --git a/core/res/res/drawable-mdpi/stat_notify_alarm.png b/core/res/res/drawable-mdpi/stat_notify_alarm.png Binary files differdeleted file mode 100644 index 1b01b850619d..000000000000 --- a/core/res/res/drawable-mdpi/stat_notify_alarm.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/stat_sys_data_bluetooth_connected.png b/core/res/res/drawable-mdpi/stat_sys_data_bluetooth_connected.png Binary files differdeleted file mode 100755 index f09b83bfad67..000000000000 --- a/core/res/res/drawable-mdpi/stat_sys_data_bluetooth_connected.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/stat_sys_data_connected_1x.png b/core/res/res/drawable-mdpi/stat_sys_data_connected_1x.png Binary files differdeleted file mode 100644 index 130724fd0550..000000000000 --- a/core/res/res/drawable-mdpi/stat_sys_data_connected_1x.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/stat_sys_data_connected_3g.png b/core/res/res/drawable-mdpi/stat_sys_data_connected_3g.png Binary files differdeleted file mode 100644 index a1092807a9cf..000000000000 --- a/core/res/res/drawable-mdpi/stat_sys_data_connected_3g.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/stat_sys_data_connected_e.png b/core/res/res/drawable-mdpi/stat_sys_data_connected_e.png Binary files differdeleted file mode 100644 index c55264447967..000000000000 --- a/core/res/res/drawable-mdpi/stat_sys_data_connected_e.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/stat_sys_data_connected_g.png b/core/res/res/drawable-mdpi/stat_sys_data_connected_g.png Binary files differdeleted file mode 100644 index f7edb49954c4..000000000000 --- a/core/res/res/drawable-mdpi/stat_sys_data_connected_g.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/stat_sys_data_connected_h.png b/core/res/res/drawable-mdpi/stat_sys_data_connected_h.png Binary files differdeleted file mode 100644 index 7d5413afcb6c..000000000000 --- a/core/res/res/drawable-mdpi/stat_sys_data_connected_h.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/stat_sys_data_in_1x.png b/core/res/res/drawable-mdpi/stat_sys_data_in_1x.png Binary files differdeleted file mode 100644 index 3155e632fe7b..000000000000 --- a/core/res/res/drawable-mdpi/stat_sys_data_in_1x.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/stat_sys_data_in_3g.png b/core/res/res/drawable-mdpi/stat_sys_data_in_3g.png Binary files differdeleted file mode 100644 index 01b003ce85ef..000000000000 --- a/core/res/res/drawable-mdpi/stat_sys_data_in_3g.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/stat_sys_data_in_e.png b/core/res/res/drawable-mdpi/stat_sys_data_in_e.png Binary files differdeleted file mode 100644 index bffa0eb1a5b1..000000000000 --- a/core/res/res/drawable-mdpi/stat_sys_data_in_e.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/stat_sys_data_in_g.png b/core/res/res/drawable-mdpi/stat_sys_data_in_g.png Binary files differdeleted file mode 100644 index 8884b482e3b4..000000000000 --- a/core/res/res/drawable-mdpi/stat_sys_data_in_g.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/stat_sys_data_in_h.png b/core/res/res/drawable-mdpi/stat_sys_data_in_h.png Binary files differdeleted file mode 100644 index 695b80c36bcf..000000000000 --- a/core/res/res/drawable-mdpi/stat_sys_data_in_h.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/stat_sys_data_inandout_1x.png b/core/res/res/drawable-mdpi/stat_sys_data_inandout_1x.png Binary files differdeleted file mode 100644 index 1017e3bb4166..000000000000 --- a/core/res/res/drawable-mdpi/stat_sys_data_inandout_1x.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/stat_sys_data_inandout_3g.png b/core/res/res/drawable-mdpi/stat_sys_data_inandout_3g.png Binary files differdeleted file mode 100644 index 365130064ebe..000000000000 --- a/core/res/res/drawable-mdpi/stat_sys_data_inandout_3g.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/stat_sys_data_inandout_e.png b/core/res/res/drawable-mdpi/stat_sys_data_inandout_e.png Binary files differdeleted file mode 100644 index 99533e0cade9..000000000000 --- a/core/res/res/drawable-mdpi/stat_sys_data_inandout_e.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/stat_sys_data_inandout_g.png b/core/res/res/drawable-mdpi/stat_sys_data_inandout_g.png Binary files differdeleted file mode 100644 index f4e5a12ed445..000000000000 --- a/core/res/res/drawable-mdpi/stat_sys_data_inandout_g.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/stat_sys_data_inandout_h.png b/core/res/res/drawable-mdpi/stat_sys_data_inandout_h.png Binary files differdeleted file mode 100644 index 467acd137a1c..000000000000 --- a/core/res/res/drawable-mdpi/stat_sys_data_inandout_h.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/stat_sys_data_out_1x.png b/core/res/res/drawable-mdpi/stat_sys_data_out_1x.png Binary files differdeleted file mode 100644 index 54187912f6b8..000000000000 --- a/core/res/res/drawable-mdpi/stat_sys_data_out_1x.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/stat_sys_data_out_3g.png b/core/res/res/drawable-mdpi/stat_sys_data_out_3g.png Binary files differdeleted file mode 100644 index f7f0f894d68d..000000000000 --- a/core/res/res/drawable-mdpi/stat_sys_data_out_3g.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/stat_sys_data_out_e.png b/core/res/res/drawable-mdpi/stat_sys_data_out_e.png Binary files differdeleted file mode 100644 index c91542635862..000000000000 --- a/core/res/res/drawable-mdpi/stat_sys_data_out_e.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/stat_sys_data_out_g.png b/core/res/res/drawable-mdpi/stat_sys_data_out_g.png Binary files differdeleted file mode 100644 index 5d36035158b9..000000000000 --- a/core/res/res/drawable-mdpi/stat_sys_data_out_g.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/stat_sys_data_out_h.png b/core/res/res/drawable-mdpi/stat_sys_data_out_h.png Binary files differdeleted file mode 100644 index da503054bf6f..000000000000 --- a/core/res/res/drawable-mdpi/stat_sys_data_out_h.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/stat_sys_gps_acquiring.png b/core/res/res/drawable-mdpi/stat_sys_gps_acquiring.png Binary files differdeleted file mode 100644 index 31bc94ec1e56..000000000000 --- a/core/res/res/drawable-mdpi/stat_sys_gps_acquiring.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/stat_sys_no_sim.png b/core/res/res/drawable-mdpi/stat_sys_no_sim.png Binary files differdeleted file mode 100644 index 2134d490fd01..000000000000 --- a/core/res/res/drawable-mdpi/stat_sys_no_sim.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/stat_sys_phone_call_bluetooth.png b/core/res/res/drawable-mdpi/stat_sys_phone_call_bluetooth.png Binary files differdeleted file mode 100644 index 7abfd194fb80..000000000000 --- a/core/res/res/drawable-mdpi/stat_sys_phone_call_bluetooth.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/stat_sys_r_signal_0.png b/core/res/res/drawable-mdpi/stat_sys_r_signal_0.png Binary files differdeleted file mode 100644 index bfbf18e63bde..000000000000 --- a/core/res/res/drawable-mdpi/stat_sys_r_signal_0.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/stat_sys_r_signal_1.png b/core/res/res/drawable-mdpi/stat_sys_r_signal_1.png Binary files differdeleted file mode 100644 index 896ba4d5e206..000000000000 --- a/core/res/res/drawable-mdpi/stat_sys_r_signal_1.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/stat_sys_r_signal_2.png b/core/res/res/drawable-mdpi/stat_sys_r_signal_2.png Binary files differdeleted file mode 100644 index af79eff67ff6..000000000000 --- a/core/res/res/drawable-mdpi/stat_sys_r_signal_2.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/stat_sys_r_signal_3.png b/core/res/res/drawable-mdpi/stat_sys_r_signal_3.png Binary files differdeleted file mode 100644 index 92c09c8c8728..000000000000 --- a/core/res/res/drawable-mdpi/stat_sys_r_signal_3.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/stat_sys_r_signal_4.png b/core/res/res/drawable-mdpi/stat_sys_r_signal_4.png Binary files differdeleted file mode 100644 index f04fb11b4e73..000000000000 --- a/core/res/res/drawable-mdpi/stat_sys_r_signal_4.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/stat_sys_ringer_silent.png b/core/res/res/drawable-mdpi/stat_sys_ringer_silent.png Binary files differdeleted file mode 100644 index d62f32d72b19..000000000000 --- a/core/res/res/drawable-mdpi/stat_sys_ringer_silent.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/stat_sys_ringer_vibrate.png b/core/res/res/drawable-mdpi/stat_sys_ringer_vibrate.png Binary files differdeleted file mode 100644 index 665ca38fefbd..000000000000 --- a/core/res/res/drawable-mdpi/stat_sys_ringer_vibrate.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/stat_sys_roaming_cdma_0.png b/core/res/res/drawable-mdpi/stat_sys_roaming_cdma_0.png Binary files differdeleted file mode 100755 index c61cce774ec7..000000000000 --- a/core/res/res/drawable-mdpi/stat_sys_roaming_cdma_0.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/stat_sys_roaming_cdma_flash_anim0.png b/core/res/res/drawable-mdpi/stat_sys_roaming_cdma_flash_anim0.png Binary files differdeleted file mode 100755 index d62502dcb78d..000000000000 --- a/core/res/res/drawable-mdpi/stat_sys_roaming_cdma_flash_anim0.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/stat_sys_roaming_cdma_flash_anim1.png b/core/res/res/drawable-mdpi/stat_sys_roaming_cdma_flash_anim1.png Binary files differdeleted file mode 100755 index c61cce774ec7..000000000000 --- a/core/res/res/drawable-mdpi/stat_sys_roaming_cdma_flash_anim1.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/stat_sys_signal_0.png b/core/res/res/drawable-mdpi/stat_sys_signal_0.png Binary files differdeleted file mode 100644 index cb7b7b34bf08..000000000000 --- a/core/res/res/drawable-mdpi/stat_sys_signal_0.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/stat_sys_signal_1.png b/core/res/res/drawable-mdpi/stat_sys_signal_1.png Binary files differdeleted file mode 100644 index 5376e9258d60..000000000000 --- a/core/res/res/drawable-mdpi/stat_sys_signal_1.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/stat_sys_signal_2.png b/core/res/res/drawable-mdpi/stat_sys_signal_2.png Binary files differdeleted file mode 100644 index fd543636c9d5..000000000000 --- a/core/res/res/drawable-mdpi/stat_sys_signal_2.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/stat_sys_signal_3.png b/core/res/res/drawable-mdpi/stat_sys_signal_3.png Binary files differdeleted file mode 100644 index 6c4873af3bdf..000000000000 --- a/core/res/res/drawable-mdpi/stat_sys_signal_3.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/stat_sys_signal_4.png b/core/res/res/drawable-mdpi/stat_sys_signal_4.png Binary files differdeleted file mode 100644 index a3320cbb4fd2..000000000000 --- a/core/res/res/drawable-mdpi/stat_sys_signal_4.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/stat_sys_signal_flightmode.png b/core/res/res/drawable-mdpi/stat_sys_signal_flightmode.png Binary files differdeleted file mode 100755 index 2f4fd4fe1f57..000000000000 --- a/core/res/res/drawable-mdpi/stat_sys_signal_flightmode.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/stat_sys_signal_null.png b/core/res/res/drawable-mdpi/stat_sys_signal_null.png Binary files differdeleted file mode 100644 index 5aa23f6c7348..000000000000 --- a/core/res/res/drawable-mdpi/stat_sys_signal_null.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/stat_sys_tty_mode.png b/core/res/res/drawable-mdpi/stat_sys_tty_mode.png Binary files differdeleted file mode 100644 index ed157a8722a4..000000000000 --- a/core/res/res/drawable-mdpi/stat_sys_tty_mode.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/stat_sys_vp_phone_call_bluetooth.png b/core/res/res/drawable-mdpi/stat_sys_vp_phone_call_bluetooth.png Binary files differdeleted file mode 100644 index 7abfd194fb80..000000000000 --- a/core/res/res/drawable-mdpi/stat_sys_vp_phone_call_bluetooth.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/stat_sys_wifi_signal_0.png b/core/res/res/drawable-mdpi/stat_sys_wifi_signal_0.png Binary files differdeleted file mode 100644 index 8ee3421fb72b..000000000000 --- a/core/res/res/drawable-mdpi/stat_sys_wifi_signal_0.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/stat_sys_wifi_signal_1.png b/core/res/res/drawable-mdpi/stat_sys_wifi_signal_1.png Binary files differdeleted file mode 100644 index 184fa366ed04..000000000000 --- a/core/res/res/drawable-mdpi/stat_sys_wifi_signal_1.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/stat_sys_wifi_signal_2.png b/core/res/res/drawable-mdpi/stat_sys_wifi_signal_2.png Binary files differdeleted file mode 100644 index 79935bb7e3d1..000000000000 --- a/core/res/res/drawable-mdpi/stat_sys_wifi_signal_2.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/stat_sys_wifi_signal_3.png b/core/res/res/drawable-mdpi/stat_sys_wifi_signal_3.png Binary files differdeleted file mode 100644 index d2099e616a41..000000000000 --- a/core/res/res/drawable-mdpi/stat_sys_wifi_signal_3.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/stat_sys_wifi_signal_4.png b/core/res/res/drawable-mdpi/stat_sys_wifi_signal_4.png Binary files differdeleted file mode 100644 index 2062aada3e3e..000000000000 --- a/core/res/res/drawable-mdpi/stat_sys_wifi_signal_4.png +++ /dev/null diff --git a/core/res/res/drawable-mdpi/status_bar_item_app_background_normal.9.png b/core/res/res/drawable-mdpi/status_bar_item_app_background_normal.9.png Binary files differindex c0796153ec06..bdcb37869e75 100644 --- a/core/res/res/drawable-mdpi/status_bar_item_app_background_normal.9.png +++ b/core/res/res/drawable-mdpi/status_bar_item_app_background_normal.9.png diff --git a/core/res/res/drawable-mdpi/status_bar_item_background_normal.9.png b/core/res/res/drawable-mdpi/status_bar_item_background_normal.9.png Binary files differindex b8e399d9d514..f0e4d068cbfe 100644 --- a/core/res/res/drawable-mdpi/status_bar_item_background_normal.9.png +++ b/core/res/res/drawable-mdpi/status_bar_item_background_normal.9.png diff --git a/core/res/res/drawable-mdpi/text_select_handle.png b/core/res/res/drawable-mdpi/text_select_handle.png Binary files differnew file mode 100644 index 000000000000..93a5a154eab4 --- /dev/null +++ b/core/res/res/drawable-mdpi/text_select_handle.png diff --git a/core/res/res/drawable-nodpi/loading_tile_android.png b/core/res/res/drawable-nodpi/loading_tile_android.png Binary files differnew file mode 100644 index 000000000000..8fde46fb265d --- /dev/null +++ b/core/res/res/drawable-nodpi/loading_tile_android.png diff --git a/core/res/res/drawable-nodpi/no_tile_256.png b/core/res/res/drawable-nodpi/no_tile_256.png Binary files differnew file mode 100644 index 000000000000..388234e9d484 --- /dev/null +++ b/core/res/res/drawable-nodpi/no_tile_256.png diff --git a/core/res/res/drawable-nodpi/platlogo.jpg b/core/res/res/drawable-nodpi/platlogo.jpg Binary files differnew file mode 100644 index 000000000000..0e7780c12b1a --- /dev/null +++ b/core/res/res/drawable-nodpi/platlogo.jpg diff --git a/core/res/res/drawable/battery_charge_fill.xml b/core/res/res/drawable/battery_charge_fill.xml deleted file mode 100644 index 7f6573346af2..000000000000 --- a/core/res/res/drawable/battery_charge_fill.xml +++ /dev/null @@ -1,22 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2008 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. ---> - -<level-list xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:maxLevel="29" android:drawable="@android:drawable/battery_charge_fill_empty" /> - <item android:maxLevel="49" android:drawable="@android:drawable/battery_charge_fill_warning" /> - <item android:maxLevel="100" android:drawable="@android:drawable/battery_charge_fill_full" /> -</level-list> - diff --git a/core/res/res/drawable/overscroll_edge.png b/core/res/res/drawable/overscroll_edge.png Binary files differnew file mode 100644 index 000000000000..250c827ffd96 --- /dev/null +++ b/core/res/res/drawable/overscroll_edge.png diff --git a/core/res/res/drawable/overscroll_glow.png b/core/res/res/drawable/overscroll_glow.png Binary files differnew file mode 100644 index 000000000000..69b456d6bc03 --- /dev/null +++ b/core/res/res/drawable/overscroll_glow.png diff --git a/core/res/res/drawable/status_bar_item_background.xml b/core/res/res/drawable/status_bar_item_background.xml index 088389b538a6..425a502eac68 100644 --- a/core/res/res/drawable/status_bar_item_background.xml +++ b/core/res/res/drawable/status_bar_item_background.xml @@ -16,7 +16,7 @@ <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:state_pressed="true" - android:drawable="@drawable/status_bar_item_background_pressed" /> + android:drawable="@drawable/status_bar_item_background_pressed" /> <item android:state_focused="true" android:state_pressed="false" android:drawable="@drawable/status_bar_item_background_focus" /> <item android:drawable="@drawable/status_bar_item_background_normal" /> diff --git a/core/res/res/drawable/status_icon_background.xml b/core/res/res/drawable/status_icon_background.xml deleted file mode 100644 index 9846165e1253..000000000000 --- a/core/res/res/drawable/status_icon_background.xml +++ /dev/null @@ -1,24 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -/* //device/apps/common/res/drawable/status_icon_background.xml -** -** Copyright 2007, 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. -*/ ---> - -<selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:state_selected="true" android:drawable="@drawable/icon_highlight_rectangle" /> - <item android:drawable="@color/transparent" /> -</selector> diff --git a/core/res/res/layout/alert_dialog.xml b/core/res/res/layout/alert_dialog.xml index 7ae68f900b45..25a41f867df9 100644 --- a/core/res/res/layout/alert_dialog.xml +++ b/core/res/res/layout/alert_dialog.xml @@ -80,7 +80,8 @@ android:paddingTop="2dip" android:paddingBottom="12dip" android:paddingLeft="14dip" - android:paddingRight="10dip"> + android:paddingRight="10dip" + android:overscrollMode="ifContentScrolls"> <TextView android:id="@+id/message" style="?android:attr/textAppearanceMedium" android:layout_width="match_parent" diff --git a/core/res/res/layout/battery_low.xml b/core/res/res/layout/battery_low.xml deleted file mode 100644 index 3b62fb0c78de..000000000000 --- a/core/res/res/layout/battery_low.xml +++ /dev/null @@ -1,57 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -/* //device/apps/common/res/layout/keyguard.xml -** -** Copyright 2007, 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. -*/ ---> - -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/padding" - android:orientation="vertical" - android:gravity="center" - > - - <TextView android:id="@+id/subtitle" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:textSize="18dp" - android:paddingLeft="19dp" - android:textColor="#ffffffff" - android:gravity="left" - android:text="@string/battery_low_subtitle" - /> - - <TextView android:id="@+id/level_percent" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:textSize="18dp" - android:textColor="#ffffffff" - android:gravity="left" - android:paddingBottom="10px" - android:paddingLeft="19dp" - /> - - <ImageView android:id="@+id/image" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:paddingBottom="15px" - android:src="@drawable/battery_low_battery" - android:paddingTop="10px" - /> - -</LinearLayout> - - diff --git a/core/res/res/layout/battery_status.xml b/core/res/res/layout/battery_status.xml deleted file mode 100644 index 7cfec0546507..000000000000 --- a/core/res/res/layout/battery_status.xml +++ /dev/null @@ -1,81 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2008 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. ---> - -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/frame" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:orientation="vertical" - android:gravity="center_horizontal" - > - - <FrameLayout - android:layout_width="141px" - android:layout_height="184px" - android:background="@drawable/battery_charge_background" - android:paddingTop="25px" - android:paddingLeft="1px" - > - - <LinearLayout - android:id="@+id/meter" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="vertical" - > - - <ImageView - android:layout_width="match_parent" - android:layout_height="15dip" - /> - <ImageView - android:id="@+id/spacer" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - /> - <ImageView - android:id="@+id/level" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - /> - - </LinearLayout> - - <TextView android:id="@+id/level_percent" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:textStyle="bold" - android:textSize="48dp" - android:textColor="#ffffffff" - android:gravity="center" - /> - </FrameLayout> - - <TextView android:id="@+id/status" - android:paddingTop="35dp" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:singleLine="true" - android:textStyle="bold" - android:textSize="30dp" - android:textColor="#ffffffff" - android:gravity="center_horizontal" - android:text="@string/battery_status_charging" - /> - -</LinearLayout> - - diff --git a/core/res/res/layout/heavy_weight_switcher.xml b/core/res/res/layout/heavy_weight_switcher.xml new file mode 100644 index 000000000000..9acf00911a25 --- /dev/null +++ b/core/res/res/layout/heavy_weight_switcher.xml @@ -0,0 +1,145 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" android:padding="4dp" + android:gravity="center_horizontal" + android:layout_width="wrap_content" android:layout_height="wrap_content"> + + <TextView + android:layout_width="match_parent" android:layout_height="wrap_content" + android:layout_weight="0" + android:paddingBottom="8dp" + android:text="@string/heavy_weight_switcher_text"/> + + <ImageView android:layout_width="match_parent" + android:layout_height="wrap_content" + android:scaleType="fitXY" + android:src="?android:listDivider" /> + + <LinearLayout android:id="@+id/switch_old" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:minHeight="?android:attr/listPreferredItemHeight" + android:orientation="horizontal" + android:background="@android:drawable/list_selector_background" + android:paddingRight="3dip" + android:paddingLeft="3dip" + android:paddingTop="5dip" + android:paddingBottom="14dip" + android:gravity="center_vertical" + android:focusable="true" > + + <ImageView android:id="@+id/old_app_icon" + android:layout_width="@android:dimen/app_icon_size" + android:layout_height="@android:dimen/app_icon_size" + android:layout_marginRight="11dip" + android:layout_gravity="center_vertical" + android:scaleType="fitCenter"/> + + <LinearLayout + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:duplicateParentState="true" > + <TextView android:id="@+id/old_app_action" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="?android:attr/textAppearanceMedium" + android:textStyle="bold" + android:singleLine="true" + android:layout_marginBottom="2dip" + android:duplicateParentState="true" /> + <TextView android:id="@+id/old_app_description" + android:layout_marginTop="-4dip" + android:layout_gravity="center_vertical" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="?android:attr/textAppearanceSmall" + android:duplicateParentState="true" /> + </LinearLayout> + </LinearLayout> + + <ImageView android:layout_width="match_parent" + android:layout_height="wrap_content" + android:scaleType="fitXY" + android:src="?android:listDivider" /> + + <LinearLayout android:id="@+id/switch_new" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:minHeight="?android:attr/listPreferredItemHeight" + android:orientation="horizontal" + android:background="@android:drawable/list_selector_background" + android:paddingRight="3dip" + android:paddingLeft="3dip" + android:paddingTop="5dip" + android:paddingBottom="8dip" + android:gravity="center_vertical" + android:focusable="true" > + + <ImageView android:id="@+id/new_app_icon" + android:layout_width="@android:dimen/app_icon_size" + android:layout_height="@android:dimen/app_icon_size" + android:layout_marginRight="11dip" + android:layout_gravity="center_vertical" + android:scaleType="fitCenter"/> + + <LinearLayout + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:duplicateParentState="true" > + <TextView android:id="@+id/new_app_action" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="?android:attr/textAppearanceMedium" + android:textStyle="bold" + android:singleLine="true" + android:layout_marginBottom="2dip" + android:duplicateParentState="true" /> + <TextView android:id="@+id/new_app_description" + android:layout_marginTop="-4dip" + android:layout_gravity="center_vertical" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="?android:attr/textAppearanceSmall" + android:duplicateParentState="true" /> + </LinearLayout> + </LinearLayout> + + <ImageView android:layout_width="match_parent" + android:layout_height="wrap_content" + android:scaleType="fitXY" + android:src="?android:listDivider" /> + + <TextView android:id="@+id/cancel" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:minHeight="?android:attr/listPreferredItemHeight" + android:textAppearance="?android:attr/textAppearanceMedium" + android:background="@android:drawable/list_selector_background" + android:paddingRight="6dip" + android:paddingLeft="6dip" + android:paddingTop="5dip" + android:paddingBottom="8dip" + android:textStyle="bold" + android:singleLine="true" + android:gravity="center" + android:focusable="true" + android:text="@string/cancel" /> + +</LinearLayout> diff --git a/core/res/res/layout/keyguard_screen_tab_unlock.xml b/core/res/res/layout/keyguard_screen_tab_unlock.xml index 200a1b294c0b..6edbdf9300d9 100644 --- a/core/res/res/layout/keyguard_screen_tab_unlock.xml +++ b/core/res/res/layout/keyguard_screen_tab_unlock.xml @@ -41,7 +41,6 @@ android:ellipsize="marquee" android:gravity="right|bottom" android:textAppearance="?android:attr/textAppearanceMedium" - android:textSize="22sp" /> <!-- "emergency calls only" shown when sim is missing or PUKd --> @@ -65,7 +64,7 @@ android:layout_below="@id/carrier" android:layout_marginTop="52dip" android:layout_marginLeft="20dip" - android:paddingBottom="8dip" + android:layout_marginBottom="8dip" > <TextView android:id="@+id/timeDisplay" diff --git a/core/res/res/layout/keyguard_screen_tab_unlock_land.xml b/core/res/res/layout/keyguard_screen_tab_unlock_land.xml index 23505c2699d8..e66b49255925 100644 --- a/core/res/res/layout/keyguard_screen_tab_unlock_land.xml +++ b/core/res/res/layout/keyguard_screen_tab_unlock_land.xml @@ -46,7 +46,6 @@ android:ellipsize="marquee" android:gravity="right|bottom" android:textAppearance="?android:attr/textAppearanceMedium" - android:textSize="22sp" /> <!-- "emergency calls only" shown when sim is missing or PUKd --> @@ -66,7 +65,7 @@ android:layout_height="wrap_content" android:layout_below="@id/carrier" android:layout_marginTop="56dip" - android:paddingBottom="8dip" + android:layout_marginBottom="8dip" > <TextView android:id="@+id/timeDisplay" diff --git a/core/res/res/layout/keyguard_screen_unlock_landscape.xml b/core/res/res/layout/keyguard_screen_unlock_landscape.xml index b5cd4424c63b..c1b406fa7bfd 100644 --- a/core/res/res/layout/keyguard_screen_unlock_landscape.xml +++ b/core/res/res/layout/keyguard_screen_unlock_landscape.xml @@ -58,19 +58,18 @@ android:ellipsize="marquee" android:gravity="right|bottom" /> - <com.android.internal.widget.DigitalClock android:id="@+id/time" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:layout_alignParentLeft="true" android:layout_marginTop="8dip" - android:paddingBottom="8dip" > <TextView android:id="@+id/timeDisplay" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:gravity="bottom" android:singleLine="true" android:ellipsize="none" android:textSize="72sp" @@ -85,9 +84,8 @@ <TextView android:id="@+id/am_pm" android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_toRightOf="@id/timeDisplay" - android:layout_alignBaseline="@id/timeDisplay" + android:layout_height="match_parent" + android:gravity="bottom" android:singleLine="true" android:ellipsize="none" android:textSize="22sp" diff --git a/core/res/res/layout/keyguard_screen_unlock_portrait.xml b/core/res/res/layout/keyguard_screen_unlock_portrait.xml index 9ac0a471af84..74a0eee653f2 100644 --- a/core/res/res/layout/keyguard_screen_unlock_portrait.xml +++ b/core/res/res/layout/keyguard_screen_unlock_portrait.xml @@ -55,12 +55,12 @@ android:layout_alignParentTop="true" android:layout_marginTop="15dip" android:layout_marginLeft="20dip" - android:paddingBottom="8dip" > <TextView android:id="@+id/timeDisplay" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:gravity="bottom" android:singleLine="true" android:ellipsize="none" android:textSize="56sp" @@ -74,9 +74,8 @@ <TextView android:id="@+id/am_pm" android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_toRightOf="@id/timeDisplay" - android:layout_alignBaseline="@id/timeDisplay" + android:layout_height="match_parent" + android:gravity="bottom" android:singleLine="true" android:ellipsize="none" android:textSize="18sp" diff --git a/core/res/res/layout/launch_warning.xml b/core/res/res/layout/launch_warning.xml new file mode 100644 index 000000000000..1923ff075231 --- /dev/null +++ b/core/res/res/layout/launch_warning.xml @@ -0,0 +1,65 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* Copyright 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. +*/ +--> + +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <ImageView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingTop="6dp" + android:paddingBottom="6dp" + android:scaleType="fitXY" + android:gravity="fill_horizontal" + android:src="@android:drawable/divider_horizontal_dark" /> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingTop="10dip" + android:orientation="horizontal"> + <ImageView android:id="@+id/replace_app_icon" + android:layout_width="@android:dimen/app_icon_size" + android:layout_height="@android:dimen/app_icon_size" + android:scaleType="fitCenter" /> + <TextView android:id="@+id/replace_message" + style="?android:attr/textAppearanceMedium" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:padding="5dip" /> + </LinearLayout> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingTop="10dip" + android:orientation="horizontal"> + <ImageView android:id="@+id/original_app_icon" + android:layout_width="@android:dimen/app_icon_size" + android:layout_height="@android:dimen/app_icon_size" + android:scaleType="fitCenter" /> + <TextView android:id="@+id/original_message" + style="?android:attr/textAppearanceMedium" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:padding="5dip" /> + </LinearLayout> +</LinearLayout> diff --git a/core/res/res/layout/preference_dialog_edittext.xml b/core/res/res/layout/preference_dialog_edittext.xml index 5be5773fcfb9..b41e7745c7be 100644 --- a/core/res/res/layout/preference_dialog_edittext.xml +++ b/core/res/res/layout/preference_dialog_edittext.xml @@ -17,7 +17,8 @@ <!-- Layout used as the dialog's content View for EditTextPreference. --> <ScrollView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" - android:layout_height="match_parent"> + android:layout_height="match_parent" + android:overscrollMode="ifContentScrolls"> <LinearLayout android:id="@+android:id/edittext_container" diff --git a/core/res/res/layout/select_dialog.xml b/core/res/res/layout/select_dialog.xml index c665f7a7ed97..6e4e5e122edf 100644 --- a/core/res/res/layout/select_dialog.xml +++ b/core/res/res/layout/select_dialog.xml @@ -31,4 +31,5 @@ android:layout_marginTop="5px" android:cacheColorHint="@null" android:divider="@android:drawable/divider_horizontal_bright" - android:scrollbars="vertical" /> + android:scrollbars="vertical" + android:overscrollMode="ifContentScrolls" /> diff --git a/core/res/res/layout/status_bar.xml b/core/res/res/layout/status_bar.xml deleted file mode 100644 index e8d88661ccb1..000000000000 --- a/core/res/res/layout/status_bar.xml +++ /dev/null @@ -1,103 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -/* apps/common/assets/default/default/skins/StatusBar.xml -** -** Copyright 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. -*/ ---> - -<!-- android:background="@drawable/status_bar_closed_default_background" --> -<com.android.server.status.StatusBarView xmlns:android="http://schemas.android.com/apk/res/android" - android:background="@drawable/statusbar_background" - android:orientation="vertical" - android:focusable="true" - android:descendantFocusability="afterDescendants" - > - - <LinearLayout android:id="@+id/icons" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="horizontal"> - - <com.android.server.status.IconMerger android:id="@+id/notificationIcons" - android:layout_width="0dip" - android:layout_weight="1" - android:layout_height="match_parent" - android:layout_alignParentLeft="true" - android:paddingLeft="6dip" - android:gravity="center_vertical" - android:orientation="horizontal"/> - - <LinearLayout android:id="@+id/statusIcons" - android:layout_width="wrap_content" - android:layout_height="match_parent" - android:layout_alignParentRight="true" - android:paddingRight="6dip" - android:gravity="center_vertical" - android:orientation="horizontal"/> - </LinearLayout> - - <LinearLayout android:id="@+id/ticker" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:paddingLeft="6dip" - android:animationCache="false" - android:orientation="horizontal" > - <ImageSwitcher android:id="@+id/tickerIcon" - android:layout_width="wrap_content" - android:layout_height="match_parent" - android:layout_marginRight="8dip" - > - <com.android.server.status.AnimatedImageView - android:layout_width="25dip" - android:layout_height="25dip" - /> - <com.android.server.status.AnimatedImageView - android:layout_width="25dip" - android:layout_height="25dip" - /> - </ImageSwitcher> - <com.android.server.status.TickerView android:id="@+id/tickerText" - android:layout_width="0dip" - android:layout_weight="1" - android:layout_height="wrap_content" - android:paddingTop="2dip" - android:paddingRight="10dip"> - <TextView - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:singleLine="true" - android:textColor="#ff000000" /> - <TextView - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:singleLine="true" - android:textColor="#ff000000" /> - </com.android.server.status.TickerView> - </LinearLayout> - - <com.android.server.status.DateView android:id="@+id/date" - android:layout_width="wrap_content" - android:layout_height="match_parent" - android:singleLine="true" - android:textSize="16sp" - android:textStyle="bold" - android:gravity="center_vertical|left" - android:paddingLeft="6px" - android:paddingRight="6px" - android:textColor="?android:attr/textColorPrimaryInverse" - android:background="@drawable/statusbar_background" - /> -</com.android.server.status.StatusBarView> diff --git a/core/res/res/layout/status_bar_expanded.xml b/core/res/res/layout/status_bar_expanded.xml deleted file mode 100644 index 30138a742c74..000000000000 --- a/core/res/res/layout/status_bar_expanded.xml +++ /dev/null @@ -1,144 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -/* apps/common/assets/default/default/skins/StatusBar.xml -** -** Copyright 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. -*/ ---> - -<com.android.server.status.ExpandedView xmlns:android="http://schemas.android.com/apk/res/android" - android:orientation="vertical" - android:focusable="true" - android:descendantFocusability="afterDescendants" - > - - <LinearLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="horizontal" - android:paddingTop="3dp" - android:paddingBottom="5dp" - android:paddingRight="3dp" - android:background="@drawable/status_bar_header_background" - > - <LinearLayout - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_weight="1" - android:layout_marginTop="1dp" - android:layout_marginLeft="5dp" - android:layout_gravity="center_vertical" - android:paddingBottom="1dp" - android:orientation="vertical" - > - <TextView android:id="@+id/plmnLabel" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical" - android:textAppearance="?android:attr/textAppearanceLarge" - android:textColor="?android:attr/textColorSecondaryInverse" - android:paddingLeft="4dp" - /> - <TextView android:id="@+id/spnLabel" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical" - android:textAppearance="?android:attr/textAppearanceLarge" - android:textColor="?android:attr/textColorSecondaryInverse" - android:paddingLeft="4dp" - /> - </LinearLayout> - <TextView android:id="@+id/clear_all_button" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="center_vertical" - android:layout_marginTop="4dp" - android:layout_marginBottom="1dp" - android:textSize="14sp" - android:textColor="#ff000000" - android:text="@string/status_bar_clear_all_button" - style="?android:attr/buttonStyle" - android:paddingLeft="15dp" - android:paddingRight="15dp" - android:background="@drawable/btn_default_small" - /> - </LinearLayout> - - <FrameLayout - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_weight="1" - > - <ScrollView - android:id="@+id/scroll" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:fadingEdge="none" - > - <com.android.server.status.NotificationLinearLayout - android:id="@+id/notificationLinearLayout" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="vertical" - > - - <TextView android:id="@+id/noNotificationsTitle" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:background="@drawable/title_bar_portrait" - android:paddingLeft="5dp" - android:textAppearance="@style/TextAppearance.StatusBarTitle" - android:text="@string/status_bar_no_notifications_title" - /> - - <TextView android:id="@+id/ongoingTitle" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:background="@drawable/title_bar_portrait" - android:paddingLeft="5dp" - android:textAppearance="@style/TextAppearance.StatusBarTitle" - android:text="@string/status_bar_ongoing_events_title" - /> - <LinearLayout android:id="@+id/ongoingItems" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="vertical" - /> - - <TextView android:id="@+id/latestTitle" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:background="@drawable/title_bar_portrait" - android:paddingLeft="5dp" - android:textAppearance="@style/TextAppearance.StatusBarTitle" - android:text="@string/status_bar_latest_events_title" - /> - <LinearLayout android:id="@+id/latestItems" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="vertical" - /> - </com.android.server.status.NotificationLinearLayout> - </ScrollView> - - <ImageView - android:layout_width="match_parent" - android:layout_height="match_parent" - android:src="@drawable/title_bar_shadow" - android:scaleType="fitXY" - /> - - </FrameLayout> -</com.android.server.status.ExpandedView> diff --git a/core/res/res/layout/status_bar_icon.xml b/core/res/res/layout/status_bar_icon.xml deleted file mode 100644 index 05367923c397..000000000000 --- a/core/res/res/layout/status_bar_icon.xml +++ /dev/null @@ -1,46 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -/* apps/common/assets/default/default/skins/StatusBar.xml -** -** Copyright 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. -*/ ---> - -<!-- The icons are a fixed size so an app can't mess everything up with bogus images --> -<!-- TODO: the icons are hard coded to 25x25 pixels. Their size should come froem a theme --> -<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="25dp" - android:layout_height="25dp" - > - - <com.android.server.status.AnimatedImageView android:id="@+id/image" - android:layout_width="match_parent" - android:layout_height="match_parent" - /> - - <TextView android:id="@+id/number" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_gravity="right|bottom" - android:layout_marginRight="1dp" - android:layout_marginBottom="1dp" - android:textSize="10sp" - android:textColor="#ffffffff" - android:background="@drawable/ic_notification_overlay" - android:gravity="center" - android:textStyle="bold" - /> - -</FrameLayout> diff --git a/core/res/res/layout/status_bar_latest_event.xml b/core/res/res/layout/status_bar_latest_event.xml deleted file mode 100644 index 59cc90db3367..000000000000 --- a/core/res/res/layout/status_bar_latest_event.xml +++ /dev/null @@ -1,24 +0,0 @@ -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="65sp" - android:orientation="vertical" - > - - <com.android.server.status.LatestItemView android:id="@+id/content" - android:layout_width="match_parent" - android:layout_height="64sp" - android:background="@drawable/status_bar_item_background" - android:focusable="true" - android:clickable="true" - android:paddingRight="6sp" - > - </com.android.server.status.LatestItemView> - - <View - android:layout_width="match_parent" - android:layout_height="1sp" - android:background="@drawable/divider_horizontal_bright" - /> - -</LinearLayout> - diff --git a/core/res/res/layout/status_bar_latest_event_content.xml b/core/res/res/layout/status_bar_latest_event_content.xml index c3aa041075f1..c64b90ea1daa 100644 --- a/core/res/res/layout/status_bar_latest_event_content.xml +++ b/core/res/res/layout/status_bar_latest_event_content.xml @@ -12,22 +12,22 @@ android:orientation="horizontal" android:paddingTop="3dp" > - <com.android.server.status.AnimatedImageView android:id="@+id/icon" + <!--com.android.server.status.AnimatedImageView android:id="@+id/icon" --> + <ImageView android:id="@+id/icon" android:layout_width="25dp" android:layout_height="25dp" android:scaleType="fitCenter" android:src="@drawable/arrow_down_float"/> <TextView android:id="@+id/title" + android:textAppearance="@style/TextAppearance.StatusBar.EventContent.Title" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="1" android:singleLine="true" android:ellipsize="marquee" android:fadingEdge="horizontal" - android:textStyle="bold" - android:textSize="18sp" android:paddingLeft="4dp" - android:textColor="#ff000000" /> + /> </LinearLayout> <LinearLayout android:layout_width="match_parent" @@ -35,23 +35,22 @@ android:orientation="horizontal" > <TextView android:id="@+id/text" + android:textAppearance="@style/TextAppearance.StatusBar.EventContent" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" - android:textColor="#ff000000" android:singleLine="true" android:ellipsize="marquee" android:fadingEdge="horizontal" - android:textSize="14sp" android:paddingLeft="4dp" /> <android.widget.DateTimeView android:id="@+id/time" + android:textAppearance="@style/TextAppearance.StatusBar.EventContent" android:layout_marginLeft="4dp" android:layout_width="wrap_content" android:layout_height="wrap_content" android:singleLine="true" - android:textSize="14sp" android:paddingRight="5dp" - android:textColor="#ff000000" /> + /> </LinearLayout> </LinearLayout> diff --git a/core/res/res/layout/status_bar_tracking.xml b/core/res/res/layout/status_bar_tracking.xml deleted file mode 100644 index c0a7a977545f..000000000000 --- a/core/res/res/layout/status_bar_tracking.xml +++ /dev/null @@ -1,48 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - Copyright (C) 2008 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. ---> - -<com.android.server.status.TrackingView xmlns:android="http://schemas.android.com/apk/res/android" - android:orientation="vertical" - android:visibility="gone" - android:focusable="true" - android:descendantFocusability="afterDescendants" - android:paddingBottom="0px" - android:paddingLeft="0px" - android:paddingRight="0px" - > - - <com.android.server.status.TrackingPatternView - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_weight="1" - /> - - <com.android.server.status.CloseDragHandle android:id="@+id/close" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="vertical" - > - <ImageView - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_gravity="bottom" - android:scaleType="fitXY" - android:src="@drawable/status_bar_close_on"/> - - </com.android.server.status.CloseDragHandle> - -</com.android.server.status.TrackingView> diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml index 5e7b693018a8..dda1fc53b54f 100644 --- a/core/res/res/values-cs/strings.xml +++ b/core/res/res/values-cs/strings.xml @@ -34,6 +34,7 @@ <string name="defaultVoiceMailAlphaTag" msgid="2660020990097733077">"Hlasová schránka"</string> <string name="defaultMsisdnAlphaTag" msgid="2850889754919584674">"MSISDN1"</string> <string name="mmiError" msgid="5154499457739052907">"Problém s připojením nebo neplatný kód MMI."</string> + <string name="mmiFdnError" msgid="5224398216385316471">"Operace je omezena pouze na povolená telefonní čísla."</string> <string name="serviceEnabled" msgid="8147278346414714315">"Služba byla zapnuta."</string> <string name="serviceEnabledFor" msgid="6856228140453471041">"Služba byla zapnuta pro:"</string> <string name="serviceDisabled" msgid="1937553226592516411">"Služba byla vypnuta."</string> @@ -171,6 +172,8 @@ <string name="permgroupdesc_storage" msgid="9203302214915355774">"Přístup ke kartě SD."</string> <string name="permlab_statusBar" msgid="7417192629601890791">"zakázání či změny stavového řádku"</string> <string name="permdesc_statusBar" msgid="1365473595331989732">"Umožňuje aplikaci zakázat stavový řádek nebo přidat či odebrat systémové ikony."</string> + <string name="permlab_statusBarService" msgid="7247281911387931485">"stavový řádek"</string> + <string name="permdesc_statusBarService" msgid="4097605867643520920">"Umožňuje aplikaci být stavovým řádkem."</string> <string name="permlab_expandStatusBar" msgid="1148198785937489264">"rozbalení a sbalení stavového řádku"</string> <string name="permdesc_expandStatusBar" msgid="7088604400110768665">"Umožňuje aplikaci rozbalit či sbalit stavový řádek."</string> <string name="permlab_processOutgoingCalls" msgid="1136262550878335980">"zachycení odchozích hovorů"</string> @@ -285,10 +288,6 @@ <string name="permdesc_readContacts" msgid="3371591512896545975">"Umožňuje aplikaci načíst všechna data kontaktů (adresy) uložená ve vašem telefonu. Škodlivé aplikace poté mohou dalším lidem odeslat vaše data."</string> <string name="permlab_writeContacts" msgid="644616215860933284">"zápis dat kontaktů"</string> <string name="permdesc_writeContacts" msgid="3924383579108183601">"Umožňuje aplikaci změnit kontaktní údaje (adresu) uložené v telefonu. Škodlivé aplikace mohou pomocí tohoto nastavení vymazat či pozměnit kontaktní údaje."</string> - <string name="permlab_writeOwnerData" msgid="4892555913849295393">"zápis informací o vlastníkovi"</string> - <string name="permdesc_writeOwnerData" msgid="2344055317969787124">"Umožňuje aplikaci změnit informace o vlastníkovi telefonu uložené v telefonu. Škodlivé aplikace mohou pomocí tohoto nastavení vymazat či pozměnit informace o vlastníkovi."</string> - <string name="permlab_readOwnerData" msgid="6668525984731523563">"čtení informací o vlastníkovi"</string> - <string name="permdesc_readOwnerData" msgid="3088486383128434507">"Umožňuje aplikaci číst informace o vlastníkovi telefonu uložená v telefonu. Škodlivé aplikace mohou pomocí tohoto nastavení načíst informace o vlastníkovi."</string> <string name="permlab_readCalendar" msgid="6898987798303840534">"Čtení událostí v kalendáři"</string> <string name="permdesc_readCalendar" msgid="5533029139652095734">"Umožňuje aplikaci načíst všechny události kalendáře uložené ve vašem telefonu. Škodlivé aplikace poté mohou dalším lidem odeslat události z vašeho kalendáře."</string> <string name="permlab_writeCalendar" msgid="3894879352594904361">"Přidávání nebo úprava událostí v kalendáři a odesílání e-mailů hostům"</string> @@ -311,8 +310,8 @@ <string name="permdesc_modifyAudioSettings" msgid="5793461287365991922">"Umožňuje aplikaci změnit globální nastavení zvuku, například hlasitost či směrování."</string> <string name="permlab_recordAudio" msgid="3876049771427466323">"nahrání zvuku"</string> <string name="permdesc_recordAudio" msgid="6493228261176552356">"Umožňuje aplikaci získat přístup k nahrávání zvuku."</string> - <string name="permlab_camera" msgid="8059288807274039014">"pořizování fotografií"</string> - <string name="permdesc_camera" msgid="9013476258810982546">"Umožňuje aplikaci pořizovat fotografie pomocí fotoaparátu. Toto nastavení aplikaci umožní shromažďovat fotografie toho, na co je zrovna fotoaparát namířen."</string> + <string name="permlab_camera" msgid="3616391919559751192">"pořizování fotografií a videí"</string> + <string name="permdesc_camera" msgid="6004878235852154239">"Umožňuje aplikaci pořizovat fotografie a videa pomocí fotoaparátu. Toto nastavení aplikaci umožní získávat snímky toho, na co je zrovna fotoaparát namířen."</string> <string name="permlab_brick" msgid="8337817093326370537">"trvalé vypnutí telefonu"</string> <string name="permdesc_brick" msgid="5569526552607599221">"Umožňuje aplikaci trvale vypnout celý telefon. Toto je velmi nebezpečné nastavení."</string> <string name="permlab_reboot" msgid="2898560872462638242">"vynucení restartování telefonu"</string> @@ -335,6 +334,8 @@ <string name="permdesc_vibrate" msgid="2886677177257789187">"Umožňuje aplikaci ovládat vibrace."</string> <string name="permlab_flashlight" msgid="2155920810121984215">"ovládání kontrolky"</string> <string name="permdesc_flashlight" msgid="6433045942283802309">"Umožňuje aplikaci ovládat kontrolku."</string> + <string name="permlab_accessUsb" msgid="7362327818655760496">"přístup k zařízením USB"</string> + <string name="permdesc_accessUsb" msgid="2414271762914049292">"Umožní aplikaci přístup k zařízením USB."</string> <string name="permlab_hardware_test" msgid="4148290860400659146">"testování hardwaru"</string> <string name="permdesc_hardware_test" msgid="3668894686500081699">"Umožňuje aplikaci ovládat různé periferie pro účely testování hardwaru."</string> <string name="permlab_callPhone" msgid="3925836347681847954">"přímé volání na telefonní čísla"</string> @@ -573,16 +574,6 @@ <string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"Alt"</string> <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string> <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string> - <string name="status_bar_clear_all_button" msgid="7774721344716731603">"Vymazat"</string> - <string name="status_bar_no_notifications_title" msgid="4755261167193833213">"Žádná oznámení"</string> - <string name="status_bar_ongoing_events_title" msgid="1682504513316879202">"Probíhající"</string> - <string name="status_bar_latest_events_title" msgid="6594767438577593172">"Oznámení"</string> - <string name="battery_status_text_percent_format" msgid="7660311274698797147">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string> - <string name="battery_status_charging" msgid="756617993998772213">"Nabíjení..."</string> - <string name="battery_low_title" msgid="7923774589611311406">"Prosím připojte dobíjecí zařízení"</string> - <string name="battery_low_subtitle" msgid="7388781709819722764">"Baterie je vybitá:"</string> - <string name="battery_low_percent_format" msgid="696154104579022959">"Zbývá <xliff:g id="NUMBER">%d%%</xliff:g> nebo méně."</string> - <string name="battery_low_why" msgid="7279169609518386372">"Využití baterie"</string> <string name="factorytest_failed" msgid="5410270329114212041">"Test továrního nastavení se nezdařil"</string> <string name="factorytest_not_system" msgid="4435201656767276723">"Test FACTORY_TEST lze provést pouze u balíčků nainstalovaných ve složce /system/app."</string> <string name="factorytest_no_action" msgid="872991874799998561">"Nebyl nalezen žádný balíček umožňující test FACTORY_TEST."</string> @@ -708,12 +699,9 @@ <string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string> <string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string> <string name="selectAll" msgid="6876518925844129331">"Vybrat vše"</string> - <string name="selectText" msgid="3889149123626888637">"Označit text"</string> - <string name="stopSelectingText" msgid="4157931463872320996">"Zastavit označování textu"</string> + <!-- outdated translation 3889149123626888637 --> <string name="selectText" msgid="4862359311088898878">"Označit text"</string> <string name="cut" msgid="3092569408438626261">"Vyjmout"</string> - <string name="cutAll" msgid="2436383270024931639">"Vyjmout vše"</string> <string name="copy" msgid="2681946229533511987">"Kopírovat"</string> - <string name="copyAll" msgid="2590829068100113057">"Kopírovat vše"</string> <string name="paste" msgid="5629880836805036433">"Vložit"</string> <string name="copyUrl" msgid="2538211579596067402">"Kopírovat adresu URL"</string> <string name="inputMethod" msgid="1653630062304567879">"Metoda zadávání dat"</string> @@ -744,6 +732,22 @@ <string name="force_close" msgid="3653416315450806396">"Ukončit aplikaci"</string> <string name="report" msgid="4060218260984795706">"Nahlásit"</string> <string name="wait" msgid="7147118217226317732">"Počkat"</string> + <!-- no translation found for launch_warning_title (8323761616052121936) --> + <skip /> + <!-- no translation found for launch_warning_replace (6202498949970281412) --> + <skip /> + <!-- no translation found for launch_warning_original (188102023021668683) --> + <skip /> + <string name="smv_application" msgid="295583804361236288">"Aplikace <xliff:g id="APPLICATION">%1$s</xliff:g> (proces <xliff:g id="PROCESS">%2$s</xliff:g>) porušila své vlastní vynucené zásady StrictMode."</string> + <string name="smv_process" msgid="5120397012047462446">"Proces <xliff:g id="PROCESS">%1$s</xliff:g> porušil své vlastní vynucené zásady StrictMode."</string> + <string name="heavy_weight_notification" msgid="9087063985776626166">"Běží aplikace <xliff:g id="APP">%1$s</xliff:g>"</string> + <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"Tuto možnost vyberte, chcete-li přepnout na aplikaci."</string> + <string name="heavy_weight_switcher_title" msgid="1135403633766694316">"Přepnout mezi aplikacemi?"</string> + <string name="heavy_weight_switcher_text" msgid="4592075610079319667">"Než spustíte novou aplikaci, je třeba zastavit jinou spuštěnou aplikaci."</string> + <string name="old_app_action" msgid="493129172238566282">"Návrat do aplikace <xliff:g id="OLD_APP">%1$s</xliff:g>"</string> + <string name="old_app_description" msgid="942967900237208466">"Nespouštět novou aplikaci."</string> + <string name="new_app_action" msgid="5472756926945440706">"Spustit aplikaci <xliff:g id="OLD_APP">%1$s</xliff:g>"</string> + <string name="new_app_description" msgid="6830398339826789493">"Zastavit starou aplikaci bez uložení."</string> <string name="sendText" msgid="5132506121645618310">"Vyberte činnost s textem"</string> <string name="volume_ringtone" msgid="6885421406845734650">"Hlasitost vyzvánění"</string> <string name="volume_music" msgid="5421651157138628171">"Hlasitost médií"</string> diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml index 4b6668c43aa7..7b3e82c270ff 100644 --- a/core/res/res/values-da/strings.xml +++ b/core/res/res/values-da/strings.xml @@ -34,6 +34,7 @@ <string name="defaultVoiceMailAlphaTag" msgid="2660020990097733077">"Voicemail"</string> <string name="defaultMsisdnAlphaTag" msgid="2850889754919584674">"MSISDN1"</string> <string name="mmiError" msgid="5154499457739052907">"Forbindelsesproblemer eller ugyldigt MMI-nummer."</string> + <string name="mmiFdnError" msgid="5224398216385316471">"Du kan kun foretage handlinger med faste opkaldsnumre."</string> <string name="serviceEnabled" msgid="8147278346414714315">"Tjenesten blev aktiveret."</string> <string name="serviceEnabledFor" msgid="6856228140453471041">"Tjenesten blev aktiveret for:"</string> <string name="serviceDisabled" msgid="1937553226592516411">"Tjenesten er deaktiveret."</string> @@ -171,6 +172,8 @@ <string name="permgroupdesc_storage" msgid="9203302214915355774">"Få adgang til SD-kortet."</string> <string name="permlab_statusBar" msgid="7417192629601890791">"deaktiver eller rediger statuslinje"</string> <string name="permdesc_statusBar" msgid="1365473595331989732">"Tillader, at et program deaktiverer statuslinjen eller tilføjer eller fjerner systemikoner."</string> + <string name="permlab_statusBarService" msgid="7247281911387931485">"statusbjælke"</string> + <string name="permdesc_statusBarService" msgid="4097605867643520920">"Tillader, at programmet er statusbjælken."</string> <string name="permlab_expandStatusBar" msgid="1148198785937489264">"udvid/skjul statuslinje"</string> <string name="permdesc_expandStatusBar" msgid="7088604400110768665">"Tillader, at et program udvider eller skjuler statuslinjen."</string> <string name="permlab_processOutgoingCalls" msgid="1136262550878335980">"opfang udgående opkald"</string> @@ -285,10 +288,6 @@ <string name="permdesc_readContacts" msgid="3371591512896545975">"Tillader, at et program læser alle kontaktdata (adresser), der er gemt på din telefon. Ondsindede programmer kan bruge dette til at sende dine data til andre mennesker."</string> <string name="permlab_writeContacts" msgid="644616215860933284">"skriv kontaktdata"</string> <string name="permdesc_writeContacts" msgid="3924383579108183601">"Tillader, at et program ændrer kontaktdata (adresser), der er gemt på din telefon. Ondsindede programmer kan bruge dette til at slette eller ændre kontaktdata."</string> - <string name="permlab_writeOwnerData" msgid="4892555913849295393">"skriv ejerdata"</string> - <string name="permdesc_writeOwnerData" msgid="2344055317969787124">"Tillader, at et program ændrer rtelefonens ejerdata, der er gemt på din telefon. Ondsindede programmer kan bruge dette til at slette eller ændre ejerdata."</string> - <string name="permlab_readOwnerData" msgid="6668525984731523563">"læs ejerdata"</string> - <string name="permdesc_readOwnerData" msgid="3088486383128434507">"Tillader, at et program læser telefonens ejerdata, der er gemt på din telefon. Ondsindede programmer kan bruge dette til at læse ejerdata."</string> <string name="permlab_readCalendar" msgid="6898987798303840534">"læs kalenderbegivenheder"</string> <string name="permdesc_readCalendar" msgid="5533029139652095734">"Tillader, at et program læser alle kalenderbegivenheder, der er gemt på din telefon. Ondsindede programmer kan bruge dette til at sende dine kalenderbegivenheder til andre mennesker."</string> <string name="permlab_writeCalendar" msgid="3894879352594904361">"tilføj eller rediger kalenderbegivenheder, og send e-mail til gæster"</string> @@ -311,8 +310,8 @@ <string name="permdesc_modifyAudioSettings" msgid="5793461287365991922">"Tillader, at et program ændrer globale lydindstillinger som f.eks. lydstyrke og kanalisering."</string> <string name="permlab_recordAudio" msgid="3876049771427466323">"optag lyd"</string> <string name="permdesc_recordAudio" msgid="6493228261176552356">"Tillader, at et program får adgang til lydregistreringsstien."</string> - <string name="permlab_camera" msgid="8059288807274039014">"tag billeder"</string> - <string name="permdesc_camera" msgid="9013476258810982546">"Tillader, at programmet tager billeder med kameraet. Dette giver programmet mulighed for altid at indsamle de billeder, kameraet ser."</string> + <string name="permlab_camera" msgid="3616391919559751192">"tag billeder og optag video"</string> + <string name="permdesc_camera" msgid="6004878235852154239">"Tillader, at programmet tager billeder og optager video med kameraet. Dette giver programmet mulighed for altid at indsamle de billeder, kameraet ser."</string> <string name="permlab_brick" msgid="8337817093326370537">"deaktiver telefonen permanent"</string> <string name="permdesc_brick" msgid="5569526552607599221">"Tillader, at programmet deaktiverer hele telefonen permanent. Dette er meget farligt."</string> <string name="permlab_reboot" msgid="2898560872462638242">"tving telefon til at genstarte"</string> @@ -335,6 +334,8 @@ <string name="permdesc_vibrate" msgid="2886677177257789187">"Lader programmet kontrollere vibratoren."</string> <string name="permlab_flashlight" msgid="2155920810121984215">"kontroller lommelygte"</string> <string name="permdesc_flashlight" msgid="6433045942283802309">"Tillader, at programmet kontrollerer lommelygten."</string> + <string name="permlab_accessUsb" msgid="7362327818655760496">"adgang til USB-enheder."</string> + <string name="permdesc_accessUsb" msgid="2414271762914049292">"Tillader, at programmet har adgang til USB-enheder."</string> <string name="permlab_hardware_test" msgid="4148290860400659146">"test hardware"</string> <string name="permdesc_hardware_test" msgid="3668894686500081699">"Tillader, at et program kontrollerer forskellige perifere enheder for at teste hardwaren."</string> <string name="permlab_callPhone" msgid="3925836347681847954">"ring direkte op til telefonnumre"</string> @@ -573,16 +574,6 @@ <string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string> <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string> <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string> - <string name="status_bar_clear_all_button" msgid="7774721344716731603">"Ryd"</string> - <string name="status_bar_no_notifications_title" msgid="4755261167193833213">"Ingen meddelelser"</string> - <string name="status_bar_ongoing_events_title" msgid="1682504513316879202">"I gang"</string> - <string name="status_bar_latest_events_title" msgid="6594767438577593172">"Meddelelser"</string> - <string name="battery_status_text_percent_format" msgid="7660311274698797147">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string> - <string name="battery_status_charging" msgid="756617993998772213">"Oplader ..."</string> - <string name="battery_low_title" msgid="7923774589611311406">"Forbind oplader"</string> - <string name="battery_low_subtitle" msgid="7388781709819722764">"Batteriet er ved at være fladt:"</string> - <string name="battery_low_percent_format" msgid="696154104579022959">"<xliff:g id="NUMBER">%d%%</xliff:g> eller mindre tilbage."</string> - <string name="battery_low_why" msgid="7279169609518386372">"Batteriforbrug"</string> <string name="factorytest_failed" msgid="5410270329114212041">"Fabrikstest mislykkedes"</string> <string name="factorytest_not_system" msgid="4435201656767276723">"Handlingen FACTORY_TEST understøttes kun af pakker installeret i /system/app."</string> <string name="factorytest_no_action" msgid="872991874799998561">"Der blev ikke fundet nogen pakke, som leverer handlingen FACTORY_TEST."</string> @@ -708,12 +699,9 @@ <string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string> <string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string> <string name="selectAll" msgid="6876518925844129331">"Vælg alle"</string> - <string name="selectText" msgid="3889149123626888637">"Marker tekst"</string> - <string name="stopSelectingText" msgid="4157931463872320996">"Hold op med at markere tekst"</string> + <!-- outdated translation 3889149123626888637 --> <string name="selectText" msgid="4862359311088898878">"Marker tekst"</string> <string name="cut" msgid="3092569408438626261">"Klip"</string> - <string name="cutAll" msgid="2436383270024931639">"Klip alle"</string> <string name="copy" msgid="2681946229533511987">"Kopier"</string> - <string name="copyAll" msgid="2590829068100113057">"Kopier alle"</string> <string name="paste" msgid="5629880836805036433">"Indsæt"</string> <string name="copyUrl" msgid="2538211579596067402">"Kopier webadresse"</string> <string name="inputMethod" msgid="1653630062304567879">"Inputmetode"</string> @@ -744,6 +732,22 @@ <string name="force_close" msgid="3653416315450806396">"Tving til at lukke"</string> <string name="report" msgid="4060218260984795706">"Rapporter"</string> <string name="wait" msgid="7147118217226317732">"Vent"</string> + <!-- no translation found for launch_warning_title (8323761616052121936) --> + <skip /> + <!-- no translation found for launch_warning_replace (6202498949970281412) --> + <skip /> + <!-- no translation found for launch_warning_original (188102023021668683) --> + <skip /> + <string name="smv_application" msgid="295583804361236288">"Programmet <xliff:g id="APPLICATION">%1$s</xliff:g> (proces <xliff:g id="PROCESS">%2$s</xliff:g>) har overtrådt sin egen StrictMode-politik."</string> + <string name="smv_process" msgid="5120397012047462446">"Processen <xliff:g id="PROCESS">%1$s</xliff:g> har overtrådt sin egen StrictMode-politik."</string> + <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g> er i gang"</string> + <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"Vælg for at skifte program"</string> + <string name="heavy_weight_switcher_title" msgid="1135403633766694316">"Skift program?"</string> + <string name="heavy_weight_switcher_text" msgid="4592075610079319667">"Der kører et andet program, der skal stoppes, før du kan starte et nyt."</string> + <string name="old_app_action" msgid="493129172238566282">"Tilbage til <xliff:g id="OLD_APP">%1$s</xliff:g>"</string> + <string name="old_app_description" msgid="942967900237208466">"Start ikke det nye program."</string> + <string name="new_app_action" msgid="5472756926945440706">"Start <xliff:g id="OLD_APP">%1$s</xliff:g>"</string> + <string name="new_app_description" msgid="6830398339826789493">"Stop det gamle program uden at gemme."</string> <string name="sendText" msgid="5132506121645618310">"Vælg en handling for teksten"</string> <string name="volume_ringtone" msgid="6885421406845734650">"Lydstyrke for opkald"</string> <string name="volume_music" msgid="5421651157138628171">"Lydstyrke for medier"</string> diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml index 11095c0067c3..4c5cc8c09ab1 100644 --- a/core/res/res/values-de/strings.xml +++ b/core/res/res/values-de/strings.xml @@ -34,6 +34,7 @@ <string name="defaultVoiceMailAlphaTag" msgid="2660020990097733077">"Mailbox"</string> <string name="defaultMsisdnAlphaTag" msgid="2850889754919584674">"MSISDN1"</string> <string name="mmiError" msgid="5154499457739052907">"Verbindungsproblem oder ungültiger MMI-Code."</string> + <string name="mmiFdnError" msgid="5224398216385316471">"Der Vorgang ist nur für Ihre zugelassenen Rufnummern möglich."</string> <string name="serviceEnabled" msgid="8147278346414714315">"Dienst wurde aktiviert."</string> <string name="serviceEnabledFor" msgid="6856228140453471041">"Dienst wurde aktiviert für:"</string> <string name="serviceDisabled" msgid="1937553226592516411">"Dienst wurde deaktiviert."</string> @@ -171,6 +172,8 @@ <string name="permgroupdesc_storage" msgid="9203302214915355774">"Greift auf die SD-Karte zu."</string> <string name="permlab_statusBar" msgid="7417192629601890791">"Statusleiste deaktivieren oder ändern"</string> <string name="permdesc_statusBar" msgid="1365473595331989732">"Ermöglicht der Anwendung, die Statusanzeige zu deaktivieren oder Systemsymbole hinzuzufügen oder zu entfernen."</string> + <string name="permlab_statusBarService" msgid="7247281911387931485">"Statusleiste"</string> + <string name="permdesc_statusBarService" msgid="4097605867643520920">"Die Anwendung wird damit in der Statusleiste angezeigt."</string> <string name="permlab_expandStatusBar" msgid="1148198785937489264">"Statusleiste ein-/ausblenden"</string> <string name="permdesc_expandStatusBar" msgid="7088604400110768665">"Ermöglicht der Anwendung, die Statusleiste ein- oder auszublenden."</string> <string name="permlab_processOutgoingCalls" msgid="1136262550878335980">"Abgehende Anrufe abfangen"</string> @@ -239,7 +242,7 @@ <string name="permdesc_readInputState" msgid="5132879321450325445">"Ermöglicht Anwendungen, die von Ihnen gedrückten Tasten zu überwachen (etwa die Eingabe eines Passworts). Dies gilt auch für die Interaktion mit anderen Anwendungen. Sollte für normale Anwendungen nicht benötigt werden."</string> <string name="permlab_bindInputMethod" msgid="3360064620230515776">"An eine Eingabemethode binden"</string> <string name="permdesc_bindInputMethod" msgid="3734838321027317228">"Ermöglicht dem Halter, sich an die Oberfläche einer Eingabemethode auf oberster Ebene zu binden. Sollte nie für normale Anwendungen benötigt werden."</string> - <string name="permlab_bindWallpaper" msgid="8716400279937856462">"An ein Hintergrundbild binden"</string> + <string name="permlab_bindWallpaper" msgid="8716400279937856462">"An einen Hintergrund binden"</string> <string name="permdesc_bindWallpaper" msgid="5287754520361915347">"Ermöglicht dem Halter, sich an die Oberfläche einer Eingabemethode auf oberster Ebene zu binden. Sollte nie für normale Anwendungen benötigt werden."</string> <string name="permlab_bindDeviceAdmin" msgid="8704986163711455010">"Interaktion mit einem Geräteadministrator"</string> <string name="permdesc_bindDeviceAdmin" msgid="8714424333082216979">"Ermöglicht dem Halter, Intents an einen Geräteadministrator zu senden. Sollte nie für normale Anwendungen benötigt werden."</string> @@ -285,10 +288,6 @@ <string name="permdesc_readContacts" msgid="3371591512896545975">"Ermöglicht einer Anwendung, alle auf Ihrem Telefon gespeicherten Kontaktdaten (Adressen) zu lesen. Schädliche Anwendungen können so Ihre Daten an andere Personen senden."</string> <string name="permlab_writeContacts" msgid="644616215860933284">"Kontaktdaten schreiben"</string> <string name="permdesc_writeContacts" msgid="3924383579108183601">"Ermöglicht einer Anwendung, die auf Ihrem Telefon gespeicherten Kontaktdaten (Adressen) zu ändern. Schädliche Anwendungen können so Ihre Kontaktdaten löschen oder verändern."</string> - <string name="permlab_writeOwnerData" msgid="4892555913849295393">"Eigentümerdaten schreiben"</string> - <string name="permdesc_writeOwnerData" msgid="2344055317969787124">"Ermöglicht einer Anwendung, die auf Ihrem Telefon gespeicherten Eigentümerdaten zu ändern. Schädliche Anwendungen können so Eigentümerdaten löschen oder verändern."</string> - <string name="permlab_readOwnerData" msgid="6668525984731523563">"Eigentümerdaten lesen"</string> - <string name="permdesc_readOwnerData" msgid="3088486383128434507">"Ermöglicht einer Anwendung, die auf Ihrem Telefon gespeicherten Eigentümerdaten zu lesen. Schädliche Anwendungen können so Eigentümerdaten lesen."</string> <string name="permlab_readCalendar" msgid="6898987798303840534">"Kalendereinträge lesen"</string> <string name="permdesc_readCalendar" msgid="5533029139652095734">"Ermöglicht einer Anwendung, alle auf Ihrem Telefon gespeicherten Kalenderereignisse zu lesen. Schädliche Anwendungen können so Ihre Kalenderereignisse an andere Personen senden."</string> <string name="permlab_writeCalendar" msgid="3894879352594904361">"Kalendereinträge hinzufügen oder ändern und E-Mails an Gäste senden"</string> @@ -311,8 +310,8 @@ <string name="permdesc_modifyAudioSettings" msgid="5793461287365991922">"Ermöglicht der Anwendung, Änderungen an allgemeinen Audioeinstellungen wie Lautstärke und Weiterleitung vorzunehmen."</string> <string name="permlab_recordAudio" msgid="3876049771427466323">"Audio aufnehmen"</string> <string name="permdesc_recordAudio" msgid="6493228261176552356">"Ermöglicht der Anwendung, auf den Pfad für Audioaufzeichnungen zuzugreifen."</string> - <string name="permlab_camera" msgid="8059288807274039014">"Fotos aufnehmen"</string> - <string name="permdesc_camera" msgid="9013476258810982546">"Ermöglicht der Anwendung, Fotos mit der Kamera aufzunehmen. So kann die Anwendung jederzeit Bilder zusammentragen, die von der Kamera erfasst werden."</string> + <string name="permlab_camera" msgid="3616391919559751192">"Bilder und Videos aufnehmen"</string> + <string name="permdesc_camera" msgid="6004878235852154239">"Ermöglicht der Anwendung, Fotos und Videos mit der Kamera aufzunehmen. So kann die Anwendung jederzeit Bilder zusammentragen, die von der Kamera erfasst werden."</string> <string name="permlab_brick" msgid="8337817093326370537">"Telefon dauerhaft deaktivieren."</string> <string name="permdesc_brick" msgid="5569526552607599221">"Ermöglicht der Anwendung, das gesamte Telefon dauerhaft zu deaktivieren. Dies birgt hohe Risiken."</string> <string name="permlab_reboot" msgid="2898560872462638242">"Neustart des Telefons erzwingen"</string> @@ -335,6 +334,8 @@ <string name="permdesc_vibrate" msgid="2886677177257789187">"Ermöglicht der Anwendung, den Vibrationsalarm zu steuern."</string> <string name="permlab_flashlight" msgid="2155920810121984215">"Lichtanzeige steuern"</string> <string name="permdesc_flashlight" msgid="6433045942283802309">"Ermöglicht der Anwendung, die Lichtanzeige zu steuern."</string> + <string name="permlab_accessUsb" msgid="7362327818655760496">"auf USB-Geräte zugreifen"</string> + <string name="permdesc_accessUsb" msgid="2414271762914049292">"Die Anwendung erhält Zugriff auf USB-Geräte."</string> <string name="permlab_hardware_test" msgid="4148290860400659146">"Hardware testen"</string> <string name="permdesc_hardware_test" msgid="3668894686500081699">"Ermöglicht einer Anwendung, verschiedene Peripherie-Geräte zu Hardware-Testzwecken zu steuern."</string> <string name="permlab_callPhone" msgid="3925836347681847954">"Telefonnummern direkt anrufen"</string> @@ -359,10 +360,10 @@ <string name="permdesc_devicePower" msgid="4577331933252444818">"Ermöglicht der Anwendung, das Telefon ein- oder auszuschalten."</string> <string name="permlab_factoryTest" msgid="3715225492696416187">"In Werkstestmodus ausführen"</string> <string name="permdesc_factoryTest" msgid="8136644990319244802">"Führt einen systemnahen Herstellertest durch, in dessen Rahmen auf die gesamte Telefonhardware zugegriffen werden kann. Nur verfügbar, wenn ein Telefon im Werkstestmodus ausgeführt wird."</string> - <string name="permlab_setWallpaper" msgid="6627192333373465143">"Hintergrundbild festlegen"</string> - <string name="permdesc_setWallpaper" msgid="6417041752170585837">"Ermöglicht der Anwendung, das System-Hintergrundbild festzulegen."</string> - <string name="permlab_setWallpaperHints" msgid="3600721069353106851">"Größenhinweise für Hintergrundbild festlegen"</string> - <string name="permdesc_setWallpaperHints" msgid="6019479164008079626">"Ermöglicht der Anwendung, die Größenhinweise für das Hintergrundbild festzulegen."</string> + <string name="permlab_setWallpaper" msgid="6627192333373465143">"Hintergrund festlegen"</string> + <string name="permdesc_setWallpaper" msgid="6417041752170585837">"Ermöglicht der Anwendung, den System-Hintergrund festzulegen."</string> + <string name="permlab_setWallpaperHints" msgid="3600721069353106851">"Größenhinweise für Hintergrund festlegen"</string> + <string name="permdesc_setWallpaperHints" msgid="6019479164008079626">"Ermöglicht der Anwendung, die Größenhinweise für den Hintergrund festzulegen."</string> <string name="permlab_masterClear" msgid="2315750423139697397">"System auf Werkseinstellung zurücksetzen"</string> <string name="permdesc_masterClear" msgid="5033465107545174514">"Ermöglicht einer Anwendung, das System komplett auf Werkseinstellung zurückzusetzen. Hierbei werden alle Daten, Konfigurationen und installierten Anwendungen gelöscht."</string> <string name="permlab_setTime" msgid="2021614829591775646">"Zeit einstellen"</string> @@ -573,16 +574,6 @@ <string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string> <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string> <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string> - <string name="status_bar_clear_all_button" msgid="7774721344716731603">"Löschen"</string> - <string name="status_bar_no_notifications_title" msgid="4755261167193833213">"Keine Benachrichtigungen"</string> - <string name="status_bar_ongoing_events_title" msgid="1682504513316879202">"Aktuell"</string> - <string name="status_bar_latest_events_title" msgid="6594767438577593172">"Benachrichtigungen"</string> - <string name="battery_status_text_percent_format" msgid="7660311274698797147">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string> - <string name="battery_status_charging" msgid="756617993998772213">"Wird aufgeladen..."</string> - <string name="battery_low_title" msgid="7923774589611311406">"Ladegerät anschließen"</string> - <string name="battery_low_subtitle" msgid="7388781709819722764">"Akku ist fast leer."</string> - <string name="battery_low_percent_format" msgid="696154104579022959">"<xliff:g id="NUMBER">%d%%</xliff:g> oder weniger verbleiben."</string> - <string name="battery_low_why" msgid="7279169609518386372">"Akkuverbrauch"</string> <string name="factorytest_failed" msgid="5410270329114212041">"Werkstest fehlgeschlagen"</string> <string name="factorytest_not_system" msgid="4435201656767276723">"Die Aktion FACTORY_TEST wird nur für unter \"/system/app\" gespeicherte Pakete unterstützt."</string> <string name="factorytest_no_action" msgid="872991874799998561">"Es wurden kein Paket mit der Aktion FACTORY_TEST gefunden."</string> @@ -708,12 +699,9 @@ <string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string> <string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string> <string name="selectAll" msgid="6876518925844129331">"Alles auswählen"</string> - <string name="selectText" msgid="3889149123626888637">"Text auswählen"</string> - <string name="stopSelectingText" msgid="4157931463872320996">"Textauswahl beenden"</string> + <!-- outdated translation 3889149123626888637 --> <string name="selectText" msgid="4862359311088898878">"Text auswählen"</string> <string name="cut" msgid="3092569408438626261">"Ausschneiden"</string> - <string name="cutAll" msgid="2436383270024931639">"Alles ausschneiden"</string> <string name="copy" msgid="2681946229533511987">"Kopieren"</string> - <string name="copyAll" msgid="2590829068100113057">"Alles kopieren"</string> <string name="paste" msgid="5629880836805036433">"Einfügen"</string> <string name="copyUrl" msgid="2538211579596067402">"URL kopieren"</string> <string name="inputMethod" msgid="1653630062304567879">"Eingabemethode"</string> @@ -744,6 +732,22 @@ <string name="force_close" msgid="3653416315450806396">"Schließen erzwingen"</string> <string name="report" msgid="4060218260984795706">"Bericht"</string> <string name="wait" msgid="7147118217226317732">"Warten"</string> + <!-- no translation found for launch_warning_title (8323761616052121936) --> + <skip /> + <!-- no translation found for launch_warning_replace (6202498949970281412) --> + <skip /> + <!-- no translation found for launch_warning_original (188102023021668683) --> + <skip /> + <string name="smv_application" msgid="295583804361236288">"Die Anwendung <xliff:g id="APPLICATION">%1$s</xliff:g> (Prozess <xliff:g id="PROCESS">%2$s</xliff:g>) hat gegen ihre selbsterzwungene StrictMode-Richtlinie verstoßen."</string> + <string name="smv_process" msgid="5120397012047462446">"Der Prozess <xliff:g id="PROCESS">%1$s</xliff:g> hat gegen seine selbsterzwungene StrictMode-Richtlinie verstoßen."</string> + <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g> läuft"</string> + <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"Auswählen zum Wechseln in die Anwendung"</string> + <string name="heavy_weight_switcher_title" msgid="1135403633766694316">"Anwendung wechseln?"</string> + <string name="heavy_weight_switcher_text" msgid="4592075610079319667">"Eine andere Anwendung wird bereits ausgeführt und muss vor dem Start einer neuen Anwendung beendet werden."</string> + <string name="old_app_action" msgid="493129172238566282">"Zu <xliff:g id="OLD_APP">%1$s</xliff:g> zurückkehren"</string> + <string name="old_app_description" msgid="942967900237208466">"Starten Sie die neue Anwendung nicht."</string> + <string name="new_app_action" msgid="5472756926945440706">"<xliff:g id="OLD_APP">%1$s</xliff:g> starten"</string> + <string name="new_app_description" msgid="6830398339826789493">"Beenden Sie die Anwendung ohne zu speichern."</string> <string name="sendText" msgid="5132506121645618310">"Aktion für Text auswählen"</string> <string name="volume_ringtone" msgid="6885421406845734650">"Klingeltonlautstärke"</string> <string name="volume_music" msgid="5421651157138628171">"Medienlautstärke"</string> @@ -844,7 +848,7 @@ <string name="sync_binding_label" msgid="3687969138375092423">"Synchronisieren"</string> <string name="accessibility_binding_label" msgid="4148120742096474641">"Eingabehilfen"</string> <string name="wallpaper_binding_label" msgid="1240087844304687662">"Hintergrund"</string> - <string name="chooser_wallpaper" msgid="7873476199295190279">"Hintergrundbild ändern"</string> + <string name="chooser_wallpaper" msgid="7873476199295190279">"Hintergrund ändern"</string> <string name="pptp_vpn_description" msgid="2688045385181439401">"Point-to-Point-Tunneling-Protokoll"</string> <string name="l2tp_vpn_description" msgid="3750692169378923304">"Layer-2-Tunneling-Protokoll"</string> <string name="l2tp_ipsec_psk_vpn_description" msgid="3945043564008303239">"L2TP/IPSec-VPN mit vorinstalliertem Schlüssel"</string> diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml index d6b1e09fc3c8..45863e72a872 100644 --- a/core/res/res/values-el/strings.xml +++ b/core/res/res/values-el/strings.xml @@ -34,6 +34,7 @@ <string name="defaultVoiceMailAlphaTag" msgid="2660020990097733077">"Αυτόματος τηλεφωνητής"</string> <string name="defaultMsisdnAlphaTag" msgid="2850889754919584674">"MSISDN1"</string> <string name="mmiError" msgid="5154499457739052907">"Πρόβλημα σύνδεσης ή μη έγκυρος κώδικας MMI."</string> + <string name="mmiFdnError" msgid="5224398216385316471">"Η λειτουργία περιορίζεται μόνο σε καθορισμένους αριθμούς κλήσης."</string> <string name="serviceEnabled" msgid="8147278346414714315">"Η υπηρεσία ενεργοποιήθηκε."</string> <string name="serviceEnabledFor" msgid="6856228140453471041">"Η υπηρεσία ενεργοποιήθηκε για:"</string> <string name="serviceDisabled" msgid="1937553226592516411">"Η υπηρεσία έχει απενεργοποιηθεί."</string> @@ -171,6 +172,8 @@ <string name="permgroupdesc_storage" msgid="9203302214915355774">"Πρόσβαση στην κάρτα SD."</string> <string name="permlab_statusBar" msgid="7417192629601890791">"απενεργοποίηση ή τροποποίηση γραμμής κατάστασης"</string> <string name="permdesc_statusBar" msgid="1365473595331989732">"Επιτρέπει στην εφαρμογή να απενεργοποιεί τη γραμμή κατάστασης ή να προσθέτει και να αφαιρεί εικονίδια συστήματος."</string> + <string name="permlab_statusBarService" msgid="7247281911387931485">"γραμμή κατάστασης"</string> + <string name="permdesc_statusBarService" msgid="4097605867643520920">"Επιτρέπει στην εφαρμογή να βρίσκεται στη γραμμή κατάστασης."</string> <string name="permlab_expandStatusBar" msgid="1148198785937489264">"ανάπτυξη/σύμπτυξη γραμμής κατάστασης"</string> <string name="permdesc_expandStatusBar" msgid="7088604400110768665">"Επιτρέπει σε μια εφαρμογή να αναπτύξει ή να συμπτύξει την γραμμή κατάστασης."</string> <string name="permlab_processOutgoingCalls" msgid="1136262550878335980">"φραγή εξερχόμενων κλήσεων"</string> @@ -285,10 +288,6 @@ <string name="permdesc_readContacts" msgid="3371591512896545975">"Επιτρέπει σε μια εφαρμογή την ανάγνωση όλων των δεδομένων επαφής (διεύθυνσης) που είναι αποθηκευμένα στο τηλέφωνό σας. Κακόβουλες εφαρμογές μπορούν να το χρησιμοποιήσουν για να αποστείλουν τα δεδομένα σας σε τρίτους."</string> <string name="permlab_writeContacts" msgid="644616215860933284">"εγγραφή δεδομένων επαφής"</string> <string name="permdesc_writeContacts" msgid="3924383579108183601">"Επιτρέπει σε μια εφαρμογή να τροποποιεί τα δεδομένα επαφής (διεύθυνσης) που είναι αποθηκευμένα στο τηλέφωνό σας. Κακόβουλες εφαρμογές μπορούν να το χρησιμοποιήσουν για να διαγράψουν ή να τροποποιήσουν τα δεδομένα επαφών σας."</string> - <string name="permlab_writeOwnerData" msgid="4892555913849295393">"εγγραφή δεδομένων κατόχου"</string> - <string name="permdesc_writeOwnerData" msgid="2344055317969787124">"Επιτρέπει σε μια εφαρμογή να τροποποιήσει τα δεδομένα κατόχου τηλεφώνου στο τηλέφωνό σας. Κακόβουλες εφαρμογές μπορούν να το χρησιμοποιήσουν για να τροποποιήσουν τα δεδομένα κατόχου."</string> - <string name="permlab_readOwnerData" msgid="6668525984731523563">"ανάγνωση δεδομένων κατόχου"</string> - <string name="permdesc_readOwnerData" msgid="3088486383128434507">"Επιτρέπει σε μια εφαρμογή την ανάγνωση των δεδομένων κατόχου τηλεφώνου που είναι αποθηκευμένα στο τηλέφωνό σας. Κακόβουλες εφαρμογές μπορούν να το χρησιμοποιήσουν για την ανάγνωση δεδομένων κατόχου τηλεφώνου."</string> <string name="permlab_readCalendar" msgid="6898987798303840534">"ανάγνωση συμβάντων ημερολογίου"</string> <string name="permdesc_readCalendar" msgid="5533029139652095734">"Επιτρέπει σε μια εφαρμογή να αναγνώσει όλα τα συμβάντα ημερολογίου που είναι αποθηκευμένα στο τηλέφωνό σας. Κακόβουλες εφαρμογές μπορούν να το χρησιμοποιήσουν για να αποστείλουν συμβάντα ημερολογίου σε άλλους χρήστες."</string> <string name="permlab_writeCalendar" msgid="3894879352594904361">"προσθήκη ή τροποποίηση συμβάντων του ημερολογίου και αποστολή μηνυμάτων ηλεκτρονικού ταχυδρομείου στους προσκεκλημένους"</string> @@ -311,8 +310,8 @@ <string name="permdesc_modifyAudioSettings" msgid="5793461287365991922">"Επιτρέπει σε μια εφαρμογή να τροποποιήσει καθολικές ρυθμίσεις ήχου όπως ένταση ήχου και δρομολόγηση ήχου."</string> <string name="permlab_recordAudio" msgid="3876049771427466323">"εγγραφή ήχου"</string> <string name="permdesc_recordAudio" msgid="6493228261176552356">"Επιτρέπει σε μια εφαρμογή την πρόσβαση στη διαδρομή εγγραφής ήχου."</string> - <string name="permlab_camera" msgid="8059288807274039014">"λήψη φωτογραφιών"</string> - <string name="permdesc_camera" msgid="9013476258810982546">"Επιτρέπει σε μια εφαρμογή τη λήψη φωτογραφιών με την κάμερα. Αυτό επιτρέπει στην εφαρμογή να συλλέξει εικόνες, στις οποίες εστιάζει η κάμερα οποιαδήποτε στιγμή."</string> + <string name="permlab_camera" msgid="3616391919559751192">"λήψη φωτογραφιών και βίντεο"</string> + <string name="permdesc_camera" msgid="6004878235852154239">"Επιτρέπει σε μια εφαρμογή τη λήψη φωτογραφιών και βίντεο με την κάμερα. Αυτό επιτρέπει στην εφαρμογή να συλλέξει εικόνες, στις οποίες εστιάζει η κάμερα οποιαδήποτε στιγμή."</string> <string name="permlab_brick" msgid="8337817093326370537">"μόνιμη απενεργοποίηση τηλεφώνου"</string> <string name="permdesc_brick" msgid="5569526552607599221">"Επιτρέπει στην εφαρμογή τη μόνιμη απενεργοποίηση όλων των λειτουργιών του τηλεφώνου, το οποίο είναι εξαιρετικά επικίνδυνο."</string> <string name="permlab_reboot" msgid="2898560872462638242">"αναγκαστική επανεκκίνηση τηλεφώνου"</string> @@ -335,6 +334,8 @@ <string name="permdesc_vibrate" msgid="2886677177257789187">"Επιτρέπει στην εφαρμογή τον έλεγχο του δονητή."</string> <string name="permlab_flashlight" msgid="2155920810121984215">"έλεγχος φακού"</string> <string name="permdesc_flashlight" msgid="6433045942283802309">"Επιτρέπει στην εφαρμογή τον έλεγχο του φακού."</string> + <string name="permlab_accessUsb" msgid="7362327818655760496">"πρόσβαση σε συσκευές USB"</string> + <string name="permdesc_accessUsb" msgid="2414271762914049292">"Επιτρέπει στην εφαρμογή την πρόσβαση σε συσκευές USB."</string> <string name="permlab_hardware_test" msgid="4148290860400659146">"δοκιμή υλικού"</string> <string name="permdesc_hardware_test" msgid="3668894686500081699">"Επιτρέπει σε μια εφαρμογή τον έλεγχο διαφόρων περιφερειακών για την εκτέλεση δοκιμών υλικού."</string> <string name="permlab_callPhone" msgid="3925836347681847954">"απευθείας κλήση τηλεφωνικών αριθμών"</string> @@ -573,16 +574,6 @@ <string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string> <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string> <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string> - <string name="status_bar_clear_all_button" msgid="7774721344716731603">"Εκκαθάριση"</string> - <string name="status_bar_no_notifications_title" msgid="4755261167193833213">"Δεν υπάρχουν ειδοποιήσεις"</string> - <string name="status_bar_ongoing_events_title" msgid="1682504513316879202">"Εν εξελίξει"</string> - <string name="status_bar_latest_events_title" msgid="6594767438577593172">"Ειδοποιήσεις"</string> - <string name="battery_status_text_percent_format" msgid="7660311274698797147">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string> - <string name="battery_status_charging" msgid="756617993998772213">"Φόρτιση..."</string> - <string name="battery_low_title" msgid="7923774589611311406">"Συνδέστε τον φορτιστή"</string> - <string name="battery_low_subtitle" msgid="7388781709819722764">"Η στάθμη της μπαταρίας είναι χαμηλή:"</string> - <string name="battery_low_percent_format" msgid="696154104579022959">"Απομένει <xliff:g id="NUMBER">%d%%</xliff:g> ή λιγότερο."</string> - <string name="battery_low_why" msgid="7279169609518386372">"Χρήση μπαταρίας"</string> <string name="factorytest_failed" msgid="5410270329114212041">"Η εργοστασιακή δοκιμή απέτυχε"</string> <string name="factorytest_not_system" msgid="4435201656767276723">"Η ενέργεια FACTORY_TEST υποστηρίζεται μόνο για πακέτα που είναι εγκατεστημένα στον κατάλογο /system/app."</string> <string name="factorytest_no_action" msgid="872991874799998561">"Δεν βρέθηκε πακέτο που να παρέχει την ενέργεια FACTORY_TEST."</string> @@ -708,12 +699,9 @@ <string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string> <string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string> <string name="selectAll" msgid="6876518925844129331">"Επιλογή όλων"</string> - <string name="selectText" msgid="3889149123626888637">"Επιλογή κειμένου"</string> - <string name="stopSelectingText" msgid="4157931463872320996">"Διακοπή επιλογής κειμένου"</string> + <!-- outdated translation 3889149123626888637 --> <string name="selectText" msgid="4862359311088898878">"Επιλογή κειμένου"</string> <string name="cut" msgid="3092569408438626261">"Αποκοπή"</string> - <string name="cutAll" msgid="2436383270024931639">"Αποκοπή όλων"</string> <string name="copy" msgid="2681946229533511987">"Αντιγραφή"</string> - <string name="copyAll" msgid="2590829068100113057">"Αντιγραφή όλων"</string> <string name="paste" msgid="5629880836805036433">"Επικόλληση"</string> <string name="copyUrl" msgid="2538211579596067402">"Αντιγραφή διεύθυνσης URL"</string> <string name="inputMethod" msgid="1653630062304567879">"Μέθοδος εισόδου"</string> @@ -744,6 +732,22 @@ <string name="force_close" msgid="3653416315450806396">"Αναγκαστικό κλείσιμο"</string> <string name="report" msgid="4060218260984795706">"Αναφορά"</string> <string name="wait" msgid="7147118217226317732">"Αναμονή"</string> + <!-- no translation found for launch_warning_title (8323761616052121936) --> + <skip /> + <!-- no translation found for launch_warning_replace (6202498949970281412) --> + <skip /> + <!-- no translation found for launch_warning_original (188102023021668683) --> + <skip /> + <string name="smv_application" msgid="295583804361236288">"Η εφαρμογή <xliff:g id="APPLICATION">%1$s</xliff:g> (διεργασία <xliff:g id="PROCESS">%2$s</xliff:g>) παραβίασε την αυτοεπιβαλόμενη πολιτική StrictMode."</string> + <string name="smv_process" msgid="5120397012047462446">"Η διεργασία <xliff:g id="PROCESS">%1$s</xliff:g> παραβίασε την αυτοεπιβαλόμενη πολιτική StrictMode."</string> + <string name="heavy_weight_notification" msgid="9087063985776626166">"Η εφαρμογή <xliff:g id="APP">%1$s</xliff:g> εκτελείται"</string> + <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"Επιλέξτε για αλλαγή σε εφαρμογή"</string> + <string name="heavy_weight_switcher_title" msgid="1135403633766694316">"Αλλαγή εφαρμογών;"</string> + <string name="heavy_weight_switcher_text" msgid="4592075610079319667">"Εκτελείται ήδη άλλη εφαρμογή την οποία πρέπει να διακόψετε για να είναι δυνατή η εκτέλεση της νέας."</string> + <string name="old_app_action" msgid="493129172238566282">"Επιστροφή σε <xliff:g id="OLD_APP">%1$s</xliff:g>"</string> + <string name="old_app_description" msgid="942967900237208466">"Μην εκκινήσετε τη νέα εφαρμογή."</string> + <string name="new_app_action" msgid="5472756926945440706">"Εκκίνηση της εφαρμογής <xliff:g id="OLD_APP">%1$s</xliff:g>"</string> + <string name="new_app_description" msgid="6830398339826789493">"Διακόψτε την παλιά εφαρμογή χωρίς αποθήκευση."</string> <string name="sendText" msgid="5132506121645618310">"Επιλέξτε μια ενέργεια για το κείμενο"</string> <string name="volume_ringtone" msgid="6885421406845734650">"Ένταση ειδοποίησης ήχου"</string> <string name="volume_music" msgid="5421651157138628171">"Ένταση ήχου πολυμέσων"</string> diff --git a/core/res/res/values-en-rAU/strings.xml b/core/res/res/values-en-rAU/strings.xml index 3de378bee211..1cbfbbaad9e9 100644 --- a/core/res/res/values-en-rAU/strings.xml +++ b/core/res/res/values-en-rAU/strings.xml @@ -1079,11 +1079,9 @@ <skip /> <!-- no translation found for cut (5845613239192595662) --> <skip /> - <!-- no translation found for cutAll (4474519683293791451) --> <skip /> <!-- no translation found for copy (8603721575469529820) --> <skip /> - <!-- no translation found for copyAll (4777548804630476932) --> <skip /> <!-- no translation found for paste (6458036735811828538) --> <skip /> diff --git a/core/res/res/values-en-rSG/strings.xml b/core/res/res/values-en-rSG/strings.xml index 2ec6b0b02c48..09a84906c0ee 100644 --- a/core/res/res/values-en-rSG/strings.xml +++ b/core/res/res/values-en-rSG/strings.xml @@ -1074,11 +1074,9 @@ <skip /> <!-- no translation found for cut (5845613239192595662) --> <skip /> - <!-- no translation found for cutAll (4474519683293791451) --> <skip /> <!-- no translation found for copy (8603721575469529820) --> <skip /> - <!-- no translation found for copyAll (4777548804630476932) --> <skip /> <!-- no translation found for paste (6458036735811828538) --> <skip /> diff --git a/core/res/res/values-en-rUS/strings.xml b/core/res/res/values-en-rUS/strings.xml index 05f30fca350f..fdc0d6961569 100644 --- a/core/res/res/values-en-rUS/strings.xml +++ b/core/res/res/values-en-rUS/strings.xml @@ -1115,11 +1115,9 @@ <skip /> <!-- no translation found for cut (5845613239192595662) --> <skip /> - <!-- no translation found for cutAll (4474519683293791451) --> <skip /> <!-- no translation found for copy (8603721575469529820) --> <skip /> - <!-- no translation found for copyAll (4777548804630476932) --> <skip /> <!-- no translation found for paste (6458036735811828538) --> <skip /> diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml index 09532028b2a5..2765e9673b7b 100644 --- a/core/res/res/values-es-rUS/strings.xml +++ b/core/res/res/values-es-rUS/strings.xml @@ -34,6 +34,7 @@ <string name="defaultVoiceMailAlphaTag" msgid="2660020990097733077">"Correo de voz"</string> <string name="defaultMsisdnAlphaTag" msgid="2850889754919584674">"MSISDN1"</string> <string name="mmiError" msgid="5154499457739052907">"Problema de conexión o código incorrecto de MMI."</string> + <string name="mmiFdnError" msgid="5224398216385316471">"La operación está limitada a números de marcación fija."</string> <string name="serviceEnabled" msgid="8147278346414714315">"Se ha activado el servicio."</string> <string name="serviceEnabledFor" msgid="6856228140453471041">"Se activó el servicio para:"</string> <string name="serviceDisabled" msgid="1937553226592516411">"Se ha desactivado el servicio."</string> @@ -171,6 +172,8 @@ <string name="permgroupdesc_storage" msgid="9203302214915355774">"Acceder a la tarjeta SD."</string> <string name="permlab_statusBar" msgid="7417192629601890791">"desactivar o modificar la barra de estado"</string> <string name="permdesc_statusBar" msgid="1365473595331989732">"Admite que la aplicación desactive la barra de estado, o agregue y elimine íconos del sistema."</string> + <string name="permlab_statusBarService" msgid="7247281911387931485">"barra de estado"</string> + <string name="permdesc_statusBarService" msgid="4097605867643520920">"Permite que la aplicación sea la barra de estado."</string> <string name="permlab_expandStatusBar" msgid="1148198785937489264">"expandir o reducir la barra de estado"</string> <string name="permdesc_expandStatusBar" msgid="7088604400110768665">"Admite que la aplicación expanda o reduzca la barra de estado."</string> <string name="permlab_processOutgoingCalls" msgid="1136262550878335980">"interceptar llamadas salientes"</string> @@ -285,10 +288,6 @@ <string name="permdesc_readContacts" msgid="3371591512896545975">"Admite una aplicación que lee todos los datos de (direcciones) de contactos almacenados en tu teléfono. Las aplicaciones maliciosas pueden utilizarlo para enviar tus eventos de calendario a otras personas."</string> <string name="permlab_writeContacts" msgid="644616215860933284">"escribir datos de contacto"</string> <string name="permdesc_writeContacts" msgid="3924383579108183601">"Admite una aplicación que modifica los datos de (dirección de) contacto guardados en tu teléfono. Las aplicaciones maliciosas pueden utilizarlo para borrar o modificar los datos de contacto."</string> - <string name="permlab_writeOwnerData" msgid="4892555913849295393">"escribir datos del propietario"</string> - <string name="permdesc_writeOwnerData" msgid="2344055317969787124">"Admite una aplicación que modifica los datos del propietario del teléfono guardados en tu teléfono. Las aplicaciones maliciosas pueden utilizarlo para borrar o modificar los datos del propietario."</string> - <string name="permlab_readOwnerData" msgid="6668525984731523563">"leer datos del propietario"</string> - <string name="permdesc_readOwnerData" msgid="3088486383128434507">"Admite una aplicación que lee los datos del propietario del teléfono guardados en tu teléfono. Las aplicaciones maliciosas pueden utilizarlo para leer los datos del propietario del teléfono."</string> <string name="permlab_readCalendar" msgid="6898987798303840534">"Leer eventos del calendario"</string> <string name="permdesc_readCalendar" msgid="5533029139652095734">"Admite que una aplicación lea todos los eventos de calendario almacenados en tu teléfono. Las aplicaciones maliciosas pueden utilizarlo para enviar tus eventos de calendario a otras personas."</string> <string name="permlab_writeCalendar" msgid="3894879352594904361">"Agregar o cambiar eventos del calendario y enviar un correo electrónico a los invitados"</string> @@ -311,8 +310,8 @@ <string name="permdesc_modifyAudioSettings" msgid="5793461287365991922">"Admite que la aplicación modifique la configuración global de audio, como el volumen y el enrutamiento."</string> <string name="permlab_recordAudio" msgid="3876049771427466323">"grabar audio"</string> <string name="permdesc_recordAudio" msgid="6493228261176552356">"Admite que la aplicación acceda a la ruta de grabación de audio."</string> - <string name="permlab_camera" msgid="8059288807274039014">"tomar fotografías"</string> - <string name="permdesc_camera" msgid="9013476258810982546">"Admite una aplicación que toma fotografías con la cámara. Esto permite que la aplicación en cualquier momento recopile imágenes que esté viendo la cámara."</string> + <string name="permlab_camera" msgid="3616391919559751192">"tomar fotografías y grabar videos"</string> + <string name="permdesc_camera" msgid="6004878235852154239">"Admite una aplicación que toma fotografías y graba video con la cámara. Esto permite que la aplicación en cualquier momento recopile imágenes que esté viendo la cámara."</string> <string name="permlab_brick" msgid="8337817093326370537">"desactivar teléfono de manera permanente"</string> <string name="permdesc_brick" msgid="5569526552607599221">"Admite que la aplicación desactive todo el teléfono de manera permanente. Esto es muy peligroso."</string> <string name="permlab_reboot" msgid="2898560872462638242">"provocar el reinicio del teléfono"</string> @@ -335,6 +334,8 @@ <string name="permdesc_vibrate" msgid="2886677177257789187">"Admite que la aplicación controle el vibrador."</string> <string name="permlab_flashlight" msgid="2155920810121984215">"controlar linterna"</string> <string name="permdesc_flashlight" msgid="6433045942283802309">"Admite que la aplicación controle la linterna."</string> + <string name="permlab_accessUsb" msgid="7362327818655760496">"acceder a dispositivos USB"</string> + <string name="permdesc_accessUsb" msgid="2414271762914049292">"Permite que la aplicación acceda a dispositivos USB."</string> <string name="permlab_hardware_test" msgid="4148290860400659146">"probar el hardware"</string> <string name="permdesc_hardware_test" msgid="3668894686500081699">"Admite que la aplicación controle diversos periféricos con el fin de probar el hardware."</string> <string name="permlab_callPhone" msgid="3925836347681847954">"llamar directamente a números de teléfono"</string> @@ -573,16 +574,6 @@ <string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string> <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string> <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string> - <string name="status_bar_clear_all_button" msgid="7774721344716731603">"Borrar"</string> - <string name="status_bar_no_notifications_title" msgid="4755261167193833213">"No hay notificaciones"</string> - <string name="status_bar_ongoing_events_title" msgid="1682504513316879202">"Continuo"</string> - <string name="status_bar_latest_events_title" msgid="6594767438577593172">"Notificaciones"</string> - <string name="battery_status_text_percent_format" msgid="7660311274698797147">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string> - <string name="battery_status_charging" msgid="756617993998772213">"Cargando..."</string> - <string name="battery_low_title" msgid="7923774589611311406">"Conecta el cargador"</string> - <string name="battery_low_subtitle" msgid="7388781709819722764">"Hay poca batería:"</string> - <string name="battery_low_percent_format" msgid="696154104579022959">"Restan <xliff:g id="NUMBER">%d%%</xliff:g> o menos."</string> - <string name="battery_low_why" msgid="7279169609518386372">"Uso de la batería"</string> <string name="factorytest_failed" msgid="5410270329114212041">"Error en la prueba de fábrica"</string> <string name="factorytest_not_system" msgid="4435201656767276723">"La acción FACTORY_TEST se admite solamente en paquetes instalados en /system/app."</string> <string name="factorytest_no_action" msgid="872991874799998561">"No se ha encontrado ningún paquete que proporcione la acción FACTORY_TEST ."</string> @@ -708,12 +699,9 @@ <string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string> <string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string> <string name="selectAll" msgid="6876518925844129331">"Seleccionar todos"</string> - <string name="selectText" msgid="3889149123626888637">"Seleccionar texto"</string> - <string name="stopSelectingText" msgid="4157931463872320996">"Detener la selección de texto"</string> + <!-- outdated translation 3889149123626888637 --> <string name="selectText" msgid="4862359311088898878">"Seleccionar texto"</string> <string name="cut" msgid="3092569408438626261">"Cortar"</string> - <string name="cutAll" msgid="2436383270024931639">"Cortar llamada"</string> <string name="copy" msgid="2681946229533511987">"Copiar"</string> - <string name="copyAll" msgid="2590829068100113057">"Copiar todo"</string> <string name="paste" msgid="5629880836805036433">"Pegar"</string> <string name="copyUrl" msgid="2538211579596067402">"Copiar URL"</string> <string name="inputMethod" msgid="1653630062304567879">"Método de entrada"</string> @@ -744,6 +732,22 @@ <string name="force_close" msgid="3653416315450806396">"Provocar acercamiento"</string> <string name="report" msgid="4060218260984795706">"Notificar"</string> <string name="wait" msgid="7147118217226317732">"Espera"</string> + <!-- no translation found for launch_warning_title (8323761616052121936) --> + <skip /> + <!-- no translation found for launch_warning_replace (6202498949970281412) --> + <skip /> + <!-- no translation found for launch_warning_original (188102023021668683) --> + <skip /> + <string name="smv_application" msgid="295583804361236288">"La aplicación <xliff:g id="APPLICATION">%1$s</xliff:g> (proceso <xliff:g id="PROCESS">%2$s</xliff:g>) ha violado su política StrictMode autoimpuesta."</string> + <string name="smv_process" msgid="5120397012047462446">"El proceso <xliff:g id="PROCESS">%1$s</xliff:g> ha violado su política StrictMode autoimpuesta."</string> + <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g> Correr"</string> + <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"Selecciona cambiar la aplicación"</string> + <string name="heavy_weight_switcher_title" msgid="1135403633766694316">"¿Deseas cambiar aplicaciones?"</string> + <string name="heavy_weight_switcher_text" msgid="4592075610079319667">"Ya se está ejecutando una aplicación que debe detenerse antes de iniciar una nueva."</string> + <string name="old_app_action" msgid="493129172238566282">"Regresar a <xliff:g id="OLD_APP">%1$s</xliff:g>"</string> + <string name="old_app_description" msgid="942967900237208466">"No inicies la nueva aplicación."</string> + <string name="new_app_action" msgid="5472756926945440706">"Inicio <xliff:g id="OLD_APP">%1$s</xliff:g>"</string> + <string name="new_app_description" msgid="6830398339826789493">"Interrumpe la aplicación anterior sin guardar."</string> <string name="sendText" msgid="5132506121645618310">"Selecciona una acción para el texto"</string> <string name="volume_ringtone" msgid="6885421406845734650">"Volumen del timbre"</string> <string name="volume_music" msgid="5421651157138628171">"Volumen de los medios"</string> diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml index 3cb068e1d25f..0d2f932a0f10 100644 --- a/core/res/res/values-es/strings.xml +++ b/core/res/res/values-es/strings.xml @@ -34,6 +34,7 @@ <string name="defaultVoiceMailAlphaTag" msgid="2660020990097733077">"Buzón de voz"</string> <string name="defaultMsisdnAlphaTag" msgid="2850889754919584674">"MSISDN1"</string> <string name="mmiError" msgid="5154499457739052907">"Se ha producido un problema de conexión o el código MMI no es válido."</string> + <string name="mmiFdnError" msgid="5224398216385316471">"La operación solo es válida para números de marcación fija."</string> <string name="serviceEnabled" msgid="8147278346414714315">"El servicio se ha habilitado."</string> <string name="serviceEnabledFor" msgid="6856228140453471041">"Se ha habilitado el servicio para:"</string> <string name="serviceDisabled" msgid="1937553226592516411">"El servicio se ha inhabilitado."</string> @@ -143,7 +144,7 @@ <string name="global_action_silent_mode_on_status" msgid="3289841937003758806">"El sonido está desactivado. Activar."</string> <string name="global_action_silent_mode_off_status" msgid="1506046579177066419">"El sonido está activado. Desactivar."</string> <string name="global_actions_toggle_airplane_mode" msgid="5884330306926307456">"Modo avión"</string> - <string name="global_actions_airplane_mode_on_status" msgid="2719557982608919750">"Modo avión desactivado. Activar."</string> + <string name="global_actions_airplane_mode_on_status" msgid="2719557982608919750">"Modo avión activado. Desactivar."</string> <string name="global_actions_airplane_mode_off_status" msgid="5075070442854490296">"Modo avión desactivado. Activar."</string> <string name="safeMode" msgid="2788228061547930246">"Modo seguro"</string> <string name="android_system_label" msgid="6577375335728551336">"Sistema Android"</string> @@ -171,6 +172,8 @@ <string name="permgroupdesc_storage" msgid="9203302214915355774">"Acceder a la tarjeta SD"</string> <string name="permlab_statusBar" msgid="7417192629601890791">"inhabilitar o modificar la barra de estado"</string> <string name="permdesc_statusBar" msgid="1365473595331989732">"Permite que las aplicaciones inhabiliten la barra de estado, o añadan y eliminen iconos del sistema."</string> + <string name="permlab_statusBarService" msgid="7247281911387931485">"barra de estado"</string> + <string name="permdesc_statusBarService" msgid="4097605867643520920">"La aplicación puede aparecer en la barra de estado."</string> <string name="permlab_expandStatusBar" msgid="1148198785937489264">"expandir/contraer la barra de estado"</string> <string name="permdesc_expandStatusBar" msgid="7088604400110768665">"Permite que la aplicación expanda y contraiga la barra de estado."</string> <string name="permlab_processOutgoingCalls" msgid="1136262550878335980">"interceptar llamadas salientes"</string> @@ -285,10 +288,6 @@ <string name="permdesc_readContacts" msgid="3371591512896545975">"Permite que una aplicación lea todos los datos de contacto (direcciones) almacenados en el teléfono. Las aplicaciones malintencionadas pueden utilizar este permiso para enviar tus datos a otras personas."</string> <string name="permlab_writeContacts" msgid="644616215860933284">"escribir datos de contacto"</string> <string name="permdesc_writeContacts" msgid="3924383579108183601">"Permite que una aplicación modifique los datos de contacto (direcciones) almacenados en el teléfono. Las aplicaciones malintencionadas pueden utilizar este permiso para borrar o modificar tus datos de contacto."</string> - <string name="permlab_writeOwnerData" msgid="4892555913849295393">"escribir datos de propietario"</string> - <string name="permdesc_writeOwnerData" msgid="2344055317969787124">"Permite que una aplicación modifique los datos del propietario del teléfono almacenados en el teléfono. Las aplicaciones malintencionadas pueden utilizar este permiso para borrar o modificar los datos del propietario."</string> - <string name="permlab_readOwnerData" msgid="6668525984731523563">"leer datos del propietario"</string> - <string name="permdesc_readOwnerData" msgid="3088486383128434507">"Permite que una aplicación lea los datos del propietario del teléfono almacenados en el teléfono. Las aplicaciones malintencionadas pueden utilizar este permiso para leer los datos del propietario del teléfono."</string> <string name="permlab_readCalendar" msgid="6898987798303840534">"leer eventos de calendario"</string> <string name="permdesc_readCalendar" msgid="5533029139652095734">"Permite que una aplicación lea todos los eventos de calendario almacenados en el teléfono. Las aplicaciones malintencionadas pueden utilizar este permiso para enviar tus eventos de calendario a otras personas."</string> <string name="permlab_writeCalendar" msgid="3894879352594904361">"añadir o modificar eventos de calendario y enviar mensajes de correo electrónico a los invitados"</string> @@ -311,8 +310,8 @@ <string name="permdesc_modifyAudioSettings" msgid="5793461287365991922">"Permite que la aplicación modifique la configuración de audio global como, por ejemplo, el volumen y la salida."</string> <string name="permlab_recordAudio" msgid="3876049771427466323">"grabar sonido"</string> <string name="permdesc_recordAudio" msgid="6493228261176552356">"Permite que la aplicación acceda a la ruta de grabación de audio."</string> - <string name="permlab_camera" msgid="8059288807274039014">"realizar fotografías"</string> - <string name="permdesc_camera" msgid="9013476258810982546">"Permite que la aplicación realice fotografías con la cámara. De esta forma, la aplicación puede recopilar en cualquier momento las imágenes que ve la cámara."</string> + <string name="permlab_camera" msgid="3616391919559751192">"realizar fotografías y vídeos"</string> + <string name="permdesc_camera" msgid="6004878235852154239">"Permite que la aplicación realice fotografías y vídeos con la cámara. De este modo, puede recopilar en cualquier momento las imágenes que ve la cámara."</string> <string name="permlab_brick" msgid="8337817093326370537">"inhabilitar el teléfono de forma permanente"</string> <string name="permdesc_brick" msgid="5569526552607599221">"Permite que la aplicación inhabilite todas las funciones del teléfono de forma permanente. Este permiso es muy peligroso."</string> <string name="permlab_reboot" msgid="2898560872462638242">"forzar reinicio del teléfono"</string> @@ -335,6 +334,8 @@ <string name="permdesc_vibrate" msgid="2886677177257789187">"Permite que la aplicación controle la función de vibración."</string> <string name="permlab_flashlight" msgid="2155920810121984215">"controlar linterna"</string> <string name="permdesc_flashlight" msgid="6433045942283802309">"Permite que la aplicación controle la función de linterna."</string> + <string name="permlab_accessUsb" msgid="7362327818655760496">"acceso a dispositivos USB"</string> + <string name="permdesc_accessUsb" msgid="2414271762914049292">"La aplicación puede acceder a dispositivos USB."</string> <string name="permlab_hardware_test" msgid="4148290860400659146">"probar hardware"</string> <string name="permdesc_hardware_test" msgid="3668894686500081699">"Permite que la aplicación controle distintos periféricos con fines de prueba del hardware."</string> <string name="permlab_callPhone" msgid="3925836347681847954">"llamar directamente a números de teléfono"</string> @@ -573,16 +574,6 @@ <string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string> <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g> <xliff:g id="AMPM">%P</xliff:g>"</string> <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g> <xliff:g id="AMPM">%p</xliff:g>"</string> - <string name="status_bar_clear_all_button" msgid="7774721344716731603">"Borrar"</string> - <string name="status_bar_no_notifications_title" msgid="4755261167193833213">"No tienes notificaciones"</string> - <string name="status_bar_ongoing_events_title" msgid="1682504513316879202">"Entrante"</string> - <string name="status_bar_latest_events_title" msgid="6594767438577593172">"Notificaciones"</string> - <string name="battery_status_text_percent_format" msgid="7660311274698797147">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string> - <string name="battery_status_charging" msgid="756617993998772213">"Cargando..."</string> - <string name="battery_low_title" msgid="7923774589611311406">"Conecta el cargador"</string> - <string name="battery_low_subtitle" msgid="7388781709819722764">"Se está agotando la batería:"</string> - <string name="battery_low_percent_format" msgid="696154104579022959">"<xliff:g id="NUMBER">%d%%</xliff:g> o menos disponible"</string> - <string name="battery_low_why" msgid="7279169609518386372">"Uso de la batería"</string> <string name="factorytest_failed" msgid="5410270329114212041">"Fallo en la prueba de fábrica"</string> <string name="factorytest_not_system" msgid="4435201656767276723">"La acción FACTORY_TEST solo es compatible con los paquetes instalados en /system/app."</string> <string name="factorytest_no_action" msgid="872991874799998561">"No se ha encontrado ningún paquete que proporcione la acción FACTORY_TEST."</string> @@ -708,12 +699,9 @@ <string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string> <string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string> <string name="selectAll" msgid="6876518925844129331">"Seleccionar todo"</string> - <string name="selectText" msgid="3889149123626888637">"Seleccionar texto"</string> - <string name="stopSelectingText" msgid="4157931463872320996">"Detener selección de texto"</string> + <!-- outdated translation 3889149123626888637 --> <string name="selectText" msgid="4862359311088898878">"Seleccionar texto"</string> <string name="cut" msgid="3092569408438626261">"Cortar"</string> - <string name="cutAll" msgid="2436383270024931639">"Cortar todo"</string> <string name="copy" msgid="2681946229533511987">"Copiar"</string> - <string name="copyAll" msgid="2590829068100113057">"Copiar todo"</string> <string name="paste" msgid="5629880836805036433">"Pegar"</string> <string name="copyUrl" msgid="2538211579596067402">"Copiar URL"</string> <string name="inputMethod" msgid="1653630062304567879">"Método de introducción de texto"</string> @@ -744,6 +732,22 @@ <string name="force_close" msgid="3653416315450806396">"Forzar cierre"</string> <string name="report" msgid="4060218260984795706">"Informe"</string> <string name="wait" msgid="7147118217226317732">"Esperar"</string> + <!-- no translation found for launch_warning_title (8323761616052121936) --> + <skip /> + <!-- no translation found for launch_warning_replace (6202498949970281412) --> + <skip /> + <!-- no translation found for launch_warning_original (188102023021668683) --> + <skip /> + <string name="smv_application" msgid="295583804361236288">"La aplicación <xliff:g id="APPLICATION">%1$s</xliff:g> (proceso <xliff:g id="PROCESS">%2$s</xliff:g>) ha infringido su política StrictMode autoaplicable."</string> + <string name="smv_process" msgid="5120397012047462446">"El proceso <xliff:g id="PROCESS">%1$s</xliff:g> ha infringido su política StrictMode autoaplicable."</string> + <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g> en ejecución"</string> + <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"Seleccionar para cambiar a la aplicación"</string> + <string name="heavy_weight_switcher_title" msgid="1135403633766694316">"¿Cambiar de aplicación?"</string> + <string name="heavy_weight_switcher_text" msgid="4592075610079319667">"Se está ejecutando otra aplicación. Para iniciar una aplicación nueva debes detenerla."</string> + <string name="old_app_action" msgid="493129172238566282">"Volver a <xliff:g id="OLD_APP">%1$s</xliff:g>"</string> + <string name="old_app_description" msgid="942967900237208466">"No iniciar la aplicación nueva"</string> + <string name="new_app_action" msgid="5472756926945440706">"Iniciar <xliff:g id="OLD_APP">%1$s</xliff:g>"</string> + <string name="new_app_description" msgid="6830398339826789493">"Detener la aplicación anterior sin guardar"</string> <string name="sendText" msgid="5132506121645618310">"Seleccionar la opción para compartir"</string> <string name="volume_ringtone" msgid="6885421406845734650">"Volumen del timbre"</string> <string name="volume_music" msgid="5421651157138628171">"Volumen multimedia"</string> diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml index 5efcf326abb0..b4f80467a5bc 100644 --- a/core/res/res/values-fr/strings.xml +++ b/core/res/res/values-fr/strings.xml @@ -34,6 +34,7 @@ <string name="defaultVoiceMailAlphaTag" msgid="2660020990097733077">"Messagerie vocale"</string> <string name="defaultMsisdnAlphaTag" msgid="2850889754919584674">"MSISDN1"</string> <string name="mmiError" msgid="5154499457739052907">"Problème de connexion ou code IHM non valide."</string> + <string name="mmiFdnError" msgid="5224398216385316471">"Opération réservée aux numéros autorisés"</string> <string name="serviceEnabled" msgid="8147278346414714315">"Le service a été activé."</string> <string name="serviceEnabledFor" msgid="6856228140453471041">"Ce service a été activé pour :"</string> <string name="serviceDisabled" msgid="1937553226592516411">"Ce service a été désactivé."</string> @@ -171,6 +172,8 @@ <string name="permgroupdesc_storage" msgid="9203302214915355774">"Accès à la carte SD"</string> <string name="permlab_statusBar" msgid="7417192629601890791">"Désactivation ou modification de la barre d\'état"</string> <string name="permdesc_statusBar" msgid="1365473595331989732">"Permet à une application de désactiver la barre d\'état ou d\'ajouter/supprimer des icônes système."</string> + <string name="permlab_statusBarService" msgid="7247281911387931485">"barre d\'état"</string> + <string name="permdesc_statusBarService" msgid="4097605867643520920">"Permet à l\'application de faire office de barre d\'état."</string> <string name="permlab_expandStatusBar" msgid="1148198785937489264">"Agrandir/réduire la barre d\'état"</string> <string name="permdesc_expandStatusBar" msgid="7088604400110768665">"Permet à l\'application de réduire ou d\'agrandir la barre d\'état."</string> <string name="permlab_processOutgoingCalls" msgid="1136262550878335980">"Interception d\'appels sortants"</string> @@ -285,10 +288,6 @@ <string name="permdesc_readContacts" msgid="3371591512896545975">"Permet à une application de lire toutes les données des contacts (adresses) enregistrées sur votre téléphone. Des applications malveillantes peuvent utiliser cette fonctionnalité pour envoyer vos données à d\'autres personnes."</string> <string name="permlab_writeContacts" msgid="644616215860933284">"Édition des données d\'un contact"</string> <string name="permdesc_writeContacts" msgid="3924383579108183601">"Permet à une application de modifier toutes les données de contact (adresses) enregistrées sur le téléphone. Des applications malveillantes peuvent utiliser cette fonctionnalité pour effacer ou modifier vos données de contact."</string> - <string name="permlab_writeOwnerData" msgid="4892555913849295393">"Édition les données du propriétaire"</string> - <string name="permdesc_writeOwnerData" msgid="2344055317969787124">"Permet à une application de modifier les données du propriétaire du téléphone enregistrées sur votre appareil. Des applications malveillantes peuvent utiliser cette fonctionnalité pour effacer ou modifier ces données."</string> - <string name="permlab_readOwnerData" msgid="6668525984731523563">"Lecture des données du propriétaire"</string> - <string name="permdesc_readOwnerData" msgid="3088486383128434507">"Permet à une application de lire les données du propriétaire du téléphone enregistrées sur votre appareil. Des applications malveillantes peuvent utiliser cette fonctionnalité pour lire ces données."</string> <string name="permlab_readCalendar" msgid="6898987798303840534">"lire des événements de l\'agenda"</string> <string name="permdesc_readCalendar" msgid="5533029139652095734">"Permet à une application de lire tous les événements de l\'agenda enregistrés sur votre téléphone. Des applications malveillantes peuvent utiliser cette fonctionnalité pour envoyer les événements de votre agenda à d\'autres personnes."</string> <string name="permlab_writeCalendar" msgid="3894879352594904361">"ajouter ou modifier des événements d\'agenda et envoyer des e-mails aux invités"</string> @@ -311,8 +310,8 @@ <string name="permdesc_modifyAudioSettings" msgid="5793461287365991922">"Permet à l\'application de modifier les paramètres audio généraux (p. ex. le volume et le routage)."</string> <string name="permlab_recordAudio" msgid="3876049771427466323">"Enregistrement de fichier audio"</string> <string name="permdesc_recordAudio" msgid="6493228261176552356">"Permet à l\'application d\'accéder au chemin de l\'enregistrement audio."</string> - <string name="permlab_camera" msgid="8059288807274039014">"Prise de photos"</string> - <string name="permdesc_camera" msgid="9013476258810982546">"Permet à l\'application de prendre des clichés avec l\'appareil photo. Cette fonctionnalité permet à l\'application de récupérer à tout moment les images perçues par l\'appareil."</string> + <string name="permlab_camera" msgid="3616391919559751192">"prendre des photos et enregistrer des vidéos"</string> + <string name="permdesc_camera" msgid="6004878235852154239">"Permet de prendre des photos et d\'enregistrer des vidéos avec l\'appareil photo. Cette fonctionnalité permet à l\'application de récupérer à tout moment les images perçues par l\'appareil."</string> <string name="permlab_brick" msgid="8337817093326370537">"Désactivation définitive du téléphone"</string> <string name="permdesc_brick" msgid="5569526552607599221">"Permet à l\'application de désactiver définitivement le téléphone. Cette fonctionnalité est très dangereuse."</string> <string name="permlab_reboot" msgid="2898560872462638242">"Redémarrage forcé du téléphone"</string> @@ -335,6 +334,8 @@ <string name="permdesc_vibrate" msgid="2886677177257789187">"Permet à l\'application de contrôler le vibreur."</string> <string name="permlab_flashlight" msgid="2155920810121984215">"Contrôle de la lampe de poche"</string> <string name="permdesc_flashlight" msgid="6433045942283802309">"Permet à l\'application de contrôler la lampe de poche."</string> + <string name="permlab_accessUsb" msgid="7362327818655760496">"accéder aux périphériques USB"</string> + <string name="permdesc_accessUsb" msgid="2414271762914049292">"Autorise l\'application à accéder aux périphériques USB"</string> <string name="permlab_hardware_test" msgid="4148290860400659146">"Tests du matériel"</string> <string name="permdesc_hardware_test" msgid="3668894686500081699">"Permet à l\'application de contrôler différents périphériques à des fins de test matériel."</string> <string name="permlab_callPhone" msgid="3925836347681847954">"Appel direct des numéros de téléphone"</string> @@ -573,16 +574,6 @@ <string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string> <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string> <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string> - <string name="status_bar_clear_all_button" msgid="7774721344716731603">"Effacer"</string> - <string name="status_bar_no_notifications_title" msgid="4755261167193833213">"Aucune notification"</string> - <string name="status_bar_ongoing_events_title" msgid="1682504513316879202">"En cours"</string> - <string name="status_bar_latest_events_title" msgid="6594767438577593172">"Notifications"</string> - <string name="battery_status_text_percent_format" msgid="7660311274698797147">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string> - <string name="battery_status_charging" msgid="756617993998772213">"Chargement..."</string> - <string name="battery_low_title" msgid="7923774589611311406">"Branchez le chargeur"</string> - <string name="battery_low_subtitle" msgid="7388781709819722764">"Le niveau de la batterie est bas :"</string> - <string name="battery_low_percent_format" msgid="696154104579022959">"Maximum <xliff:g id="NUMBER">%d%%</xliff:g> restants."</string> - <string name="battery_low_why" msgid="7279169609518386372">"Utilisation de la batterie"</string> <string name="factorytest_failed" msgid="5410270329114212041">"Échec du test usine"</string> <string name="factorytest_not_system" msgid="4435201656767276723">"L\'action FACTORY_TEST est uniquement prise en charge pour les paquets de données installés dans in/system/app."</string> <string name="factorytest_no_action" msgid="872991874799998561">"Impossible de trouver un paquet proposant l\'action FACTORY_TEST."</string> @@ -708,12 +699,9 @@ <string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string> <string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string> <string name="selectAll" msgid="6876518925844129331">"Tout sélectionner"</string> - <string name="selectText" msgid="3889149123626888637">"Sélectionner le texte"</string> - <string name="stopSelectingText" msgid="4157931463872320996">"Arrêter sélection de texte"</string> + <!-- outdated translation 3889149123626888637 --> <string name="selectText" msgid="4862359311088898878">"Sélectionner le texte"</string> <string name="cut" msgid="3092569408438626261">"Couper"</string> - <string name="cutAll" msgid="2436383270024931639">"Tout couper"</string> <string name="copy" msgid="2681946229533511987">"Copier"</string> - <string name="copyAll" msgid="2590829068100113057">"Tout copier"</string> <string name="paste" msgid="5629880836805036433">"Coller"</string> <string name="copyUrl" msgid="2538211579596067402">"Copier l\'URL"</string> <string name="inputMethod" msgid="1653630062304567879">"Mode de saisie"</string> @@ -744,6 +732,22 @@ <string name="force_close" msgid="3653416315450806396">"Forcer la fermeture"</string> <string name="report" msgid="4060218260984795706">"Rapport"</string> <string name="wait" msgid="7147118217226317732">"Attendre"</string> + <!-- no translation found for launch_warning_title (8323761616052121936) --> + <skip /> + <!-- no translation found for launch_warning_replace (6202498949970281412) --> + <skip /> + <!-- no translation found for launch_warning_original (188102023021668683) --> + <skip /> + <string name="smv_application" msgid="295583804361236288">"L\'application <xliff:g id="APPLICATION">%1$s</xliff:g> (processus <xliff:g id="PROCESS">%2$s</xliff:g>) a enfreint ses propres règles du mode strict."</string> + <string name="smv_process" msgid="5120397012047462446">"Le processus <xliff:g id="PROCESS">%1$s</xliff:g> a enfreint ses propres règles du mode strict."</string> + <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g> en cours d\'exécution"</string> + <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"Sélectionner pour changer d\'application"</string> + <string name="heavy_weight_switcher_title" msgid="1135403633766694316">"Passer d\'une application à l\'autre ?"</string> + <string name="heavy_weight_switcher_text" msgid="4592075610079319667">"Une autre application est déjà en cours d\'exécution. Veuillez l\'arrêter avant d\'en démarrer une nouvelle."</string> + <string name="old_app_action" msgid="493129172238566282">"Revenir à <xliff:g id="OLD_APP">%1$s</xliff:g>"</string> + <string name="old_app_description" msgid="942967900237208466">"Ne pas démarrer la nouvelle application"</string> + <string name="new_app_action" msgid="5472756926945440706">"Démarrer <xliff:g id="OLD_APP">%1$s</xliff:g>"</string> + <string name="new_app_description" msgid="6830398339826789493">"Arrêtez l\'ancienne application sans enregistrer."</string> <string name="sendText" msgid="5132506121645618310">"Sélectionner une action pour le texte"</string> <string name="volume_ringtone" msgid="6885421406845734650">"Volume de la sonnerie"</string> <string name="volume_music" msgid="5421651157138628171">"Volume"</string> diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml index d0b3b8a06f7b..d4019b6b1668 100644 --- a/core/res/res/values-it/strings.xml +++ b/core/res/res/values-it/strings.xml @@ -34,6 +34,7 @@ <string name="defaultVoiceMailAlphaTag" msgid="2660020990097733077">"Segreteria"</string> <string name="defaultMsisdnAlphaTag" msgid="2850889754919584674">"MSISDN1"</string> <string name="mmiError" msgid="5154499457739052907">"Problema di connessione o codice MMI non valido."</string> + <string name="mmiFdnError" msgid="5224398216385316471">"Operazione limitata solo ai numeri di selezione fissa."</string> <string name="serviceEnabled" msgid="8147278346414714315">"Il servizio è stato attivato."</string> <string name="serviceEnabledFor" msgid="6856228140453471041">"Il servizio è stato attivato per:"</string> <string name="serviceDisabled" msgid="1937553226592516411">"Il servizio è stato disattivato."</string> @@ -171,6 +172,8 @@ <string name="permgroupdesc_storage" msgid="9203302214915355774">"Accesso alla scheda SD."</string> <string name="permlab_statusBar" msgid="7417192629601890791">"disattivare o modificare la barra di stato"</string> <string name="permdesc_statusBar" msgid="1365473595331989732">"Consente all\'applicazione di disattivare la barra di stato o di aggiungere e rimuovere icone di sistema."</string> + <string name="permlab_statusBarService" msgid="7247281911387931485">"barra di stato"</string> + <string name="permdesc_statusBarService" msgid="4097605867643520920">"Consente di visualizzare l\'applicazione nella barra di stato."</string> <string name="permlab_expandStatusBar" msgid="1148198785937489264">"espansione/compressione barra di stato"</string> <string name="permdesc_expandStatusBar" msgid="7088604400110768665">"Consente all\'applicazione di espandere o comprimere la barra di stato."</string> <string name="permlab_processOutgoingCalls" msgid="1136262550878335980">"intercettazione chiamate in uscita"</string> @@ -285,10 +288,6 @@ <string name="permdesc_readContacts" msgid="3371591512896545975">"Consente la lettura da parte di un\'applicazione di tutti i dati (gli indirizzi) di contatto memorizzati sul telefono. Le applicazioni dannose possono sfruttare questa possibilità per inviare i dati ad altre persone."</string> <string name="permlab_writeContacts" msgid="644616215860933284">"scrittura dati di contatto"</string> <string name="permdesc_writeContacts" msgid="3924383579108183601">"Consente a un\'applicazione di modificare i dati (gli indirizzi) di contatto memorizzati sul telefono. Le applicazioni dannose possono sfruttare questa possibilità per cancellare o modificare i dati di contatto."</string> - <string name="permlab_writeOwnerData" msgid="4892555913849295393">"scrittura dati proprietario"</string> - <string name="permdesc_writeOwnerData" msgid="2344055317969787124">"Consente a un\'applicazione di modificare i dati del proprietario del telefono memorizzati sul telefono. Le applicazioni dannose possono sfruttare questa possibilità per cancellare o modificare tali dati."</string> - <string name="permlab_readOwnerData" msgid="6668525984731523563">"lettura dati proprietario"</string> - <string name="permdesc_readOwnerData" msgid="3088486383128434507">"Consente a un\'applicazione di leggere i dati del proprietario del telefono memorizzati sul telefono. Le applicazioni dannose possono sfruttare questa possibilità per leggere tali dati."</string> <string name="permlab_readCalendar" msgid="6898987798303840534">"leggere eventi di calendario"</string> <string name="permdesc_readCalendar" msgid="5533029139652095734">"Consente la lettura da parte di un\'applicazione di tutti gli eventi di calendario memorizzati sul telefono. Le applicazioni dannose possono sfruttare questa possibilità per inviare i tuoi eventi di calendario ad altre persone."</string> <string name="permlab_writeCalendar" msgid="3894879352594904361">"aggiungere o modificare eventi di calendario e inviare email agli invitati"</string> @@ -311,8 +310,8 @@ <string name="permdesc_modifyAudioSettings" msgid="5793461287365991922">"Consente all\'applicazione di modificare impostazioni audio globali come volume e routing."</string> <string name="permlab_recordAudio" msgid="3876049771427466323">"registrazione audio"</string> <string name="permdesc_recordAudio" msgid="6493228261176552356">"Consente l\'accesso dell\'applicazione al percorso di registrazione dell\'audio."</string> - <string name="permlab_camera" msgid="8059288807274039014">"acquisizione foto"</string> - <string name="permdesc_camera" msgid="9013476258810982546">"Consente di scattare foto nell\'applicazione con la fotocamera. L\'applicazione può acquisire in qualsiasi momento le immagini rilevate dalla fotocamera."</string> + <string name="permlab_camera" msgid="3616391919559751192">"acquisizione di foto e video"</string> + <string name="permdesc_camera" msgid="6004878235852154239">"Consente di scattare foto e riprendere video nell\'applicazione con la fotocamera. L\'applicazione può acquisire in qualsiasi momento le immagini rilevate dalla fotocamera."</string> <string name="permlab_brick" msgid="8337817093326370537">"disattivazione telefono"</string> <string name="permdesc_brick" msgid="5569526552607599221">"Consente all\'applicazione di disattivare l\'intero telefono in modo definitivo. Questa autorizzazione è molto pericolosa."</string> <string name="permlab_reboot" msgid="2898560872462638242">"riavvio forzato del telefono"</string> @@ -335,6 +334,8 @@ <string name="permdesc_vibrate" msgid="2886677177257789187">"Consente all\'applicazione di controllare la vibrazione."</string> <string name="permlab_flashlight" msgid="2155920810121984215">"controllo flash"</string> <string name="permdesc_flashlight" msgid="6433045942283802309">"Consente all\'applicazione di controllare il flash."</string> + <string name="permlab_accessUsb" msgid="7362327818655760496">"accesso a dispositivi USB"</string> + <string name="permdesc_accessUsb" msgid="2414271762914049292">"Consente all\'applicazione di accedere ai dispositivi USB"</string> <string name="permlab_hardware_test" msgid="4148290860400659146">"esecuzione test hardware"</string> <string name="permdesc_hardware_test" msgid="3668894686500081699">"Consente all\'applicazione di controllare varie periferiche per il test dell\'hardware."</string> <string name="permlab_callPhone" msgid="3925836347681847954">"chiamata diretta n. telefono"</string> @@ -434,8 +435,8 @@ <string-array name="phoneTypes"> <item msgid="8901098336658710359">"Casa"</item> <item msgid="869923650527136615">"Cellulare"</item> - <item msgid="7897544654242874543">"Ufficio"</item> - <item msgid="1103601433382158155">"Fax ufficio"</item> + <item msgid="7897544654242874543">"Lavoro"</item> + <item msgid="1103601433382158155">"Fax lavoro"</item> <item msgid="1735177144948329370">"Fax casa"</item> <item msgid="603878674477207394">"Cercapersone"</item> <item msgid="1650824275177931637">"Altro"</item> @@ -443,24 +444,24 @@ </string-array> <string-array name="emailAddressTypes"> <item msgid="8073994352956129127">"Casa"</item> - <item msgid="7084237356602625604">"Ufficio"</item> + <item msgid="7084237356602625604">"Lavoro"</item> <item msgid="1112044410659011023">"Altro"</item> <item msgid="2374913952870110618">"Personalizzato"</item> </string-array> <string-array name="postalAddressTypes"> <item msgid="6880257626740047286">"Casa"</item> - <item msgid="5629153956045109251">"Ufficio"</item> + <item msgid="5629153956045109251">"Lavoro"</item> <item msgid="4966604264500343469">"Altro"</item> <item msgid="4932682847595299369">"Personalizzato"</item> </string-array> <string-array name="imAddressTypes"> <item msgid="1738585194601476694">"Casa"</item> - <item msgid="1359644565647383708">"Uffico"</item> + <item msgid="1359644565647383708">"Lavoro"</item> <item msgid="7868549401053615677">"Altro"</item> <item msgid="3145118944639869809">"Personalizzato"</item> </string-array> <string-array name="organizationTypes"> - <item msgid="7546335612189115615">"Ufficio"</item> + <item msgid="7546335612189115615">"Lavoro"</item> <item msgid="4378074129049520373">"Altro"</item> <item msgid="3455047468583965104">"Personalizzato"</item> </string-array> @@ -477,8 +478,8 @@ <string name="phoneTypeCustom" msgid="1644738059053355820">"Personalizzato"</string> <string name="phoneTypeHome" msgid="2570923463033985887">"Casa"</string> <string name="phoneTypeMobile" msgid="6501463557754751037">"Cellulare"</string> - <string name="phoneTypeWork" msgid="8863939667059911633">"Ufficio"</string> - <string name="phoneTypeFaxWork" msgid="3517792160008890912">"Fax ufficio"</string> + <string name="phoneTypeWork" msgid="8863939667059911633">"Lavoro"</string> + <string name="phoneTypeFaxWork" msgid="3517792160008890912">"Fax lavoro"</string> <string name="phoneTypeFaxHome" msgid="2067265972322971467">"Fax casa"</string> <string name="phoneTypePager" msgid="7582359955394921732">"Cercapersone"</string> <string name="phoneTypeOther" msgid="1544425847868765990">"Altro"</string> @@ -492,7 +493,7 @@ <string name="phoneTypeTelex" msgid="3367879952476250512">"Telex"</string> <string name="phoneTypeTtyTdd" msgid="8606514378585000044">"TTY TDD"</string> <string name="phoneTypeWorkMobile" msgid="1311426989184065709">"Cellulare lavoro"</string> - <string name="phoneTypeWorkPager" msgid="649938731231157056">"Cercapersone ufficio"</string> + <string name="phoneTypeWorkPager" msgid="649938731231157056">"Cercapersone lavoro"</string> <string name="phoneTypeAssistant" msgid="5596772636128562884">"Assistente"</string> <string name="phoneTypeMms" msgid="7254492275502768992">"MMS"</string> <string name="eventTypeBirthday" msgid="2813379844211390740">"Compleanno"</string> @@ -500,16 +501,16 @@ <string name="eventTypeOther" msgid="5834288791948564594">"Evento"</string> <string name="emailTypeCustom" msgid="8525960257804213846">"Personalizzato"</string> <string name="emailTypeHome" msgid="449227236140433919">"Casa"</string> - <string name="emailTypeWork" msgid="3548058059601149973">"Ufficio"</string> + <string name="emailTypeWork" msgid="3548058059601149973">"Lavoro"</string> <string name="emailTypeOther" msgid="2923008695272639549">"Altro"</string> <string name="emailTypeMobile" msgid="119919005321166205">"Cellulare"</string> <string name="postalTypeCustom" msgid="8903206903060479902">"Personalizzato"</string> <string name="postalTypeHome" msgid="8165756977184483097">"Casa"</string> - <string name="postalTypeWork" msgid="5268172772387694495">"Ufficio"</string> + <string name="postalTypeWork" msgid="5268172772387694495">"Lavoro"</string> <string name="postalTypeOther" msgid="2726111966623584341">"Altro"</string> <string name="imTypeCustom" msgid="2074028755527826046">"Personalizzato"</string> <string name="imTypeHome" msgid="6241181032954263892">"Casa"</string> - <string name="imTypeWork" msgid="1371489290242433090">"Ufficio"</string> + <string name="imTypeWork" msgid="1371489290242433090">"Lavoro"</string> <string name="imTypeOther" msgid="5377007495735915478">"Altro"</string> <string name="imProtocolCustom" msgid="6919453836618749992">"Personalizzato"</string> <string name="imProtocolAim" msgid="7050360612368383417">"AIM"</string> @@ -521,7 +522,7 @@ <string name="imProtocolIcq" msgid="1574870433606517315">"ICQ"</string> <string name="imProtocolJabber" msgid="2279917630875771722">"Jabber"</string> <string name="imProtocolNetMeeting" msgid="8287625655986827971">"NetMeeting"</string> - <string name="orgTypeWork" msgid="29268870505363872">"Ufficio"</string> + <string name="orgTypeWork" msgid="29268870505363872">"Lavoro"</string> <string name="orgTypeOther" msgid="3951781131570124082">"Altro"</string> <string name="orgTypeCustom" msgid="225523415372088322">"Personalizzato"</string> <string name="contact_status_update_attribution" msgid="5112589886094402795">"tramite <xliff:g id="SOURCE">%1$s</xliff:g>"</string> @@ -573,16 +574,6 @@ <string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string> <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string> <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string> - <string name="status_bar_clear_all_button" msgid="7774721344716731603">"Cancella"</string> - <string name="status_bar_no_notifications_title" msgid="4755261167193833213">"Nessuna notifica"</string> - <string name="status_bar_ongoing_events_title" msgid="1682504513316879202">"In corso"</string> - <string name="status_bar_latest_events_title" msgid="6594767438577593172">"Notifiche"</string> - <string name="battery_status_text_percent_format" msgid="7660311274698797147">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string> - <string name="battery_status_charging" msgid="756617993998772213">"In carica..."</string> - <string name="battery_low_title" msgid="7923774589611311406">"Collegare il caricabatterie"</string> - <string name="battery_low_subtitle" msgid="7388781709819722764">"Batteria quasi scarica:"</string> - <string name="battery_low_percent_format" msgid="696154104579022959">"<xliff:g id="NUMBER">%d%%</xliff:g> rimanente o meno."</string> - <string name="battery_low_why" msgid="7279169609518386372">"Utilizzo batteria"</string> <string name="factorytest_failed" msgid="5410270329114212041">"Test di fabbrica non riuscito"</string> <string name="factorytest_not_system" msgid="4435201656767276723">"L\'azione FACTORY_TEST è supportata soltanto per i pacchetti installati in /system/app."</string> <string name="factorytest_no_action" msgid="872991874799998561">"Nessun pacchetto trovato che fornisca l\'azione FACTORY_TEST."</string> @@ -708,12 +699,9 @@ <string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string> <string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string> <string name="selectAll" msgid="6876518925844129331">"Seleziona tutto"</string> - <string name="selectText" msgid="3889149123626888637">"Seleziona testo"</string> - <string name="stopSelectingText" msgid="4157931463872320996">"Termina selezione testo"</string> + <!-- outdated translation 3889149123626888637 --> <string name="selectText" msgid="4862359311088898878">"Seleziona testo"</string> <string name="cut" msgid="3092569408438626261">"Taglia"</string> - <string name="cutAll" msgid="2436383270024931639">"Taglia tutto"</string> <string name="copy" msgid="2681946229533511987">"Copia"</string> - <string name="copyAll" msgid="2590829068100113057">"Copia tutto"</string> <string name="paste" msgid="5629880836805036433">"Incolla"</string> <string name="copyUrl" msgid="2538211579596067402">"Copia URL"</string> <string name="inputMethod" msgid="1653630062304567879">"Metodo inserimento"</string> @@ -744,6 +732,22 @@ <string name="force_close" msgid="3653416315450806396">"Termina"</string> <string name="report" msgid="4060218260984795706">"Segnala"</string> <string name="wait" msgid="7147118217226317732">"Attendi"</string> + <!-- no translation found for launch_warning_title (8323761616052121936) --> + <skip /> + <!-- no translation found for launch_warning_replace (6202498949970281412) --> + <skip /> + <!-- no translation found for launch_warning_original (188102023021668683) --> + <skip /> + <string name="smv_application" msgid="295583804361236288">"L\'applicazione <xliff:g id="APPLICATION">%1$s</xliff:g> (processo <xliff:g id="PROCESS">%2$s</xliff:g>) ha violato la norma StrictMode autoimposta."</string> + <string name="smv_process" msgid="5120397012047462446">"Il processo <xliff:g id="PROCESS">%1$s</xliff:g> ha violato la norma StrictMode autoimposta."</string> + <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g> in esecuzione"</string> + <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"Seleziona per passare all\'applicazione"</string> + <string name="heavy_weight_switcher_title" msgid="1135403633766694316">"Scambiare le applicazioni?"</string> + <string name="heavy_weight_switcher_text" msgid="4592075610079319667">"Un\'altra applicazione già in esecuzione deve essere chiusa prima di poterne avviare un\'altra."</string> + <string name="old_app_action" msgid="493129172238566282">"Torna a <xliff:g id="OLD_APP">%1$s</xliff:g> "</string> + <string name="old_app_description" msgid="942967900237208466">"Non avviare la nuova applicazione."</string> + <string name="new_app_action" msgid="5472756926945440706">"Avvia <xliff:g id="OLD_APP">%1$s</xliff:g>"</string> + <string name="new_app_description" msgid="6830398339826789493">"Interrompi la vecchia applicazione senza salvare."</string> <string name="sendText" msgid="5132506121645618310">"Selezione un\'opzione di invio"</string> <string name="volume_ringtone" msgid="6885421406845734650">"Volume suoneria"</string> <string name="volume_music" msgid="5421651157138628171">"Volume app. multimediali"</string> diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml index 5816ef73d77c..ae44bb276c54 100644 --- a/core/res/res/values-ja/strings.xml +++ b/core/res/res/values-ja/strings.xml @@ -34,6 +34,7 @@ <string name="defaultVoiceMailAlphaTag" msgid="2660020990097733077">"ボイスメール"</string> <string name="defaultMsisdnAlphaTag" msgid="2850889754919584674">"MSISDN1"</string> <string name="mmiError" msgid="5154499457739052907">"接続に問題があるか、MMIコードが正しくありません。"</string> + <string name="mmiFdnError" msgid="5224398216385316471">"発信番号制限で指定された番号に対してのみ操作できます。"</string> <string name="serviceEnabled" msgid="8147278346414714315">"サービスが有効になりました。"</string> <string name="serviceEnabledFor" msgid="6856228140453471041">"次のサービスが有効になりました:"</string> <string name="serviceDisabled" msgid="1937553226592516411">"サービスが無効になりました。"</string> @@ -171,6 +172,8 @@ <string name="permgroupdesc_storage" msgid="9203302214915355774">"SDカードにアクセスします。"</string> <string name="permlab_statusBar" msgid="7417192629601890791">"ステータスバーの無効化や変更"</string> <string name="permdesc_statusBar" msgid="1365473595331989732">"ステータスバーの無効化やシステムアイコンの追加や削除をアプリケーションに許可します。"</string> + <string name="permlab_statusBarService" msgid="7247281911387931485">"ステータスバーへの表示"</string> + <string name="permdesc_statusBarService" msgid="4097605867643520920">"アプリケーションのステータスバーへの表示を許可します。"</string> <string name="permlab_expandStatusBar" msgid="1148198785937489264">"ステータスバーの拡大/縮小"</string> <string name="permdesc_expandStatusBar" msgid="7088604400110768665">"ステータスバーの拡大や縮小をアプリケーションに許可します。"</string> <string name="permlab_processOutgoingCalls" msgid="1136262550878335980">"発信の傍受"</string> @@ -285,10 +288,6 @@ <string name="permdesc_readContacts" msgid="3371591512896545975">"端末に保存した連絡先(アドレス)データの読み取りをアプリケーションに許可します。悪意のあるアプリケーションがデータを他人に送信する恐れがあります。"</string> <string name="permlab_writeContacts" msgid="644616215860933284">"連絡先データの書き込み"</string> <string name="permdesc_writeContacts" msgid="3924383579108183601">"端末に保存した連絡先(アドレス)データの変更をアプリケーションに許可します。悪意のあるアプリケーションが連絡先データを消去/変更する恐れがあります。"</string> - <string name="permlab_writeOwnerData" msgid="4892555913849295393">"所有者データの書き込み"</string> - <string name="permdesc_writeOwnerData" msgid="2344055317969787124">"端末に保存した所有者のデータの変更をアプリケーションに許可します。悪意のあるアプリケーションが所有者のデータを消去/変更する恐れがあります。"</string> - <string name="permlab_readOwnerData" msgid="6668525984731523563">"所有者データの読み取り"</string> - <string name="permdesc_readOwnerData" msgid="3088486383128434507">"携帯電話に保存した所有者データの読み取りをアプリケーションに許可します。悪意のあるアプリケーションが所有者データを読み取る恐れがあります。"</string> <string name="permlab_readCalendar" msgid="6898987798303840534">"カレンダーの予定の読み取り"</string> <string name="permdesc_readCalendar" msgid="5533029139652095734">"端末に保存したカレンダーの予定の読み取りをアプリケーションに許可します。悪意のあるアプリケーションがカレンダーの予定を他人に送信する恐れがあります。"</string> <string name="permlab_writeCalendar" msgid="3894879352594904361">"カレンダーの予定の追加や変更を行い、ゲストにメールを送信する"</string> @@ -311,8 +310,8 @@ <string name="permdesc_modifyAudioSettings" msgid="5793461287365991922">"音量や転送などの音声全般の設定の変更をアプリケーションに許可します。"</string> <string name="permlab_recordAudio" msgid="3876049771427466323">"録音"</string> <string name="permdesc_recordAudio" msgid="6493228261176552356">"オーディオ録音パスへのアクセスをアプリケーションに許可します。"</string> - <string name="permlab_camera" msgid="8059288807274039014">"写真の撮影"</string> - <string name="permdesc_camera" msgid="9013476258810982546">"カメラでの写真撮影をアプリケーションに許可します。アプリケーションはカメラから画像をいつでも収集できます。"</string> + <string name="permlab_camera" msgid="3616391919559751192">"写真と動画の撮影"</string> + <string name="permdesc_camera" msgid="6004878235852154239">"カメラでの写真と動画の撮影をアプリケーションに許可します。許可すると、カメラがとらえている画像をアプリケーションがいつでも取得できるようになります。"</string> <string name="permlab_brick" msgid="8337817093326370537">"端末を永続的に無効にする"</string> <string name="permdesc_brick" msgid="5569526552607599221">"携帯電話全体を永続的に無効にすることをアプリケーションに許可します。この許可は非常に危険です。"</string> <string name="permlab_reboot" msgid="2898560872462638242">"端末の再起動"</string> @@ -335,6 +334,8 @@ <string name="permdesc_vibrate" msgid="2886677177257789187">"バイブレーションの制御をアプリケーションに許可します。"</string> <string name="permlab_flashlight" msgid="2155920810121984215">"ライトのコントロール"</string> <string name="permdesc_flashlight" msgid="6433045942283802309">"ライトの制御をアプリケーションに許可します。"</string> + <string name="permlab_accessUsb" msgid="7362327818655760496">"USBデバイスへのアクセス"</string> + <string name="permdesc_accessUsb" msgid="2414271762914049292">"USBデバイスへのアクセスをアプリケーションに許可します。"</string> <string name="permlab_hardware_test" msgid="4148290860400659146">"ハードウェアのテスト"</string> <string name="permdesc_hardware_test" msgid="3668894686500081699">"ハードウェアのテストのためにさまざまな周辺機器を制御することをアプリケーションに許可します。"</string> <string name="permlab_callPhone" msgid="3925836347681847954">"電話番号発信"</string> @@ -573,16 +574,6 @@ <string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string> <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string> <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string> - <string name="status_bar_clear_all_button" msgid="7774721344716731603">"通知を消去"</string> - <string name="status_bar_no_notifications_title" msgid="4755261167193833213">"通知なし"</string> - <string name="status_bar_ongoing_events_title" msgid="1682504513316879202">"実行中"</string> - <string name="status_bar_latest_events_title" msgid="6594767438577593172">"通知"</string> - <string name="battery_status_text_percent_format" msgid="7660311274698797147">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string> - <string name="battery_status_charging" msgid="756617993998772213">"充電中..."</string> - <string name="battery_low_title" msgid="7923774589611311406">"充電してください"</string> - <string name="battery_low_subtitle" msgid="7388781709819722764">"電池が残り少なくなっています:"</string> - <string name="battery_low_percent_format" msgid="696154104579022959">"残り<xliff:g id="NUMBER">%d%%</xliff:g>未満です。"</string> - <string name="battery_low_why" msgid="7279169609518386372">"電池使用量"</string> <string name="factorytest_failed" msgid="5410270329114212041">"出荷時試験が失敗"</string> <string name="factorytest_not_system" msgid="4435201656767276723">"FACTORY_TEST操作は、/system/appにインストールされたパッケージのみが対象です。"</string> <string name="factorytest_no_action" msgid="872991874799998561">"FACTORY_TEST操作を行うパッケージは見つかりませんでした。"</string> @@ -708,12 +699,9 @@ <string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string> <string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string> <string name="selectAll" msgid="6876518925844129331">"すべて選択"</string> - <string name="selectText" msgid="3889149123626888637">"テキストを選択"</string> - <string name="stopSelectingText" msgid="4157931463872320996">"テキストの選択を終了"</string> + <!-- outdated translation 3889149123626888637 --> <string name="selectText" msgid="4862359311088898878">"テキストを選択"</string> <string name="cut" msgid="3092569408438626261">"切り取り"</string> - <string name="cutAll" msgid="2436383270024931639">"すべて切り取り"</string> <string name="copy" msgid="2681946229533511987">"コピー"</string> - <string name="copyAll" msgid="2590829068100113057">"すべてコピー"</string> <string name="paste" msgid="5629880836805036433">"貼り付け"</string> <string name="copyUrl" msgid="2538211579596067402">"URLをコピー"</string> <string name="inputMethod" msgid="1653630062304567879">"入力方法"</string> @@ -744,6 +732,22 @@ <string name="force_close" msgid="3653416315450806396">"強制終了"</string> <string name="report" msgid="4060218260984795706">"レポート"</string> <string name="wait" msgid="7147118217226317732">"待機"</string> + <!-- no translation found for launch_warning_title (8323761616052121936) --> + <skip /> + <!-- no translation found for launch_warning_replace (6202498949970281412) --> + <skip /> + <!-- no translation found for launch_warning_original (188102023021668683) --> + <skip /> + <string name="smv_application" msgid="295583804361236288">"アプリケーション<xliff:g id="APPLICATION">%1$s</xliff:g>(プロセス<xliff:g id="PROCESS">%2$s</xliff:g>)でStrictModeポリシー違反がありました。"</string> + <string name="smv_process" msgid="5120397012047462446">"プロセス<xliff:g id="PROCESS">%1$s</xliff:g>でStrictModeポリシー違反がありました。"</string> + <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g>を実行中"</string> + <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"アプリケーションを切り替える"</string> + <string name="heavy_weight_switcher_title" msgid="1135403633766694316">"アプリケーションを切り替えますか?"</string> + <string name="heavy_weight_switcher_text" msgid="4592075610079319667">"別のアプリケーションが既に実行中です。新しいアプリケーションを起動する前に実行中のアプリケーションを停止してください。"</string> + <string name="old_app_action" msgid="493129172238566282">"<xliff:g id="OLD_APP">%1$s</xliff:g>に戻る"</string> + <string name="old_app_description" msgid="942967900237208466">"新しいアプリケーションを起動しません。"</string> + <string name="new_app_action" msgid="5472756926945440706">"<xliff:g id="OLD_APP">%1$s</xliff:g>を起動"</string> + <string name="new_app_description" msgid="6830398339826789493">"実行中のアプリケーションを保存せずに停止します。"</string> <string name="sendText" msgid="5132506121645618310">"アプリケーションを選択"</string> <string name="volume_ringtone" msgid="6885421406845734650">"着信音量"</string> <string name="volume_music" msgid="5421651157138628171">"メディアの音量"</string> diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml index 8aaf76106e46..040f9e87dc66 100644 --- a/core/res/res/values-ko/strings.xml +++ b/core/res/res/values-ko/strings.xml @@ -34,6 +34,7 @@ <string name="defaultVoiceMailAlphaTag" msgid="2660020990097733077">"음성메일"</string> <string name="defaultMsisdnAlphaTag" msgid="2850889754919584674">"MSISDN1"</string> <string name="mmiError" msgid="5154499457739052907">"연결에 문제가 있거나 MMI 코드가 잘못되었습니다."</string> + <string name="mmiFdnError" msgid="5224398216385316471">"발신 허용 번호에서만 수행할 수 있는 작업입니다."</string> <string name="serviceEnabled" msgid="8147278346414714315">"서비스를 사용하도록 설정했습니다."</string> <string name="serviceEnabledFor" msgid="6856228140453471041">"사용 설정된 서비스 목록:"</string> <string name="serviceDisabled" msgid="1937553226592516411">"서비스가 사용 중지되었습니다."</string> @@ -171,6 +172,8 @@ <string name="permgroupdesc_storage" msgid="9203302214915355774">"SD 카드에 액세스합니다."</string> <string name="permlab_statusBar" msgid="7417192629601890791">"상태 표시줄 사용 중지 또는 수정"</string> <string name="permdesc_statusBar" msgid="1365473595331989732">"애플리케이션이 상태 표시줄을 사용 중지하거나 시스템 아이콘을 추가 및 제거할 수 있도록 합니다."</string> + <string name="permlab_statusBarService" msgid="7247281911387931485">"상태 표시줄"</string> + <string name="permdesc_statusBarService" msgid="4097605867643520920">"애플리케이션이 상태 표시줄이 되도록 허용합니다."</string> <string name="permlab_expandStatusBar" msgid="1148198785937489264">"상태 표시줄 확장/축소"</string> <string name="permdesc_expandStatusBar" msgid="7088604400110768665">"애플리케이션이 상태 표시줄을 확장하거나 축소할 수 있도록 합니다."</string> <string name="permlab_processOutgoingCalls" msgid="1136262550878335980">"발신전화 가로채기"</string> @@ -195,8 +198,8 @@ <string name="permdesc_setDebugApp" msgid="5584310661711990702">"애플리케이션이 다른 애플리케이션에 대해 디버깅을 사용할 수 있도록 합니다. 이 경우 악성 애플리케이션이 다른 애플리케이션을 중지시킬 수 있습니다."</string> <string name="permlab_changeConfiguration" msgid="8214475779521218295">"UI 설정 변경"</string> <string name="permdesc_changeConfiguration" msgid="3465121501528064399">"애플리케이션이 로케일 또는 전체 글꼴 크기와 같은 현재 구성을 변경할 수 있도록 합니다."</string> - <string name="permlab_enableCarMode" msgid="5684504058192921098">"차량 모드 사용"</string> - <string name="permdesc_enableCarMode" msgid="5673461159384850628">"애플리케이션이 차량 모드를 사용할 수 있도록 합니다."</string> + <string name="permlab_enableCarMode" msgid="5684504058192921098">"운전모드 사용"</string> + <string name="permdesc_enableCarMode" msgid="5673461159384850628">"애플리케이션이 운전모드를 사용할 수 있도록 합니다."</string> <string name="permlab_killBackgroundProcesses" msgid="8373714752793061963">"백그라운드 프로세스 종료"</string> <string name="permdesc_killBackgroundProcesses" msgid="2908829602869383753">"메모리가 부족하지 않은 경우에도 애플리케이션이 다른 애플리케이션의 백그라운드 프로세스를 중단할 수 있도록 합니다."</string> <string name="permlab_forceStopPackages" msgid="1447830113260156236">"다른 애플리케이션 강제 종료"</string> @@ -285,10 +288,6 @@ <string name="permdesc_readContacts" msgid="3371591512896545975">"애플리케이션이 휴대전화에 저장된 모든 연락처(주소) 데이터를 읽을 수 있도록 합니다. 이 경우 악성 애플리케이션이 데이터를 다른 사람에게 보낼 수 있습니다."</string> <string name="permlab_writeContacts" msgid="644616215860933284">"연락처 데이터 작성"</string> <string name="permdesc_writeContacts" msgid="3924383579108183601">"애플리케이션이 휴대전화에 저장된 연락처(주소) 데이터를 수정할 수 있도록 합니다. 이 경우 악성 애플리케이션이 연락처 데이터를 지우거나 수정할 수 있습니다."</string> - <string name="permlab_writeOwnerData" msgid="4892555913849295393">"소유자 데이터 작성"</string> - <string name="permdesc_writeOwnerData" msgid="2344055317969787124">"애플리케이션이 휴대전화에 저장된 소유자 데이터를 수정할 수 있도록 합니다. 단, 악성 애플리케이션이 이 기능을 이용하여 소유자 데이터를 지우거나 수정할 수 있습니다."</string> - <string name="permlab_readOwnerData" msgid="6668525984731523563">"소유자 데이터 읽기"</string> - <string name="permdesc_readOwnerData" msgid="3088486383128434507">"애플리케이션이 휴대전화에 저장된 휴대전화 소유자 데이터를 읽을 수 있도록 합니다. 이 경우 악성 애플리케이션이 휴대전화 소유자 데이터를 읽을 수 있습니다."</string> <string name="permlab_readCalendar" msgid="6898987798303840534">"캘린더 일정 읽기"</string> <string name="permdesc_readCalendar" msgid="5533029139652095734">"애플리케이션이 휴대전화에 저장된 모든 캘린더 일정을 읽을 수 있도록 합니다. 이 경우 악성 애플리케이션이 캘린더 일정을 다른 사람에게 보낼 수 있습니다."</string> <string name="permlab_writeCalendar" msgid="3894879352594904361">"캘린더 일정 추가/수정 및 참석자에게 이메일 전송"</string> @@ -311,8 +310,8 @@ <string name="permdesc_modifyAudioSettings" msgid="5793461287365991922">"애플리케이션이 볼륨 및 경로 지정 같은 전체 오디오 설정을 수정할 수 있도록 합니다."</string> <string name="permlab_recordAudio" msgid="3876049771427466323">"오디오 녹음"</string> <string name="permdesc_recordAudio" msgid="6493228261176552356">"애플리케이션이 오디오 레코드 경로에 액세스할 수 있도록 합니다."</string> - <string name="permlab_camera" msgid="8059288807274039014">"사진 촬영"</string> - <string name="permdesc_camera" msgid="9013476258810982546">"애플리케이션이 카메라로 사진을 찍을 수 있도록 합니다. 이 경우 애플리케이션이 카메라에 보여지는 화면을 언제든지 수집할 수 있습니다."</string> + <string name="permlab_camera" msgid="3616391919559751192">"사진과 동영상 찍기"</string> + <string name="permdesc_camera" msgid="6004878235852154239">"애플리케이션이 카메라로 사진과 동영상을 찍을 수 있도록 합니다. 이 경우 애플리케이션이 카메라에 보여지는 화면을 언제든지 수집할 수 있습니다."</string> <string name="permlab_brick" msgid="8337817093326370537">"휴대전화를 영구적으로 사용 중지"</string> <string name="permdesc_brick" msgid="5569526552607599221">"애플리케이션이 휴대전화를 영구적으로 사용 중지할 수 있게 합니다. 이 기능은 매우 위험합니다."</string> <string name="permlab_reboot" msgid="2898560872462638242">"휴대전화 강제로 다시 부팅"</string> @@ -335,6 +334,8 @@ <string name="permdesc_vibrate" msgid="2886677177257789187">"애플리케이션이 진동을 제어할 수 있도록 합니다."</string> <string name="permlab_flashlight" msgid="2155920810121984215">"카메라 플래시 제어"</string> <string name="permdesc_flashlight" msgid="6433045942283802309">"애플리케이션이 카메라 플래시를 제어할 수 있도록 합니다."</string> + <string name="permlab_accessUsb" msgid="7362327818655760496">"USB 장치 액세스"</string> + <string name="permdesc_accessUsb" msgid="2414271762914049292">"애플리케이션이 USB 장치에 액세스하도록 허용합니다."</string> <string name="permlab_hardware_test" msgid="4148290860400659146">"하드웨어 테스트"</string> <string name="permdesc_hardware_test" msgid="3668894686500081699">"애플리케이션이 하드웨어를 테스트할 목적으로 다양한 주변장치를 제어할 수 있도록 합니다."</string> <string name="permlab_callPhone" msgid="3925836347681847954">"전화번호 자동 연결"</string> @@ -573,16 +574,6 @@ <string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string> <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="AMPM">%P</xliff:g> <xliff:g id="HOUR">%-l</xliff:g>:00"</string> <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="AMPM">%p</xliff:g> <xliff:g id="HOUR">%-l</xliff:g>:00"</string> - <string name="status_bar_clear_all_button" msgid="7774721344716731603">"지우기"</string> - <string name="status_bar_no_notifications_title" msgid="4755261167193833213">"알림 없음"</string> - <string name="status_bar_ongoing_events_title" msgid="1682504513316879202">"진행 중"</string> - <string name="status_bar_latest_events_title" msgid="6594767438577593172">"알림"</string> - <string name="battery_status_text_percent_format" msgid="7660311274698797147">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string> - <string name="battery_status_charging" msgid="756617993998772213">"충전 중..."</string> - <string name="battery_low_title" msgid="7923774589611311406">"충전기를 연결하세요."</string> - <string name="battery_low_subtitle" msgid="7388781709819722764">"배터리 전원이 부족합니다."</string> - <string name="battery_low_percent_format" msgid="696154104579022959">"잔여 배터리가 <xliff:g id="NUMBER">%d%%</xliff:g> 이하입니다."</string> - <string name="battery_low_why" msgid="7279169609518386372">"배터리 사용량"</string> <string name="factorytest_failed" msgid="5410270329114212041">"출고 테스트 불합격"</string> <string name="factorytest_not_system" msgid="4435201656767276723">"FACTORY_TEST 작업은 /system/app 디렉토리에 설치된 패키지에 대해서만 지원됩니다."</string> <string name="factorytest_no_action" msgid="872991874799998561">"FACTORY_TEST 작업을 제공하는 패키지가 없습니다."</string> @@ -708,12 +699,9 @@ <string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string> <string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string> <string name="selectAll" msgid="6876518925844129331">"모두 선택"</string> - <string name="selectText" msgid="3889149123626888637">"텍스트 선택"</string> - <string name="stopSelectingText" msgid="4157931463872320996">"텍스트 선택 중지"</string> + <!-- outdated translation 3889149123626888637 --> <string name="selectText" msgid="4862359311088898878">"텍스트 선택"</string> <string name="cut" msgid="3092569408438626261">"잘라내기"</string> - <string name="cutAll" msgid="2436383270024931639">"모두 잘라내기"</string> <string name="copy" msgid="2681946229533511987">"복사"</string> - <string name="copyAll" msgid="2590829068100113057">"모두 복사"</string> <string name="paste" msgid="5629880836805036433">"붙여넣기"</string> <string name="copyUrl" msgid="2538211579596067402">"URL 복사"</string> <string name="inputMethod" msgid="1653630062304567879">"입력 방법"</string> @@ -744,6 +732,22 @@ <string name="force_close" msgid="3653416315450806396">"닫기"</string> <string name="report" msgid="4060218260984795706">"신고"</string> <string name="wait" msgid="7147118217226317732">"대기"</string> + <!-- no translation found for launch_warning_title (8323761616052121936) --> + <skip /> + <!-- no translation found for launch_warning_replace (6202498949970281412) --> + <skip /> + <!-- no translation found for launch_warning_original (188102023021668683) --> + <skip /> + <string name="smv_application" msgid="295583804361236288">"애플리케이션 <xliff:g id="APPLICATION">%1$s</xliff:g>(프로세스 <xliff:g id="PROCESS">%2$s</xliff:g>)이(가) 자체 시행 StrictMode 정책을 위반했습니다."</string> + <string name="smv_process" msgid="5120397012047462446">"프로세스(<xliff:g id="PROCESS">%1$s</xliff:g>)가 자체 시행 StrictMode 정책을 위반했습니다."</string> + <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g> 실행 중"</string> + <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"애플리케이션으로 전환하려면 선택"</string> + <string name="heavy_weight_switcher_title" msgid="1135403633766694316">"애플리케이션을 전환하시겠습니까?"</string> + <string name="heavy_weight_switcher_text" msgid="4592075610079319667">"다른 애플리케이션이 이미 실행 중입니다. 새 애플리케이션을 시작하려면 실행 중인 애플리케이션을 중단해야 합니다."</string> + <string name="old_app_action" msgid="493129172238566282">"<xliff:g id="OLD_APP">%1$s</xliff:g>(으)로 돌아가기"</string> + <string name="old_app_description" msgid="942967900237208466">"새 애플리케이션을 시작하지 마세요."</string> + <string name="new_app_action" msgid="5472756926945440706">"<xliff:g id="OLD_APP">%1$s</xliff:g> 시작"</string> + <string name="new_app_description" msgid="6830398339826789493">"저장하지 않고 이전 애플리케이션을 중단합니다."</string> <string name="sendText" msgid="5132506121645618310">"텍스트에 대한 작업 선택"</string> <string name="volume_ringtone" msgid="6885421406845734650">"벨소리 볼륨"</string> <string name="volume_music" msgid="5421651157138628171">"미디어 볼륨"</string> @@ -853,8 +857,8 @@ <string name="reset" msgid="2448168080964209908">"재설정"</string> <string name="submit" msgid="1602335572089911941">"제출"</string> <string name="description_star" msgid="2654319874908576133">"즐겨찾기"</string> - <string name="car_mode_disable_notification_title" msgid="3164768212003864316">"차량 모드 사용"</string> - <string name="car_mode_disable_notification_message" msgid="668663626721675614">"차량 모드를 종료하려면 선택하세요."</string> + <string name="car_mode_disable_notification_title" msgid="3164768212003864316">"운전모드 사용"</string> + <string name="car_mode_disable_notification_message" msgid="668663626721675614">"운전모드를 종료하려면 선택하세요."</string> <string name="tethered_notification_title" msgid="3146694234398202601">"테더링 또는 핫스팟 사용"</string> <string name="tethered_notification_message" msgid="3067108323903048927">"구성하려면 터치하세요."</string> <string name="throttle_warning_notification_title" msgid="4890894267454867276">"높은 모바일 데이터 사용량"</string> diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml index 6acf4a64fde0..4ca84ed06bb0 100644 --- a/core/res/res/values-nb/strings.xml +++ b/core/res/res/values-nb/strings.xml @@ -34,6 +34,7 @@ <string name="defaultVoiceMailAlphaTag" msgid="2660020990097733077">"Telefonsvarer"</string> <string name="defaultMsisdnAlphaTag" msgid="2850889754919584674">"MSISDN1"</string> <string name="mmiError" msgid="5154499457739052907">"Tilkoblingsproblem eller ugyldig MMI-kode."</string> + <string name="mmiFdnError" msgid="5224398216385316471">"Handlingen kan kun utføres på numre med anropsbegrensning."</string> <string name="serviceEnabled" msgid="8147278346414714315">"Tjenesten ble aktivert."</string> <string name="serviceEnabledFor" msgid="6856228140453471041">"Tjenesten ble aktivert for:"</string> <string name="serviceDisabled" msgid="1937553226592516411">"Tjenesten ble deaktivert."</string> @@ -171,6 +172,8 @@ <string name="permgroupdesc_storage" msgid="9203302214915355774">"Tilgang til minnekortet."</string> <string name="permlab_statusBar" msgid="7417192629601890791">"deaktivere eller endre statusfeltet"</string> <string name="permdesc_statusBar" msgid="1365473595331989732">"Lar applikasjonen deaktivere statusfeltet, samt legge til og fjerne systemikoner."</string> + <string name="permlab_statusBarService" msgid="7247281911387931485">"statusrad"</string> + <string name="permdesc_statusBarService" msgid="4097605867643520920">"Tillater programmet å vises i statusfeltet."</string> <string name="permlab_expandStatusBar" msgid="1148198785937489264">"utvide/slå sammen statusfeltet"</string> <string name="permdesc_expandStatusBar" msgid="7088604400110768665">"Lar applikasjonen utvide eller slå sammen statusfeltet."</string> <string name="permlab_processOutgoingCalls" msgid="1136262550878335980">"avskjære utgående anrop"</string> @@ -285,10 +288,6 @@ <string name="permdesc_readContacts" msgid="3371591512896545975">"Lar applikasjonen lese all kontakt- og adresseinformasjon lagret på telefonen. Ondsinnede applikasjoner kan bruke dette til å sende personlige data til andre."</string> <string name="permlab_writeContacts" msgid="644616215860933284">"skrive kontaktinformasjon"</string> <string name="permdesc_writeContacts" msgid="3924383579108183601">"Lar applikasjonen endre kontakt- og adresseinformasjon lagret på telefonen. Ondsinnede applikasjoner kan bruke dette til å redigere eller endre kontaktinformasjonen."</string> - <string name="permlab_writeOwnerData" msgid="4892555913849295393">"skrive eierinformasjon"</string> - <string name="permdesc_writeOwnerData" msgid="2344055317969787124">"Lar applikasjonen endre dataene om telefoneieren. Ondsinnede applikasjoner kan bruke dette til å slette eller redigere telefonens eierdata."</string> - <string name="permlab_readOwnerData" msgid="6668525984731523563">"lese eierinformasjon"</string> - <string name="permdesc_readOwnerData" msgid="3088486383128434507">"Lar applikasjonen lese dataene om telefoneieren. Ondsinnede applikasjoner kan bruke dette til å lese telefonens eierdata."</string> <string name="permlab_readCalendar" msgid="6898987798303840534">"les kalenderaktiviteter"</string> <string name="permdesc_readCalendar" msgid="5533029139652095734">"Lar applikasjonen lese alle kalenderhendelser lagret på telefonen. Ondsinnede applikasjoner kan bruke dette til å sende kalenderhendelser til andre."</string> <string name="permlab_writeCalendar" msgid="3894879352594904361">"legg til eller endre kalenderaktiviteter og send e-postmelding til gjestene"</string> @@ -311,8 +310,8 @@ <string name="permdesc_modifyAudioSettings" msgid="5793461287365991922">"Lar applikasjonen endre globale lydinnstillinger som volum og ruting."</string> <string name="permlab_recordAudio" msgid="3876049771427466323">"ta opp lyd"</string> <string name="permdesc_recordAudio" msgid="6493228261176552356">"Gir applikasjonen tilgang til opptaksstien for lyd."</string> - <string name="permlab_camera" msgid="8059288807274039014">"ta bilder"</string> - <string name="permdesc_camera" msgid="9013476258810982546">"Lar applikasjonen ta bilder med kameraet. Dette gir applikasjonen til når som helst å se og lagre det kameraet ser."</string> + <string name="permlab_camera" msgid="3616391919559751192">"ta bilder og videoer"</string> + <string name="permdesc_camera" msgid="6004878235852154239">"Tillat programmet å ta bilder og videoer med kameraet. Programmet kan dermed til enhver tid samle inn bilder som kameraet fanger inn."</string> <string name="permlab_brick" msgid="8337817093326370537">"deaktivere telefonen permanent"</string> <string name="permdesc_brick" msgid="5569526552607599221">"Lar applikasjonen deaktivere hele telefonen permanent. Dette er svært farlig."</string> <string name="permlab_reboot" msgid="2898560872462638242">"tvinge omstart av telefon"</string> @@ -335,6 +334,8 @@ <string name="permdesc_vibrate" msgid="2886677177257789187">"Lar applikasjonen kontrollere vibratoren."</string> <string name="permlab_flashlight" msgid="2155920810121984215">"kontrollere lommelykten"</string> <string name="permdesc_flashlight" msgid="6433045942283802309">"Lar applikasjonen kontrollere lommelykten."</string> + <string name="permlab_accessUsb" msgid="7362327818655760496">"tilgang til USB-enheter"</string> + <string name="permdesc_accessUsb" msgid="2414271762914049292">"Tillater programmet å få tilgang til USB-enheter."</string> <string name="permlab_hardware_test" msgid="4148290860400659146">"teste maskinvare"</string> <string name="permdesc_hardware_test" msgid="3668894686500081699">"Lar applikasjonen styre diverse enheter med det formål å teste maskinvaren."</string> <string name="permlab_callPhone" msgid="3925836347681847954">"ringe telefonnummer direkte"</string> @@ -573,16 +574,6 @@ <string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string> <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string> <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string> - <string name="status_bar_clear_all_button" msgid="7774721344716731603">"Fjern"</string> - <string name="status_bar_no_notifications_title" msgid="4755261167193833213">"Ingen varslinger"</string> - <string name="status_bar_ongoing_events_title" msgid="1682504513316879202">"Aktiviteter"</string> - <string name="status_bar_latest_events_title" msgid="6594767438577593172">"Varslinger"</string> - <string name="battery_status_text_percent_format" msgid="7660311274698797147">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string> - <string name="battery_status_charging" msgid="756617993998772213">"Lader…"</string> - <string name="battery_low_title" msgid="7923774589611311406">"Koble til en lader"</string> - <string name="battery_low_subtitle" msgid="7388781709819722764">"Batteriet er nesten tomt:"</string> - <string name="battery_low_percent_format" msgid="696154104579022959">"mindre enn <xliff:g id="NUMBER">%d%%</xliff:g> igjen."</string> - <string name="battery_low_why" msgid="7279169609518386372">"Batteribruk"</string> <string name="factorytest_failed" msgid="5410270329114212041">"Fabrikktesten feilet"</string> <string name="factorytest_not_system" msgid="4435201656767276723">"The FACTORY_TEST action is only supported for packages installed in /system/app."</string> <string name="factorytest_no_action" msgid="872991874799998561">"No package was found that provides the FACTORY_TEST action."</string> @@ -708,12 +699,9 @@ <string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string> <string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string> <string name="selectAll" msgid="6876518925844129331">"Merk alt"</string> - <string name="selectText" msgid="3889149123626888637">"Merk tekst"</string> - <string name="stopSelectingText" msgid="4157931463872320996">"Slutt å merke tekst"</string> + <!-- outdated translation 3889149123626888637 --> <string name="selectText" msgid="4862359311088898878">"Merk tekst"</string> <string name="cut" msgid="3092569408438626261">"Klipp ut"</string> - <string name="cutAll" msgid="2436383270024931639">"Klipp ut alt"</string> <string name="copy" msgid="2681946229533511987">"Kopier"</string> - <string name="copyAll" msgid="2590829068100113057">"Kopier alt"</string> <string name="paste" msgid="5629880836805036433">"Lim inn"</string> <string name="copyUrl" msgid="2538211579596067402">"Kopier URL"</string> <string name="inputMethod" msgid="1653630062304567879">"Inndatametode"</string> @@ -744,6 +732,22 @@ <string name="force_close" msgid="3653416315450806396">"Tving avslutning"</string> <string name="report" msgid="4060218260984795706">"Rapportér"</string> <string name="wait" msgid="7147118217226317732">"Vent"</string> + <!-- no translation found for launch_warning_title (8323761616052121936) --> + <skip /> + <!-- no translation found for launch_warning_replace (6202498949970281412) --> + <skip /> + <!-- no translation found for launch_warning_original (188102023021668683) --> + <skip /> + <string name="smv_application" msgid="295583804361236288">"Programmet <xliff:g id="APPLICATION">%1$s</xliff:g> (prosessen <xliff:g id="PROCESS">%2$s</xliff:g>) har brutt de selvpålagte StrictMode-retningslinjene."</string> + <string name="smv_process" msgid="5120397012047462446">"Prosessen<xliff:g id="PROCESS">%1$s</xliff:g> har brutt de selvpålagte StrictMode-retningslinjene."</string> + <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g> kjører"</string> + <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"Velg for å bytte til programmet"</string> + <string name="heavy_weight_switcher_title" msgid="1135403633766694316">"Bytt programmer?"</string> + <string name="heavy_weight_switcher_text" msgid="4592075610079319667">"Et annet program kjører og må stoppes før du kan starte et nytt program."</string> + <string name="old_app_action" msgid="493129172238566282">"Gå tilbake til <xliff:g id="OLD_APP">%1$s</xliff:g>"</string> + <string name="old_app_description" msgid="942967900237208466">"Ikke start det nye programmet."</string> + <string name="new_app_action" msgid="5472756926945440706">"Start <xliff:g id="OLD_APP">%1$s</xliff:g>"</string> + <string name="new_app_description" msgid="6830398339826789493">"Stopp det gamle programmet uten å lagre det."</string> <string name="sendText" msgid="5132506121645618310">"Velg mål for tekst"</string> <string name="volume_ringtone" msgid="6885421406845734650">"Ringetonevolum"</string> <string name="volume_music" msgid="5421651157138628171">"Medievolum"</string> diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml index a9586e1e9016..c8f00689cd8d 100644 --- a/core/res/res/values-nl/strings.xml +++ b/core/res/res/values-nl/strings.xml @@ -34,6 +34,7 @@ <string name="defaultVoiceMailAlphaTag" msgid="2660020990097733077">"Voicemail"</string> <string name="defaultMsisdnAlphaTag" msgid="2850889754919584674">"MSISDN1"</string> <string name="mmiError" msgid="5154499457739052907">"Verbindingsprobleem of ongeldige MMI-code."</string> + <string name="mmiFdnError" msgid="5224398216385316471">"Bewerking is beperkt tot vaste nummers."</string> <string name="serviceEnabled" msgid="8147278346414714315">"Service is ingeschakeld."</string> <string name="serviceEnabledFor" msgid="6856228140453471041">"Service is ingeschakeld voor:"</string> <string name="serviceDisabled" msgid="1937553226592516411">"Service is uitgeschakeld."</string> @@ -171,6 +172,8 @@ <string name="permgroupdesc_storage" msgid="9203302214915355774">"Toegang tot de SD-kaart."</string> <string name="permlab_statusBar" msgid="7417192629601890791">"statusbalk uitschakelen of wijzigen"</string> <string name="permdesc_statusBar" msgid="1365473595331989732">"Hiermee kan een toepassing de statusbalk uitschakelen of systeempictogrammen toevoegen en verwijderen."</string> + <string name="permlab_statusBarService" msgid="7247281911387931485">"statusbalk"</string> + <string name="permdesc_statusBarService" msgid="4097605867643520920">"Hiermee kan de toepassing de statusbalk zijn."</string> <string name="permlab_expandStatusBar" msgid="1148198785937489264">"statusbalk uitvouwen/samenvouwen"</string> <string name="permdesc_expandStatusBar" msgid="7088604400110768665">"Hiermee kan de toepassing de statusbalk uitvouwen of samenvouwen."</string> <string name="permlab_processOutgoingCalls" msgid="1136262550878335980">"uitgaande oproepen onderscheppen"</string> @@ -285,10 +288,6 @@ <string name="permdesc_readContacts" msgid="3371591512896545975">"Hiermee kan een toepassing alle contactgegevens (adresgegevens) zien die op uw telefoon zijn opgeslagen. Schadelijke toepassingen kunnen hiervan gebruik maken om uw gegevens te verzenden naar andere personen."</string> <string name="permlab_writeContacts" msgid="644616215860933284">"contactgegevens schrijven"</string> <string name="permdesc_writeContacts" msgid="3924383579108183601">"Hiermee kan een toepassing de op uw telefoon opgeslagen contactgegevens (adresgegevens) wijzigen. Schadelijke toepassingen kunnen hiermee uw contactgegevens verwijderen of wijzigen."</string> - <string name="permlab_writeOwnerData" msgid="4892555913849295393">"gegevens eigenaar schrijven"</string> - <string name="permdesc_writeOwnerData" msgid="2344055317969787124">"Hiermee kan een toepassing de op uw telefoon opgeslagen gegevens van de eigenaar wijzigen. Schadelijke toepassingen kunnen hiermee gegevens van de eigenaar verwijderen of wijzigen."</string> - <string name="permlab_readOwnerData" msgid="6668525984731523563">"gegevens eigenaar lezen"</string> - <string name="permdesc_readOwnerData" msgid="3088486383128434507">"Hiermee kan een toepassing de op uw telefoon opgeslagen gegevens van de eigenaar lezen. Schadelijke toepassingen kunnen hiermee gegevens van de eigenaar lezen."</string> <string name="permlab_readCalendar" msgid="6898987798303840534">"agendagebeurtenissen lezen"</string> <string name="permdesc_readCalendar" msgid="5533029139652095734">"Hiermee kan een toepassing alle agendagebeurtenissen lezen die zijn opgeslagen op uw telefoon. Schadelijke toepassingen kunnen hiervan gebruik maken om uw agendagebeurtenissen te verzenden naar andere personen."</string> <string name="permlab_writeCalendar" msgid="3894879352594904361">"agendagebeurtenissen toevoegen of aanpassen en e-mail verzenden naar gasten"</string> @@ -311,8 +310,8 @@ <string name="permdesc_modifyAudioSettings" msgid="5793461287365991922">"Hiermee kan een toepassing de algemene audio-instellingen, zoals volume en omleiding, wijzigen."</string> <string name="permlab_recordAudio" msgid="3876049771427466323">"audio opnemen"</string> <string name="permdesc_recordAudio" msgid="6493228261176552356">"Hiermee krijgt de toepassing toegang tot het audio-opnamepad."</string> - <string name="permlab_camera" msgid="8059288807274039014">"foto\'s maken"</string> - <string name="permdesc_camera" msgid="9013476258810982546">"Hiermee kan een toepassing foto\'s maken met de camera. De toepassing kan op deze manier op elk gewenste moment foto\'s verzamelen van wat de camera ziet."</string> + <string name="permlab_camera" msgid="3616391919559751192">"foto\'s en video\'s maken"</string> + <string name="permdesc_camera" msgid="6004878235852154239">"Hiermee kan een toepassing foto\'s en video\'s maken met de camera. De toepassing kan op deze manier op elk gewenste moment beelden verzamelen van wat de camera ziet."</string> <string name="permlab_brick" msgid="8337817093326370537">"telefoon permanent uitschakelen"</string> <string name="permdesc_brick" msgid="5569526552607599221">"Hiermee kan de toepassing de telefoon permanent uitschakelen. Dit is erg gevaarlijk."</string> <string name="permlab_reboot" msgid="2898560872462638242">"telefoon nu opnieuw opstarten"</string> @@ -335,6 +334,8 @@ <string name="permdesc_vibrate" msgid="2886677177257789187">"Hiermee kan de toepassing de trilstand beheren."</string> <string name="permlab_flashlight" msgid="2155920810121984215">"zaklamp bedienen"</string> <string name="permdesc_flashlight" msgid="6433045942283802309">"Hiermee kan de toepassing de zaklamp bedienen."</string> + <string name="permlab_accessUsb" msgid="7362327818655760496">"toegang krijgen tot USB-apparaten"</string> + <string name="permdesc_accessUsb" msgid="2414271762914049292">"Hiermee kan de toepassing toegang krijgen tot USB-apparaten."</string> <string name="permlab_hardware_test" msgid="4148290860400659146">"hardware testen"</string> <string name="permdesc_hardware_test" msgid="3668894686500081699">"Hiermee kan de toepassing verschillende randapparaten beheren om de hardware te testen."</string> <string name="permlab_callPhone" msgid="3925836347681847954">"telefoonnummers rechtstreeks bellen"</string> @@ -573,16 +574,6 @@ <string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"Alt"</string> <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string> <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string> - <string name="status_bar_clear_all_button" msgid="7774721344716731603">"Wissen"</string> - <string name="status_bar_no_notifications_title" msgid="4755261167193833213">"Geen meldingen"</string> - <string name="status_bar_ongoing_events_title" msgid="1682504513316879202">"Actief"</string> - <string name="status_bar_latest_events_title" msgid="6594767438577593172">"Meldingen"</string> - <string name="battery_status_text_percent_format" msgid="7660311274698797147">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string> - <string name="battery_status_charging" msgid="756617993998772213">"Opladen..."</string> - <string name="battery_low_title" msgid="7923774589611311406">"Sluit de oplader aan"</string> - <string name="battery_low_subtitle" msgid="7388781709819722764">"De accu raakt op:"</string> - <string name="battery_low_percent_format" msgid="696154104579022959">"<xliff:g id="NUMBER">%d%%</xliff:g> of minder resterend."</string> - <string name="battery_low_why" msgid="7279169609518386372">"Accugebruik"</string> <string name="factorytest_failed" msgid="5410270329114212041">"Fabriekstest mislukt"</string> <string name="factorytest_not_system" msgid="4435201656767276723">"De actie FACTORY_TEST wordt alleen ondersteund voor pakketten die zijn geïnstalleerd in /system/app."</string> <string name="factorytest_no_action" msgid="872991874799998561">"Er is geen pakket gevonden dat de actie FACTORY_TEST levert."</string> @@ -708,12 +699,9 @@ <string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string> <string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string> <string name="selectAll" msgid="6876518925844129331">"Alles selecteren"</string> - <string name="selectText" msgid="3889149123626888637">"Tekst selecteren"</string> - <string name="stopSelectingText" msgid="4157931463872320996">"Stoppen met tekst selecteren"</string> + <!-- outdated translation 3889149123626888637 --> <string name="selectText" msgid="4862359311088898878">"Tekst selecteren"</string> <string name="cut" msgid="3092569408438626261">"Knippen"</string> - <string name="cutAll" msgid="2436383270024931639">"Alles knippen"</string> <string name="copy" msgid="2681946229533511987">"Kopiëren"</string> - <string name="copyAll" msgid="2590829068100113057">"Alles kopiëren"</string> <string name="paste" msgid="5629880836805036433">"Plakken"</string> <string name="copyUrl" msgid="2538211579596067402">"URL kopiëren"</string> <string name="inputMethod" msgid="1653630062304567879">"Invoermethode"</string> @@ -744,6 +732,22 @@ <string name="force_close" msgid="3653416315450806396">"Nu sluiten"</string> <string name="report" msgid="4060218260984795706">"Rapport"</string> <string name="wait" msgid="7147118217226317732">"Wachten"</string> + <!-- no translation found for launch_warning_title (8323761616052121936) --> + <skip /> + <!-- no translation found for launch_warning_replace (6202498949970281412) --> + <skip /> + <!-- no translation found for launch_warning_original (188102023021668683) --> + <skip /> + <string name="smv_application" msgid="295583804361236288">"De toepassing <xliff:g id="APPLICATION">%1$s</xliff:g> (proces <xliff:g id="PROCESS">%2$s</xliff:g>) heeft het zelf afgedwongen StrictMode-beleid geschonden."</string> + <string name="smv_process" msgid="5120397012047462446">"Het proces <xliff:g id="PROCESS">%1$s</xliff:g> heeft het zelf afgedwongen StrictMode-beleid geschonden."</string> + <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g> wordt uitgevoerd"</string> + <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"Selecteren om over te schakelen naar toepassing"</string> + <string name="heavy_weight_switcher_title" msgid="1135403633766694316">"Toepassingen wijzigen?"</string> + <string name="heavy_weight_switcher_text" msgid="4592075610079319667">"Er wordt al een andere toepassing uitgevoerd die moet worden gestopt voordat u een nieuwe toepassing kunt starten."</string> + <string name="old_app_action" msgid="493129172238566282">"Terug naar <xliff:g id="OLD_APP">%1$s</xliff:g>"</string> + <string name="old_app_description" msgid="942967900237208466">"De nieuwe toepassing niet starten."</string> + <string name="new_app_action" msgid="5472756926945440706">"<xliff:g id="OLD_APP">%1$s</xliff:g> starten"</string> + <string name="new_app_description" msgid="6830398339826789493">"De oude toepassing stoppen zonder opslaan."</string> <string name="sendText" msgid="5132506121645618310">"Selecteer een actie voor tekst"</string> <string name="volume_ringtone" msgid="6885421406845734650">"Belvolume"</string> <string name="volume_music" msgid="5421651157138628171">"Mediavolume"</string> diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml index c1f403336325..ae897a8d87a8 100644 --- a/core/res/res/values-pl/strings.xml +++ b/core/res/res/values-pl/strings.xml @@ -34,6 +34,7 @@ <string name="defaultVoiceMailAlphaTag" msgid="2660020990097733077">"Poczta głosowa"</string> <string name="defaultMsisdnAlphaTag" msgid="2850889754919584674">"MSISDN1"</string> <string name="mmiError" msgid="5154499457739052907">"Problem z połączeniem lub błędny kod MMI."</string> + <string name="mmiFdnError" msgid="5224398216385316471">"Operacja jest ograniczona wyłącznie do numerów ustalonych."</string> <string name="serviceEnabled" msgid="8147278346414714315">"Usługa była włączona."</string> <string name="serviceEnabledFor" msgid="6856228140453471041">"Usługa została włączona dla:"</string> <string name="serviceDisabled" msgid="1937553226592516411">"Usługa została wyłączona."</string> @@ -171,6 +172,8 @@ <string name="permgroupdesc_storage" msgid="9203302214915355774">"Dostęp do karty SD."</string> <string name="permlab_statusBar" msgid="7417192629601890791">"wyłączanie lub zmienianie paska stanu"</string> <string name="permdesc_statusBar" msgid="1365473595331989732">"Pozwala aplikacjom na wyłączenie paska stanu lub dodawanie i usuwanie ikon systemowych."</string> + <string name="permlab_statusBarService" msgid="7247281911387931485">"pasek stanu"</string> + <string name="permdesc_statusBarService" msgid="4097605867643520920">"Zezwala aplikacji na występowanie na pasku stanu."</string> <string name="permlab_expandStatusBar" msgid="1148198785937489264">"rozwijanie/zwijanie paska stanu"</string> <string name="permdesc_expandStatusBar" msgid="7088604400110768665">"Pozwala aplikacji na rozwijanie lub zwijanie paska stanu."</string> <string name="permlab_processOutgoingCalls" msgid="1136262550878335980">"przechwytywanie połączeń wychodzących"</string> @@ -285,10 +288,6 @@ <string name="permdesc_readContacts" msgid="3371591512896545975">"Pozwala aplikacji na czytanie wszystkich danych kontaktowych (adresowych) zapisanych w telefonie. Szkodliwe aplikacje mogą to wykorzystać, aby wysyłać dane użytkownika do innych ludzi."</string> <string name="permlab_writeContacts" msgid="644616215860933284">"zapisywanie danych kontaktowych"</string> <string name="permdesc_writeContacts" msgid="3924383579108183601">"Pozwala aplikacji na zmianę danych kontaktowych (adresowych) zapisanych w telefonie. Szkodliwe aplikacje mogą to wykorzystać, aby usunąć lub zmienić dane kontaktowe."</string> - <string name="permlab_writeOwnerData" msgid="4892555913849295393">"zapisywanie danych właściciela"</string> - <string name="permdesc_writeOwnerData" msgid="2344055317969787124">"Pozwala aplikacji na zmianę danych właściciela zapisanych w telefonie. Szkodliwe aplikacje mogą to wykorzystać, aby wymazać lub zmienić dane właściciela."</string> - <string name="permlab_readOwnerData" msgid="6668525984731523563">"czytanie danych właściciela"</string> - <string name="permdesc_readOwnerData" msgid="3088486383128434507">"Pozwala aplikacji na czytanie danych właściciela zapisanych w telefonie. Szkodliwe aplikacje mogą to wykorzystać do odczytania danych właściciela."</string> <string name="permlab_readCalendar" msgid="6898987798303840534">"odczytywanie wydarzeń w kalendarzu"</string> <string name="permdesc_readCalendar" msgid="5533029139652095734">"Pozwala aplikacji na odczytywanie wszystkich wydarzeń z kalendarza, zapisanych w telefonie. Szkodliwe aplikacje mogą to wykorzystać do rozsyłania wydarzeń z kalendarza do innych ludzi."</string> <string name="permlab_writeCalendar" msgid="3894879352594904361">"dodawanie i modyfikowanie wydarzeń w kalendarzu oraz wysyłanie wiadomości e-mail do gości"</string> @@ -311,8 +310,8 @@ <string name="permdesc_modifyAudioSettings" msgid="5793461287365991922">"Pozwala aplikacjom na zmianę globalnych ustawień audio, takich jak głośność i routing."</string> <string name="permlab_recordAudio" msgid="3876049771427466323">"nagrywanie dźwięku"</string> <string name="permdesc_recordAudio" msgid="6493228261176552356">"Pozwala aplikacji na dostęp do ścieżki nagrywania dźwięku."</string> - <string name="permlab_camera" msgid="8059288807274039014">"robienie zdjęć"</string> - <string name="permdesc_camera" msgid="9013476258810982546">"Pozwala aplikacji na wykonywanie zdjęć za pomocą aparatu. Dzięki temu może ona pobierać zdjęcia z aparatu w dowolnym momencie."</string> + <string name="permlab_camera" msgid="3616391919559751192">"wykonywanie zdjęć i filmów wideo"</string> + <string name="permdesc_camera" msgid="6004878235852154239">"Zezwala aplikacji na wykonywanie zdjęć i rejestrowanie filmów wideo przy użyciu aparatu fotograficznego. Umożliwia to aplikacji gromadzenie w dowolnym momencie zdjęć tego, na co skierowany jest obiektyw aparatu."</string> <string name="permlab_brick" msgid="8337817093326370537">"wyłączenie telefonu na stałe"</string> <string name="permdesc_brick" msgid="5569526552607599221">"Pozwala aplikacji na wyłączenie całego telefonu na stałe. Jest to bardzo niebezpieczne."</string> <string name="permlab_reboot" msgid="2898560872462638242">"wymuszanie ponownego uruchomienia telefonu"</string> @@ -335,6 +334,8 @@ <string name="permdesc_vibrate" msgid="2886677177257789187">"Pozwala aplikacjom na kontrolowanie wibracji."</string> <string name="permlab_flashlight" msgid="2155920810121984215">"kontrolowanie latarki"</string> <string name="permdesc_flashlight" msgid="6433045942283802309">"Pozwala aplikacji kontrolować latarkę."</string> + <string name="permlab_accessUsb" msgid="7362327818655760496">"dostęp do urządzeń USB"</string> + <string name="permdesc_accessUsb" msgid="2414271762914049292">"Zezwala aplikacji na dostęp do urządzeń USB."</string> <string name="permlab_hardware_test" msgid="4148290860400659146">"testowanie sprzętu"</string> <string name="permdesc_hardware_test" msgid="3668894686500081699">"Pozwala aplikacji na kontrolowanie różnych urządzeń peryferyjnych w celu testowania sprzętu."</string> <string name="permlab_callPhone" msgid="3925836347681847954">"bezpośrednie wybieranie numerów telefonów"</string> @@ -573,16 +574,6 @@ <string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string> <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string> <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string> - <string name="status_bar_clear_all_button" msgid="7774721344716731603">"Wyczyść"</string> - <string name="status_bar_no_notifications_title" msgid="4755261167193833213">"Brak powiadomień"</string> - <string name="status_bar_ongoing_events_title" msgid="1682504513316879202">"Bieżące"</string> - <string name="status_bar_latest_events_title" msgid="6594767438577593172">"Powiadomienia"</string> - <string name="battery_status_text_percent_format" msgid="7660311274698797147">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string> - <string name="battery_status_charging" msgid="756617993998772213">"Ładowanie..."</string> - <string name="battery_low_title" msgid="7923774589611311406">"Podłącz ładowarkę"</string> - <string name="battery_low_subtitle" msgid="7388781709819722764">"Bateria się rozładowuje:"</string> - <string name="battery_low_percent_format" msgid="696154104579022959">"Pozostało: <xliff:g id="NUMBER">%d%%</xliff:g> lub mniej."</string> - <string name="battery_low_why" msgid="7279169609518386372">"Użycie baterii"</string> <string name="factorytest_failed" msgid="5410270329114212041">"Nieudany test fabryczny"</string> <string name="factorytest_not_system" msgid="4435201656767276723">"Czynność FACTORY_TEST jest obsługiwana tylko dla pakietów zainstalowanych w katalogu /system/app."</string> <string name="factorytest_no_action" msgid="872991874799998561">"Nie znaleziono żadnego pakietu, który zapewnia działanie FACTORY_TEST."</string> @@ -708,12 +699,9 @@ <string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string> <string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string> <string name="selectAll" msgid="6876518925844129331">"Zaznacz wszystko"</string> - <string name="selectText" msgid="3889149123626888637">"Zaznacz tekst"</string> - <string name="stopSelectingText" msgid="4157931463872320996">"Zatrzymaj wybieranie tekstu"</string> + <!-- outdated translation 3889149123626888637 --> <string name="selectText" msgid="4862359311088898878">"Zaznacz tekst"</string> <string name="cut" msgid="3092569408438626261">"Wytnij"</string> - <string name="cutAll" msgid="2436383270024931639">"Wytnij wszystko"</string> <string name="copy" msgid="2681946229533511987">"Kopiuj"</string> - <string name="copyAll" msgid="2590829068100113057">"Kopiuj wszystko"</string> <string name="paste" msgid="5629880836805036433">"Wklej"</string> <string name="copyUrl" msgid="2538211579596067402">"Kopiuj adres URL"</string> <string name="inputMethod" msgid="1653630062304567879">"Metoda wprowadzania"</string> @@ -744,6 +732,22 @@ <string name="force_close" msgid="3653416315450806396">"Wymuś zamknięcie"</string> <string name="report" msgid="4060218260984795706">"Zgłoś"</string> <string name="wait" msgid="7147118217226317732">"Czekaj"</string> + <!-- no translation found for launch_warning_title (8323761616052121936) --> + <skip /> + <!-- no translation found for launch_warning_replace (6202498949970281412) --> + <skip /> + <!-- no translation found for launch_warning_original (188102023021668683) --> + <skip /> + <string name="smv_application" msgid="295583804361236288">"Aplikacja <xliff:g id="APPLICATION">%1$s</xliff:g> (proces <xliff:g id="PROCESS">%2$s</xliff:g>) naruszyła wymuszone przez siebie zasady StrictMode."</string> + <string name="smv_process" msgid="5120397012047462446">"Proces <xliff:g id="PROCESS">%1$s</xliff:g> naruszył wymuszone przez siebie zasady StrictMode."</string> + <string name="heavy_weight_notification" msgid="9087063985776626166">"Działa <xliff:g id="APP">%1$s</xliff:g>"</string> + <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"Wybierz, aby przełączyć się na aplikację"</string> + <string name="heavy_weight_switcher_title" msgid="1135403633766694316">"Czy przełączyć aplikacje?"</string> + <string name="heavy_weight_switcher_text" msgid="4592075610079319667">"Działa już inna aplikacja, którą trzeba zatrzymać, aby możliwe było uruchomienie nowej."</string> + <string name="old_app_action" msgid="493129172238566282">"Powrót do aplikacji <xliff:g id="OLD_APP">%1$s</xliff:g>"</string> + <string name="old_app_description" msgid="942967900237208466">"Nie uruchamiaj nowej aplikacji."</string> + <string name="new_app_action" msgid="5472756926945440706">"Uruchom <xliff:g id="OLD_APP">%1$s</xliff:g>"</string> + <string name="new_app_description" msgid="6830398339826789493">"Zatrzymaj starą aplikację bez zapisywania."</string> <string name="sendText" msgid="5132506121645618310">"Jak wysłać tekst?"</string> <string name="volume_ringtone" msgid="6885421406845734650">"Głośność dzwonka"</string> <string name="volume_music" msgid="5421651157138628171">"Głośność multimediów"</string> diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml index 750d257c887b..b9f9be2910a2 100644 --- a/core/res/res/values-pt-rPT/strings.xml +++ b/core/res/res/values-pt-rPT/strings.xml @@ -34,6 +34,7 @@ <string name="defaultVoiceMailAlphaTag" msgid="2660020990097733077">"Correio de voz"</string> <string name="defaultMsisdnAlphaTag" msgid="2850889754919584674">"MSISDN1"</string> <string name="mmiError" msgid="5154499457739052907">"Problema de ligação ou código MMI inválido."</string> + <string name="mmiFdnError" msgid="5224398216385316471">"A operação está restringida a números fixos autorizados."</string> <string name="serviceEnabled" msgid="8147278346414714315">"O serviço foi activado."</string> <string name="serviceEnabledFor" msgid="6856228140453471041">"O serviço foi activado para:"</string> <string name="serviceDisabled" msgid="1937553226592516411">"O serviço foi desactivado."</string> @@ -171,6 +172,8 @@ <string name="permgroupdesc_storage" msgid="9203302214915355774">"Aceder ao cartão SD."</string> <string name="permlab_statusBar" msgid="7417192629601890791">"desactivar ou modificar barra de estado"</string> <string name="permdesc_statusBar" msgid="1365473595331989732">"Permite à aplicação desactivar a barra de estado ou adicionar e remover ícones do sistema."</string> + <string name="permlab_statusBarService" msgid="7247281911387931485">"barra de estado"</string> + <string name="permdesc_statusBarService" msgid="4097605867643520920">"Permite que a aplicação seja apresentada na barra de estado."</string> <string name="permlab_expandStatusBar" msgid="1148198785937489264">"expandir/fechar barra de estado"</string> <string name="permdesc_expandStatusBar" msgid="7088604400110768665">"Permite à aplicação expandir ou fechar a barra de estado."</string> <string name="permlab_processOutgoingCalls" msgid="1136262550878335980">"interceptar chamadas efectuadas"</string> @@ -285,10 +288,6 @@ <string name="permdesc_readContacts" msgid="3371591512896545975">"Permite a uma aplicação ler todos os dados de contactos (endereços) armazenados no seu telefone. Algumas aplicações maliciosas podem utilizar este item para enviar os seus dados a outras pessoas."</string> <string name="permlab_writeContacts" msgid="644616215860933284">"escrever dados de contacto"</string> <string name="permdesc_writeContacts" msgid="3924383579108183601">"Permite a uma aplicação modificar os dados de contacto (endereço) armazenados no seu telefone. Algumas aplicações maliciosas podem utilizar estes dados para apagar ou modificar os dados dos seus contactos."</string> - <string name="permlab_writeOwnerData" msgid="4892555913849295393">"escrever dados do proprietário"</string> - <string name="permdesc_writeOwnerData" msgid="2344055317969787124">"Permite a uma aplicação modificar os dados do proprietário do telefone armazenados no seu telefone. Algumas aplicações maliciosas podem utilizar este item para apagar ou modificar dados do proprietário."</string> - <string name="permlab_readOwnerData" msgid="6668525984731523563">"ler dados do proprietário"</string> - <string name="permdesc_readOwnerData" msgid="3088486383128434507">"Permite a uma aplicação modificar os dados do proprietário do telefone armazenados no seu telefone. Algumas aplicações maliciosas podem utilizar este item para ler dados do proprietário do telefone."</string> <string name="permlab_readCalendar" msgid="6898987798303840534">"ler eventos da agenda"</string> <string name="permdesc_readCalendar" msgid="5533029139652095734">"Permite a uma aplicação ler todos os eventos do calendário armazenados no seu telefone. Algumas aplicações maliciosas podem utilizar este item para enviar os eventos do seu calendário a outras pessoas."</string> <string name="permlab_writeCalendar" msgid="3894879352594904361">"adicionar ou alterar eventos da agenda e enviar e-mails para os convidados"</string> @@ -311,8 +310,8 @@ <string name="permdesc_modifyAudioSettings" msgid="5793461287365991922">"Permite à aplicação modificar as definições de áudio globais, tais como o volume e o encaminhamento."</string> <string name="permlab_recordAudio" msgid="3876049771427466323">"gravar áudio"</string> <string name="permdesc_recordAudio" msgid="6493228261176552356">"Permite à aplicação aceder ao caminho de gravação de áudio."</string> - <string name="permlab_camera" msgid="8059288807274039014">"tirar fotografias"</string> - <string name="permdesc_camera" msgid="9013476258810982546">"Permite à aplicação tirar fotografias com a câmara. Isto permite que a aplicação recolha imagens captadas pela câmara em qualquer momento."</string> + <string name="permlab_camera" msgid="3616391919559751192">"tirar fotografias e vídeos"</string> + <string name="permdesc_camera" msgid="6004878235852154239">"Permite à aplicação tirar fotografias e vídeos com a câmara, permitindo que a aplicação recolha imagens captadas pela câmara em qualquer momento."</string> <string name="permlab_brick" msgid="8337817093326370537">"desactivar telefone de forma permanente"</string> <string name="permdesc_brick" msgid="5569526552607599221">"Permite à aplicação desactivar permanentemente todo o telefone. Esta acção é muito perigosa."</string> <string name="permlab_reboot" msgid="2898560872462638242">"forçar reinício do telefone"</string> @@ -335,6 +334,8 @@ <string name="permdesc_vibrate" msgid="2886677177257789187">"Permite à aplicação controlar o vibrador."</string> <string name="permlab_flashlight" msgid="2155920810121984215">"controlar lanterna"</string> <string name="permdesc_flashlight" msgid="6433045942283802309">"Permite à aplicação controlar a lanterna."</string> + <string name="permlab_accessUsb" msgid="7362327818655760496">"aceder a dispositivos USB"</string> + <string name="permdesc_accessUsb" msgid="2414271762914049292">"Permite à aplicação aceder a dispositivos USB."</string> <string name="permlab_hardware_test" msgid="4148290860400659146">"testar hardware"</string> <string name="permdesc_hardware_test" msgid="3668894686500081699">"Permite à aplicação controlar vários periféricos para fins de teste de hardware."</string> <string name="permlab_callPhone" msgid="3925836347681847954">"marcar números de telefone directamente"</string> @@ -573,16 +574,6 @@ <string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string> <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g> <xliff:g id="AMPM">%P</xliff:g>"</string> <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g> <xliff:g id="AMPM">%p</xliff:g>"</string> - <string name="status_bar_clear_all_button" msgid="7774721344716731603">"Limpar"</string> - <string name="status_bar_no_notifications_title" msgid="4755261167193833213">"Sem notificações"</string> - <string name="status_bar_ongoing_events_title" msgid="1682504513316879202">"Em curso"</string> - <string name="status_bar_latest_events_title" msgid="6594767438577593172">"Notificações"</string> - <string name="battery_status_text_percent_format" msgid="7660311274698797147">"<xliff:g id="NUMBER">%d</xliff:g> <xliff:g id="PERCENT">%%</xliff:g>"</string> - <string name="battery_status_charging" msgid="756617993998772213">"A carregar..."</string> - <string name="battery_low_title" msgid="7923774589611311406">"Ligue o carregador"</string> - <string name="battery_low_subtitle" msgid="7388781709819722764">"A bateria está a ficar fraca:"</string> - <string name="battery_low_percent_format" msgid="696154104579022959">"Restam <xliff:g id="NUMBER">%d%%</xliff:g> ou menos."</string> - <string name="battery_low_why" msgid="7279169609518386372">"Utilização da bateria"</string> <string name="factorytest_failed" msgid="5410270329114212041">"O teste de fábrica falhou"</string> <string name="factorytest_not_system" msgid="4435201656767276723">"A acção FACTORY_TEST apenas é suportada para pacotes instalados em /system/app."</string> <string name="factorytest_no_action" msgid="872991874799998561">"Não foi localizado qualquer pacote que forneça a acção FACTORY_TEST."</string> @@ -708,12 +699,9 @@ <string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string> <string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string> <string name="selectAll" msgid="6876518925844129331">"Seleccionar tudo"</string> - <string name="selectText" msgid="3889149123626888637">"Seleccionar texto"</string> - <string name="stopSelectingText" msgid="4157931463872320996">"Parar selecção de texto"</string> + <!-- outdated translation 3889149123626888637 --> <string name="selectText" msgid="4862359311088898878">"Seleccionar texto"</string> <string name="cut" msgid="3092569408438626261">"Cortar"</string> - <string name="cutAll" msgid="2436383270024931639">"Cortar tudo"</string> <string name="copy" msgid="2681946229533511987">"Copiar"</string> - <string name="copyAll" msgid="2590829068100113057">"Copiar tudo"</string> <string name="paste" msgid="5629880836805036433">"Colar"</string> <string name="copyUrl" msgid="2538211579596067402">"Copiar URL"</string> <string name="inputMethod" msgid="1653630062304567879">"Método de entrada"</string> @@ -744,6 +732,22 @@ <string name="force_close" msgid="3653416315450806396">"Forçar fecho"</string> <string name="report" msgid="4060218260984795706">"Relatório"</string> <string name="wait" msgid="7147118217226317732">"Esperar"</string> + <!-- no translation found for launch_warning_title (8323761616052121936) --> + <skip /> + <!-- no translation found for launch_warning_replace (6202498949970281412) --> + <skip /> + <!-- no translation found for launch_warning_original (188102023021668683) --> + <skip /> + <string name="smv_application" msgid="295583804361236288">"A aplicação <xliff:g id="APPLICATION">%1$s</xliff:g> (processo <xliff:g id="PROCESS">%2$s</xliff:g>) violou a política StrictMode auto-imposta."</string> + <string name="smv_process" msgid="5120397012047462446">"O processo <xliff:g id="PROCESS">%1$s</xliff:g> violou a política StrictMode auto-imposta."</string> + <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g> em execução"</string> + <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"Seleccione para mudar para a aplicação"</string> + <string name="heavy_weight_switcher_title" msgid="1135403633766694316">"Mudar aplicações?"</string> + <string name="heavy_weight_switcher_text" msgid="4592075610079319667">"Já está em execução uma outra aplicação que terá de ser parada para que possa iniciar uma nova."</string> + <string name="old_app_action" msgid="493129172238566282">"Regressar a <xliff:g id="OLD_APP">%1$s</xliff:g>"</string> + <string name="old_app_description" msgid="942967900237208466">"Não iniciar a nova aplicação."</string> + <string name="new_app_action" msgid="5472756926945440706">"Iniciar <xliff:g id="OLD_APP">%1$s</xliff:g>"</string> + <string name="new_app_description" msgid="6830398339826789493">"Parar a aplicação antiga sem guardar."</string> <string name="sendText" msgid="5132506121645618310">"Seleccionar uma acção para texto"</string> <string name="volume_ringtone" msgid="6885421406845734650">"Volume da campainha"</string> <string name="volume_music" msgid="5421651157138628171">"Volume de multimédia"</string> diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml index 12f77bc011e6..7cc266a9b731 100644 --- a/core/res/res/values-pt/strings.xml +++ b/core/res/res/values-pt/strings.xml @@ -34,6 +34,7 @@ <string name="defaultVoiceMailAlphaTag" msgid="2660020990097733077">"Correio de voz"</string> <string name="defaultMsisdnAlphaTag" msgid="2850889754919584674">"MSISDN1"</string> <string name="mmiError" msgid="5154499457739052907">"Problema de conexão ou código MMI inválido."</string> + <string name="mmiFdnError" msgid="5224398216385316471">"A operação é limitada somente a números de chamadas fixas."</string> <string name="serviceEnabled" msgid="8147278346414714315">"O serviço foi ativado."</string> <string name="serviceEnabledFor" msgid="6856228140453471041">"O serviço foi ativado para:"</string> <string name="serviceDisabled" msgid="1937553226592516411">"O serviço foi desativado."</string> @@ -171,6 +172,8 @@ <string name="permgroupdesc_storage" msgid="9203302214915355774">"Acessar o cartão SD."</string> <string name="permlab_statusBar" msgid="7417192629601890791">"desativar ou modificar a barra de status"</string> <string name="permdesc_statusBar" msgid="1365473595331989732">"Permite que o aplicativo desative a barra de status ou adicione e remova ícones do sistema."</string> + <string name="permlab_statusBarService" msgid="7247281911387931485">"barra de status"</string> + <string name="permdesc_statusBarService" msgid="4097605867643520920">"Permite que o aplicativo seja a barra de status."</string> <string name="permlab_expandStatusBar" msgid="1148198785937489264">"expandir/recolher barra de status"</string> <string name="permdesc_expandStatusBar" msgid="7088604400110768665">"Permite que o aplicativo expanda ou recolha a barra de status."</string> <string name="permlab_processOutgoingCalls" msgid="1136262550878335980">"interceptar chamadas enviadas"</string> @@ -285,10 +288,6 @@ <string name="permdesc_readContacts" msgid="3371591512896545975">"Permite que um aplicativo leia todos os dados de contato (endereço) armazenados no seu telefone. Aplicativos maliciosos podem usar isso para enviar os seus dados para outras pessoas."</string> <string name="permlab_writeContacts" msgid="644616215860933284">"gravar dados de contato"</string> <string name="permdesc_writeContacts" msgid="3924383579108183601">"Permite que um aplicativo modifique os dados de contato (endereço) armazenados no seu telefone. Aplicativos maliciosos podem usar isso para apagar ou modificar os seus dados de contato."</string> - <string name="permlab_writeOwnerData" msgid="4892555913849295393">"gravar dados do proprietário"</string> - <string name="permdesc_writeOwnerData" msgid="2344055317969787124">"Permite que um aplicativo modifique os dados de proprietário do telefone armazenados no seu telefone. Aplicativos maliciosos podem usar isso para apagar ou modificar dados do proprietário."</string> - <string name="permlab_readOwnerData" msgid="6668525984731523563">"ler dados do proprietário"</string> - <string name="permdesc_readOwnerData" msgid="3088486383128434507">"Permite que um aplicativo leia os dados de proprietário do telefone armazenados no seu telefone. Aplicativos maliciosos podem usar isso para ler os dados de proprietário do telefone."</string> <string name="permlab_readCalendar" msgid="6898987798303840534">"ler eventos da agenda"</string> <string name="permdesc_readCalendar" msgid="5533029139652095734">"Permite que um aplicativo leia todos os eventos da agenda armazenados no seu telefone. Aplicativos maliciosos podem usar isso para enviar eventos da sua agenda para outras pessoas."</string> <string name="permlab_writeCalendar" msgid="3894879352594904361">"adicionar ou modificar eventos da agenda e enviar e-mail aos convidados"</string> @@ -311,8 +310,8 @@ <string name="permdesc_modifyAudioSettings" msgid="5793461287365991922">"Permite que o aplicativo modifique as configurações globais de áudio como volume e roteamento."</string> <string name="permlab_recordAudio" msgid="3876049771427466323">"gravar áudio"</string> <string name="permdesc_recordAudio" msgid="6493228261176552356">"Permite que um aplicativo acesse o caminho de gravação do áudio."</string> - <string name="permlab_camera" msgid="8059288807274039014">"tirar fotos"</string> - <string name="permdesc_camera" msgid="9013476258810982546">"Permite que o aplicativo tire fotos com a câmera. Isso permite que o aplicativo colete imagens vistas pela câmera a qualquer momento."</string> + <string name="permlab_camera" msgid="3616391919559751192">"tirar fotos e gravar vídeos"</string> + <string name="permdesc_camera" msgid="6004878235852154239">"Permite que o aplicativo tire fotos e grave vídeos com a câmera. Isso permite que o aplicativo colete imagens vistas pela câmera a qualquer momento."</string> <string name="permlab_brick" msgid="8337817093326370537">"desativar permanentemente o telefone"</string> <string name="permdesc_brick" msgid="5569526552607599221">"Permite que o aplicativo desative o telefone inteiro permanentemente. Isso é muito perigoso."</string> <string name="permlab_reboot" msgid="2898560872462638242">"forçar reinicialização do telefone"</string> @@ -335,6 +334,8 @@ <string name="permdesc_vibrate" msgid="2886677177257789187">"Permite que o aplicativo controle o vibrador."</string> <string name="permlab_flashlight" msgid="2155920810121984215">"controlar lanterna"</string> <string name="permdesc_flashlight" msgid="6433045942283802309">"Permite que o aplicativo controle a lanterna."</string> + <string name="permlab_accessUsb" msgid="7362327818655760496">"acessar dispositivos USB"</string> + <string name="permdesc_accessUsb" msgid="2414271762914049292">"Permitir que o aplicativo acesse dispositivos USB."</string> <string name="permlab_hardware_test" msgid="4148290860400659146">"testar hardware"</string> <string name="permdesc_hardware_test" msgid="3668894686500081699">"Permite que o aplicativo controle diversos periféricos para teste do hardware."</string> <string name="permlab_callPhone" msgid="3925836347681847954">"chamar diretamente os números de telefone"</string> @@ -573,16 +574,6 @@ <string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string> <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string> <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string> - <string name="status_bar_clear_all_button" msgid="7774721344716731603">"Limpar"</string> - <string name="status_bar_no_notifications_title" msgid="4755261167193833213">"Sem notificações"</string> - <string name="status_bar_ongoing_events_title" msgid="1682504513316879202">"Em andamento"</string> - <string name="status_bar_latest_events_title" msgid="6594767438577593172">"Notificações"</string> - <string name="battery_status_text_percent_format" msgid="7660311274698797147">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string> - <string name="battery_status_charging" msgid="756617993998772213">"Carregando..."</string> - <string name="battery_low_title" msgid="7923774589611311406">"Conecte o carregador"</string> - <string name="battery_low_subtitle" msgid="7388781709819722764">"A bateria está ficando baixa:"</string> - <string name="battery_low_percent_format" msgid="696154104579022959">"<xliff:g id="NUMBER">%d%%</xliff:g> ou menos restante(s)."</string> - <string name="battery_low_why" msgid="7279169609518386372">"Uso da bateria"</string> <string name="factorytest_failed" msgid="5410270329114212041">"Falha no teste de fábrica"</string> <string name="factorytest_not_system" msgid="4435201656767276723">"A ação FACTORY_TEST é suportada apenas para pacotes instalados em /system/app."</string> <string name="factorytest_no_action" msgid="872991874799998561">"Nenhum pacote que forneça a ação FACTORY_TEST foi encontrado."</string> @@ -708,12 +699,9 @@ <string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string> <string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string> <string name="selectAll" msgid="6876518925844129331">"Selecionar tudo"</string> - <string name="selectText" msgid="3889149123626888637">"Selecionar texto"</string> - <string name="stopSelectingText" msgid="4157931463872320996">"Parar seleção de texto"</string> + <!-- outdated translation 3889149123626888637 --> <string name="selectText" msgid="4862359311088898878">"Selecionar texto"</string> <string name="cut" msgid="3092569408438626261">"Recortar"</string> - <string name="cutAll" msgid="2436383270024931639">"Recortar tudo"</string> <string name="copy" msgid="2681946229533511987">"Copiar"</string> - <string name="copyAll" msgid="2590829068100113057">"Copiar tudo"</string> <string name="paste" msgid="5629880836805036433">"Colar"</string> <string name="copyUrl" msgid="2538211579596067402">"Copiar URL"</string> <string name="inputMethod" msgid="1653630062304567879">"Método de entrada"</string> @@ -744,6 +732,22 @@ <string name="force_close" msgid="3653416315450806396">"Forçar fechamento"</string> <string name="report" msgid="4060218260984795706">"Informar"</string> <string name="wait" msgid="7147118217226317732">"Aguardar"</string> + <!-- no translation found for launch_warning_title (8323761616052121936) --> + <skip /> + <!-- no translation found for launch_warning_replace (6202498949970281412) --> + <skip /> + <!-- no translation found for launch_warning_original (188102023021668683) --> + <skip /> + <string name="smv_application" msgid="295583804361236288">"O aplicativo <xliff:g id="APPLICATION">%1$s</xliff:g> (processo <xliff:g id="PROCESS">%2$s</xliff:g>) violou a política StrictMode imposta automaticamente."</string> + <string name="smv_process" msgid="5120397012047462446">"O processo <xliff:g id="PROCESS">%1$s</xliff:g> violou a política StrictMode imposta automaticamente."</string> + <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g> em execução"</string> + <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"Selecionar para alternar para o aplicativo"</string> + <string name="heavy_weight_switcher_title" msgid="1135403633766694316">"Alternar aplicativos?"</string> + <string name="heavy_weight_switcher_text" msgid="4592075610079319667">"Outro aplicativo já está em execução e deve ser interrompido antes que você inicie um novo aplicativo."</string> + <string name="old_app_action" msgid="493129172238566282">"Voltar para <xliff:g id="OLD_APP">%1$s</xliff:g>"</string> + <string name="old_app_description" msgid="942967900237208466">"Não inicie o novo aplicativo."</string> + <string name="new_app_action" msgid="5472756926945440706">"Iniciar <xliff:g id="OLD_APP">%1$s</xliff:g>"</string> + <string name="new_app_description" msgid="6830398339826789493">"Pare o aplicativo antigo sem salvar."</string> <string name="sendText" msgid="5132506121645618310">"Selecione uma ação para o texto"</string> <string name="volume_ringtone" msgid="6885421406845734650">"Volume da campainha"</string> <string name="volume_music" msgid="5421651157138628171">"Volume da mídia"</string> diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml index bd41d6003792..29b7e4681f3f 100644 --- a/core/res/res/values-ru/strings.xml +++ b/core/res/res/values-ru/strings.xml @@ -34,6 +34,7 @@ <string name="defaultVoiceMailAlphaTag" msgid="2660020990097733077">"Голосовая почта"</string> <string name="defaultMsisdnAlphaTag" msgid="2850889754919584674">"MSISDN1"</string> <string name="mmiError" msgid="5154499457739052907">"Неполадки подключения или неверный код MMI."</string> + <string name="mmiFdnError" msgid="5224398216385316471">"Операция возможна только для разрешенных номеров."</string> <string name="serviceEnabled" msgid="8147278346414714315">"Служба включена."</string> <string name="serviceEnabledFor" msgid="6856228140453471041">"Служба подключена для:"</string> <string name="serviceDisabled" msgid="1937553226592516411">"Служба отключена."</string> @@ -171,6 +172,8 @@ <string name="permgroupdesc_storage" msgid="9203302214915355774">"Доступ к SD-карте."</string> <string name="permlab_statusBar" msgid="7417192629601890791">"отключать или изменять строку состояния"</string> <string name="permdesc_statusBar" msgid="1365473595331989732">"Позволяет приложению отключать строку состояния или добавлять/удалять системные значки."</string> + <string name="permlab_statusBarService" msgid="7247281911387931485">"строка состояния"</string> + <string name="permdesc_statusBarService" msgid="4097605867643520920">"Позволяет приложению заменять строку состояния."</string> <string name="permlab_expandStatusBar" msgid="1148198785937489264">"разворачивать/сворачивать строку состояния"</string> <string name="permdesc_expandStatusBar" msgid="7088604400110768665">"Позволяет приложению разворачивать или сворачивать строку состояния."</string> <string name="permlab_processOutgoingCalls" msgid="1136262550878335980">"перехватывать исходящие вызовы"</string> @@ -285,10 +288,6 @@ <string name="permdesc_readContacts" msgid="3371591512896545975">"Позволяет приложению считывать все данные контактов (адресов), сохраненные в памяти телефона. Вредоносные приложения могут использовать эту возможность для передачи данных посторонним лицам."</string> <string name="permlab_writeContacts" msgid="644616215860933284">"перезаписывать данные контакта"</string> <string name="permdesc_writeContacts" msgid="3924383579108183601">"Позволяет приложению изменять данные (адрес) контакта, сохраненные в памяти телефона. Вредоносные приложения могут использовать эту возможность для удаления или изменения данных контакта."</string> - <string name="permlab_writeOwnerData" msgid="4892555913849295393">"перезаписывать данные о владельце"</string> - <string name="permdesc_writeOwnerData" msgid="2344055317969787124">"Позволяет приложению изменять сведения о владельце, сохраненные на телефоне. Вредоносные приложения могут использовать эту возможность для удаления или изменения данных владельца."</string> - <string name="permlab_readOwnerData" msgid="6668525984731523563">"считывать данные о владельце"</string> - <string name="permdesc_readOwnerData" msgid="3088486383128434507">"Позволяет приложению считывать сведения о владельце, сохраненные в памяти телефона. Вредоносные приложения могут использовать эту возможность для считывания данных владельца."</string> <string name="permlab_readCalendar" msgid="6898987798303840534">"считывать мероприятия в календаре"</string> <string name="permdesc_readCalendar" msgid="5533029139652095734">"Позволяет приложению считывать все события календаря, сохраненные на телефоне. Вредоносные приложения могут использовать эту возможность для передачи ваших событий календаря посторонним лицам."</string> <string name="permlab_writeCalendar" msgid="3894879352594904361">"добавлять и изменять мероприятия в календаре и отправлять письма гостям"</string> @@ -311,8 +310,8 @@ <string name="permdesc_modifyAudioSettings" msgid="5793461287365991922">"Позволяет приложению изменять глобальные аудионастройки, такие как громкость и маршрутизацию."</string> <string name="permlab_recordAudio" msgid="3876049771427466323">"записывать аудио"</string> <string name="permdesc_recordAudio" msgid="6493228261176552356">"Позволяет приложению получать доступ к пути аудиозаписи."</string> - <string name="permlab_camera" msgid="8059288807274039014">"снимать фотографии"</string> - <string name="permdesc_camera" msgid="9013476258810982546">"Позволяет приложению делать снимки с помощью камеры. Это разрешение позволяет приложению в любое время собирать изображения, видимые через объектив камеры."</string> + <string name="permlab_camera" msgid="3616391919559751192">"снимать фото и видео"</string> + <string name="permdesc_camera" msgid="6004878235852154239">"Позволяет приложению делать снимки и видео с помощью камеры в любое время."</string> <string name="permlab_brick" msgid="8337817093326370537">"отключать телефон"</string> <string name="permdesc_brick" msgid="5569526552607599221">"Позволяет данному приложению отключить телефон навсегда. Это очень опасно."</string> <string name="permlab_reboot" msgid="2898560872462638242">"принудительно перезагружать телефон"</string> @@ -335,6 +334,8 @@ <string name="permdesc_vibrate" msgid="2886677177257789187">"Позволяет приложению управлять виброзвонком."</string> <string name="permlab_flashlight" msgid="2155920810121984215">"управлять вспышкой"</string> <string name="permdesc_flashlight" msgid="6433045942283802309">"Позволяет приложению управлять вспышкой."</string> + <string name="permlab_accessUsb" msgid="7362327818655760496">"доступ к USB-устройствам"</string> + <string name="permdesc_accessUsb" msgid="2414271762914049292">"Позволяет приложению получать доступ к USB-устройствам."</string> <string name="permlab_hardware_test" msgid="4148290860400659146">"проверять аппаратное обеспечение"</string> <string name="permdesc_hardware_test" msgid="3668894686500081699">"Позволяет приложению управлять различными периферийными устройствами для проверки аппаратного обеспечения."</string> <string name="permlab_callPhone" msgid="3925836347681847954">"посылать прямые вызовы на номера телефонов"</string> @@ -573,16 +574,6 @@ <string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string> <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string> <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string> - <string name="status_bar_clear_all_button" msgid="7774721344716731603">"Очистить"</string> - <string name="status_bar_no_notifications_title" msgid="4755261167193833213">"Нет уведомлений"</string> - <string name="status_bar_ongoing_events_title" msgid="1682504513316879202">"Текущие"</string> - <string name="status_bar_latest_events_title" msgid="6594767438577593172">"Уведомления"</string> - <string name="battery_status_text_percent_format" msgid="7660311274698797147">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string> - <string name="battery_status_charging" msgid="756617993998772213">"Идет зарядка..."</string> - <string name="battery_low_title" msgid="7923774589611311406">"Подключите зарядное устройство"</string> - <string name="battery_low_subtitle" msgid="7388781709819722764">"Батарея разряжена:"</string> - <string name="battery_low_percent_format" msgid="696154104579022959">"Осталось <xliff:g id="NUMBER">%d%%</xliff:g> или меньше."</string> - <string name="battery_low_why" msgid="7279169609518386372">"Расход заряда батареи"</string> <string name="factorytest_failed" msgid="5410270329114212041">"Не удалось провести стандартный тест"</string> <string name="factorytest_not_system" msgid="4435201656767276723">"Действие FACTORY_TEST поддерживается только для пакетов, установленных в /system/app."</string> <string name="factorytest_no_action" msgid="872991874799998561">"Пакет, обеспечивающий действие FACTORY_TEST, не найден."</string> @@ -708,12 +699,9 @@ <string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string> <string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string> <string name="selectAll" msgid="6876518925844129331">"Выбрать все"</string> - <string name="selectText" msgid="3889149123626888637">"Выбрать текст"</string> - <string name="stopSelectingText" msgid="4157931463872320996">"Остановить выделение текста"</string> + <!-- outdated translation 3889149123626888637 --> <string name="selectText" msgid="4862359311088898878">"Выбрать текст"</string> <string name="cut" msgid="3092569408438626261">"Вырезать"</string> - <string name="cutAll" msgid="2436383270024931639">"Вырезать все"</string> <string name="copy" msgid="2681946229533511987">"Копировать"</string> - <string name="copyAll" msgid="2590829068100113057">"Копировать все"</string> <string name="paste" msgid="5629880836805036433">"Вставить"</string> <string name="copyUrl" msgid="2538211579596067402">"Копировать URL"</string> <string name="inputMethod" msgid="1653630062304567879">"Способ ввода"</string> @@ -728,8 +716,8 @@ <string name="dialog_alert_title" msgid="2049658708609043103">"Внимание"</string> <string name="capital_on" msgid="1544682755514494298">"ВКЛ"</string> <string name="capital_off" msgid="6815870386972805832">"ВЫКЛ"</string> - <string name="whichApplication" msgid="4533185947064773386">"Завершить действие с помощью"</string> - <string name="alwaysUse" msgid="4583018368000610438">"Использовать по умолчанию для этого действия."</string> + <string name="whichApplication" msgid="4533185947064773386">"Что использовать?"</string> + <string name="alwaysUse" msgid="4583018368000610438">"По умолчанию для этого действия"</string> <string name="clearDefaultHintMsg" msgid="4815455344600932173">"Удалить настройки по умолчанию: главный экран > \"Настройки\" > \"Приложения\" > \"Управление приложениями\"."</string> <string name="chooseActivity" msgid="1009246475582238425">"Выберите действие"</string> <string name="noApplications" msgid="1691104391758345586">"Это действие не может выполнять ни одно приложение."</string> @@ -744,6 +732,22 @@ <string name="force_close" msgid="3653416315450806396">"Закрыть"</string> <string name="report" msgid="4060218260984795706">"Отчет"</string> <string name="wait" msgid="7147118217226317732">"Подождать"</string> + <!-- no translation found for launch_warning_title (8323761616052121936) --> + <skip /> + <!-- no translation found for launch_warning_replace (6202498949970281412) --> + <skip /> + <!-- no translation found for launch_warning_original (188102023021668683) --> + <skip /> + <string name="smv_application" msgid="295583804361236288">"Приложение <xliff:g id="APPLICATION">%1$s</xliff:g> (процесс <xliff:g id="PROCESS">%2$s</xliff:g>) нарушило собственную политику StrictMode."</string> + <string name="smv_process" msgid="5120397012047462446">"Процесс <xliff:g id="PROCESS">%1$s</xliff:g> нарушил собственную политику StrictMode."</string> + <string name="heavy_weight_notification" msgid="9087063985776626166">"Приложение <xliff:g id="APP">%1$s</xliff:g> запущено"</string> + <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"Нажмите, чтобы перейти к приложению"</string> + <string name="heavy_weight_switcher_title" msgid="1135403633766694316">"Переключить приложения?"</string> + <string name="heavy_weight_switcher_text" msgid="4592075610079319667">"Выполняется другое приложение, которое должно быть остановлено прежде, чем запускать новое."</string> + <string name="old_app_action" msgid="493129172238566282">"Вернуться к приложению <xliff:g id="OLD_APP">%1$s</xliff:g>"</string> + <string name="old_app_description" msgid="942967900237208466">"Не запускать новое приложение."</string> + <string name="new_app_action" msgid="5472756926945440706">"Запустить приложение <xliff:g id="OLD_APP">%1$s</xliff:g>"</string> + <string name="new_app_description" msgid="6830398339826789493">"Остановить старое приложение без сохранения изменений."</string> <string name="sendText" msgid="5132506121645618310">"Выберите действие для текста"</string> <string name="volume_ringtone" msgid="6885421406845734650">"Громкость звонка"</string> <string name="volume_music" msgid="5421651157138628171">"Громкость мультимедиа"</string> @@ -779,11 +783,11 @@ <string name="perms_hide" msgid="7283915391320676226"><b>"Скрыть"</b></string> <string name="perms_show_all" msgid="2671791163933091180"><b>"Показать все"</b></string> <string name="usb_storage_activity_title" msgid="2399289999608900443">"Запоминающее устройство USB"</string> - <string name="usb_storage_title" msgid="5901459041398751495">"устройство USB подключено"</string> + <string name="usb_storage_title" msgid="5901459041398751495">"USB-подключение установлено"</string> <string name="usb_storage_message" msgid="4796759646167247178">"Телефон подключен к компьютеру через порт USB. Нажмите кнопку ниже, если необходимо копировать файлы с компьютера на SD-карту устройства Android (или наоборот)."</string> <string name="usb_storage_button_mount" msgid="1052259930369508235">"Включить USB-накопитель"</string> <string name="usb_storage_error_message" msgid="2534784751603345363">"При использовании SD-карты как USB-накопителя возникла неполадка."</string> - <string name="usb_storage_notification_title" msgid="8175892554757216525">"устройство USB подключено"</string> + <string name="usb_storage_notification_title" msgid="8175892554757216525">"USB-подключение установлено"</string> <string name="usb_storage_notification_message" msgid="7380082404288219341">"Выберите копирование файлов на компьютер или с компьютера."</string> <string name="usb_storage_stop_notification_title" msgid="2336058396663516017">"Выключить USB-накопитель"</string> <string name="usb_storage_stop_notification_message" msgid="2591813490269841539">"Выберите, чтобы выключить USB-накопитель."</string> diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml index b7cbc522799c..b53f21970401 100644 --- a/core/res/res/values-sv/strings.xml +++ b/core/res/res/values-sv/strings.xml @@ -34,6 +34,7 @@ <string name="defaultVoiceMailAlphaTag" msgid="2660020990097733077">"Röstbrevlåda"</string> <string name="defaultMsisdnAlphaTag" msgid="2850889754919584674">"MSISDN1"</string> <string name="mmiError" msgid="5154499457739052907">"Anslutningsproblem eller ogiltig MMI-kod."</string> + <string name="mmiFdnError" msgid="5224398216385316471">"Endast fasta nummer kan användas."</string> <string name="serviceEnabled" msgid="8147278346414714315">"Tjänsten har aktiverats."</string> <string name="serviceEnabledFor" msgid="6856228140453471041">"Tjänsten har aktiverats för:"</string> <string name="serviceDisabled" msgid="1937553226592516411">"Tjänsten har inaktiverats."</string> @@ -171,6 +172,8 @@ <string name="permgroupdesc_storage" msgid="9203302214915355774">"Få åtkomst till SD-kortet."</string> <string name="permlab_statusBar" msgid="7417192629601890791">"inaktivera eller ändra statusfält"</string> <string name="permdesc_statusBar" msgid="1365473595331989732">"Tillåter att programmet inaktiverar statusfältet eller lägger till och tar bort systemikoner."</string> + <string name="permlab_statusBarService" msgid="7247281911387931485">"statusfält"</string> + <string name="permdesc_statusBarService" msgid="4097605867643520920">"Tillåter att programmet visas i statusfältet."</string> <string name="permlab_expandStatusBar" msgid="1148198785937489264">"expandera/komprimera statusfält"</string> <string name="permdesc_expandStatusBar" msgid="7088604400110768665">"Tillåter att program expanderar eller komprimerar statusfältet."</string> <string name="permlab_processOutgoingCalls" msgid="1136262550878335980">"spärra utgående samtal"</string> @@ -285,10 +288,6 @@ <string name="permdesc_readContacts" msgid="3371591512896545975">"Tillåter att ett program läser alla kontaktuppgifter (adresser) som har lagrats på din telefon. Skadliga program kan använda detta för att skicka dina data till andra personer."</string> <string name="permlab_writeContacts" msgid="644616215860933284">"skriva kontaktuppgifter"</string> <string name="permdesc_writeContacts" msgid="3924383579108183601">"Tillåter att ett program ändrar kontaktuppgifter (adress) som har lagrats på din telefon. Skadliga program kan använda detta för att radera eller ändra kontaktuppgifter."</string> - <string name="permlab_writeOwnerData" msgid="4892555913849295393">"skriva ägarinformation"</string> - <string name="permdesc_writeOwnerData" msgid="2344055317969787124">"Tillåter att ett program ändrar information om telefonens ägare som har lagrats på din telefon. Skadliga program kan använda detta för att radera eller ändra ägaruppgifter."</string> - <string name="permlab_readOwnerData" msgid="6668525984731523563">"läsa information om ägare"</string> - <string name="permdesc_readOwnerData" msgid="3088486383128434507">"Tillåter att ett program läser information om telefonens ägare som har lagrats på telefonen. Skadliga program kan använda detta för att läsa telefonens ägaruppgifter."</string> <string name="permlab_readCalendar" msgid="6898987798303840534">"läsa kalenderhändelser"</string> <string name="permdesc_readCalendar" msgid="5533029139652095734">"Tillåter att ett program läser alla händelser i kalendern som har lagrats på din telefon. Skadliga program kan använda detta för att skicka din kalender till andra personer."</string> <string name="permlab_writeCalendar" msgid="3894879352594904361">"lägg till och ändra kalenderhändelser och skicka e-post till gäster"</string> @@ -311,8 +310,8 @@ <string name="permdesc_modifyAudioSettings" msgid="5793461287365991922">"Tillåter att ett program ändrar globala ljudinställningar, till exempel volym och routning."</string> <string name="permlab_recordAudio" msgid="3876049771427466323">"spela in ljud"</string> <string name="permdesc_recordAudio" msgid="6493228261176552356">"Tillåter att program får åtkomst till sökvägen för ljudinspelning."</string> - <string name="permlab_camera" msgid="8059288807274039014">"ta bilder"</string> - <string name="permdesc_camera" msgid="9013476258810982546">"Tillåter att program tar kort med kameran. Då kan programmet när som helst samla bilderna som visas i kameran."</string> + <string name="permlab_camera" msgid="3616391919559751192">"ta bilder och spela in videoklipp"</string> + <string name="permdesc_camera" msgid="6004878235852154239">"Tillåter att program tar kort och spelar in video med kameran. Då kan programmet när som helst fotografera eller spela in det som visas i kameran."</string> <string name="permlab_brick" msgid="8337817093326370537">"inaktivera telefonen permanent"</string> <string name="permdesc_brick" msgid="5569526552607599221">"Tillåter att programmet inaktiverar hela telefonen permanent. Detta är mycket farligt."</string> <string name="permlab_reboot" msgid="2898560872462638242">"tvinga omstart av telefon"</string> @@ -335,6 +334,8 @@ <string name="permdesc_vibrate" msgid="2886677177257789187">"Tillåter att programmet styr vibratorn."</string> <string name="permlab_flashlight" msgid="2155920810121984215">"styra lampa"</string> <string name="permdesc_flashlight" msgid="6433045942283802309">"Tillåter att programmet styr lampan."</string> + <string name="permlab_accessUsb" msgid="7362327818655760496">"åtkomst till USB-enheter"</string> + <string name="permdesc_accessUsb" msgid="2414271762914049292">"Tillåter att programmet använder USB-enheter."</string> <string name="permlab_hardware_test" msgid="4148290860400659146">"testa maskinvara"</string> <string name="permdesc_hardware_test" msgid="3668894686500081699">"Tillåter att ett program styr kringutrustning i syfte att testa maskinvara."</string> <string name="permlab_callPhone" msgid="3925836347681847954">"ringa telefonnummer direkt"</string> @@ -573,16 +574,6 @@ <string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string> <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string> <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string> - <string name="status_bar_clear_all_button" msgid="7774721344716731603">"Ta bort"</string> - <string name="status_bar_no_notifications_title" msgid="4755261167193833213">"Inga aviseringar"</string> - <string name="status_bar_ongoing_events_title" msgid="1682504513316879202">"Pågående"</string> - <string name="status_bar_latest_events_title" msgid="6594767438577593172">"Meddelanden"</string> - <string name="battery_status_text_percent_format" msgid="7660311274698797147">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string> - <string name="battery_status_charging" msgid="756617993998772213">"Laddar…"</string> - <string name="battery_low_title" msgid="7923774589611311406">"Anslut laddaren"</string> - <string name="battery_low_subtitle" msgid="7388781709819722764">"Batteriet håller på att ta slut:"</string> - <string name="battery_low_percent_format" msgid="696154104579022959">"<xliff:g id="NUMBER">%d%%</xliff:g> eller mindre kvar."</string> - <string name="battery_low_why" msgid="7279169609518386372">"Batteriförbrukning"</string> <string name="factorytest_failed" msgid="5410270329114212041">"Det gick fel vid fabrikstestet"</string> <string name="factorytest_not_system" msgid="4435201656767276723">"Åtgärden FACTORY_TEST stöds endast för paket som har installerats i /system/app."</string> <string name="factorytest_no_action" msgid="872991874799998561">"Vi hittade inget paket som erbjuder åtgärden FACTORY_TEST."</string> @@ -708,12 +699,9 @@ <string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string> <string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string> <string name="selectAll" msgid="6876518925844129331">"Välj alla"</string> - <string name="selectText" msgid="3889149123626888637">"Markera text"</string> - <string name="stopSelectingText" msgid="4157931463872320996">"Sluta välja text"</string> + <!-- outdated translation 3889149123626888637 --> <string name="selectText" msgid="4862359311088898878">"Markera text"</string> <string name="cut" msgid="3092569408438626261">"Klipp ut"</string> - <string name="cutAll" msgid="2436383270024931639">"Klipp ut alla"</string> <string name="copy" msgid="2681946229533511987">"Kopiera"</string> - <string name="copyAll" msgid="2590829068100113057">"Kopiera alla"</string> <string name="paste" msgid="5629880836805036433">"Klistra in"</string> <string name="copyUrl" msgid="2538211579596067402">"Kopiera webbadress"</string> <string name="inputMethod" msgid="1653630062304567879">"Indatametod"</string> @@ -744,6 +732,22 @@ <string name="force_close" msgid="3653416315450806396">"Tvinga fram en stängning"</string> <string name="report" msgid="4060218260984795706">"Rapportera"</string> <string name="wait" msgid="7147118217226317732">"Vänta"</string> + <!-- no translation found for launch_warning_title (8323761616052121936) --> + <skip /> + <!-- no translation found for launch_warning_replace (6202498949970281412) --> + <skip /> + <!-- no translation found for launch_warning_original (188102023021668683) --> + <skip /> + <string name="smv_application" msgid="295583804361236288">"Programmet <xliff:g id="APPLICATION">%1$s</xliff:g> (processen <xliff:g id="PROCESS">%2$s</xliff:g>) har brutit mot sin egen StrictMode-policy."</string> + <string name="smv_process" msgid="5120397012047462446">"Processen <xliff:g id="PROCESS">%1$s</xliff:g> har brutit mot sin egen StrictMode-policy."</string> + <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g> körs"</string> + <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"Välj om du vill växla till programmet"</string> + <string name="heavy_weight_switcher_title" msgid="1135403633766694316">"Vill du byta program?"</string> + <string name="heavy_weight_switcher_text" msgid="4592075610079319667">"Ett annat program som körs måste avslutas innan du kan starta ett nytt."</string> + <string name="old_app_action" msgid="493129172238566282">"Gå tillbaka till <xliff:g id="OLD_APP">%1$s</xliff:g>"</string> + <string name="old_app_description" msgid="942967900237208466">"Starta inte det nya programmet."</string> + <string name="new_app_action" msgid="5472756926945440706">"Starta <xliff:g id="OLD_APP">%1$s</xliff:g>"</string> + <string name="new_app_description" msgid="6830398339826789493">"Avbryt det gamla programmet utan att spara."</string> <string name="sendText" msgid="5132506121645618310">"Välj en åtgärd för text"</string> <string name="volume_ringtone" msgid="6885421406845734650">"Ringvolym"</string> <string name="volume_music" msgid="5421651157138628171">"Mediavolym"</string> diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml index cb1e39900726..ea46db0ed1fd 100644 --- a/core/res/res/values-tr/strings.xml +++ b/core/res/res/values-tr/strings.xml @@ -34,6 +34,7 @@ <string name="defaultVoiceMailAlphaTag" msgid="2660020990097733077">"Sesli Mesaj"</string> <string name="defaultMsisdnAlphaTag" msgid="2850889754919584674">"MSISDN1"</string> <string name="mmiError" msgid="5154499457739052907">"Bağlantı sorunu veya geçersiz MMI kodu."</string> + <string name="mmiFdnError" msgid="5224398216385316471">"İşlem sadece sabit arama numaralarıyla sınırlandırılmıştır."</string> <string name="serviceEnabled" msgid="8147278346414714315">"Hizmet etkindi."</string> <string name="serviceEnabledFor" msgid="6856228140453471041">"Hizmet şunun için etkinleştirildi:"</string> <string name="serviceDisabled" msgid="1937553226592516411">"Hizmet devre dışı bırakıldı."</string> @@ -171,6 +172,8 @@ <string name="permgroupdesc_storage" msgid="9203302214915355774">"SD karta erişin."</string> <string name="permlab_statusBar" msgid="7417192629601890791">"durum çubuğunu devre dışı bırak veya değiştir"</string> <string name="permdesc_statusBar" msgid="1365473595331989732">"Uygulamanın durum çubuğunu devre dışı bırakmasına veya sistem simgeleri ekleyip kaldırmasına izin verir."</string> + <string name="permlab_statusBarService" msgid="7247281911387931485">"durum çubuğu"</string> + <string name="permdesc_statusBarService" msgid="4097605867643520920">"Uygulamanın durum çubuğu olmasına izin verir."</string> <string name="permlab_expandStatusBar" msgid="1148198785937489264">"durum çubuğunu genişlet/daralt"</string> <string name="permdesc_expandStatusBar" msgid="7088604400110768665">"Uygulamanın, durum çubuğunu genişletip daraltmasına izin verir."</string> <string name="permlab_processOutgoingCalls" msgid="1136262550878335980">"giden aramalarda araya gir"</string> @@ -285,10 +288,6 @@ <string name="permdesc_readContacts" msgid="3371591512896545975">"Uygulamaların telefonunuzda depolanan tüm kişi (adres) verilerini okumasına izin verir. Kötü amaçlı uygulamalar bu işlevi verilerinizi başkalarına göndermek için kullanabilir."</string> <string name="permlab_writeContacts" msgid="644616215860933284">"kişi verileri yaz"</string> <string name="permdesc_writeContacts" msgid="3924383579108183601">"Uygulamaların telefonunuzda depolanan kişi (adres) verilerini değiştirmesine izin verir. Kötü amaçlı uygulamalar bu işlevi kişi verilerinizi silmek veya değiştirmek için kullanabilir."</string> - <string name="permlab_writeOwnerData" msgid="4892555913849295393">"sahip verilerini yaz"</string> - <string name="permdesc_writeOwnerData" msgid="2344055317969787124">"Uygulamaların telefonunuzda depolanan telefon sahibi verilerini değiştirmesine izin verir. Kötü amaçlı uygulamalar bu işlevi kullanıcı verilerini silmek veya değiştirmek için kullanabilir."</string> - <string name="permlab_readOwnerData" msgid="6668525984731523563">"sahip verilerini oku"</string> - <string name="permdesc_readOwnerData" msgid="3088486383128434507">"Uygulamaların telefonunuzda depolanan telefon sahibi verilerini okumasına izin verir. Kötü amaçlı uygulamalar bunu telefon sahibi verilerini okumak için kullanabilir."</string> <string name="permlab_readCalendar" msgid="6898987798303840534">"takvim etkinliklerini oku"</string> <string name="permdesc_readCalendar" msgid="5533029139652095734">"Uygulamaların telefonunuzda depolanan takvim etkinliklerinin tümünü okumasına izin verir. Kötü amaçlı uygulamalar bunu, takvim etkinliklerinizi başkalarına göndermek için kullanabilir."</string> <string name="permlab_writeCalendar" msgid="3894879352594904361">"takvim etkinlikleri ekle veya değiştir ve konuklara e-posta gönder"</string> @@ -311,8 +310,8 @@ <string name="permdesc_modifyAudioSettings" msgid="5793461287365991922">"Uygulamaların, ses düzeyi ve yönlendirme gibi genel ses ayarlarını değiştirmesine izin verir."</string> <string name="permlab_recordAudio" msgid="3876049771427466323">"ses kaydet"</string> <string name="permdesc_recordAudio" msgid="6493228261176552356">"Uygulamanın, ses kayıt yoluna erişmesine izin verir."</string> - <string name="permlab_camera" msgid="8059288807274039014">"resim çek"</string> - <string name="permdesc_camera" msgid="9013476258810982546">"Uygulamaların kamera ile resim çekmesine izin verir. Bu işlev herhangi bir zamanda kameranın görmekte olduğu görüntüyü uygulamaların toplamasına izin verir."</string> + <string name="permlab_camera" msgid="3616391919559751192">"resim çekme ve görüntü kaydetme"</string> + <string name="permdesc_camera" msgid="6004878235852154239">"Uygulamanın kamera ile resim çekmesine ve görüntü almasına izin verir. Bu işlev herhangi bir zamanda uygulamanın kamera görüntülerini yakalamasına olanak tanır."</string> <string name="permlab_brick" msgid="8337817093326370537">"telefonu tamamen devre dışı bırak"</string> <string name="permdesc_brick" msgid="5569526552607599221">"Uygulamaların tüm telefonu kalıcı olarak devre dışı bırakmasına izin verir. Bu çok tehlikelidir."</string> <string name="permlab_reboot" msgid="2898560872462638242">"telefonu yeniden başlamaya zorla"</string> @@ -335,6 +334,8 @@ <string name="permdesc_vibrate" msgid="2886677177257789187">"Uygulamanın titreşimi denetlemesine izin verir."</string> <string name="permlab_flashlight" msgid="2155920810121984215">"flaşı denetle"</string> <string name="permdesc_flashlight" msgid="6433045942283802309">"Uygulamaların flaş ışığını denetlemesine izin verir."</string> + <string name="permlab_accessUsb" msgid="7362327818655760496">"USB cihazlarına erişme"</string> + <string name="permdesc_accessUsb" msgid="2414271762914049292">"Uygulamaların USB cihazlarına erişimine izin verir"</string> <string name="permlab_hardware_test" msgid="4148290860400659146">"donanımı test et"</string> <string name="permdesc_hardware_test" msgid="3668894686500081699">"Uygulamanın donanım testi için çeşitli çevre birimlerini denetlemesine izin verir."</string> <string name="permlab_callPhone" msgid="3925836347681847954">"telefon numaralarına doğrudan çağrı yap"</string> @@ -573,16 +574,6 @@ <string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string> <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g> <xliff:g id="AMPM">%P</xliff:g>"</string> <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g> <xliff:g id="AMPM">%p</xliff:g>"</string> - <string name="status_bar_clear_all_button" msgid="7774721344716731603">"Temizle"</string> - <string name="status_bar_no_notifications_title" msgid="4755261167193833213">"Bildirim yok"</string> - <string name="status_bar_ongoing_events_title" msgid="1682504513316879202">"Sürüyor"</string> - <string name="status_bar_latest_events_title" msgid="6594767438577593172">"Bildirimler"</string> - <string name="battery_status_text_percent_format" msgid="7660311274698797147">"<xliff:g id="NUMBER">%d</xliff:g> <xliff:g id="PERCENT">%%</xliff:g>"</string> - <string name="battery_status_charging" msgid="756617993998772213">"Şarj oluyor…"</string> - <string name="battery_low_title" msgid="7923774589611311406">"Lütfen şarj cihazını takın"</string> - <string name="battery_low_subtitle" msgid="7388781709819722764">"Pil tükeniyor:"</string> - <string name="battery_low_percent_format" msgid="696154104579022959">"<xliff:g id="NUMBER">%d%%</xliff:g> veya daha az kaldı."</string> - <string name="battery_low_why" msgid="7279169609518386372">"Pil kullanımı"</string> <string name="factorytest_failed" msgid="5410270329114212041">"Fabrika testi yapılamadı"</string> <string name="factorytest_not_system" msgid="4435201656767276723">"FACTORY_TEST işlemi yalnızca /system/app dizinine yüklenmiş paketler için desteklenir."</string> <string name="factorytest_no_action" msgid="872991874799998561">"FACTORY_TEST işlemini sağlayan hiçbir paket bulunamadı."</string> @@ -708,12 +699,9 @@ <string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string> <string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string> <string name="selectAll" msgid="6876518925844129331">"Tümünü seç"</string> - <string name="selectText" msgid="3889149123626888637">"Metin seç"</string> - <string name="stopSelectingText" msgid="4157931463872320996">"Metin seçmeyi durdur"</string> + <!-- outdated translation 3889149123626888637 --> <string name="selectText" msgid="4862359311088898878">"Metin seç"</string> <string name="cut" msgid="3092569408438626261">"Kes"</string> - <string name="cutAll" msgid="2436383270024931639">"Tümünü kes"</string> <string name="copy" msgid="2681946229533511987">"Kopyala"</string> - <string name="copyAll" msgid="2590829068100113057">"Tümünü kopyala"</string> <string name="paste" msgid="5629880836805036433">"Yapıştır"</string> <string name="copyUrl" msgid="2538211579596067402">"URL\'yi kopyala"</string> <string name="inputMethod" msgid="1653630062304567879">"Giriş yöntemi"</string> @@ -744,6 +732,22 @@ <string name="force_close" msgid="3653416315450806396">"Kapanmaya zorla"</string> <string name="report" msgid="4060218260984795706">"Rapor"</string> <string name="wait" msgid="7147118217226317732">"Bekle"</string> + <!-- no translation found for launch_warning_title (8323761616052121936) --> + <skip /> + <!-- no translation found for launch_warning_replace (6202498949970281412) --> + <skip /> + <!-- no translation found for launch_warning_original (188102023021668683) --> + <skip /> + <string name="smv_application" msgid="295583804361236288">"<xliff:g id="APPLICATION">%1$s</xliff:g> uygulaması (<xliff:g id="PROCESS">%2$s</xliff:g> işlemi) kendiliğinden uyguladığı StrictMode politikasını ihlal etti."</string> + <string name="smv_process" msgid="5120397012047462446">"<xliff:g id="PROCESS">%1$s</xliff:g> işlemi kendiliğinden uyguladığı StrictMode politikasını ihlal etti."</string> + <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g> çalışıyor"</string> + <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"Uygulama değiştirmeyi seçin"</string> + <string name="heavy_weight_switcher_title" msgid="1135403633766694316">"Uygulamaların arasında geçiş yapılsın mı?"</string> + <string name="heavy_weight_switcher_text" msgid="4592075610079319667">"Başka bir uygulama zaten çalışıyor. Yeni bir uygulama başlatmadan bu uygulama durdurulmalıdır."</string> + <string name="old_app_action" msgid="493129172238566282">"<xliff:g id="OLD_APP">%1$s</xliff:g> öğesine geri dön"</string> + <string name="old_app_description" msgid="942967900237208466">"Yeni uygulamayı başlatmayın."</string> + <string name="new_app_action" msgid="5472756926945440706">"<xliff:g id="OLD_APP">%1$s</xliff:g> uygulamasını başlat"</string> + <string name="new_app_description" msgid="6830398339826789493">"Eski uygulamayı kaydetmeden durdurun."</string> <string name="sendText" msgid="5132506121645618310">"Metin için bir işlem seçin"</string> <string name="volume_ringtone" msgid="6885421406845734650">"Zil sesi düzeyi"</string> <string name="volume_music" msgid="5421651157138628171">"Medya ses düzeyi"</string> diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml index 834a84ca1bc3..b9636128ed05 100644 --- a/core/res/res/values-zh-rCN/strings.xml +++ b/core/res/res/values-zh-rCN/strings.xml @@ -34,6 +34,7 @@ <string name="defaultVoiceMailAlphaTag" msgid="2660020990097733077">"语音信箱"</string> <string name="defaultMsisdnAlphaTag" msgid="2850889754919584674">"MSISDN1"</string> <string name="mmiError" msgid="5154499457739052907">"出现连接问题或 MMI 码无效。"</string> + <string name="mmiFdnError" msgid="5224398216385316471">"只能对固定拨号号码执行此类操作。"</string> <string name="serviceEnabled" msgid="8147278346414714315">"已启用服务。"</string> <string name="serviceEnabledFor" msgid="6856228140453471041">"已针对以下内容启用了服务:"</string> <string name="serviceDisabled" msgid="1937553226592516411">"已停用服务。"</string> @@ -171,6 +172,8 @@ <string name="permgroupdesc_storage" msgid="9203302214915355774">"访问 SD 卡。"</string> <string name="permlab_statusBar" msgid="7417192629601890791">"停用或修改状态栏"</string> <string name="permdesc_statusBar" msgid="1365473595331989732">"允许应用程序停用状态栏或者增删系统图标。"</string> + <string name="permlab_statusBarService" msgid="7247281911387931485">"状态栏"</string> + <string name="permdesc_statusBarService" msgid="4097605867643520920">"允许以状态栏形式显示应用程序。"</string> <string name="permlab_expandStatusBar" msgid="1148198785937489264">"展开/收拢状态栏"</string> <string name="permdesc_expandStatusBar" msgid="7088604400110768665">"允许应用程序展开或收拢状态栏。"</string> <string name="permlab_processOutgoingCalls" msgid="1136262550878335980">"拦截外拨电话"</string> @@ -285,10 +288,6 @@ <string name="permdesc_readContacts" msgid="3371591512896545975">"允许应用程序读取您手机上存储的所有联系人(地址)数据。恶意应用程序可借此将您的数据发送给其他人。"</string> <string name="permlab_writeContacts" msgid="644616215860933284">"写入联系数据"</string> <string name="permdesc_writeContacts" msgid="3924383579108183601">"允许应用程序修改您手机上存储的联系人(地址)数据。恶意应用程序可借此清除或修改您的联系人数据。"</string> - <string name="permlab_writeOwnerData" msgid="4892555913849295393">"写入所有者数据"</string> - <string name="permdesc_writeOwnerData" msgid="2344055317969787124">"允许应用程序修改您手机上存储的手机所有者数据。恶意应用程序可借此清除或修改所有者数据。"</string> - <string name="permlab_readOwnerData" msgid="6668525984731523563">"读取所有者数据"</string> - <string name="permdesc_readOwnerData" msgid="3088486383128434507">"允许应用程序读取您手机上存储的手机所有者数据。恶意应用程序可借此读取手机所有者数据。"</string> <string name="permlab_readCalendar" msgid="6898987798303840534">"读取日历活动"</string> <string name="permdesc_readCalendar" msgid="5533029139652095734">"允许应用程序读取您手机上存储的所有日历活动。恶意应用程序可借此将您的日历活动发送给其他人。"</string> <string name="permlab_writeCalendar" msgid="3894879352594904361">"添加或修改日历活动以及向邀请对象发送电子邮件"</string> @@ -311,8 +310,8 @@ <string name="permdesc_modifyAudioSettings" msgid="5793461287365991922">"允许应用程序修改整个系统的音频设置,如音量和路由。"</string> <string name="permlab_recordAudio" msgid="3876049771427466323">"录音"</string> <string name="permdesc_recordAudio" msgid="6493228261176552356">"允许应用程序访问录音路径。"</string> - <string name="permlab_camera" msgid="8059288807274039014">"拍照"</string> - <string name="permdesc_camera" msgid="9013476258810982546">"允许应用程序使用相机拍照,这样应用程序可随时收集进入相机镜头的图像。"</string> + <string name="permlab_camera" msgid="3616391919559751192">"拍摄照片和视频"</string> + <string name="permdesc_camera" msgid="6004878235852154239">"允许应用程序使用相机拍摄照片和视频,这样应用程序可随时收集进入相机镜头中的图片。"</string> <string name="permlab_brick" msgid="8337817093326370537">"永久停用手机"</string> <string name="permdesc_brick" msgid="5569526552607599221">"允许应用程序永久停用整个手机,这非常危险。"</string> <string name="permlab_reboot" msgid="2898560872462638242">"强行重新启动手机"</string> @@ -335,6 +334,8 @@ <string name="permdesc_vibrate" msgid="2886677177257789187">"允许应用程序控制振动器。"</string> <string name="permlab_flashlight" msgid="2155920810121984215">"控制闪光灯"</string> <string name="permdesc_flashlight" msgid="6433045942283802309">"允许应用程序控制闪光灯。"</string> + <string name="permlab_accessUsb" msgid="7362327818655760496">"访问 USB 设备"</string> + <string name="permdesc_accessUsb" msgid="2414271762914049292">"允许应用程序访问 USB 设备。"</string> <string name="permlab_hardware_test" msgid="4148290860400659146">"测试硬件"</string> <string name="permdesc_hardware_test" msgid="3668894686500081699">"允许应用程序控制各外围设备以进行硬件测试。"</string> <string name="permlab_callPhone" msgid="3925836347681847954">"直接拨打电话号码"</string> @@ -573,16 +574,6 @@ <string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string> <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="AMPM">%P</xliff:g><xliff:g id="HOUR">%-l</xliff:g>点"</string> <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="AMPM">%p</xliff:g><xliff:g id="HOUR">%-l</xliff:g>点"</string> - <string name="status_bar_clear_all_button" msgid="7774721344716731603">"清除"</string> - <string name="status_bar_no_notifications_title" msgid="4755261167193833213">"无通知"</string> - <string name="status_bar_ongoing_events_title" msgid="1682504513316879202">"正在进行的"</string> - <string name="status_bar_latest_events_title" msgid="6594767438577593172">"通知"</string> - <string name="battery_status_text_percent_format" msgid="7660311274698797147">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string> - <string name="battery_status_charging" msgid="756617993998772213">"正在充电..."</string> - <string name="battery_low_title" msgid="7923774589611311406">"请连接充电器"</string> - <string name="battery_low_subtitle" msgid="7388781709819722764">"电量所剩不多:"</string> - <string name="battery_low_percent_format" msgid="696154104579022959">"电量剩余 <xliff:g id="NUMBER">%d%%</xliff:g> 或更少。"</string> - <string name="battery_low_why" msgid="7279169609518386372">"电量使用情况"</string> <string name="factorytest_failed" msgid="5410270329114212041">"出厂测试失败"</string> <string name="factorytest_not_system" msgid="4435201656767276723">"只有在 /system/app 中安装的包支持 FACTORY_TEST 操作。"</string> <string name="factorytest_no_action" msgid="872991874799998561">"未发现支持 FACTORY_TEST 操作的包。"</string> @@ -708,12 +699,9 @@ <string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string> <string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string> <string name="selectAll" msgid="6876518925844129331">"全选"</string> - <string name="selectText" msgid="3889149123626888637">"选择文字"</string> - <string name="stopSelectingText" msgid="4157931463872320996">"停止选择文字"</string> + <!-- outdated translation 3889149123626888637 --> <string name="selectText" msgid="4862359311088898878">"选择文字"</string> <string name="cut" msgid="3092569408438626261">"剪切"</string> - <string name="cutAll" msgid="2436383270024931639">"全部剪切"</string> <string name="copy" msgid="2681946229533511987">"复制"</string> - <string name="copyAll" msgid="2590829068100113057">"全部复制"</string> <string name="paste" msgid="5629880836805036433">"粘贴"</string> <string name="copyUrl" msgid="2538211579596067402">"复制网址"</string> <string name="inputMethod" msgid="1653630062304567879">"输入法"</string> @@ -744,6 +732,22 @@ <string name="force_close" msgid="3653416315450806396">"强行关闭"</string> <string name="report" msgid="4060218260984795706">"报告"</string> <string name="wait" msgid="7147118217226317732">"等待"</string> + <!-- no translation found for launch_warning_title (8323761616052121936) --> + <skip /> + <!-- no translation found for launch_warning_replace (6202498949970281412) --> + <skip /> + <!-- no translation found for launch_warning_original (188102023021668683) --> + <skip /> + <string name="smv_application" msgid="295583804361236288">"应用程序<xliff:g id="APPLICATION">%1$s</xliff:g>(<xliff:g id="PROCESS">%2$s</xliff:g> 进程)违反了自我强制执行的严格模式 (StrictMode) 政策。"</string> + <string name="smv_process" msgid="5120397012047462446">"进程 <xliff:g id="PROCESS">%1$s</xliff:g> 违反了自我强制执行的严格模式 (StrictMode) 政策。"</string> + <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g>正在运行"</string> + <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"选择以切换到该应用程序"</string> + <string name="heavy_weight_switcher_title" msgid="1135403633766694316">"要切换应用程序吗?"</string> + <string name="heavy_weight_switcher_text" msgid="4592075610079319667">"已有一个应用程序正在运行,要启动新的应用程序,您必须先停止该应用程序。"</string> + <string name="old_app_action" msgid="493129172238566282">"返回至<xliff:g id="OLD_APP">%1$s</xliff:g>"</string> + <string name="old_app_description" msgid="942967900237208466">"不要启动新的应用程序。"</string> + <string name="new_app_action" msgid="5472756926945440706">"启动<xliff:g id="OLD_APP">%1$s</xliff:g>"</string> + <string name="new_app_description" msgid="6830398339826789493">"停止旧的应用程序,但不保存。"</string> <string name="sendText" msgid="5132506121645618310">"选择要对文字执行的操作"</string> <string name="volume_ringtone" msgid="6885421406845734650">"铃声音量"</string> <string name="volume_music" msgid="5421651157138628171">"媒体音量"</string> diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml index 549f5710d15f..939510c01151 100644 --- a/core/res/res/values-zh-rTW/strings.xml +++ b/core/res/res/values-zh-rTW/strings.xml @@ -34,6 +34,7 @@ <string name="defaultVoiceMailAlphaTag" msgid="2660020990097733077">"語音留言"</string> <string name="defaultMsisdnAlphaTag" msgid="2850889754919584674">"MSISDN1"</string> <string name="mmiError" msgid="5154499457739052907">"連線發生問題或錯誤的 MMI 碼。"</string> + <string name="mmiFdnError" msgid="5224398216385316471">"僅限對固定撥號號碼執行此作業。"</string> <string name="serviceEnabled" msgid="8147278346414714315">"服務已啟用。"</string> <string name="serviceEnabledFor" msgid="6856228140453471041">"已啟用服務:"</string> <string name="serviceDisabled" msgid="1937553226592516411">"服務已停用。"</string> @@ -171,6 +172,8 @@ <string name="permgroupdesc_storage" msgid="9203302214915355774">"存取 SD 卡。"</string> <string name="permlab_statusBar" msgid="7417192629601890791">"停用或變更狀態列"</string> <string name="permdesc_statusBar" msgid="1365473595331989732">"允許應用程式停用狀態列或新增、移除系統圖示。"</string> + <string name="permlab_statusBarService" msgid="7247281911387931485">"狀態列"</string> + <string name="permdesc_statusBarService" msgid="4097605867643520920">"允許應用程式以狀態列顯示。"</string> <string name="permlab_expandStatusBar" msgid="1148198785937489264">"展開/收攏狀態列"</string> <string name="permdesc_expandStatusBar" msgid="7088604400110768665">"允許應用程式展開或收攏狀態列。"</string> <string name="permlab_processOutgoingCalls" msgid="1136262550878335980">"攔截撥出電話"</string> @@ -285,10 +288,6 @@ <string name="permdesc_readContacts" msgid="3371591512896545975">"允許應用程式讀取手機上所有聯絡人 (地址)。請注意:惡意程式可能利用此功能將您的資料傳送給其他人。"</string> <string name="permlab_writeContacts" msgid="644616215860933284">"輸入聯絡人資料"</string> <string name="permdesc_writeContacts" msgid="3924383579108183601">"允許應用程式更改聯絡資訊 (地址)。請注意:惡意程式可能利用此功能,清除或修改聯絡資料。"</string> - <string name="permlab_writeOwnerData" msgid="4892555913849295393">"寫入持有者的資料"</string> - <string name="permdesc_writeOwnerData" msgid="2344055317969787124">"允許應用程式更改手機持有者的資料。請注意:惡意程式可能利用此功能,清除或修改持有者的資料。"</string> - <string name="permlab_readOwnerData" msgid="6668525984731523563">"讀取持有者的資料"</string> - <string name="permdesc_readOwnerData" msgid="3088486383128434507">"允許應用程式讀取手機持有者資料。請注意:惡意程式可能利用此功能讀取持有者的資料。"</string> <string name="permlab_readCalendar" msgid="6898987798303840534">"讀取日曆活動"</string> <string name="permdesc_readCalendar" msgid="5533029139652095734">"允許應用程式讀取手機上所有日曆資料。請注意:惡意程式可能利用此功能將您的日曆資料傳送給其他人。"</string> <string name="permlab_writeCalendar" msgid="3894879352594904361">"新增或修改日曆活動,並傳送電子郵件給他人"</string> @@ -311,8 +310,8 @@ <string name="permdesc_modifyAudioSettings" msgid="5793461287365991922">"允許應用程式編輯全域音訊設定,例如音量與路由。"</string> <string name="permlab_recordAudio" msgid="3876049771427466323">"錄製音訊"</string> <string name="permdesc_recordAudio" msgid="6493228261176552356">"允許應用程式存取音訊錄製路徑。"</string> - <string name="permlab_camera" msgid="8059288807274039014">"照相"</string> - <string name="permdesc_camera" msgid="9013476258810982546">"允許應用程式使用相機拍照。此功能可讓應用程式隨時透過相機拍攝照片。"</string> + <string name="permlab_camera" msgid="3616391919559751192">"拍照和拍攝影片"</string> + <string name="permdesc_camera" msgid="6004878235852154239">"允許應用程式使用相機拍照和錄影,此功能可讓應用程式隨時透過相機收集圖片。"</string> <string name="permlab_brick" msgid="8337817093326370537">"永久停用電話"</string> <string name="permdesc_brick" msgid="5569526552607599221">"允許應用程式永久停用手機。此項操作非常危險。"</string> <string name="permlab_reboot" msgid="2898560872462638242">"強制重開機"</string> @@ -335,6 +334,8 @@ <string name="permdesc_vibrate" msgid="2886677177257789187">"允許應用程式控制震動。"</string> <string name="permlab_flashlight" msgid="2155920810121984215">"控制閃光燈"</string> <string name="permdesc_flashlight" msgid="6433045942283802309">"允許應用程式控制閃光燈。"</string> + <string name="permlab_accessUsb" msgid="7362327818655760496">"存取 USB 裝置"</string> + <string name="permdesc_accessUsb" msgid="2414271762914049292">"允許應用程式存取 USB 裝置。"</string> <string name="permlab_hardware_test" msgid="4148290860400659146">"測試硬體"</string> <string name="permdesc_hardware_test" msgid="3668894686500081699">"允許應用程式控制各種週邊設備,以供測試用。"</string> <string name="permlab_callPhone" msgid="3925836347681847954">"直接撥打電話號碼"</string> @@ -573,16 +574,6 @@ <string name="password_keyboard_label_alt_key" msgid="1284820942620288678">"ALT"</string> <string name="hour_ampm" msgid="4329881288269772723">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%P</xliff:g>"</string> <string name="hour_cap_ampm" msgid="1829009197680861107">"<xliff:g id="HOUR">%-l</xliff:g><xliff:g id="AMPM">%p</xliff:g>"</string> - <string name="status_bar_clear_all_button" msgid="7774721344716731603">"清除"</string> - <string name="status_bar_no_notifications_title" msgid="4755261167193833213">"沒有通知"</string> - <string name="status_bar_ongoing_events_title" msgid="1682504513316879202">"進行中"</string> - <string name="status_bar_latest_events_title" msgid="6594767438577593172">"通知"</string> - <string name="battery_status_text_percent_format" msgid="7660311274698797147">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string> - <string name="battery_status_charging" msgid="756617993998772213">"充電中"</string> - <string name="battery_low_title" msgid="7923774589611311406">"請連接充電器"</string> - <string name="battery_low_subtitle" msgid="7388781709819722764">"電池電量即將不足:"</string> - <string name="battery_low_percent_format" msgid="696154104579022959">"還剩 <xliff:g id="NUMBER">%d%%</xliff:g> 以下。"</string> - <string name="battery_low_why" msgid="7279169609518386372">"電池使用狀況"</string> <string name="factorytest_failed" msgid="5410270329114212041">"出廠測試失敗"</string> <string name="factorytest_not_system" msgid="4435201656767276723">"只有安裝在 /system/app 裡的程式才能支援 FACTORY_TEST 操作。"</string> <string name="factorytest_no_action" msgid="872991874799998561">"找不到提供 FACTORY_TEST 的程式。"</string> @@ -708,12 +699,9 @@ <string name="elapsed_time_short_format_mm_ss" msgid="4431555943828711473">"<xliff:g id="MINUTES">%1$02d</xliff:g>:<xliff:g id="SECONDS">%2$02d</xliff:g>"</string> <string name="elapsed_time_short_format_h_mm_ss" msgid="1846071997616654124">"<xliff:g id="HOURS">%1$d</xliff:g>:<xliff:g id="MINUTES">%2$02d</xliff:g>:<xliff:g id="SECONDS">%3$02d</xliff:g>"</string> <string name="selectAll" msgid="6876518925844129331">"全部選取"</string> - <string name="selectText" msgid="3889149123626888637">"選取文字"</string> - <string name="stopSelectingText" msgid="4157931463872320996">"停止選取文字"</string> + <!-- outdated translation 3889149123626888637 --> <string name="selectText" msgid="4862359311088898878">"選取文字"</string> <string name="cut" msgid="3092569408438626261">"剪下"</string> - <string name="cutAll" msgid="2436383270024931639">"全部剪下"</string> <string name="copy" msgid="2681946229533511987">"複製"</string> - <string name="copyAll" msgid="2590829068100113057">"全部複製"</string> <string name="paste" msgid="5629880836805036433">"貼上"</string> <string name="copyUrl" msgid="2538211579596067402">"複製網址"</string> <string name="inputMethod" msgid="1653630062304567879">"輸入方式"</string> @@ -744,6 +732,22 @@ <string name="force_close" msgid="3653416315450806396">"強制關閉"</string> <string name="report" msgid="4060218260984795706">"回報"</string> <string name="wait" msgid="7147118217226317732">"等待"</string> + <!-- no translation found for launch_warning_title (8323761616052121936) --> + <skip /> + <!-- no translation found for launch_warning_replace (6202498949970281412) --> + <skip /> + <!-- no translation found for launch_warning_original (188102023021668683) --> + <skip /> + <string name="smv_application" msgid="295583804361236288">"應用程式 <xliff:g id="APPLICATION">%1$s</xliff:g> (處理程序 <xliff:g id="PROCESS">%2$s</xliff:g>) 已違反其自行強制實施的嚴格模式 (StrictMode) 政策。"</string> + <string name="smv_process" msgid="5120397012047462446">"處理程序 <xliff:g id="PROCESS">%1$s</xliff:g> 已違反其自行強制實施的嚴格模式 (StrictMode) 政策。"</string> + <string name="heavy_weight_notification" msgid="9087063985776626166">"<xliff:g id="APP">%1$s</xliff:g> 執行中"</string> + <string name="heavy_weight_notification_detail" msgid="2423977499339403402">"選取以切換到該應用程式"</string> + <string name="heavy_weight_switcher_title" msgid="1135403633766694316">"切換應用程式?"</string> + <string name="heavy_weight_switcher_text" msgid="4592075610079319667">"其他應用程式已在執行中,您必須停止執行該應用程式,才能啟動新的應用程式。"</string> + <string name="old_app_action" msgid="493129172238566282">"返回 <xliff:g id="OLD_APP">%1$s</xliff:g>"</string> + <string name="old_app_description" msgid="942967900237208466">"請勿啟動新的應用程式。"</string> + <string name="new_app_action" msgid="5472756926945440706">"啟動 <xliff:g id="OLD_APP">%1$s</xliff:g>"</string> + <string name="new_app_description" msgid="6830398339826789493">"停止舊的應用程式,且不儲存。"</string> <string name="sendText" msgid="5132506121645618310">"訊息傳送方式"</string> <string name="volume_ringtone" msgid="6885421406845734650">"鈴聲音量"</string> <string name="volume_music" msgid="5421651157138628171">"媒體音量"</string> diff --git a/core/res/res/values/arrays.xml b/core/res/res/values/arrays.xml index 4672c0ed7ca4..aeee8af0487b 100644 --- a/core/res/res/values/arrays.xml +++ b/core/res/res/values/arrays.xml @@ -117,28 +117,4 @@ <item>3</item> </integer-array> - <!-- Do not translate. Defines the slots for the right-hand side icons. That is to say, the - icons in the status bar that are not notifications. --> - <string-array name="status_bar_icon_order"> - <item><xliff:g id="id">clock</xliff:g></item> - <item><xliff:g id="id">secure</xliff:g></item> - <item><xliff:g id="id">alarm_clock</xliff:g></item> - <item><xliff:g id="id">battery</xliff:g></item> - <item><xliff:g id="id">phone_signal</xliff:g></item> - <item><xliff:g id="id">phone_evdo_signal</xliff:g></item> - <item><xliff:g id="id">data_connection</xliff:g></item> - <item><xliff:g id="id">cdma_eri</xliff:g></item> - <item><xliff:g id="id">tty</xliff:g></item> - <item><xliff:g id="id">volume</xliff:g></item> - <item><xliff:g id="id">mute</xliff:g></item> - <item><xliff:g id="id">speakerphone</xliff:g></item> - <item><xliff:g id="id">wifi</xliff:g></item> - <item><xliff:g id="id">tty</xliff:g></item> - <item><xliff:g id="id">bluetooth</xliff:g></item> - <item><xliff:g id="id">gps</xliff:g></item> - <item><xliff:g id="id">sync_active</xliff:g></item> - <item><xliff:g id="id">sync_failing</xliff:g></item> - <item><xliff:g id="id">ime</xliff:g></item> - </string-array> - </resources> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 6d6c47f3b3d0..13c3e7eacab9 100644..100755 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -916,6 +916,25 @@ <enum name="KEYCODE_MEDIA_REWIND" value="89" /> <enum name="KEYCODE_MEDIA_FAST_FORWARD" value="90" /> <enum name="KEYCODE_MUTE" value="91" /> + <enum name="KEYCODE_PAGE_UP" value="92" /> + <enum name="KEYCODE_PAGE_DOWN" value="93" /> + <enum name="KEYCODE_PICTSYMBOLS" value="94" /> + <enum name="KEYCODE_SWITCH_CHARSET" value="95" /> + <enum name="KEYCODE_BUTTON_A" value="96" /> + <enum name="KEYCODE_BUTTON_B" value="97" /> + <enum name="KEYCODE_BUTTON_C" value="98" /> + <enum name="KEYCODE_BUTTON_X" value="99" /> + <enum name="KEYCODE_BUTTON_Y" value="100" /> + <enum name="KEYCODE_BUTTON_Z" value="101" /> + <enum name="KEYCODE_BUTTON_L1" value="102" /> + <enum name="KEYCODE_BUTTON_R1" value="103" /> + <enum name="KEYCODE_BUTTON_L2" value="104" /> + <enum name="KEYCODE_BUTTON_R2" value="105" /> + <enum name="KEYCODE_BUTTON_THUMBL" value="106" /> + <enum name="KEYCODE_BUTTON_THUMBR" value="107" /> + <enum name="KEYCODE_BUTTON_START" value="108" /> + <enum name="KEYCODE_BUTTON_SELECT" value="109" /> + <enum name="KEYCODE_BUTTON_MODE" value="110" /> </attr> <!-- ***************************************************************** --> @@ -1255,6 +1274,12 @@ be saved. --> <attr name="saveEnabled" format="boolean" /> + <!-- Specifies whether to filter touches when the view's window is obscured by + another visible window. When set to true, the view will not receive touches + whenever a toast, dialog or other window appears above the view's window. + Refer to the {@link android.view.View} security documentation for more details. --> + <attr name="filterTouchesWhenObscured" format="boolean" /> + <!-- Defines the quality of translucent drawing caches. This property is used only when the drawing cache is enabled and translucent. The default value is auto. --> <attr name="drawingCacheQuality"> @@ -1307,6 +1332,19 @@ <code>public void sayHello(View v)</code> method of your context (typically, your Activity). --> <attr name="onClick" format="string" /> + + <!-- Defines overscrolling behavior. This property is used only if the + View is scrollable. Overscrolling is the ability for the user to + scroll a View beyond its content boundaries into empty space. --> + <attr name="overscrollMode"> + <!-- Always allow the user to overscroll the content. --> + <enum name="always" value="0" /> + <!-- Only allow the user to overscroll content if the content is large + enough to meaningfully scroll. --> + <enum name="ifContentScrolls" value="1" /> + <!-- Never overscroll. --> + <enum name="never" value="2" /> + </attr> </declare-styleable> <!-- Attributes that can be used with a {@link android.view.ViewGroup} or any @@ -1740,6 +1778,10 @@ <!-- When set to false, the ListView will not draw the divider before each footer view. The default value is true. --> <attr name="footerDividersEnabled" format="boolean" /> + <!-- Drawable to draw above list content. --> + <attr name="overscrollHeader" format="reference|color" /> + <!-- Drawable to draw below list content. --> + <attr name="overscrollFooter" format="reference|color" /> </declare-styleable> <declare-styleable name="MenuView"> <!-- Default appearance of menu item text. --> diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index ed7447c71516..fb491201ef99 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -62,6 +62,22 @@ a reference to a Drawable resource containing the image definition. --> <attr name="icon" format="reference" /> + <!-- A Drawable resource providing an extended graphical logo for its + associated item. Use with the application tag (to supply a default + logo for all application components), or with the activity, receiver, + service, or instrumentation tag (to supply a specific logo for that + component). It may also be used with the intent-filter tag to supply + a logo to show to the user when an activity is being selected based + on a particular Intent. + + <p>The given logo will be used to display to the user a graphical + representation of its associated component; for example as the + header in the Action Bar. The primary differences between an icon + and a logo are that logos are often wider and more detailed, and are + used without an accompanying text caption. This must be a reference + to a Drawable resource containing the image definition. --> + <attr name="logo" format="reference" /> + <!-- Name of the activity to be launched to manage application's space on device. The specified activity gets automatically launched when the application's space needs to be managed and is usually invoked @@ -79,6 +95,13 @@ by applications. --> <attr name="allowClearUserData" format="boolean" /> + <!-- Option to let applications specify that user data should + never be encrypted if an Encrypted File System solution + is enabled. Specifically, this is an "opt-out" feature, meaning + that, by default, user data will be encrypted if the EFS feature + is enabled. --> + <attr name="neverEncrypt" format="boolean" /> + <!-- Option to indicate this application is only for testing purposes. For example, it may expose functionality or data outside of itself that would cause a security hole, but is useful for testing. This @@ -375,6 +398,10 @@ participate in data synchronization. --> <attr name="syncable" format="boolean" /> + <!-- Flag declaring this activity to be 'immersive'; immersive activities + should not be interrupted with other activities or notifications. --> + <attr name="immersive" format="boolean" /> + <!-- Specify the order in which content providers hosted by a process are instantiated when that process is created. Not needed unless you have providers with dependencies between each other, to make @@ -688,6 +715,7 @@ <attr name="theme" /> <attr name="label" /> <attr name="icon" /> + <attr name="logo" /> <attr name="description" /> <attr name="permission" /> <attr name="process" /> @@ -715,6 +743,18 @@ <attr name="killAfterRestore" /> <attr name="restoreNeedsApplication" /> <attr name="restoreAnyVersion" /> + <attr name="neverEncrypt" /> + <!-- Declare that this applicationn can't participate in the normal + state save/restore mechanism. Since it is not able to save and + restore its state on demand, + it can not participate in the normal activity lifecycle. It will + not be killed while in the background; the user must explicitly + quit it. Only one such app can be running at a time; if the user + tries to launch a second such app, they will be prompted + to quit the first before doing so. While the + application is running, the user will be informed of this. + @hide --> + <attr name="cantSaveState" format="boolean" /> </declare-styleable> <!-- The <code>permission</code> tag declares a security permission that can be @@ -734,6 +774,7 @@ <attr name="name" /> <attr name="label" /> <attr name="icon" /> + <attr name="logo" /> <attr name="permissionGroup" /> <attr name="description" /> <attr name="protectionLevel" /> @@ -758,6 +799,7 @@ <attr name="name" /> <attr name="label" /> <attr name="icon" /> + <attr name="logo" /> <attr name="description" /> </declare-styleable> @@ -787,6 +829,7 @@ <attr name="name" /> <attr name="label" /> <attr name="icon" /> + <attr name="logo" /> </declare-styleable> <!-- The <code>uses-permission</code> tag requests a @@ -937,6 +980,8 @@ screen, so that it retains the dimensions it was originally designed for. --> <attr name="largeScreens" format="boolean" /> + <!-- Indicates whether the application supports extra large screen form-factors. --> + <attr name="xlargeScreens" format="boolean" /> <!-- Indicates whether the application can resize itself to newer screen sizes. This is mostly used to distinguish between old applications that may not be compatible with newly introduced @@ -989,6 +1034,7 @@ <attr name="label" /> <attr name="description" /> <attr name="icon" /> + <attr name="logo" /> <attr name="process" /> <attr name="authorities" /> <attr name="syncable" /> @@ -1068,6 +1114,7 @@ <attr name="label" /> <attr name="description" /> <attr name="icon" /> + <attr name="logo" /> <attr name="permission" /> <attr name="process" /> <!-- Specify whether the service is enabled or not (that is, can be instantiated by the system). @@ -1100,6 +1147,7 @@ <attr name="label" /> <attr name="description" /> <attr name="icon" /> + <attr name="logo" /> <attr name="permission" /> <attr name="process" /> <!-- Specify whether the receiver is enabled or not (that is, can be instantiated by the system). @@ -1132,6 +1180,7 @@ <attr name="label" /> <attr name="description" /> <attr name="icon" /> + <attr name="logo" /> <attr name="launchMode" /> <attr name="screenOrientation" /> <attr name="configChanges" /> @@ -1157,6 +1206,7 @@ this activity. A value besides "unspecified" here overrides any value in the theme. --> <attr name="windowSoftInputMode" /> + <attr name="immersive" /> </declare-styleable> <!-- The <code>activity-alias</code> tag declares a new @@ -1185,6 +1235,7 @@ <attr name="label" /> <attr name="description" /> <attr name="icon" /> + <attr name="logo" /> <attr name="permission" /> <!-- Specify whether the activity-alias is enabled or not (that is, can be instantiated by the system). It can also be specified for an application as a whole, in which case a value of "false" @@ -1254,6 +1305,7 @@ parent="AndroidManifestActivity AndroidManifestReceiver AndroidManifestService"> <attr name="label" /> <attr name="icon" /> + <attr name="logo" /> <attr name="priority" /> </declare-styleable> @@ -1360,6 +1412,7 @@ <attr name="targetPackage" /> <attr name="label" /> <attr name="icon" /> + <attr name="logo" /> <attr name="handleProfiling" /> <attr name="functionalTest" /> </declare-styleable> diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 088ab44e84b0..d565c68e5c57 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -19,7 +19,35 @@ <!-- These resources are around just to allow their values to be customized for different hardware and product builds. --> -<resources> +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> + <!-- Component to be used as the status bar service. Must implement the IStatusBar + interface. This name is in the ComponentName flattened format (package/class) --> + <string name="config_statusBarComponent">com.android.systemui/com.android.systemui.statusbar.StatusBarService</string> + + <!-- Do not translate. Defines the slots for the right-hand side icons. That is to say, the + icons in the status bar that are not notifications. --> + <string-array name="config_statusBarIcons"> + <item><xliff:g id="id">ime</xliff:g></item> + <item><xliff:g id="id">sync_failing</xliff:g></item> + <item><xliff:g id="id">sync_active</xliff:g></item> + <item><xliff:g id="id">gps</xliff:g></item> + <item><xliff:g id="id">bluetooth</xliff:g></item> + <item><xliff:g id="id">tty</xliff:g></item> + <item><xliff:g id="id">speakerphone</xliff:g></item> + <item><xliff:g id="id">mute</xliff:g></item> + <item><xliff:g id="id">volume</xliff:g></item> + <item><xliff:g id="id">tty</xliff:g></item> + <item><xliff:g id="id">wifi</xliff:g></item> + <item><xliff:g id="id">cdma_eri</xliff:g></item> + <item><xliff:g id="id">data_connection</xliff:g></item> + <item><xliff:g id="id">phone_evdo_signal</xliff:g></item> + <item><xliff:g id="id">phone_signal</xliff:g></item> + <item><xliff:g id="id">battery</xliff:g></item> + <item><xliff:g id="id">alarm_clock</xliff:g></item> + <item><xliff:g id="id">secure</xliff:g></item> + <item><xliff:g id="id">clock</xliff:g></item> + </string-array> + <!-- Flag indicating whether the surface flinger has limited alpha compositing functionality in hardware. If set, the window manager will disable alpha trasformation in animations where not @@ -230,9 +258,6 @@ <!-- Allow the menu hard key to be disabled in LockScreen on some devices --> <bool name="config_disableMenuKeyInLockScreen">false</bool> - <!-- Control whether status bar should distinguish HSPA data icon form UMTS data icon on devices --> - <bool name="config_hspa_data_distinguishable">false</bool> - <!-- Array of light sensor LUX values to define our levels for auto backlight brightness support. The N entries of this array define N + 1 zones as follows: diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index 8a927576bed0..11f3e5017931 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -19,15 +19,19 @@ --> <resources> <!-- The width that is used when creating thumbnails of applications. --> - <dimen name="thumbnail_width">84dp</dimen> + <dimen name="thumbnail_width">0dp</dimen> <!-- The height that is used when creating thumbnails of applications. --> - <dimen name="thumbnail_height">63dp</dimen> + <dimen name="thumbnail_height">0dp</dimen> <!-- The standard size (both width and height) of an application icon that will be displayed in the app launcher and elsewhere. --> <dimen name="app_icon_size">48dip</dimen> <dimen name="toast_y_offset">64dip</dimen> <!-- Height of the status bar --> <dimen name="status_bar_height">25dip</dimen> + <!-- Height of the status bar --> + <dimen name="status_bar_icon_size">25dip</dimen> + <!-- Margin at the edge of the screen to ignore touch events for in the windowshade. --> + <dimen name="status_bar_edge_ignore">5dp</dimen> <!-- Size of the fastscroll hint letter --> <dimen name="fastscroll_overlay_size">104dp</dimen> <!-- Width of the fastscroll thumb --> @@ -38,6 +42,4 @@ <dimen name="password_keyboard_key_height">56dip</dimen> <!-- Default correction for the space key in the password keyboard --> <dimen name="password_keyboard_spacebar_vertical_correction">4dip</dimen> - <!-- Margin at the edge of the screen to ignore touch events for in the windowshade. --> - <dimen name="status_bar_edge_ignore">5dp</dimen> </resources> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 98c3a0af6ac4..28a7cca1adc1 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -869,8 +869,11 @@ <public type="drawable" name="stat_sys_download" id="0x01080081" /> <public type="drawable" name="stat_sys_download_done" id="0x01080082" /> <public type="drawable" name="stat_sys_headset" id="0x01080083" /> + <!-- @deprecated Replaced by a private asset in the phone app. --> <public type="drawable" name="stat_sys_phone_call" id="0x01080084" /> + <!-- @deprecated Replaced by a private asset in the phone app. --> <public type="drawable" name="stat_sys_phone_call_forward" id="0x01080085" /> + <!-- @deprecated Replaced by a private asset in the phone app. --> <public type="drawable" name="stat_sys_phone_call_on_hold" id="0x01080086" /> <public type="drawable" name="stat_sys_speakerphone" id="0x01080087" /> <public type="drawable" name="stat_sys_upload" id="0x01080088" /> @@ -1132,7 +1135,9 @@ <public type="style" name="Widget.ProgressBar.Large.Inverse" id="0x0103005c" /> <public type="style" name="Widget.ProgressBar.Small.Inverse" id="0x0103005d" /> + <!-- @deprecated Replaced by a private asset in the phone app. --> <public type="drawable" name="stat_sys_vp_phone_call" id="0x010800a7" /> + <!-- @deprecated Replaced by a private asset in the phone app. --> <public type="drawable" name="stat_sys_vp_phone_call_on_hold" id="0x010800a8" /> <public type="anim" name="anticipate_interpolator" id="0x010a0007" /> @@ -1239,4 +1244,44 @@ <public type="anim" name="cycle_interpolator" id="0x010a000c" /> +<!-- =============================================================== + Resources introduced in Gingerbread. + =============================================================== --> + + <public type="attr" name="logo" id="0x010102be" /> + <public type="attr" name="xlargeScreens" id="0x010102bf" /> + <public type="attr" name="immersive" id="0x010102c0" /> + <public type="attr" name="overscrollMode" id="0x010102c1" /> + <public type="attr" name="overscrollHeader" id="0x010102c2" /> + <public type="attr" name="overscrollFooter" id="0x010102c3" /> + <public type="attr" name="filterTouchesWhenObscured" id="0x010102c4" /> + + <public-padding type="attr" name="kraken_resource_pad" end="0x01010300" /> + + <public-padding type="id" name="kraken_resource_pad" end="0x01020040" /> + <public-padding type="anim" name="kraken_resource_pad" end="0x010a0020" /> + + <!-- presence drawables for videochat or audiochat capable contacts --> + <public type="drawable" name="presence_video_away" id="0x010800ac" /> + <public type="drawable" name="presence_video_busy" id="0x010800ad" /> + <public type="drawable" name="presence_video_online" id="0x010800ae" /> + <public type="drawable" name="presence_audio_away" id="0x010800af" /> + <public type="drawable" name="presence_audio_busy" id="0x010800b0" /> + <public type="drawable" name="presence_audio_online" id="0x010800b1" /> + + <public-padding type="drawable" name="kraken_resource_pad" end="0x01080100" /> + + <public type="style" name="TextAppearance.StatusBar.Title" id="0x01030065" /> + <public type="style" name="TextAppearance.StatusBar.Icon" id="0x01030066" /> + <public type="style" name="TextAppearance.StatusBar.EventContent" id="0x01030067" /> + <public type="style" name="TextAppearance.StatusBar.EventContent.Title" id="0x01030068" /> + + <public-padding type="style" name="kraken_resource_pad" end="0x01030090" /> + + <public-padding type="string" name="kraken_resource_pad" end="0x01040020" /> + <public-padding type="integer" name="kraken_resource_pad" end="0x010e0010" /> + <public-padding type="layout" name="kraken_resource_pad" end="0x01090020" /> + <public-padding type="dimen" name="kraken_resource_pad" end="0x01050010" /> + <public-padding type="color" name="kraken_resource_pad" end="0x01060020" /> + <public-padding type="array" name="kraken_resource_pad" end="0x01070010" /> </resources> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 86bfe94cbf8f..59136392ccb8 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -60,6 +60,10 @@ <!-- Displayed when the user dialed an MMI code whose function could not be performed. This will be displayed in a toast. --> <string name="mmiError">Connection problem or invalid MMI code.</string> + <!-- Displayed when the user dialed an MMI code whose function + could not be performed because FDN is enabled. This will be displayed in a toast. --> + <string name="mmiFdnError">Operation is restricted to fixed dialing numbers only.</string> + <!-- Displayed when a phone feature such as call barring was activated. --> <string name="serviceEnabled">Service was enabled.</string> <!-- Displayed in front of the list of a set of service classes @@ -389,6 +393,11 @@ the status bar or add and remove system icons.</string> <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permlab_statusBarService">status bar</string> + <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permdesc_statusBarService">Allows the application to be the status bar.</string> + + <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permlab_expandStatusBar">expand/collapse status bar</string> <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permdesc_expandStatusBar">Allows application to @@ -775,20 +784,6 @@ applications can use this to erase or modify your contact data.</string> <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> - <string name="permlab_writeOwnerData">write owner data</string> - <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> - <string name="permdesc_writeOwnerData">Allows an application to modify the - phone owner data stored on your phone. Malicious - applications can use this to erase or modify owner data.</string> - - <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> - <string name="permlab_readOwnerData">read owner data</string> - <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> - <string name="permdesc_readOwnerData">Allows an application read the - phone owner data stored on your phone. Malicious - applications can use this to read phone owner data.</string> - - <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permlab_readCalendar">read calendar events</string> <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permdesc_readCalendar">Allows an application to read all @@ -863,9 +858,9 @@ the audio record path.</string> <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> - <string name="permlab_camera">take pictures</string> + <string name="permlab_camera">take pictures and videos</string> <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> - <string name="permdesc_camera">Allows application to take pictures + <string name="permdesc_camera">Allows application to take pictures and videos with the camera. This allows the application at any time to collect images the camera is seeing.</string> @@ -930,6 +925,11 @@ the flashlight.</string> <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permlab_accessUsb">access USB devices</string> + <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permdesc_accessUsb">Allows the application to access USB devices.</string> + + <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permlab_hardware_test">test hardware</string> <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permdesc_hardware_test">Allows the application to control @@ -1563,45 +1563,6 @@ <!-- A format string for 12-hour time of day, just the hour, not the minute, with capital "AM" or "PM" (example: "3PM"). --> <string name="hour_cap_ampm">"<xliff:g id="hour" example="3">%-l</xliff:g><xliff:g id="ampm" example="PM">%p</xliff:g>"</string> - <!-- The text for the button in the notification window-shade that clears - all of the currently visible notifications. --> - <string name="status_bar_clear_all_button">Clear</string> - - <!-- The label in the bar at the top of the status bar when there are no notifications - showing. --> - <string name="status_bar_no_notifications_title">No notifications</string> - - <!-- The label for the group of notifications for ongoing events in the opened version of - the status bar. An ongoing call is the prime example of this. The MP3 music player - might be another example. --> - <string name="status_bar_ongoing_events_title">Ongoing</string> - - <!-- The label for the group of notifications for recent events in the opened version of - the status bar. Recently received text messsages (SMS), emails, calendar alerts, etc. --> - <string name="status_bar_latest_events_title">Notifications</string> - - <!-- The big percent text in the middle of the battery icon that appears when you plug in - the charger. --> - <string name="battery_status_text_percent_format"><xliff:g id="number" example="50">%d</xliff:g><xliff:g id="percent" example="%">%%</xliff:g></string> - - <!-- The big percent text in the middle of the battery icon that appears when you plug in - the charger. This indicates the current status of the battery. --> - <string name="battery_status_charging">Charging\u2026</string> - - <!-- When the battery is low, this is displayed to the user in a dialog. The title of the low battery alert. --> - <string name="battery_low_title">Please connect charger</string> - - <!-- When the battery is low, this is displayed to the user in a dialog. The subtitle of the low battery alert. --> - <string name="battery_low_subtitle">The battery is getting low:</string> - - <!-- A message that appears when the battery level is getting low in a dialog. This is appened to the subtitle of the low battery alert. --> - <string name="battery_low_percent_format"><xliff:g id="number">%d%%</xliff:g> - or less remaining.</string> - - <!-- When the battery is low, this is the label of the button to go to the - power usage activity to find out what drained the battery. --> - <string name="battery_low_why">Battery use</string> - <!-- Title of the alert when something went wrong in the factory test. --> <string name="factorytest_failed">Factory test failed</string> <!-- Error message displayed when a non-system application tries to start a factory test. --> @@ -1878,24 +1839,15 @@ <!-- Item on EditText context menu. This action is used to select all text in the edit field. --> <string name="selectAll">Select all</string> - <!-- Item on EditText context menu. This action is used to start selecting text in the edit field. --> - <string name="selectText">Select text</string> - - <!-- Item on EditText context menu. This action is used to start selecting text in the edit field. --> - <string name="stopSelectingText">Stop selecting text</string> + <!-- Item on EditText context menu. This action is used to start selecting text in the edit field. [CHAR LIMIT=20] --> + <string name="selectText">Select word</string> <!-- Item on EditText context menu. This action is used to cut selected the text into the clipboard. --> <string name="cut">Cut</string> - <!-- Item on EditText context menu. This action is used to cut all the text into the clipboard. --> - <string name="cutAll">Cut all</string> - <!-- Item on EditText context menu. This action is used to cut selected the text into the clipboard. --> <string name="copy">Copy</string> - <!-- Item on EditText context menu. This action is used to copy all the text into the clipboard. --> - <string name="copyAll">Copy all</string> - <!-- Item on EditText context menu. This action is used t o paste from the clipboard into the eidt field --> <string name="paste">Paste</string> @@ -1947,7 +1899,7 @@ <string name="noApplications">No applications can perform this action.</string> <!-- Title of the alert when an application has crashed. --> <string name="aerr_title">Sorry!</string> - <!-- Text of the alert that is displayed when an application is not responding. --> + <!-- Text of the alert that is displayed when an application has crashed. --> <string name="aerr_application">The application <xliff:g id="application">%1$s</xliff:g> (process <xliff:g id="process">%2$s</xliff:g>) has stopped unexpectedly. Please try again.</string> <!-- Text of the alert that is displayed when an application has crashed. --> @@ -1969,9 +1921,43 @@ <string name="report">Report</string> <!-- Button allowing the user to choose to wait for an application that is not responding to become responsive again. --> <string name="wait">Wait</string> - + <!-- [CHAR LIMIT=25] Title of the alert when application launches on top of another. --> + <string name="launch_warning_title">Application redirected</string> + <!-- [CHAR LIMIT=50] Title of the alert when application launches on top of another. --> + <string name="launch_warning_replace"><xliff:g id="app_name">%1$s</xliff:g> is now running.</string> + <!-- [CHAR LIMIT=50] Title of the alert when application launches on top of another. --> + <string name="launch_warning_original"><xliff:g id="app_name">%1$s</xliff:g> was originally launched.</string> + + <!-- Text of the alert that is displayed when an application has violated StrictMode. --> + <string name="smv_application">The application <xliff:g id="application">%1$s</xliff:g> + (process <xliff:g id="process">%2$s</xliff:g>) has violated its self-enforced StrictMode policy.</string> + <!-- Text of the alert that is displayed when an application has violated StrictMode. --> + <string name="smv_process">The process <xliff:g id="process">%1$s</xliff:g> has + has violated its self-enforced StrictMode policy.</string> + + <!-- Notification text to tell the user that a heavy-weight application is running. --> + <string name="heavy_weight_notification"><xliff:g id="app">%1$s</xliff:g> running</string> + + <!-- Notification details to tell the user that a heavy-weight application is running. --> + <string name="heavy_weight_notification_detail">Select to switch to application</string> + + <!-- Title of dialog prompting whether user wants to switch between heavy-weight apps. --> + <string name="heavy_weight_switcher_title">Switch applications?</string> + + <!-- Descriptive text for switching to a new heavy-weight application. --> + <string name="heavy_weight_switcher_text">Another application is already running + that must be stopped before you can start a new one.</string> + + <string name="old_app_action">Return to <xliff:g id="old_app">%1$s</xliff:g></string> + <string name="old_app_description">Don\'t start the new application.</string> + + <string name="new_app_action">Start <xliff:g id="old_app">%1$s</xliff:g></string> + <string name="new_app_description">Stop the old application without saving.</string> + <!-- Displayed in the title of the chooser for things to do with text that - is to be sent to another application. For example, I can send text through SMS or IM. A dialog with those choices would be shown, and this would be the title. --> + is to be sent to another application. For example, I can send + text through SMS or IM. A dialog with those choices would be shown, + and this would be the title. --> <string name="sendText">Select an action for text</string> <!-- Title of the dialog where the user is adjusting the phone ringer volume --> diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml index b5fff968f9d1..1c60ba0b1d37 100644 --- a/core/res/res/values/styles.xml +++ b/core/res/res/values/styles.xml @@ -187,11 +187,26 @@ <!-- Status Bar Styles --> - <style name="TextAppearance.StatusBarTitle"> - <item name="android:textAppearance">?android:attr/textAppearanceSmall</item> - <item name="android:textStyle">bold</item> + <style name="TextAppearance.StatusBar"> + <item name="android:textSize">14sp</item> + <item name="android:textStyle">normal</item> <item name="android:textColor">?android:attr/textColorPrimary</item> </style> + <style name="TextAppearance.StatusBar.Ticker"> + </style> + <style name="TextAppearance.StatusBar.Title"> + <item name="android:textStyle">bold</item> + </style> + + <style name="TextAppearance.StatusBar.Icon"> + <item name="android:textStyle">bold</item> + </style> + <style name="TextAppearance.StatusBar.EventContent"> + </style> + <style name="TextAppearance.StatusBar.EventContent.Title"> + <item name="android:textSize">16sp</item> + <item name="android:textStyle">bold</item> + </style> <!-- Widget Styles --> @@ -491,7 +506,7 @@ <item name="android:fadingEdge">vertical</item> <item name="listSelector">@android:drawable/menu_selector</item> <!-- Light background for the list in menus, so the divider for bright themes --> - <item name="android:divider">@android:drawable/divider_horizontal_bright</item> + <item name="android:divider">@android:drawable/divider_horizontal_dark</item> </style> <style name="Widget.GridView" parent="Widget.AbsListView"> @@ -661,7 +676,7 @@ </style> <style name="TextAppearance.Widget.IconMenu.Item" parent="TextAppearance.Small"> - <item name="android:textColor">?textColorPrimaryInverse</item> + <item name="android:textColor">?textColorPrimary</item> </style> <style name="TextAppearance.Widget.EditText"> diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml index d585d9e11f63..2311bdd28698 100644 --- a/core/res/res/values/themes.xml +++ b/core/res/res/values/themes.xml @@ -122,9 +122,10 @@ <!-- Panel attributes --> <item name="panelBackground">@android:drawable/menu_background</item> <item name="panelFullBackground">@android:drawable/menu_background_fill_parent_width</item> - <item name="panelColorBackground">#fff</item> - <item name="panelColorForeground">?android:attr/textColorPrimaryInverse</item> - <item name="panelTextAppearance">?android:attr/textAppearanceInverse</item> + <!-- These three attributes do not seems to be used by the framework. Declared public though --> + <item name="panelColorBackground">#000</item> + <item name="panelColorForeground">?android:attr/textColorPrimary</item> + <item name="panelTextAppearance">?android:attr/textAppearance</item> <!-- Scrollbar attributes --> <item name="scrollbarFadeDuration">250</item> @@ -488,8 +489,8 @@ <item name="android:itemTextAppearance">@android:style/TextAppearance.Widget.IconMenu.Item</item> <item name="android:itemBackground">@android:drawable/menu_selector</item> <item name="android:itemIconDisabledAlpha">?android:attr/disabledAlpha</item> - <item name="android:horizontalDivider">@android:drawable/divider_horizontal_bright</item> - <item name="android:verticalDivider">@android:drawable/divider_vertical_bright</item> + <item name="android:horizontalDivider">@android:drawable/divider_horizontal_dark</item> + <item name="android:verticalDivider">@android:drawable/divider_vertical_dark</item> <item name="android:windowAnimationStyle">@android:style/Animation.OptionsPanel</item> <item name="android:moreIcon">@android:drawable/ic_menu_more</item> <item name="android:background">@null</item> @@ -497,7 +498,7 @@ <style name="Theme.ExpandedMenu"> <!-- Menu/item attributes --> - <item name="android:itemTextAppearance">?android:attr/textAppearanceLargeInverse</item> + <item name="android:itemTextAppearance">?android:attr/textAppearanceLarge</item> <item name="android:listViewStyle">@android:style/Widget.ListView.Menu</item> <item name="android:windowAnimationStyle">@android:style/Animation.OptionsPanel</item> <item name="android:background">@null</item> @@ -521,5 +522,12 @@ <item name="android:windowAnimationStyle">@android:style/Animation.RecentApplications</item> <item name="android:textColor">@android:color/secondary_text_nofocus</item> </style> + + <!-- Default theme for window that looks like a toast. --> + <style name="Theme.Toast" parent="@android:style/Theme.Dialog"> + <item name="android:windowBackground">@android:drawable/toast_frame</item> + <item name="android:windowAnimationStyle">@android:style/Animation.Toast</item> + <item name="android:backgroundDimEnabled">false</item> + </style> </resources> diff --git a/core/res/res/xml/power_profile.xml b/core/res/res/xml/power_profile.xml index ce623e8d4d35..30312b369499 100644 --- a/core/res/res/xml/power_profile.xml +++ b/core/res/res/xml/power_profile.xml @@ -18,6 +18,7 @@ --> <device name="Android"> + <!-- All values are in mAh except as noted --> <item name="none">0</item> <item name="screen.on">0.1</item> <item name="bluetooth.active">0.1</item> @@ -48,4 +49,6 @@ <array name="cpu.active"> <value>0.2</value> </array> + <!-- This is the battery capacity in mAh --> + <item name="battery.capacity">1000</item> </device> diff --git a/core/tests/ConnectivityManagerTest/AndroidManifest.xml b/core/tests/ConnectivityManagerTest/AndroidManifest.xml index c318577ff91a..d298d40cafde 100644 --- a/core/tests/ConnectivityManagerTest/AndroidManifest.xml +++ b/core/tests/ConnectivityManagerTest/AndroidManifest.xml @@ -24,23 +24,35 @@ <application> <uses-library android:name="android.test.runner" /> <activity android:name="ConnectivityManagerTestActivity" - android:label="CMTest"> + android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> - <category android:name="android.intent.category.TEST" /> + <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> <!-- This declares that this app uses the instrumentation test runner targeting - the package of browserpowertest. To run the tests use the command: - "adb shell am instrument -w com.android.connectivitymanagertest/.ConnectivityManagerTestRunner" + the package of connectivitymanagertest. To run the tests use the command: + "adb shell am instrument -e ssid <SSID> -w + com.android.connectivitymanagertest/.ConnectivityManagerTestRunner", + the access point <SSID> should be an open AP. --> <instrumentation android:name=".ConnectivityManagerTestRunner" android:targetPackage="com.android.connectivitymanagertest" android:label="Test runner for Connectivity Manager Tests" /> + <!-- + To run the unit tests use the command: + "adb shell am instrument -w + com.android.connectivitymanagertest/.ConnectivityManagerUnitTestRunner" + --> + <instrumentation android:name=".ConnectivityManagerUnitTestRunner" + android:targetPackage="com.android.connectivitymanagertest" + android.label="Test runner for unit tests" + /> + <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> diff --git a/core/tests/ConnectivityManagerTest/res/values/strings.xml b/core/tests/ConnectivityManagerTest/res/values/strings.xml new file mode 100644 index 000000000000..fb6e82f83af8 --- /dev/null +++ b/core/tests/ConnectivityManagerTest/res/values/strings.xml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <string name="app_name">ConnectivityManagerTest</string> +</resources> diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerUnitTestRunner.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerUnitTestRunner.java new file mode 100644 index 000000000000..6adfc7489910 --- /dev/null +++ b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerUnitTestRunner.java @@ -0,0 +1,47 @@ +/* + * 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.connectivitymanagertest; + +import android.os.Bundle; +import android.test.InstrumentationTestRunner; +import android.test.InstrumentationTestSuite; +import android.util.Log; +import com.android.connectivitymanagertest.unit.WifiSoftAPTest; + +import junit.framework.TestSuite; + +/** + * Instrumentation Test Runner for all unit tests + * + * adb shell am instrument \ + * -w com.android.connectivitymanagertest/.ConnectivityManagerUnitTestRunner + */ + +public class ConnectivityManagerUnitTestRunner extends InstrumentationTestRunner { + @Override + public TestSuite getAllTests() { + TestSuite suite = new InstrumentationTestSuite(this); + suite.addTestSuite(WifiSoftAPTest.class); + return suite; + } + + + @Override + public ClassLoader getLoader() { + return ConnectivityManagerUnitTestRunner.class.getClassLoader(); + } +} diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/functional/ConnectivityManagerMobileTest.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/functional/ConnectivityManagerMobileTest.java index cdaefc853079..ad8d444114d7 100644 --- a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/functional/ConnectivityManagerMobileTest.java +++ b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/functional/ConnectivityManagerMobileTest.java @@ -91,30 +91,30 @@ public class ConnectivityManagerMobileTest // DISCONNECTING, DISCONNECTED, UNKNOWN private void waitForNetworkState(int networkType, State expectedState, long timeout) { long startTime = System.currentTimeMillis(); - // In case the broadcast is already sent out, no need to wait - if (cmActivity.mCM.getNetworkInfo(networkType).getState() == expectedState) { - return; - } else { - while (true) { - if ((System.currentTimeMillis() - startTime) > timeout) { + while (true) { + if ((System.currentTimeMillis() - startTime) > timeout) { + if (cmActivity.mCM.getNetworkInfo(networkType).getState() != expectedState) { assertFalse("Wait for network state timeout", true); + } else { + // the broadcast has been sent out. the state has been changed. + return; + } + } + Log.v(LOG_TAG, "Wait for the connectivity state for network: " + networkType + + " to be " + expectedState.toString()); + synchronized (cmActivity.connectivityObject) { + try { + cmActivity.connectivityObject.wait(STATE_TRANSITION_SHORT_TIMEOUT); + } catch (InterruptedException e) { + e.printStackTrace(); } - Log.v(LOG_TAG, "Wait for the connectivity state for network: " + networkType + - " to be " + expectedState.toString()); - synchronized (cmActivity.connectivityObject) { - try { - cmActivity.connectivityObject.wait(STATE_TRANSITION_SHORT_TIMEOUT); - } catch (InterruptedException e) { - e.printStackTrace(); - } - if ((cmActivity.mNetworkInfo.getType() != networkType) || - (cmActivity.mNetworkInfo.getState() != expectedState)) { - Log.v(LOG_TAG, "network state for " + cmActivity.mNetworkInfo.getType() + - "is: " + cmActivity.mNetworkInfo.getState()); - continue; - } - break; + if ((cmActivity.mNetworkInfo.getType() != networkType) || + (cmActivity.mNetworkInfo.getState() != expectedState)) { + Log.v(LOG_TAG, "network state for " + cmActivity.mNetworkInfo.getType() + + "is: " + cmActivity.mNetworkInfo.getState()); + continue; } + break; } } } @@ -123,26 +123,26 @@ public class ConnectivityManagerMobileTest // WIFI_STATE_ENALBING, WIFI_STATE_UNKNOWN private void waitForWifiState(int expectedState, long timeout) { long startTime = System.currentTimeMillis(); - if (cmActivity.mWifiState == expectedState) { - return; - } else { - while (true) { - if ((System.currentTimeMillis() - startTime) > timeout) { + while (true) { + if ((System.currentTimeMillis() - startTime) > timeout) { + if (cmActivity.mWifiState != expectedState) { assertFalse("Wait for Wifi state timeout", true); + } else { + return; + } + } + Log.v(LOG_TAG, "Wait for wifi state to be: " + expectedState); + synchronized (cmActivity.wifiObject) { + try { + cmActivity.wifiObject.wait(5*1000); + } catch (InterruptedException e) { + e.printStackTrace(); } - Log.v(LOG_TAG, "Wait for wifi state to be: " + expectedState); - synchronized (cmActivity.wifiObject) { - try { - cmActivity.wifiObject.wait(5*1000); - } catch (InterruptedException e) { - e.printStackTrace(); - } - if (cmActivity.mWifiState != expectedState) { - Log.v(LOG_TAG, "Wifi state is: " + cmActivity.mWifiNetworkInfo.getState()); - continue; - } - break; + if (cmActivity.mWifiState != expectedState) { + Log.v(LOG_TAG, "Wifi state is: " + cmActivity.mWifiNetworkInfo.getState()); + continue; } + break; } } } diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/unit/WifiSoftAPTest.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/unit/WifiSoftAPTest.java new file mode 100644 index 000000000000..3f43e4851c4f --- /dev/null +++ b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/unit/WifiSoftAPTest.java @@ -0,0 +1,86 @@ +/* + * 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.connectivitymanagertest.unit; + +import android.content.BroadcastReceiver; +import android.content.Intent; +import android.content.Context; +import android.app.Instrumentation; +import android.os.Handler; +import android.os.Message; +import android.net.ConnectivityManager; +import android.net.wifi.WifiManager; +import android.net.wifi.WifiConfiguration; +import android.net.wifi.WifiConfiguration.KeyMgmt; + +import android.test.suitebuilder.annotation.LargeTest; +import android.test.AndroidTestCase; + +import java.util.ArrayList; + +import android.util.Log; + +/** + * Test Wifi soft AP configuration + */ +public class WifiSoftAPTest extends AndroidTestCase { + + private WifiManager mWifiManager; + private WifiConfiguration mWifiConfig = null; + private final String TAG = "WifiSoftAPTest"; + private final int DURATION = 10000; + + @Override + protected void setUp() throws Exception { + super.setUp(); + mWifiManager = (WifiManager) getContext().getSystemService(Context.WIFI_SERVICE); + assertNotNull(mWifiManager); + assertTrue(mWifiManager.setWifiApEnabled(null, true)); + mWifiConfig = mWifiManager.getWifiApConfiguration(); + if (mWifiConfig != null) { + Log.v(TAG, "mWifiConfig is " + mWifiConfig.toString()); + } else { + Log.v(TAG, "mWifiConfig is null."); + } + } + + @Override + protected void tearDown() throws Exception { + Log.v(TAG, "turn off wifi tethering"); + mWifiManager.setWifiApEnabled(null, false); + super.tearDown(); + } + + // Test case 1: Test the soft AP SSID with letters + @LargeTest + public void testApSsidWithAlphabet() { + WifiConfiguration config = new WifiConfiguration(); + config.SSID = "abcdefghijklmnopqrstuvwxyz"; + config.allowedKeyManagement.set(KeyMgmt.NONE); + mWifiConfig = config; + assertTrue(mWifiManager.setWifiApEnabled(mWifiConfig, true)); + try { + Thread.sleep(DURATION); + } catch (InterruptedException e) { + Log.v(TAG, "exception " + e.getStackTrace()); + assertFalse(true); + } + assertNotNull(mWifiManager.getWifiApConfiguration()); + assertEquals("wifi AP state is not enabled", WifiManager.WIFI_AP_STATE_ENABLED, + mWifiManager.getWifiApState()); + } +} diff --git a/core/tests/coretests/Android.mk b/core/tests/coretests/Android.mk index 245c67cf4002..b49680506802 100644 --- a/core/tests/coretests/Android.mk +++ b/core/tests/coretests/Android.mk @@ -11,8 +11,8 @@ LOCAL_SRC_FILES := \ $(call all-java-files-under, DisabledTestApp/src) \ $(call all-java-files-under, EnabledTestApp/src) -LOCAL_STATIC_JAVA_LIBRARIES += android-common - +LOCAL_DX_FLAGS := --core-library +LOCAL_STATIC_JAVA_LIBRARIES := core-tests-supportlib android-common frameworks-core-util-lib LOCAL_JAVA_LIBRARIES := android.test.runner LOCAL_PACKAGE_NAME := FrameworksCoreTests diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml index ce73ae1eb433..f09421bca74f 100644 --- a/core/tests/coretests/AndroidManifest.xml +++ b/core/tests/coretests/AndroidManifest.xml @@ -36,12 +36,16 @@ android:description="@string/permdesc_testDenied" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> + <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> + <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> <uses-permission android:name="android.permission.BROADCAST_STICKY" /> + <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" /> <uses-permission android:name="android.permission.CLEAR_APP_CACHE" /> <uses-permission android:name="android.permission.CLEAR_APP_USER_DATA" /> <uses-permission android:name="android.permission.DELETE_CACHE_FILES" /> + <uses-permission android:name="android.permission.DOWNLOAD_CACHE_NON_PURGEABLE" /> <uses-permission android:name="android.permission.GET_PACKAGE_SIZE" /> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.READ_CONTACTS" /> diff --git a/core/tests/coretests/res/raw/v21_im.vcf b/core/tests/coretests/res/raw/v21_im.vcf new file mode 100644 index 000000000000..cc1aabb74941 --- /dev/null +++ b/core/tests/coretests/res/raw/v21_im.vcf @@ -0,0 +1,5 @@ +BEGIN:VCARD +VERSION:2.1 +X-ANDROID-CUSTOM:vnd.android.cursor.item/nickname;Nick;1;;;;;;;;;;;;; +X-GOOGLE-TALK:hhh@gmail.com +END:VCARD diff --git a/core/tests/coretests/res/raw/v21_invalid_multiple_line.vcf b/core/tests/coretests/res/raw/v21_invalid_multiple_line.vcf new file mode 100644 index 000000000000..9c81fd52a14b --- /dev/null +++ b/core/tests/coretests/res/raw/v21_invalid_multiple_line.vcf @@ -0,0 +1,7 @@ +BEGIN:VCARD +VERSION:2.1 +N:;Omega;;; +EMAIL;INTERNET:"Omega" + <omega@example.com> +FN:Omega +END:VCARD diff --git a/core/tests/coretests/res/raw/v30_comma_separated.vcf b/core/tests/coretests/res/raw/v30_comma_separated.vcf index 98a7f20588a1..f1baf880972f 100644 --- a/core/tests/coretests/res/raw/v30_comma_separated.vcf +++ b/core/tests/coretests/res/raw/v30_comma_separated.vcf @@ -1,5 +1,5 @@ BEGIN:VCARD
VERSION:3.0
-N:F;G;M;;
-TEL;TYPE=PAGER,WORK,MSG:6101231234@pagersample.com
+N;TYPE=PREF,HOME:F;G;M;;
+TEL;TYPE="COMMA,SEPARATED:INSIDE.DQUOTE",PREF:1
END:VCARD
diff --git a/core/tests/coretests/res/raw/v30_multibyte_param.vcf b/core/tests/coretests/res/raw/v30_multibyte_param.vcf new file mode 100644 index 000000000000..cd200e563d11 --- /dev/null +++ b/core/tests/coretests/res/raw/v30_multibyte_param.vcf @@ -0,0 +1,5 @@ +BEGIN:VCARD
+VERSION:3.0
+N:F;G;M;;
+TEL;TYPE="费":1
+END:VCARD
diff --git a/core/tests/coretests/res/raw/v30_pager.vcf b/core/tests/coretests/res/raw/v30_pager.vcf new file mode 100644 index 000000000000..98a7f20588a1 --- /dev/null +++ b/core/tests/coretests/res/raw/v30_pager.vcf @@ -0,0 +1,5 @@ +BEGIN:VCARD
+VERSION:3.0
+N:F;G;M;;
+TEL;TYPE=PAGER,WORK,MSG:6101231234@pagersample.com
+END:VCARD
diff --git a/core/tests/coretests/res/raw/v40_sort_as.vcf b/core/tests/coretests/res/raw/v40_sort_as.vcf new file mode 100644 index 000000000000..6f6bc3b15662 --- /dev/null +++ b/core/tests/coretests/res/raw/v40_sort_as.vcf @@ -0,0 +1,6 @@ +BEGIN:VCARD
+VERSION:4.0
+FN:安藤 ロイド
+N;SORT-AS="あんどう;ろいど":安藤;ロイド;;;
+ORG;TYPE=WORK;SORT-AS="ぐーぐる;けんさくぶもん":グーグル;検索部門
+END:VCARD
diff --git a/core/tests/coretests/src/android/app/activity/LifecycleTest.java b/core/tests/coretests/src/android/app/activity/LifecycleTest.java index 768a9a446e08..ed01fac5e0a9 100644 --- a/core/tests/coretests/src/android/app/activity/LifecycleTest.java +++ b/core/tests/coretests/src/android/app/activity/LifecycleTest.java @@ -18,10 +18,7 @@ package android.app.activity; import android.content.ComponentName; import android.content.Intent; -import android.test.FlakyTest; -import android.test.suitebuilder.annotation.LargeTest; import android.test.suitebuilder.annotation.MediumTest; -import android.test.suitebuilder.annotation.SmallTest; import android.test.suitebuilder.annotation.Suppress; public class LifecycleTest extends ActivityTestsBase { @@ -37,7 +34,7 @@ public class LifecycleTest extends ActivityTestsBase { LaunchpadActivity.class)); } - @LargeTest + @MediumTest public void testBasic() throws Exception { mIntent = mTopIntent; runLaunchpad(LaunchpadActivity.LIFECYCLE_BASIC); diff --git a/core/tests/coretests/src/android/app/activity/MetaDataTest.java b/core/tests/coretests/src/android/app/activity/MetaDataTest.java index 214bc9106cdc..5b9c0e903135 100644 --- a/core/tests/coretests/src/android/app/activity/MetaDataTest.java +++ b/core/tests/coretests/src/android/app/activity/MetaDataTest.java @@ -27,7 +27,6 @@ import android.content.res.TypedArray; import android.content.res.XmlResourceParser; import android.os.Bundle; import android.test.AndroidTestCase; -import android.test.suitebuilder.annotation.MediumTest; import android.test.suitebuilder.annotation.SmallTest; import com.android.frameworks.coretests.R; import org.xmlpull.v1.XmlPullParser; @@ -134,7 +133,7 @@ public class MetaDataTest extends AndroidTestCase { assertNull("Meta data returned when not requested", si.metaData); } - @MediumTest + @SmallTest public void testProviderWithData() throws Exception { ComponentName cn = new ComponentName(mContext, LocalProvider.class); ProviderInfo pi = mContext.getPackageManager().resolveContentProvider( diff --git a/core/tests/coretests/src/android/bluetooth/BluetoothStressTest.java b/core/tests/coretests/src/android/bluetooth/BluetoothStressTest.java index d8d9eba14dc3..149685c2a6fa 100644 --- a/core/tests/coretests/src/android/bluetooth/BluetoothStressTest.java +++ b/core/tests/coretests/src/android/bluetooth/BluetoothStressTest.java @@ -78,4 +78,50 @@ public class BluetoothStressTest extends InstrumentationTestCase { mTestUtils.disable(adapter); } + + public void testPair() { + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + BluetoothDevice device = adapter.getRemoteDevice(BluetoothTestRunner.sHeadsetAddress); + + mTestUtils.enable(adapter); + mTestUtils.pair(adapter, device); + mTestUtils.unpair(adapter, device); + mTestUtils.disable(adapter); + } + + public void testConnectA2dp() { + int iterations = BluetoothTestRunner.sConnectA2dpIterations; + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + BluetoothDevice device = adapter.getRemoteDevice(BluetoothTestRunner.sA2dpAddress); + + mTestUtils.enable(adapter); + mTestUtils.pair(adapter, device); + + for (int i = 0; i < iterations; i++) { + mTestUtils.writeOutput("connectA2dp iteration " + (i + 1) + " of " + iterations); + mTestUtils.connectA2dp(adapter, device); + mTestUtils.disconnectA2dp(adapter, device); + } + + // TODO: Unpair from device if device can accept pairing after unpairing + mTestUtils.disable(adapter); + } + + public void testConnectHeadset() { + int iterations = BluetoothTestRunner.sConnectHeadsetIterations; + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + BluetoothDevice device = adapter.getRemoteDevice(BluetoothTestRunner.sHeadsetAddress); + + mTestUtils.enable(adapter); + mTestUtils.pair(adapter, device); + + for (int i = 0; i < iterations; i++) { + mTestUtils.writeOutput("connectHeadset iteration " + (i + 1) + " of " + iterations); + mTestUtils.connectHeadset(adapter, device); + mTestUtils.disconnectHeadset(adapter, device); + } + + // TODO: Unpair from device if device can accept pairing after unpairing + mTestUtils.disable(adapter); + } } diff --git a/core/tests/coretests/src/android/bluetooth/BluetoothTestRunner.java b/core/tests/coretests/src/android/bluetooth/BluetoothTestRunner.java index cf0ff99d1283..2e6daa33f6e0 100644 --- a/core/tests/coretests/src/android/bluetooth/BluetoothTestRunner.java +++ b/core/tests/coretests/src/android/bluetooth/BluetoothTestRunner.java @@ -26,6 +26,11 @@ public class BluetoothTestRunner extends InstrumentationTestRunner { public static int sEnableIterations = 100; public static int sDiscoverableIterations = 1000; public static int sScanIterations = 1000; + public static int sConnectHeadsetIterations = 100; + public static int sConnectA2dpIterations = 100; + + public static String sHeadsetAddress = ""; + public static String sA2dpAddress = ""; @Override public TestSuite getAllTests() { @@ -69,5 +74,33 @@ public class BluetoothTestRunner extends InstrumentationTestRunner { // Invalid argument, fall back to default value } } + + val = arguments.getString("connect_a2dp_iterations"); + if (val != null) { + try { + sConnectA2dpIterations = Integer.parseInt(val); + } catch (NumberFormatException e) { + // Invalid argument, fall back to default value + } + } + + val = arguments.getString("connect_headset_iterations"); + if (val != null) { + try { + sConnectHeadsetIterations = Integer.parseInt(val); + } catch (NumberFormatException e) { + // Invalid argument, fall back to default value + } + } + + val = arguments.getString("headset_address"); + if (val != null) { + sHeadsetAddress = val; + } + + val = arguments.getString("a2dp_address"); + if (val != null) { + sA2dpAddress = val; + } } } diff --git a/core/tests/coretests/src/android/bluetooth/BluetoothTestUtils.java b/core/tests/coretests/src/android/bluetooth/BluetoothTestUtils.java index 82de5098e66b..e9311e08f2ea 100644 --- a/core/tests/coretests/src/android/bluetooth/BluetoothTestUtils.java +++ b/core/tests/coretests/src/android/bluetooth/BluetoothTestUtils.java @@ -16,6 +16,7 @@ package android.bluetooth; +import android.bluetooth.BluetoothHeadset.ServiceListener; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -57,6 +58,36 @@ public class BluetoothTestUtils extends Assert { */ private static final int CANCEL_DISCOVERY_TIMEOUT = 5000; + /** + * Timeout for {@link BluetoothDevice#createBond()} in ms. + */ + private static final int PAIR_TIMEOUT = 20000; + + /** + * Timeout for {@link BluetoothDevice#removeBond()} in ms. + */ + private static final int UNPAIR_TIMEOUT = 20000; + + /** + * Timeout for {@link BluetoothA2dp#connectSink(BluetoothDevice)} in ms. + */ + private static final int CONNECT_A2DP_TIMEOUT = 20000; + + /** + * Timeout for {@link BluetoothA2dp#disconnectSink(BluetoothDevice)} in ms. + */ + private static final int DISCONNECT_A2DP_TIMEOUT = 20000; + + /** + * Timeout for {@link BluetoothHeadset#connectHeadset(BluetoothDevice)} in ms. + */ + private static final int CONNECT_HEADSET_TIMEOUT = 20000; + + /** + * Timeout for {@link BluetoothHeadset#disconnectHeadset(BluetoothDevice)} in ms. + */ + private static final int DISCONNECT_HEADSET_TIMEOUT = 20000; + private static final int DISCOVERY_STARTED_FLAG = 1; private static final int DISCOVERY_FINISHED_FLAG = 1 << 1; private static final int SCAN_MODE_NONE_FLAG = 1 << 2; @@ -66,6 +97,23 @@ public class BluetoothTestUtils extends Assert { private static final int STATE_TURNING_ON_FLAG = 1 << 6; private static final int STATE_ON_FLAG = 1 << 7; private static final int STATE_TURNING_OFF_FLAG = 1 << 8; + private static final int PAIR_STATE_FLAG = 1 << 9; + private static final int PROFILE_A2DP_FLAG = 1 << 10; + private static final int PROFILE_HEADSET_FLAG = 1 << 11; + + private static final int PAIR_STATE_BONDED = 1; + private static final int PAIR_STATE_BONDING = 1 << 1; + private static final int PAIR_STATE_NONE = 1 << 2; + + private static final int A2DP_STATE_DISCONNECTED = 1; + private static final int A2DP_STATE_CONNECTING = 1 << 1; + private static final int A2DP_STATE_CONNECTED = 1 << 2; + private static final int A2DP_STATE_DISCONNECTING = 1 << 3; + private static final int A2DP_STATE_PLAYING = 1 << 4; + + private static final int HEADSET_STATE_DISCONNECTED = 1; + private static final int HEADSET_STATE_CONNECTING = 1 << 1; + private static final int HEADSET_STATE_CONNECTED = 1 << 2; /** * Time between polls in ms. @@ -76,11 +124,43 @@ public class BluetoothTestUtils extends Assert { private BufferedWriter mOutputWriter; + private BluetoothA2dp mA2dp; + + private BluetoothHeadset mHeadset; + private String mOutputFile; private String mTag; + private class HeadsetServiceListener implements ServiceListener { + private boolean mConnected = false; + + @Override + public void onServiceConnected() { + synchronized (this) { + mConnected = true; + } + } + + @Override + public void onServiceDisconnected() { + synchronized (this) { + mConnected = false; + } + } + + public boolean isConnected() { + synchronized (this) { + return mConnected; + } + } + } + + private HeadsetServiceListener mHeadsetServiceListener = new HeadsetServiceListener(); private class BluetoothReceiver extends BroadcastReceiver { private int mFiredFlags = 0; + private int mPairFiredFlags = 0; + private int mA2dpFiredFlags = 0; + private int mHeadsetFiredFlags = 0; @Override public void onReceive(Context context, Intent intent) { @@ -122,6 +202,58 @@ public class BluetoothTestUtils extends Assert { mFiredFlags |= STATE_TURNING_OFF_FLAG; break; } + } else if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(intent.getAction())) { + mFiredFlags |= PAIR_STATE_FLAG; + int state = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE, -1); + assertNotSame(state, -1); + switch (state) { + case BluetoothDevice.BOND_BONDED: + mPairFiredFlags |= PAIR_STATE_BONDED; + break; + case BluetoothDevice.BOND_BONDING: + mPairFiredFlags |= PAIR_STATE_BONDING; + break; + case BluetoothDevice.BOND_NONE: + mPairFiredFlags |= PAIR_STATE_NONE; + break; + } + } else if (BluetoothA2dp.ACTION_SINK_STATE_CHANGED.equals(intent.getAction())) { + mFiredFlags |= PROFILE_A2DP_FLAG; + int state = intent.getIntExtra(BluetoothA2dp.EXTRA_SINK_STATE, -1); + assertNotSame(state, -1); + switch (state) { + case BluetoothA2dp.STATE_DISCONNECTED: + mA2dpFiredFlags |= A2DP_STATE_DISCONNECTED; + break; + case BluetoothA2dp.STATE_CONNECTING: + mA2dpFiredFlags |= A2DP_STATE_CONNECTING; + break; + case BluetoothA2dp.STATE_CONNECTED: + mA2dpFiredFlags |= A2DP_STATE_CONNECTED; + break; + case BluetoothA2dp.STATE_DISCONNECTING: + mA2dpFiredFlags |= A2DP_STATE_DISCONNECTING; + break; + case BluetoothA2dp.STATE_PLAYING: + mA2dpFiredFlags |= A2DP_STATE_PLAYING; + break; + } + } else if (BluetoothHeadset.ACTION_STATE_CHANGED.equals(intent.getAction())) { + mFiredFlags |= PROFILE_HEADSET_FLAG; + int state = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, + BluetoothHeadset.STATE_ERROR); + assertNotSame(state, BluetoothHeadset.STATE_ERROR); + switch (state) { + case BluetoothHeadset.STATE_DISCONNECTED: + mHeadsetFiredFlags |= HEADSET_STATE_DISCONNECTED; + break; + case BluetoothHeadset.STATE_CONNECTING: + mHeadsetFiredFlags |= HEADSET_STATE_CONNECTING; + break; + case BluetoothHeadset.STATE_CONNECTED: + mHeadsetFiredFlags |= HEADSET_STATE_CONNECTED; + break; + } } } } @@ -132,9 +264,30 @@ public class BluetoothTestUtils extends Assert { } } + public int getPairFiredFlags() { + synchronized (this) { + return mPairFiredFlags; + } + } + + public int getA2dpFiredFlags() { + synchronized (this) { + return mA2dpFiredFlags; + } + } + + public int getHeadsetFiredFlags() { + synchronized (this) { + return mHeadsetFiredFlags; + } + } + public void resetFiredFlags() { synchronized (this) { mFiredFlags = 0; + mPairFiredFlags = 0; + mA2dpFiredFlags = 0; + mHeadsetFiredFlags = 0; } } } @@ -419,6 +572,351 @@ public class BluetoothTestUtils extends Assert { } + public void pair(BluetoothAdapter adapter, BluetoothDevice device) { + int mask = PAIR_STATE_FLAG; + int pairMask = PAIR_STATE_BONDING | PAIR_STATE_BONDED; + mReceiver.resetFiredFlags(); + + if (!adapter.isEnabled()) { + fail("pair() bluetooth not enabled"); + } + + int state = device.getBondState(); + switch (state) { + case BluetoothDevice.BOND_BONDED: + assertTrue(adapter.getBondedDevices().contains(device)); + return; + case BluetoothDevice.BOND_BONDING: + // Don't check for received intents since we might have missed them. + mask = pairMask = 0; + break; + case BluetoothDevice.BOND_NONE: + assertFalse(adapter.getBondedDevices().contains(device)); + assertTrue(device.createBond()); + break; + default: + fail("pair() invalide state: state=" + state); + } + + long s = System.currentTimeMillis(); + while (System.currentTimeMillis() - s < PAIR_TIMEOUT) { + state = device.getBondState(); + if (state == BluetoothDevice.BOND_BONDED) { + assertTrue(adapter.getBondedDevices().contains(device)); + if ((mReceiver.getFiredFlags() & mask) == mask + && (mReceiver.getPairFiredFlags() & pairMask) == pairMask) { + writeOutput(String.format("pair() completed in %d ms: device=%s", + (System.currentTimeMillis() - s), device)); + return; + } + } + sleep(POLL_TIME); + } + + int firedFlags = mReceiver.getFiredFlags(); + int pairFiredFlags = mReceiver.getPairFiredFlags(); + mReceiver.resetFiredFlags(); + fail(String.format("pair() timeout: state=%d (expected %d), flags=0x%x (expected 0x%x), " + + "pairFlags=0x%x (expected 0x%x)", state, BluetoothDevice.BOND_BONDED, firedFlags, + mask, pairFiredFlags, pairMask)); + } + + public void unpair(BluetoothAdapter adapter, BluetoothDevice device) { + int mask = PAIR_STATE_FLAG; + int pairMask = PAIR_STATE_NONE; + mReceiver.resetFiredFlags(); + + if (!adapter.isEnabled()) { + fail("unpair() bluetooth not enabled"); + } + + int state = device.getBondState(); + switch (state) { + case BluetoothDevice.BOND_BONDED: + assertTrue(adapter.getBondedDevices().contains(device)); + assertTrue(device.removeBond()); + break; + case BluetoothDevice.BOND_BONDING: + assertTrue(device.removeBond()); + break; + case BluetoothDevice.BOND_NONE: + assertFalse(adapter.getBondedDevices().contains(device)); + return; + default: + fail("unpair() invalid state: state=" + state); + } + + assertTrue(device.removeBond()); + + long s = System.currentTimeMillis(); + while (System.currentTimeMillis() - s < UNPAIR_TIMEOUT) { + if (device.getBondState() == BluetoothDevice.BOND_NONE) { + assertFalse(adapter.getBondedDevices().contains(device)); + if ((mReceiver.getFiredFlags() & mask) == mask + && (mReceiver.getPairFiredFlags() & pairMask) == pairMask) { + writeOutput(String.format("unpair() completed in %d ms: device=%s", + (System.currentTimeMillis() - s), device)); + return; + } + } + } + + int firedFlags = mReceiver.getFiredFlags(); + int pairFiredFlags = mReceiver.getPairFiredFlags(); + mReceiver.resetFiredFlags(); + fail(String.format("unpair() timeout: state=%d (expected %d), flags=0x%x (expected 0x%x), " + + "pairFlags=0x%x (expected 0x%x)", state, BluetoothDevice.BOND_BONDED, firedFlags, + mask, pairFiredFlags, pairMask)); + } + + public void connectA2dp(BluetoothAdapter adapter, BluetoothDevice device) { + int mask = PROFILE_A2DP_FLAG; + int a2dpMask1 = A2DP_STATE_CONNECTING | A2DP_STATE_CONNECTED | A2DP_STATE_PLAYING; + int a2dpMask2 = a2dpMask1 ^ A2DP_STATE_CONNECTED; + int a2dpMask3 = a2dpMask1 ^ A2DP_STATE_PLAYING; + mReceiver.resetFiredFlags(); + + if (!adapter.isEnabled()) { + fail("connectA2dp() bluetooth not enabled"); + } + + if (!adapter.getBondedDevices().contains(device)) { + fail("connectA2dp() device not paired: device=" + device); + } + + int state = mA2dp.getSinkState(device); + switch (state) { + case BluetoothA2dp.STATE_CONNECTED: + case BluetoothA2dp.STATE_PLAYING: + assertTrue(mA2dp.isSinkConnected(device)); + return; + case BluetoothA2dp.STATE_DISCONNECTING: + case BluetoothA2dp.STATE_DISCONNECTED: + assertFalse(mA2dp.isSinkConnected(device)); + assertTrue(mA2dp.connectSink(device)); + break; + case BluetoothA2dp.STATE_CONNECTING: + assertFalse(mA2dp.isSinkConnected(device)); + // Don't check for received intents since we might have missed them. + mask = a2dpMask1 = a2dpMask2 = a2dpMask3 = 0; + break; + default: + fail("connectA2dp() invalid state: state=" + state); + } + + long s = System.currentTimeMillis(); + while (System.currentTimeMillis() - s < CONNECT_A2DP_TIMEOUT) { + state = mA2dp.getSinkState(device); + if (state == BluetoothA2dp.STATE_CONNECTED || state == BluetoothA2dp.STATE_PLAYING) { + assertTrue(mA2dp.isSinkConnected(device)); + // Check whether STATE_CONNECTING and (STATE_CONNECTED or STATE_PLAYING) intents + // have fired if we are checking if intents should be fired. + int firedFlags = mReceiver.getFiredFlags(); + int a2dpFiredFlags = mReceiver.getA2dpFiredFlags(); + if ((mReceiver.getFiredFlags() & mask) == mask + && ((a2dpFiredFlags & a2dpMask1) == a2dpMask1 + || (a2dpFiredFlags & a2dpMask2) == a2dpMask2 + || (a2dpFiredFlags & a2dpMask3) == a2dpMask3)) { + mReceiver.resetFiredFlags(); + writeOutput(String.format("connectA2dp() completed in %d ms: device=%s", + (System.currentTimeMillis() - s), device)); + return; + } + } + sleep(POLL_TIME); + } + + int firedFlags = mReceiver.getFiredFlags(); + int a2dpFiredFlags = mReceiver.getA2dpFiredFlags(); + mReceiver.resetFiredFlags(); + fail(String.format("connectA2dp() timeout: state=%d (expected %d or %d), " + + "flags=0x%x (expected 0x%x), a2dpFlags=0x%x (expected 0x%x or 0x%x or 0x%x)", + state, BluetoothHeadset.STATE_CONNECTED, BluetoothA2dp.STATE_PLAYING, firedFlags, + mask, a2dpFiredFlags, a2dpMask1, a2dpMask2, a2dpMask3)); + } + + public void disconnectA2dp(BluetoothAdapter adapter, BluetoothDevice device) { + int mask = PROFILE_A2DP_FLAG; + int a2dpMask = A2DP_STATE_DISCONNECTING | A2DP_STATE_DISCONNECTED; + mReceiver.resetFiredFlags(); + + if (!adapter.isEnabled()) { + fail("disconnectA2dp() bluetooth not enabled"); + } + + if (!adapter.getBondedDevices().contains(device)) { + fail("disconnectA2dp() device not paired: device=" + device); + } + + int state = mA2dp.getSinkState(device); + switch (state) { + case BluetoothA2dp.STATE_DISCONNECTED: + assertFalse(mA2dp.isSinkConnected(device)); + return; + case BluetoothA2dp.STATE_CONNECTED: + case BluetoothA2dp.STATE_PLAYING: + assertTrue(mA2dp.isSinkConnected(device)); + assertTrue(mA2dp.disconnectSink(device)); + break; + case BluetoothA2dp.STATE_CONNECTING: + assertFalse(mA2dp.isSinkConnected(device)); + assertTrue(mA2dp.disconnectSink(device)); + break; + case BluetoothA2dp.STATE_DISCONNECTING: + assertFalse(mA2dp.isSinkConnected(device)); + // Don't check for received intents since we might have missed them. + mask = a2dpMask = 0; + break; + default: + fail("disconnectA2dp() invalid state: state=" + state); + } + + long s = System.currentTimeMillis(); + while (System.currentTimeMillis() - s < DISCONNECT_A2DP_TIMEOUT) { + state = mA2dp.getSinkState(device); + if (state == BluetoothA2dp.STATE_DISCONNECTED) { + assertFalse(mA2dp.isSinkConnected(device)); + if ((mReceiver.getFiredFlags() & mask) == mask + && (mReceiver.getA2dpFiredFlags() & a2dpMask) == a2dpMask) { + mReceiver.resetFiredFlags(); + writeOutput(String.format("disconnectA2dp() completed in %d ms: device=%s", + (System.currentTimeMillis() - s), device)); + return; + } + } + sleep(POLL_TIME); + } + + int firedFlags = mReceiver.getFiredFlags(); + int a2dpFiredFlags = mReceiver.getA2dpFiredFlags(); + mReceiver.resetFiredFlags(); + fail(String.format("disconnectA2dp() timeout: state=%d (expected %d), " + + "flags=0x%x (expected 0x%x), a2dpFlags=0x%x (expected 0x%x)", state, + BluetoothA2dp.STATE_DISCONNECTED, firedFlags, mask, a2dpFiredFlags, a2dpMask)); + } + + public void connectHeadset(BluetoothAdapter adapter, BluetoothDevice device) { + int mask = PROFILE_HEADSET_FLAG; + int headsetMask = HEADSET_STATE_CONNECTING | HEADSET_STATE_CONNECTED; + mReceiver.resetFiredFlags(); + + if (!adapter.isEnabled()) { + fail("connectHeadset() bluetooth not enabled"); + } + + if (!adapter.getBondedDevices().contains(device)) { + fail("connectHeadset() device not paired: device=" + device); + } + + while (!mHeadsetServiceListener.isConnected()) { + sleep(POLL_TIME); + } + + int state = mHeadset.getState(device); + switch (state) { + case BluetoothHeadset.STATE_CONNECTED: + assertTrue(mHeadset.isConnected(device)); + return; + case BluetoothHeadset.STATE_DISCONNECTED: + assertFalse(mHeadset.isConnected(device)); + mHeadset.connectHeadset(device); + break; + case BluetoothHeadset.STATE_CONNECTING: + assertFalse(mHeadset.isConnected(device)); + // Don't check for received intents since we might have missed them. + mask = headsetMask = 0; + break; + case BluetoothHeadset.STATE_ERROR: + fail("connectHeadset() error state"); + break; + default: + fail("connectHeadset() invalid state: state=" + state); + } + + long s = System.currentTimeMillis(); + while (System.currentTimeMillis() - s < CONNECT_HEADSET_TIMEOUT) { + state = mHeadset.getState(device); + if (state == BluetoothHeadset.STATE_CONNECTED) { + assertTrue(mHeadset.isConnected(device)); + if ((mReceiver.getFiredFlags() & mask) == mask + && (mReceiver.getHeadsetFiredFlags() & headsetMask) == headsetMask) { + mReceiver.resetFiredFlags(); + writeOutput(String.format("connectHeadset() completed in %d ms: device=%s", + (System.currentTimeMillis() - s), device)); + return; + } + } + sleep(POLL_TIME); + } + + int firedFlags = mReceiver.getFiredFlags(); + int headsetFiredFlags = mReceiver.getHeadsetFiredFlags(); + mReceiver.resetFiredFlags(); + fail(String.format("connectHeadset() timeout: state=%d (expected %d), " + + "flags=0x%x (expected 0x%x), headsetFlags=0x%s (expected 0x%x)", state, + BluetoothHeadset.STATE_CONNECTED, firedFlags, mask, headsetFiredFlags, + headsetMask)); + } + + public void disconnectHeadset(BluetoothAdapter adapter, BluetoothDevice device) { + int mask = PROFILE_HEADSET_FLAG; + int headsetMask = HEADSET_STATE_DISCONNECTED; + mReceiver.resetFiredFlags(); + + if (!adapter.isEnabled()) { + fail("disconnectHeadset() bluetooth not enabled"); + } + + if (!adapter.getBondedDevices().contains(device)) { + fail("disconnectHeadset() device not paired: device=" + device); + } + + while (!mHeadsetServiceListener.isConnected()) { + sleep(POLL_TIME); + } + + int state = mHeadset.getState(device); + switch (state) { + case BluetoothHeadset.STATE_CONNECTED: + mHeadset.disconnectHeadset(device); + break; + case BluetoothHeadset.STATE_CONNECTING: + mHeadset.disconnectHeadset(device); + break; + case BluetoothHeadset.STATE_DISCONNECTED: + return; + case BluetoothHeadset.STATE_ERROR: + fail("disconnectHeadset() error state"); + break; + default: + fail("disconnectHeadset() invalid state: state=" + state); + } + + long s = System.currentTimeMillis(); + while (System.currentTimeMillis() - s < DISCONNECT_HEADSET_TIMEOUT) { + state = mHeadset.getState(device); + if (state == BluetoothHeadset.STATE_DISCONNECTED) { + assertFalse(mHeadset.isConnected(device)); + if ((mReceiver.getFiredFlags() & mask) == mask + && (mReceiver.getHeadsetFiredFlags() & headsetMask) == headsetMask) { + mReceiver.resetFiredFlags(); + writeOutput(String.format("disconnectHeadset() completed in %d ms: device=%s", + (System.currentTimeMillis() - s), device)); + return; + } + } + sleep(POLL_TIME); + } + + int firedFlags = mReceiver.getFiredFlags(); + int headsetFiredFlags = mReceiver.getHeadsetFiredFlags(); + mReceiver.resetFiredFlags(); + fail(String.format("disconnectHeadset() timeout: state=%d (expected %d), " + + "flags=0x%x (expected 0x%x), headsetFlags=0x%s (expected 0x%x)", state, + BluetoothHeadset.STATE_DISCONNECTED, firedFlags, mask, headsetFiredFlags, + headsetMask)); + } + public void writeOutput(String s) { Log.i(mTag, s); if (mOutputWriter == null) { diff --git a/core/tests/coretests/src/android/content/BrickDeniedTest.java b/core/tests/coretests/src/android/content/BrickDeniedTest.java index c7d0b7acab66..3d246b457ed5 100644 --- a/core/tests/coretests/src/android/content/BrickDeniedTest.java +++ b/core/tests/coretests/src/android/content/BrickDeniedTest.java @@ -16,13 +16,12 @@ package android.content; -import android.content.Intent; import android.test.AndroidTestCase; -import android.test.suitebuilder.annotation.MediumTest; +import android.test.suitebuilder.annotation.SmallTest; /** Test to make sure brick intents <b>don't</b> work without permission. */ public class BrickDeniedTest extends AndroidTestCase { - @MediumTest + @SmallTest public void testBrick() { // Try both the old and new brick intent names. Neither should work, // since this test application doesn't have the required permission. diff --git a/core/tests/coretests/src/android/content/MemoryFileProviderTest.java b/core/tests/coretests/src/android/content/MemoryFileProviderTest.java index 6708af6e83d8..62b4e7e1ce96 100644 --- a/core/tests/coretests/src/android/content/MemoryFileProviderTest.java +++ b/core/tests/coretests/src/android/content/MemoryFileProviderTest.java @@ -16,10 +16,11 @@ package android.content; -import android.content.ContentResolver; import android.net.Uri; import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.LargeTest; import android.test.suitebuilder.annotation.MediumTest; +import android.test.suitebuilder.annotation.SmallTest; import java.io.InputStream; import java.util.Arrays; @@ -46,7 +47,7 @@ public class MemoryFileProviderTest extends AndroidTestCase { } // tests that we don't leak file descriptors or virtual address space - @MediumTest + @LargeTest public void testClose() throws Exception { ContentResolver resolver = getContext().getContentResolver(); // open enough file descriptors that we will crash something if we leak FDs @@ -65,7 +66,7 @@ public class MemoryFileProviderTest extends AndroidTestCase { } // tests that we haven't broken AssestFileDescriptors for normal files. - @MediumTest + @SmallTest public void testFile() throws Exception { ContentResolver resolver = getContext().getContentResolver(); Uri uri = Uri.parse("content://android.content.MemoryFileProvider/file"); diff --git a/core/tests/coretests/src/android/content/SyncStorageEngineTest.java b/core/tests/coretests/src/android/content/SyncStorageEngineTest.java index f84051276bc0..0b494a768552 100644 --- a/core/tests/coretests/src/android/content/SyncStorageEngineTest.java +++ b/core/tests/coretests/src/android/content/SyncStorageEngineTest.java @@ -18,17 +18,19 @@ package android.content; import com.android.internal.os.AtomicFile; +import android.accounts.Account; +import android.os.Bundle; import android.test.AndroidTestCase; import android.test.RenamingDelegatingContext; -import android.test.suitebuilder.annotation.SmallTest; -import android.test.mock.MockContext; import android.test.mock.MockContentResolver; -import android.accounts.Account; -import android.os.Bundle; +import android.test.mock.MockContext; +import android.test.suitebuilder.annotation.LargeTest; +import android.test.suitebuilder.annotation.MediumTest; +import android.test.suitebuilder.annotation.SmallTest; -import java.util.List; import java.io.File; import java.io.FileOutputStream; +import java.util.List; public class SyncStorageEngineTest extends AndroidTestCase { @@ -57,7 +59,7 @@ public class SyncStorageEngineTest extends AndroidTestCase { /** * Test that we can create, remove and retrieve periodic syncs */ - @SmallTest + @MediumTest public void testPeriodics() throws Exception { final Account account1 = new Account("a@example.com", "example.type"); final Account account2 = new Account("b@example.com", "example.type.2"); @@ -114,7 +116,7 @@ public class SyncStorageEngineTest extends AndroidTestCase { } } - @SmallTest + @LargeTest public void testAuthorityPersistence() throws Exception { final Account account1 = new Account("a@example.com", "example.type"); final Account account2 = new Account("b@example.com", "example.type.2"); @@ -197,7 +199,7 @@ public class SyncStorageEngineTest extends AndroidTestCase { assertEquals(0, engine.getIsSyncable(account2, authority2)); } - @SmallTest + @MediumTest public void testAuthorityParsing() throws Exception { final Account account = new Account("account1", "type1"); final String authority1 = "auth1"; @@ -299,7 +301,7 @@ public class SyncStorageEngineTest extends AndroidTestCase { assertEquals(sync3s, syncs.get(0)); } - @SmallTest + @MediumTest public void testAuthorityRenaming() throws Exception { final Account account1 = new Account("acc1", "type1"); final Account account2 = new Account("acc2", "type2"); diff --git a/core/tests/coretests/src/android/content/pm/AppCacheTest.java b/core/tests/coretests/src/android/content/pm/AppCacheTest.java index dbb10b1ba36d..dd252203b808 100755 --- a/core/tests/coretests/src/android/content/pm/AppCacheTest.java +++ b/core/tests/coretests/src/android/content/pm/AppCacheTest.java @@ -16,32 +16,25 @@ package android.content.pm; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; - import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.content.pm.ApplicationInfo; -import android.content.pm.IPackageDataObserver; -import android.content.pm.IPackageStatsObserver; -import android.content.pm.PackageStats; -import android.content.pm.IPackageManager; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.StatFs; import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.LargeTest; import android.test.suitebuilder.annotation.MediumTest; import android.test.suitebuilder.annotation.SmallTest; -import android.test.suitebuilder.annotation.Suppress; import android.util.Log; -import android.os.Handler; -import android.os.RemoteException; -import android.os.ServiceManager; -import android.os.StatFs; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; public class AppCacheTest extends AndroidTestCase { private static final boolean localLOGV = false; @@ -627,7 +620,8 @@ public class AppCacheTest extends AndroidTestCase { } } - @SmallTest + // TODO: flaky test, omit from LargeTest for now + //@LargeTest public void testFreeStorage() throws Exception { boolean TRACKING = true; StatFs st = new StatFs("/data"); diff --git a/core/tests/coretests/src/android/content/pm/ComponentTest.java b/core/tests/coretests/src/android/content/pm/ComponentTest.java index ebfbd683fcfb..f1a2a9bd172e 100644 --- a/core/tests/coretests/src/android/content/pm/ComponentTest.java +++ b/core/tests/coretests/src/android/content/pm/ComponentTest.java @@ -16,6 +16,11 @@ package android.content.pm; +import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; +import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED; +import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED; +import static android.content.pm.PackageManager.GET_DISABLED_COMPONENTS; + import com.android.frameworks.coretests.enabled_app.DisabledActivity; import com.android.frameworks.coretests.enabled_app.DisabledProvider; import com.android.frameworks.coretests.enabled_app.DisabledReceiver; @@ -27,21 +32,9 @@ import com.android.frameworks.coretests.enabled_app.EnabledService; import android.content.ComponentName; import android.content.Intent; -import android.content.pm.ActivityInfo; -import android.content.pm.ComponentInfo; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.test.suitebuilder.annotation.LargeTest; +import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.MediumTest; import android.test.suitebuilder.annotation.SmallTest; -import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; -import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED; -import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED; -import static android.content.pm.PackageManager.GET_DISABLED_COMPONENTS; -import android.content.pm.ProviderInfo; -import android.content.pm.ResolveInfo; -import android.content.pm.ServiceInfo; -import android.test.AndroidTestCase; import java.util.List; @@ -134,7 +127,7 @@ public class ComponentTest extends AndroidTestCase { assertNotNull(mContext); } - @MediumTest + @SmallTest public void testResolveDisabledActivity() throws Exception { mPackageManager.setComponentEnabledSetting(DISABLED_ACTIVITY_COMPONENTNAME, COMPONENT_ENABLED_STATE_DEFAULT, @@ -150,7 +143,7 @@ public class ComponentTest extends AndroidTestCase { assertFalse(info2.activityInfo.enabled); } - @MediumTest + @SmallTest public void testResolveEnabledActivity() throws Exception { mPackageManager.setComponentEnabledSetting(ENABLED_ACTIVITY_COMPONENTNAME, COMPONENT_ENABLED_STATE_DEFAULT, @@ -183,7 +176,7 @@ public class ComponentTest extends AndroidTestCase { assertFalse(info.activityInfo.enabled); } - @MediumTest + @SmallTest public void testQueryEnabledActivity() throws Exception { mPackageManager.setComponentEnabledSetting(ENABLED_ACTIVITY_COMPONENTNAME, COMPONENT_ENABLED_STATE_DEFAULT, @@ -218,7 +211,7 @@ public class ComponentTest extends AndroidTestCase { assertFalse(activityInfo.enabled); } - @MediumTest + @SmallTest public void testGetEnabledActivityInfo() throws Exception { mPackageManager.setComponentEnabledSetting(ENABLED_ACTIVITY_COMPONENTNAME, COMPONENT_ENABLED_STATE_DEFAULT, @@ -253,7 +246,7 @@ public class ComponentTest extends AndroidTestCase { assertEquals(1, infoList.size()); } - @LargeTest + @MediumTest public void testDisableActivity() throws Exception { mPackageManager.setComponentEnabledSetting(ENABLED_ACTIVITY_COMPONENTNAME, COMPONENT_ENABLED_STATE_DEFAULT, @@ -281,7 +274,7 @@ public class ComponentTest extends AndroidTestCase { assertEquals(0, infoList.size()); } - @MediumTest + @SmallTest public void testResolveDisabledService() throws Exception { mPackageManager.setComponentEnabledSetting(DISABLED_SERVICE_COMPONENTNAME, COMPONENT_ENABLED_STATE_DEFAULT, @@ -297,7 +290,7 @@ public class ComponentTest extends AndroidTestCase { assertFalse(info2.serviceInfo.enabled); } - @MediumTest + @SmallTest public void testResolveEnabledService() throws Exception { mPackageManager.setComponentEnabledSetting(ENABLED_SERVICE_COMPONENTNAME, COMPONENT_ENABLED_STATE_DEFAULT, @@ -310,7 +303,7 @@ public class ComponentTest extends AndroidTestCase { assertTrue(info.serviceInfo.enabled); } - @MediumTest + @SmallTest public void testQueryDisabledService() throws Exception { mPackageManager.setComponentEnabledSetting(DISABLED_SERVICE_COMPONENTNAME, COMPONENT_ENABLED_STATE_DEFAULT, @@ -330,7 +323,7 @@ public class ComponentTest extends AndroidTestCase { assertFalse(info.serviceInfo.enabled); } - @MediumTest + @SmallTest public void testQueryEnabledService() throws Exception { mPackageManager.setComponentEnabledSetting(ENABLED_SERVICE_COMPONENTNAME, COMPONENT_ENABLED_STATE_DEFAULT, @@ -365,7 +358,7 @@ public class ComponentTest extends AndroidTestCase { assertFalse(serviceInfo.enabled); } - @MediumTest + @SmallTest public void testGetEnabledServiceInfo() throws Exception { mPackageManager.setComponentEnabledSetting(ENABLED_SERVICE_COMPONENTNAME, COMPONENT_ENABLED_STATE_DEFAULT, @@ -396,7 +389,7 @@ public class ComponentTest extends AndroidTestCase { assertFalse(info2.serviceInfo.enabled); } - @LargeTest + @MediumTest public void testDisableService() throws Exception { mPackageManager.setComponentEnabledSetting(ENABLED_SERVICE_COMPONENTNAME, COMPONENT_ENABLED_STATE_DEFAULT, @@ -420,7 +413,7 @@ public class ComponentTest extends AndroidTestCase { assertTrue(info3.serviceInfo.enabled); } - @MediumTest + @SmallTest public void testQueryDisabledReceiver() throws Exception { mPackageManager.setComponentEnabledSetting(DISABLED_RECEIVER_COMPONENTNAME, COMPONENT_ENABLED_STATE_DEFAULT, @@ -440,7 +433,7 @@ public class ComponentTest extends AndroidTestCase { assertFalse(info.activityInfo.enabled); } - @MediumTest + @SmallTest public void testQueryEnabledReceiver() throws Exception { mPackageManager.setComponentEnabledSetting(ENABLED_RECEIVER_COMPONENTNAME, COMPONENT_ENABLED_STATE_DEFAULT, @@ -475,7 +468,7 @@ public class ComponentTest extends AndroidTestCase { assertFalse(activityInfo.enabled); } - @MediumTest + @SmallTest public void testGetEnabledReceiverInfo() throws Exception { mPackageManager.setComponentEnabledSetting(ENABLED_RECEIVER_COMPONENTNAME, COMPONENT_ENABLED_STATE_DEFAULT, @@ -530,7 +523,7 @@ public class ComponentTest extends AndroidTestCase { } } - @MediumTest + @SmallTest public void testResolveEnabledProvider() throws Exception { mPackageManager.setComponentEnabledSetting(ENABLED_PROVIDER_COMPONENTNAME, COMPONENT_ENABLED_STATE_DEFAULT, @@ -542,7 +535,7 @@ public class ComponentTest extends AndroidTestCase { assertTrue(providerInfo.enabled); } - @MediumTest + @SmallTest public void testResolveDisabledProvider() throws Exception { mPackageManager.setComponentEnabledSetting(DISABLED_PROVIDER_COMPONENTNAME, COMPONENT_ENABLED_STATE_DEFAULT, @@ -594,7 +587,7 @@ public class ComponentTest extends AndroidTestCase { assertNull(providerInfo2); } - @MediumTest + @SmallTest public void testQueryEnabledProvider() throws Exception { mPackageManager.setComponentEnabledSetting(ENABLED_PROVIDER_COMPONENTNAME, COMPONENT_ENABLED_STATE_DEFAULT, diff --git a/core/tests/coretests/src/android/content/pm/PackageManagerTests.java b/core/tests/coretests/src/android/content/pm/PackageManagerTests.java index 1f2e97c738c2..975a4c2ddc7b 100755 --- a/core/tests/coretests/src/android/content/pm/PackageManagerTests.java +++ b/core/tests/coretests/src/android/content/pm/PackageManagerTests.java @@ -23,22 +23,10 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.content.pm.ApplicationInfo; -import android.content.pm.IPackageMoveObserver; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.content.pm.PackageParser; import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Resources; import android.content.res.Resources.NotFoundException; import android.net.Uri; -import android.test.AndroidTestCase; -import android.test.suitebuilder.annotation.SmallTest; -import android.test.suitebuilder.annotation.MediumTest; -import android.test.suitebuilder.annotation.LargeTest; -import android.test.suitebuilder.annotation.Suppress; -import android.util.DisplayMetrics; -import android.util.Log; import android.os.Environment; import android.os.FileUtils; import android.os.IBinder; @@ -52,6 +40,7 @@ import android.os.storage.StorageResultCode; import android.provider.Settings; import android.provider.Settings.SettingNotFoundException; import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.LargeTest; import android.util.DisplayMetrics; import android.util.Log; @@ -80,7 +69,7 @@ public class PackageManagerTests extends AndroidTestCase { @Override protected void setUp() throws Exception { super.setUp(); - mOrigState = getMediaState(); + mOrigState = checkMediaState(Environment.MEDIA_MOUNTED); if (!mountMedia()) { Log.i(TAG, "sdcard not mounted? Some of these tests might fail"); } @@ -89,12 +78,12 @@ public class PackageManagerTests extends AndroidTestCase { @Override protected void tearDown() throws Exception { // Restore media state. - boolean newState = getMediaState(); + boolean newState = checkMediaState(Environment.MEDIA_MOUNTED); if (newState != mOrigState) { if (mOrigState) { - getMs().mountVolume(Environment.getExternalStorageDirectory().getPath()); + mountMedia(); } else { - getMs().unmountVolume(Environment.getExternalStorageDirectory().getPath(), true); + unmountMedia(); } } super.tearDown(); @@ -369,6 +358,7 @@ public class PackageManagerTests extends AndroidTestCase { assertTrue((info.flags & ApplicationInfo.FLAG_FORWARD_LOCK) != 0); assertEquals(srcPath, drmInstallPath); assertEquals(publicSrcPath, appInstallPath); + assertTrue(info.nativeLibraryDir.startsWith(dataDir.getPath())); } else { assertFalse((info.flags & ApplicationInfo.FLAG_FORWARD_LOCK) != 0); int rLoc = getInstallLoc(flags, expInstallLocation, pkgLen); @@ -376,10 +366,12 @@ public class PackageManagerTests extends AndroidTestCase { assertEquals(srcPath, appInstallPath); assertEquals(publicSrcPath, appInstallPath); assertFalse((info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0); + assertTrue(info.nativeLibraryDir.startsWith(dataDir.getPath())); } else if (rLoc == INSTALL_LOC_SD){ assertTrue((info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0); assertTrue(srcPath.startsWith(SECURE_CONTAINERS_PREFIX)); assertTrue(publicSrcPath.startsWith(SECURE_CONTAINERS_PREFIX)); + assertTrue(info.nativeLibraryDir.startsWith(SECURE_CONTAINERS_PREFIX)); } else { // TODO handle error. Install should have failed. } @@ -575,18 +567,19 @@ public class PackageManagerTests extends AndroidTestCase { return ip; } - @MediumTest + @LargeTest public void testInstallNormalInternal() { sampleInstallFromRawResource(0, true); } - @MediumTest + @LargeTest public void testInstallFwdLockedInternal() { sampleInstallFromRawResource(PackageManager.INSTALL_FORWARD_LOCK, true); } - @MediumTest + @LargeTest public void testInstallSdcard() { + mountMedia(); sampleInstallFromRawResource(PackageManager.INSTALL_EXTERNAL, true); } @@ -675,33 +668,33 @@ public class PackageManagerTests extends AndroidTestCase { } } - @MediumTest + @LargeTest public void testReplaceFailNormalInternal() { sampleReplaceFromRawResource(0); } - @MediumTest + @LargeTest public void testReplaceFailFwdLockedInternal() { sampleReplaceFromRawResource(PackageManager.INSTALL_FORWARD_LOCK); } - @MediumTest + @LargeTest public void testReplaceFailSdcard() { sampleReplaceFromRawResource(PackageManager.INSTALL_EXTERNAL); } - @MediumTest + @LargeTest public void testReplaceNormalInternal() { sampleReplaceFromRawResource(PackageManager.INSTALL_REPLACE_EXISTING); } - @MediumTest + @LargeTest public void testReplaceFwdLockedInternal() { sampleReplaceFromRawResource(PackageManager.INSTALL_REPLACE_EXISTING | PackageManager.INSTALL_FORWARD_LOCK); } - @MediumTest + @LargeTest public void testReplaceSdcard() { sampleReplaceFromRawResource(PackageManager.INSTALL_REPLACE_EXISTING | PackageManager.INSTALL_EXTERNAL); @@ -815,32 +808,32 @@ public class PackageManagerTests extends AndroidTestCase { } } - @MediumTest + @LargeTest public void testDeleteNormalInternal() { deleteFromRawResource(0, 0); } - @MediumTest + @LargeTest public void testDeleteFwdLockedInternal() { deleteFromRawResource(PackageManager.INSTALL_FORWARD_LOCK, 0); } - @MediumTest + @LargeTest public void testDeleteSdcard() { deleteFromRawResource(PackageManager.INSTALL_EXTERNAL, 0); } - @MediumTest + @LargeTest public void testDeleteNormalInternalRetainData() { deleteFromRawResource(0, PackageManager.DONT_DELETE_DATA); } - @MediumTest + @LargeTest public void testDeleteFwdLockedInternalRetainData() { deleteFromRawResource(PackageManager.INSTALL_FORWARD_LOCK, PackageManager.DONT_DELETE_DATA); } - @MediumTest + @LargeTest public void testDeleteSdcardRetainData() { deleteFromRawResource(PackageManager.INSTALL_EXTERNAL, PackageManager.DONT_DELETE_DATA); } @@ -924,41 +917,62 @@ public class PackageManagerTests extends AndroidTestCase { return null; } - boolean getMediaState() { + boolean checkMediaState(String desired) { try { - String mPath = Environment.getExternalStorageDirectory().getPath(); - String state = getMs().getVolumeState(mPath); - return Environment.MEDIA_MOUNTED.equals(state); + String mPath = Environment.getExternalStorageDirectory().getPath(); + String actual = getMs().getVolumeState(mPath); + if (desired.equals(actual)) { + return true; + } else { + return false; + } } catch (RemoteException e) { + Log.e(TAG, "Exception while checking media state", e); return false; } } boolean mountMedia() { - if (getMediaState()) { + if (checkMediaState(Environment.MEDIA_MOUNTED)) { return true; } + + final String path = Environment.getExternalStorageDirectory().toString(); + StorageListener observer = new StorageListener(Environment.MEDIA_MOUNTED); + StorageManager sm = (StorageManager) mContext.getSystemService(Context.STORAGE_SERVICE); + sm.registerListener(observer); try { - String mPath = Environment.getExternalStorageDirectory().toString(); - int ret = getMs().mountVolume(mPath); - return ret == StorageResultCode.OperationSucceeded; - } catch (RemoteException e) { + // Wait on observer + synchronized (observer) { + int ret = getMs().mountVolume(path); + if (ret != StorageResultCode.OperationSucceeded) { + throw new Exception("Could not mount the media"); + } + long waitTime = 0; + while ((!observer.isDone()) && (waitTime < MAX_WAIT_TIME)) { + observer.wait(WAIT_TIME_INCR); + waitTime += WAIT_TIME_INCR; + } + if (!observer.isDone()) { + throw new Exception("Timed out waiting for unmount media notification"); + } + return true; + } + } catch (Exception e) { + Log.e(TAG, "Exception : " + e); return false; + } finally { + sm.unregisterListener(observer); } } private boolean unmountMedia() { - String path = Environment.getExternalStorageDirectory().getPath(); - try { - String state = getMs().getVolumeState(path); - if (Environment.MEDIA_UNMOUNTED.equals(state)) { - return true; - } - } catch (RemoteException e) { - failStr(e); + if (checkMediaState(Environment.MEDIA_UNMOUNTED)) { + return true; } - StorageListener observer = new StorageListener(); + final String path = Environment.getExternalStorageDirectory().getPath(); + StorageListener observer = new StorageListener(Environment.MEDIA_UNMOUNTED); StorageManager sm = (StorageManager) mContext.getSystemService(Context.STORAGE_SERVICE); sm.registerListener(observer); try { @@ -987,7 +1001,7 @@ public class PackageManagerTests extends AndroidTestCase { // Install pkg on sdcard InstallParams ip = sampleInstallFromRawResource(PackageManager.INSTALL_EXTERNAL, false); if (localLOGV) Log.i(TAG, "Installed pkg on sdcard"); - boolean origState = getMediaState(); + boolean origState = checkMediaState(Environment.MEDIA_MOUNTED); boolean registeredReceiver = false; SdMountReceiver receiver = new SdMountReceiver(new String[]{ip.pkg.packageName}); try { @@ -1040,7 +1054,7 @@ public class PackageManagerTests extends AndroidTestCase { * (Use PackageManagerService private api for now) * Make sure the installed package is available. */ - @MediumTest + @LargeTest public void testMountSdNormalInternal() { assertTrue(mountFromRawResource()); } @@ -1071,31 +1085,31 @@ public class PackageManagerTests extends AndroidTestCase { } catch (NameNotFoundException e) {} } - @MediumTest + @LargeTest public void testManifestInstallLocationInternal() { installFromRawResource("install.apk", R.raw.install_loc_internal, 0, true, false, -1, PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY); } - @MediumTest + @LargeTest public void testManifestInstallLocationSdcard() { installFromRawResource("install.apk", R.raw.install_loc_sdcard, 0, true, false, -1, PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL); } - @MediumTest + @LargeTest public void testManifestInstallLocationAuto() { installFromRawResource("install.apk", R.raw.install_loc_auto, 0, true, false, -1, PackageInfo.INSTALL_LOCATION_AUTO); } - @MediumTest + @LargeTest public void testManifestInstallLocationUnspecified() { installFromRawResource("install.apk", R.raw.install_loc_unspecified, 0, true, false, -1, PackageInfo.INSTALL_LOCATION_UNSPECIFIED); } - @MediumTest + @LargeTest public void testManifestInstallLocationFwdLockedFlagSdcard() { installFromRawResource("install.apk", R.raw.install_loc_unspecified, PackageManager.INSTALL_FORWARD_LOCK | @@ -1104,7 +1118,7 @@ public class PackageManagerTests extends AndroidTestCase { PackageInfo.INSTALL_LOCATION_UNSPECIFIED); } - @MediumTest + @LargeTest public void testManifestInstallLocationFwdLockedSdcard() { installFromRawResource("install.apk", R.raw.install_loc_sdcard, PackageManager.INSTALL_FORWARD_LOCK, true, false, @@ -1117,7 +1131,7 @@ public class PackageManagerTests extends AndroidTestCase { * the package via flag to install on sdcard. Make sure the new flag overrides * the old install location. */ - @MediumTest + @LargeTest public void testReplaceFlagInternalSdcard() { int iFlags = 0; int rFlags = PackageManager.INSTALL_EXTERNAL; @@ -1139,7 +1153,7 @@ public class PackageManagerTests extends AndroidTestCase { * the package with no flags or manifest option and make sure the old * install location is retained. */ - @MediumTest + @LargeTest public void testReplaceFlagSdcardInternal() { int iFlags = PackageManager.INSTALL_EXTERNAL; int rFlags = 0; @@ -1156,7 +1170,7 @@ public class PackageManagerTests extends AndroidTestCase { } } - @MediumTest + @LargeTest public void testManifestInstallLocationReplaceInternalSdcard() { int iFlags = 0; int iApk = R.raw.install_loc_internal; @@ -1179,7 +1193,7 @@ public class PackageManagerTests extends AndroidTestCase { } } - @MediumTest + @LargeTest public void testManifestInstallLocationReplaceSdcardInternal() { int iFlags = 0; int iApk = R.raw.install_loc_sdcard; @@ -1378,8 +1392,10 @@ public class PackageManagerTests extends AndroidTestCase { assertNotNull(info); if ((moveFlags & PackageManager.MOVE_INTERNAL) != 0) { assertTrue((info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) == 0); + assertTrue(info.nativeLibraryDir.startsWith(info.dataDir)); } else if ((moveFlags & PackageManager.MOVE_EXTERNAL_MEDIA) != 0){ assertTrue((info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0); + assertTrue(info.nativeLibraryDir.startsWith(SECURE_CONTAINERS_PREFIX)); } } } catch (NameNotFoundException e) { @@ -1401,7 +1417,7 @@ public class PackageManagerTests extends AndroidTestCase { fail, result); } - @MediumTest + @LargeTest public void testMoveAppInternalToExternal() { int installFlags = PackageManager.INSTALL_INTERNAL; int moveFlags = PackageManager.MOVE_EXTERNAL_MEDIA; @@ -1410,7 +1426,7 @@ public class PackageManagerTests extends AndroidTestCase { sampleMoveFromRawResource(installFlags, moveFlags, fail, result); } - @MediumTest + @LargeTest public void testMoveAppInternalToInternal() { int installFlags = PackageManager.INSTALL_INTERNAL; int moveFlags = PackageManager.MOVE_INTERNAL; @@ -1419,7 +1435,7 @@ public class PackageManagerTests extends AndroidTestCase { sampleMoveFromRawResource(installFlags, moveFlags, fail, result); } - @MediumTest + @LargeTest public void testMoveAppExternalToExternal() { int installFlags = PackageManager.INSTALL_EXTERNAL; int moveFlags = PackageManager.MOVE_EXTERNAL_MEDIA; @@ -1427,7 +1443,7 @@ public class PackageManagerTests extends AndroidTestCase { int result = PackageManager.MOVE_FAILED_INVALID_LOCATION; sampleMoveFromRawResource(installFlags, moveFlags, fail, result); } - @MediumTest + @LargeTest public void testMoveAppExternalToInternal() { int installFlags = PackageManager.INSTALL_EXTERNAL; int moveFlags = PackageManager.MOVE_INTERNAL; @@ -1435,7 +1451,7 @@ public class PackageManagerTests extends AndroidTestCase { int result = PackageManager.MOVE_SUCCEEDED; sampleMoveFromRawResource(installFlags, moveFlags, fail, result); } - @MediumTest + @LargeTest public void testMoveAppForwardLocked() { int installFlags = PackageManager.INSTALL_FORWARD_LOCK; int moveFlags = PackageManager.MOVE_EXTERNAL_MEDIA; @@ -1443,7 +1459,7 @@ public class PackageManagerTests extends AndroidTestCase { int result = PackageManager.MOVE_FAILED_FORWARD_LOCKED; sampleMoveFromRawResource(installFlags, moveFlags, fail, result); } - @MediumTest + @LargeTest public void testMoveAppFailInternalToExternalDelete() { int installFlags = 0; int moveFlags = PackageManager.MOVE_EXTERNAL_MEDIA; @@ -1477,9 +1493,9 @@ public class PackageManagerTests extends AndroidTestCase { * Test that an install error code is returned when media is unmounted * and package installed on sdcard via package manager flag. */ - @MediumTest + @LargeTest public void testInstallSdcardUnmount() { - boolean origState = getMediaState(); + boolean origState = checkMediaState(Environment.MEDIA_MOUNTED); try { // Unmount sdcard assertTrue(unmountMedia()); @@ -1502,24 +1518,24 @@ public class PackageManagerTests extends AndroidTestCase { * Unmount sdcard. Try installing an app with manifest option to install * on sdcard. Make sure it gets installed on internal flash. */ - @MediumTest + @LargeTest public void testInstallManifestSdcardUnmount() { - boolean origState = getMediaState(); - try { - // Unmount sdcard - assertTrue(unmountMedia()); - InstallParams ip = new InstallParams("install.apk", R.raw.install_loc_sdcard); - installFromRawResource(ip, 0, true, false, -1, - PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY); - } finally { - // Restore original media state - if (origState) { - mountMedia(); - } else { - unmountMedia(); - } - } - } + boolean origState = checkMediaState(Environment.MEDIA_MOUNTED); + try { + // Unmount sdcard + assertTrue(unmountMedia()); + InstallParams ip = new InstallParams("install.apk", R.raw.install_loc_sdcard); + installFromRawResource(ip, 0, true, false, -1, + PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY); + } finally { + // Restore original media state + if (origState) { + mountMedia(); + } else { + unmountMedia(); + } + } + } /*---------- Recommended install location tests ----*/ /* Precedence: FlagManifestExistingUser @@ -1534,14 +1550,14 @@ public class PackageManagerTests extends AndroidTestCase { /* * Install an app on internal flash */ - @MediumTest + @LargeTest public void testFlagI() { sampleInstallFromRawResource(PackageManager.INSTALL_INTERNAL, true); } /* * Install an app on sdcard. */ - @MediumTest + @LargeTest public void testFlagE() { sampleInstallFromRawResource(PackageManager.INSTALL_EXTERNAL, true); } @@ -1549,14 +1565,14 @@ public class PackageManagerTests extends AndroidTestCase { /* * Install an app on sdcard. */ - @MediumTest + @LargeTest public void testFlagF() { sampleInstallFromRawResource(PackageManager.INSTALL_FORWARD_LOCK, true); } /* * Install an app with both internal and external flags set. should fail */ - @MediumTest + @LargeTest public void testFlagIE() { installFromRawResource("install.apk", R.raw.install, PackageManager.INSTALL_EXTERNAL | PackageManager.INSTALL_INTERNAL, @@ -1568,7 +1584,7 @@ public class PackageManagerTests extends AndroidTestCase { /* * Install an app with both internal and external flags set. should fail */ - @MediumTest + @LargeTest public void testFlagIF() { sampleInstallFromRawResource(PackageManager.INSTALL_FORWARD_LOCK | PackageManager.INSTALL_INTERNAL, true); @@ -1576,7 +1592,7 @@ public class PackageManagerTests extends AndroidTestCase { /* * Install an app with both internal and external flags set. should fail */ - @MediumTest + @LargeTest public void testFlagEF() { installFromRawResource("install.apk", R.raw.install, PackageManager.INSTALL_FORWARD_LOCK | PackageManager.INSTALL_EXTERNAL, @@ -1587,7 +1603,7 @@ public class PackageManagerTests extends AndroidTestCase { /* * Install an app with both internal and external flags set. should fail */ - @MediumTest + @LargeTest public void testFlagIEF() { installFromRawResource("install.apk", R.raw.install, PackageManager.INSTALL_FORWARD_LOCK | PackageManager.INSTALL_INTERNAL | @@ -1600,7 +1616,7 @@ public class PackageManagerTests extends AndroidTestCase { * Install an app with both internal and manifest option set. * should install on internal. */ - @MediumTest + @LargeTest public void testFlagIManifestI() { installFromRawResource("install.apk", R.raw.install_loc_internal, PackageManager.INSTALL_INTERNAL, @@ -1612,7 +1628,7 @@ public class PackageManagerTests extends AndroidTestCase { * Install an app with both internal and manifest preference for * preferExternal. Should install on internal. */ - @MediumTest + @LargeTest public void testFlagIManifestE() { installFromRawResource("install.apk", R.raw.install_loc_sdcard, PackageManager.INSTALL_INTERNAL, @@ -1624,7 +1640,7 @@ public class PackageManagerTests extends AndroidTestCase { * Install an app with both internal and manifest preference for * auto. should install internal. */ - @MediumTest + @LargeTest public void testFlagIManifestA() { installFromRawResource("install.apk", R.raw.install_loc_auto, PackageManager.INSTALL_INTERNAL, @@ -1636,7 +1652,7 @@ public class PackageManagerTests extends AndroidTestCase { * Install an app with both external and manifest option set. * should install externally. */ - @MediumTest + @LargeTest public void testFlagEManifestI() { installFromRawResource("install.apk", R.raw.install_loc_internal, PackageManager.INSTALL_EXTERNAL, @@ -1648,7 +1664,7 @@ public class PackageManagerTests extends AndroidTestCase { * Install an app with both external and manifest preference for * preferExternal. Should install externally. */ - @MediumTest + @LargeTest public void testFlagEManifestE() { installFromRawResource("install.apk", R.raw.install_loc_sdcard, PackageManager.INSTALL_EXTERNAL, @@ -1660,7 +1676,7 @@ public class PackageManagerTests extends AndroidTestCase { * Install an app with both external and manifest preference for * auto. should install on external media. */ - @MediumTest + @LargeTest public void testFlagEManifestA() { installFromRawResource("install.apk", R.raw.install_loc_auto, PackageManager.INSTALL_EXTERNAL, @@ -1672,7 +1688,7 @@ public class PackageManagerTests extends AndroidTestCase { * Install an app with fwd locked flag set and install location set to * internal. should install internally. */ - @MediumTest + @LargeTest public void testFlagFManifestI() { installFromRawResource("install.apk", R.raw.install_loc_internal, PackageManager.INSTALL_EXTERNAL, @@ -1684,7 +1700,7 @@ public class PackageManagerTests extends AndroidTestCase { * Install an app with fwd locked flag set and install location set to * preferExternal. should install internally. */ - @MediumTest + @LargeTest public void testFlagFManifestE() { installFromRawResource("install.apk", R.raw.install_loc_sdcard, PackageManager.INSTALL_EXTERNAL, @@ -1696,7 +1712,7 @@ public class PackageManagerTests extends AndroidTestCase { * Install an app with fwd locked flag set and install location set to * auto. should install internally. */ - @MediumTest + @LargeTest public void testFlagFManifestA() { installFromRawResource("install.apk", R.raw.install_loc_auto, PackageManager.INSTALL_EXTERNAL, @@ -1711,7 +1727,7 @@ public class PackageManagerTests extends AndroidTestCase { * location should be honoured. * testFlagI/E/F/ExistingI/E - */ - @MediumTest + @LargeTest public void testFlagIExistingI() { int iFlags = PackageManager.INSTALL_INTERNAL; int rFlags = PackageManager.INSTALL_INTERNAL | PackageManager.INSTALL_REPLACE_EXISTING; @@ -1728,7 +1744,7 @@ public class PackageManagerTests extends AndroidTestCase { false, -1, -1); } - @MediumTest + @LargeTest public void testFlagIExistingE() { int iFlags = PackageManager.INSTALL_EXTERNAL; int rFlags = PackageManager.INSTALL_INTERNAL | PackageManager.INSTALL_REPLACE_EXISTING; @@ -1745,7 +1761,7 @@ public class PackageManagerTests extends AndroidTestCase { false, -1, -1); } - @MediumTest + @LargeTest public void testFlagEExistingI() { int iFlags = PackageManager.INSTALL_INTERNAL; int rFlags = PackageManager.INSTALL_EXTERNAL | PackageManager.INSTALL_REPLACE_EXISTING; @@ -1762,7 +1778,7 @@ public class PackageManagerTests extends AndroidTestCase { false, -1, -1); } - @MediumTest + @LargeTest public void testFlagEExistingE() { int iFlags = PackageManager.INSTALL_EXTERNAL; int rFlags = PackageManager.INSTALL_EXTERNAL | PackageManager.INSTALL_REPLACE_EXISTING; @@ -1779,7 +1795,7 @@ public class PackageManagerTests extends AndroidTestCase { false, -1, -1); } - @MediumTest + @LargeTest public void testFlagFExistingI() { int iFlags = PackageManager.INSTALL_INTERNAL; int rFlags = PackageManager.INSTALL_FORWARD_LOCK | PackageManager.INSTALL_REPLACE_EXISTING; @@ -1796,7 +1812,7 @@ public class PackageManagerTests extends AndroidTestCase { false, -1, -1); } - @MediumTest + @LargeTest public void testFlagFExistingE() { int iFlags = PackageManager.INSTALL_EXTERNAL; int rFlags = PackageManager.INSTALL_FORWARD_LOCK | PackageManager.INSTALL_REPLACE_EXISTING; @@ -1820,7 +1836,7 @@ public class PackageManagerTests extends AndroidTestCase { * public void testManifestI/E/A * TODO out of memory fall back behaviour. */ - @MediumTest + @LargeTest public void testManifestI() { installFromRawResource("install.apk", R.raw.install_loc_internal, 0, @@ -1828,7 +1844,7 @@ public class PackageManagerTests extends AndroidTestCase { false, -1, PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY); } - @MediumTest + @LargeTest public void testManifestE() { installFromRawResource("install.apk", R.raw.install_loc_sdcard, 0, @@ -1836,7 +1852,7 @@ public class PackageManagerTests extends AndroidTestCase { false, -1, PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL); } - @MediumTest + @LargeTest public void testManifestA() { installFromRawResource("install.apk", R.raw.install_loc_auto, 0, @@ -1851,7 +1867,7 @@ public class PackageManagerTests extends AndroidTestCase { * TODO add out of memory fall back behaviour. * testManifestI/E/AExistingI/E */ - @MediumTest + @LargeTest public void testManifestIExistingI() { int iFlags = PackageManager.INSTALL_INTERNAL; int rFlags = PackageManager.INSTALL_REPLACE_EXISTING; @@ -1868,7 +1884,7 @@ public class PackageManagerTests extends AndroidTestCase { false, -1, PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY); } - @MediumTest + @LargeTest public void testManifestIExistingE() { int iFlags = PackageManager.INSTALL_EXTERNAL; int rFlags = PackageManager.INSTALL_REPLACE_EXISTING; @@ -1885,7 +1901,7 @@ public class PackageManagerTests extends AndroidTestCase { false, -1, PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY); } - @MediumTest + @LargeTest public void testManifestEExistingI() { int iFlags = PackageManager.INSTALL_INTERNAL; int rFlags = PackageManager.INSTALL_REPLACE_EXISTING; @@ -1902,7 +1918,7 @@ public class PackageManagerTests extends AndroidTestCase { false, -1, PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL); } - @MediumTest + @LargeTest public void testManifestEExistingE() { int iFlags = PackageManager.INSTALL_EXTERNAL; int rFlags = PackageManager.INSTALL_REPLACE_EXISTING; @@ -1919,7 +1935,7 @@ public class PackageManagerTests extends AndroidTestCase { false, -1, PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL); } - @MediumTest + @LargeTest public void testManifestAExistingI() { int iFlags = PackageManager.INSTALL_INTERNAL; int rFlags = PackageManager.INSTALL_REPLACE_EXISTING; @@ -1936,7 +1952,7 @@ public class PackageManagerTests extends AndroidTestCase { false, -1, PackageInfo.INSTALL_LOCATION_AUTO); } - @MediumTest + @LargeTest public void testManifestAExistingE() { int iFlags = PackageManager.INSTALL_EXTERNAL; int rFlags = PackageManager.INSTALL_REPLACE_EXISTING; @@ -1993,37 +2009,37 @@ public class PackageManagerTests extends AndroidTestCase { setInstallLoc(origSetting); } } - @MediumTest + @LargeTest public void testExistingIUserI() { int userSetting = PackageHelper.APP_INSTALL_INTERNAL; int iFlags = PackageManager.INSTALL_INTERNAL; setExistingXUserX(userSetting, iFlags, PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY); } - @MediumTest + @LargeTest public void testExistingIUserE() { int userSetting = PackageHelper.APP_INSTALL_EXTERNAL; int iFlags = PackageManager.INSTALL_INTERNAL; setExistingXUserX(userSetting, iFlags, PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY); } - @MediumTest + @LargeTest public void testExistingIUserA() { int userSetting = PackageHelper.APP_INSTALL_AUTO; int iFlags = PackageManager.INSTALL_INTERNAL; setExistingXUserX(userSetting, iFlags, PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY); } - @MediumTest + @LargeTest public void testExistingEUserI() { int userSetting = PackageHelper.APP_INSTALL_INTERNAL; int iFlags = PackageManager.INSTALL_EXTERNAL; setExistingXUserX(userSetting, iFlags, PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL); } - @MediumTest + @LargeTest public void testExistingEUserE() { int userSetting = PackageHelper.APP_INSTALL_EXTERNAL; int iFlags = PackageManager.INSTALL_EXTERNAL; setExistingXUserX(userSetting, iFlags, PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL); } - @MediumTest + @LargeTest public void testExistingEUserA() { int userSetting = PackageHelper.APP_INSTALL_AUTO; int iFlags = PackageManager.INSTALL_EXTERNAL; @@ -2066,19 +2082,19 @@ public class PackageManagerTests extends AndroidTestCase { setInstallLoc(origSetting); } } - @MediumTest + @LargeTest public void testUserI() { int userSetting = PackageHelper.APP_INSTALL_INTERNAL; int iloc = getExpectedInstallLocation(userSetting); setUserX(true, userSetting, iloc); } - @MediumTest + @LargeTest public void testUserE() { int userSetting = PackageHelper.APP_INSTALL_EXTERNAL; int iloc = getExpectedInstallLocation(userSetting); setUserX(true, userSetting, iloc); } - @MediumTest + @LargeTest public void testUserA() { int userSetting = PackageHelper.APP_INSTALL_AUTO; int iloc = getExpectedInstallLocation(userSetting); @@ -2088,19 +2104,19 @@ public class PackageManagerTests extends AndroidTestCase { * The following set of tests turn on/off the basic * user setting for turning on install location. */ - @MediumTest + @LargeTest public void testUserPrefOffUserI() { int userSetting = PackageHelper.APP_INSTALL_INTERNAL; int iloc = PackageInfo.INSTALL_LOCATION_UNSPECIFIED; setUserX(false, userSetting, iloc); } - @MediumTest + @LargeTest public void testUserPrefOffUserE() { int userSetting = PackageHelper.APP_INSTALL_EXTERNAL; int iloc = PackageInfo.INSTALL_LOCATION_UNSPECIFIED; setUserX(false, userSetting, iloc); } - @MediumTest + @LargeTest public void testUserPrefOffA() { int userSetting = PackageHelper.APP_INSTALL_AUTO; int iloc = PackageInfo.INSTALL_LOCATION_UNSPECIFIED; @@ -2277,10 +2293,10 @@ public class PackageManagerTests extends AndroidTestCase { /* * Ensure that permissions are properly declared. */ - @MediumTest + @LargeTest public void testInstallOnSdPermissionsUnmount() { InstallParams ip = null; - boolean origMediaState = getMediaState(); + boolean origMediaState = checkMediaState(Environment.MEDIA_MOUNTED); try { // **: Upon installing a package, are its declared permissions published? int iFlags = PackageManager.INSTALL_INTERNAL; @@ -2309,10 +2325,12 @@ public class PackageManagerTests extends AndroidTestCase { * Please note that this test is very closely tied to the framework's * naming convention for secure containers. */ - @MediumTest + @LargeTest public void testInstallSdcardStaleContainer() { - boolean origMediaState = getMediaState(); + boolean origMediaState = checkMediaState(Environment.MEDIA_MOUNTED); try { + // Mount media first + mountMedia(); String outFileName = "install.apk"; int rawResId = R.raw.install; PackageManager pm = mContext.getPackageManager(); @@ -2351,9 +2369,9 @@ public class PackageManagerTests extends AndroidTestCase { * The app is then re-installed on internal storage. The sdcard is mounted * and verified that the re-installation on internal storage takes precedence. */ - @MediumTest + @LargeTest public void testInstallSdcardStaleContainerReinstall() { - boolean origMediaState = getMediaState(); + boolean origMediaState = checkMediaState(Environment.MEDIA_MOUNTED); try { // Mount media first mountMedia(); @@ -2386,7 +2404,6 @@ public class PackageManagerTests extends AndroidTestCase { } else { unmountMedia(); } - } } /* @@ -2429,7 +2446,7 @@ public class PackageManagerTests extends AndroidTestCase { * Test that an app signed with two certificates can be upgraded by the * same app signed with two certificates. */ - @MediumTest + @LargeTest public void testReplaceMatchAllCerts() { replaceCerts(APP1_CERT1_CERT2, APP1_CERT1_CERT2, true, false, -1); } @@ -2438,7 +2455,7 @@ public class PackageManagerTests extends AndroidTestCase { * Test that an app signed with two certificates cannot be upgraded * by an app signed with a different certificate. */ - @MediumTest + @LargeTest public void testReplaceMatchNoCerts1() { replaceCerts(APP1_CERT1_CERT2, APP1_CERT3, true, true, PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES); @@ -2447,7 +2464,7 @@ public class PackageManagerTests extends AndroidTestCase { * Test that an app signed with two certificates cannot be upgraded * by an app signed with a different certificate. */ - @MediumTest + @LargeTest public void testReplaceMatchNoCerts2() { replaceCerts(APP1_CERT1_CERT2, APP1_CERT3_CERT4, true, true, PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES); @@ -2456,7 +2473,7 @@ public class PackageManagerTests extends AndroidTestCase { * Test that an app signed with two certificates cannot be upgraded by * an app signed with a subset of initial certificates. */ - @MediumTest + @LargeTest public void testReplaceMatchSomeCerts1() { replaceCerts(APP1_CERT1_CERT2, APP1_CERT1, true, true, PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES); @@ -2465,7 +2482,7 @@ public class PackageManagerTests extends AndroidTestCase { * Test that an app signed with two certificates cannot be upgraded by * an app signed with the last certificate. */ - @MediumTest + @LargeTest public void testReplaceMatchSomeCerts2() { replaceCerts(APP1_CERT1_CERT2, APP1_CERT2, true, true, PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES); @@ -2474,7 +2491,7 @@ public class PackageManagerTests extends AndroidTestCase { * Test that an app signed with a certificate can be upgraded by app * signed with a superset of certificates. */ - @MediumTest + @LargeTest public void testReplaceMatchMoreCerts() { replaceCerts(APP1_CERT1, APP1_CERT1_CERT2, true, true, PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES); @@ -2484,7 +2501,7 @@ public class PackageManagerTests extends AndroidTestCase { * signed with a superset of certificates. Then verify that the an app * signed with the original set of certs cannot upgrade the new one. */ - @MediumTest + @LargeTest public void testReplaceMatchMoreCertsReplaceSomeCerts() { InstallParams ip = replaceCerts(APP1_CERT1, APP1_CERT1_CERT2, false, true, PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES); @@ -2508,37 +2525,37 @@ public class PackageManagerTests extends AndroidTestCase { private void checkSignatures(int apk1, int apk2, int expMatchResult) { checkSharedSignatures(apk1, apk2, true, false, -1, expMatchResult); } - @MediumTest + @LargeTest public void testCheckSignaturesAllMatch() { int apk1 = APP1_CERT1_CERT2; int apk2 = APP2_CERT1_CERT2; checkSignatures(apk1, apk2, PackageManager.SIGNATURE_MATCH); } - @MediumTest + @LargeTest public void testCheckSignaturesNoMatch() { int apk1 = APP1_CERT1; int apk2 = APP2_CERT2; checkSignatures(apk1, apk2, PackageManager.SIGNATURE_NO_MATCH); } - @MediumTest + @LargeTest public void testCheckSignaturesSomeMatch1() { int apk1 = APP1_CERT1_CERT2; int apk2 = APP2_CERT1; checkSignatures(apk1, apk2, PackageManager.SIGNATURE_NO_MATCH); } - @MediumTest + @LargeTest public void testCheckSignaturesSomeMatch2() { int apk1 = APP1_CERT1_CERT2; int apk2 = APP2_CERT2; checkSignatures(apk1, apk2, PackageManager.SIGNATURE_NO_MATCH); } - @MediumTest + @LargeTest public void testCheckSignaturesMoreMatch() { int apk1 = APP1_CERT1; int apk2 = APP2_CERT1_CERT2; checkSignatures(apk1, apk2, PackageManager.SIGNATURE_NO_MATCH); } - @MediumTest + @LargeTest public void testCheckSignaturesUnknown() { int apk1 = APP1_CERT1_CERT2; int apk2 = APP2_CERT1_CERT2; @@ -2567,7 +2584,7 @@ public class PackageManagerTests extends AndroidTestCase { } } } - @MediumTest + @LargeTest public void testInstallNoCertificates() { int apk1 = APP1_UNSIGNED; String apk1Name = "install1.apk"; @@ -2620,7 +2637,7 @@ public class PackageManagerTests extends AndroidTestCase { } } } - @MediumTest + @LargeTest public void testCheckSignaturesSharedAllMatch() { int apk1 = SHARED1_CERT1_CERT2; int apk2 = SHARED2_CERT1_CERT2; @@ -2629,7 +2646,7 @@ public class PackageManagerTests extends AndroidTestCase { int expMatchResult = PackageManager.SIGNATURE_MATCH; checkSharedSignatures(apk1, apk2, true, fail, retCode, expMatchResult); } - @MediumTest + @LargeTest public void testCheckSignaturesSharedNoMatch() { int apk1 = SHARED1_CERT1; int apk2 = SHARED2_CERT2; @@ -2641,7 +2658,7 @@ public class PackageManagerTests extends AndroidTestCase { /* * Test that an app signed with cert1 and cert2 cannot be replaced when signed with cert1 alone. */ - @MediumTest + @LargeTest public void testCheckSignaturesSharedSomeMatch1() { int apk1 = SHARED1_CERT1_CERT2; int apk2 = SHARED2_CERT1; @@ -2653,7 +2670,7 @@ public class PackageManagerTests extends AndroidTestCase { /* * Test that an app signed with cert1 and cert2 cannot be replaced when signed with cert2 alone. */ - @MediumTest + @LargeTest public void testCheckSignaturesSharedSomeMatch2() { int apk1 = SHARED1_CERT1_CERT2; int apk2 = SHARED2_CERT2; @@ -2662,7 +2679,7 @@ public class PackageManagerTests extends AndroidTestCase { int expMatchResult = -1; checkSharedSignatures(apk1, apk2, true, fail, retCode, expMatchResult); } - @MediumTest + @LargeTest public void testCheckSignaturesSharedUnknown() { int apk1 = SHARED1_CERT1_CERT2; int apk2 = SHARED2_CERT1_CERT2; @@ -2688,7 +2705,7 @@ public class PackageManagerTests extends AndroidTestCase { } } - @MediumTest + @LargeTest public void testReplaceFirstSharedMatchAllCerts() { int apk1 = SHARED1_CERT1; int apk2 = SHARED2_CERT1; @@ -2696,7 +2713,7 @@ public class PackageManagerTests extends AndroidTestCase { checkSignatures(apk1, apk2, PackageManager.SIGNATURE_MATCH); replaceCerts(apk1, rapk1, true, false, -1); } - @MediumTest + @LargeTest public void testReplaceSecondSharedMatchAllCerts() { int apk1 = SHARED1_CERT1; int apk2 = SHARED2_CERT1; @@ -2704,7 +2721,7 @@ public class PackageManagerTests extends AndroidTestCase { checkSignatures(apk1, apk2, PackageManager.SIGNATURE_MATCH); replaceCerts(apk2, rapk2, true, false, -1); } - @MediumTest + @LargeTest public void testReplaceFirstSharedMatchSomeCerts() { int apk1 = SHARED1_CERT1_CERT2; int apk2 = SHARED2_CERT1_CERT2; @@ -2715,7 +2732,7 @@ public class PackageManagerTests extends AndroidTestCase { installFromRawResource("install.apk", rapk1, PackageManager.INSTALL_REPLACE_EXISTING, true, fail, retCode, PackageInfo.INSTALL_LOCATION_UNSPECIFIED); } - @MediumTest + @LargeTest public void testReplaceSecondSharedMatchSomeCerts() { int apk1 = SHARED1_CERT1_CERT2; int apk2 = SHARED2_CERT1_CERT2; @@ -2726,7 +2743,7 @@ public class PackageManagerTests extends AndroidTestCase { installFromRawResource("install.apk", rapk2, PackageManager.INSTALL_REPLACE_EXISTING, true, fail, retCode, PackageInfo.INSTALL_LOCATION_UNSPECIFIED); } - @MediumTest + @LargeTest public void testReplaceFirstSharedMatchNoCerts() { int apk1 = SHARED1_CERT1; int apk2 = SHARED2_CERT1; @@ -2737,7 +2754,7 @@ public class PackageManagerTests extends AndroidTestCase { installFromRawResource("install.apk", rapk1, PackageManager.INSTALL_REPLACE_EXISTING, true, fail, retCode, PackageInfo.INSTALL_LOCATION_UNSPECIFIED); } - @MediumTest + @LargeTest public void testReplaceSecondSharedMatchNoCerts() { int apk1 = SHARED1_CERT1; int apk2 = SHARED2_CERT1; @@ -2748,7 +2765,7 @@ public class PackageManagerTests extends AndroidTestCase { installFromRawResource("install.apk", rapk2, PackageManager.INSTALL_REPLACE_EXISTING, true, fail, retCode, PackageInfo.INSTALL_LOCATION_UNSPECIFIED); } - @MediumTest + @LargeTest public void testReplaceFirstSharedMatchMoreCerts() { int apk1 = SHARED1_CERT1; int apk2 = SHARED2_CERT1; @@ -2759,7 +2776,7 @@ public class PackageManagerTests extends AndroidTestCase { installFromRawResource("install.apk", rapk1, PackageManager.INSTALL_REPLACE_EXISTING, true, fail, retCode, PackageInfo.INSTALL_LOCATION_UNSPECIFIED); } - @MediumTest + @LargeTest public void testReplaceSecondSharedMatchMoreCerts() { int apk1 = SHARED1_CERT1; int apk2 = SHARED2_CERT1; diff --git a/core/tests/coretests/src/android/database/sqlite/AbstractJDBCDriverTest.java b/core/tests/coretests/src/android/database/sqlite/AbstractJDBCDriverTest.java deleted file mode 100644 index 19c7bcb03cc9..000000000000 --- a/core/tests/coretests/src/android/database/sqlite/AbstractJDBCDriverTest.java +++ /dev/null @@ -1,211 +0,0 @@ -/* - * Copyright (C) 2008 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 java.io.File; -import java.sql.Connection; -import java.sql.DriverManager; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; - -import junit.framework.TestCase; -import android.test.suitebuilder.annotation.MediumTest; - -/** - * Tests for the most commonly used methods of sql like creating a connection, - * inserting, selecting, updating. - */ -public abstract class AbstractJDBCDriverTest extends TestCase { - - @MediumTest - public void testJDBCDriver() throws Exception { - Connection firstConnection = null; - Connection secondConnection = null; - File dbFile = getDbFile(); - String connectionURL = getConnectionURL(); - Statement firstStmt = null; - Statement secondStmt = null; - try { - Class.forName(getJDBCDriverClassName()); - firstConnection = DriverManager.getConnection(connectionURL); - secondConnection = DriverManager.getConnection(connectionURL); - - String[] ones = {"hello!", "goodbye"}; - short[] twos = {10, 20}; - String[] onesUpdated = new String[ones.length]; - for (int i = 0; i < ones.length; i++) { - onesUpdated[i] = ones[i] + twos[i]; - } - firstStmt = firstConnection.createStatement(); - firstStmt.execute("create table tbl1(one varchar(10), two smallint)"); - secondStmt = secondConnection.createStatement(); - - autoCommitInsertSelectTest(firstStmt, ones, twos); - updateSelectCommitSelectTest(firstStmt, secondStmt, ones, onesUpdated, twos); - updateSelectRollbackSelectTest(firstStmt, secondStmt, onesUpdated, ones, twos); - } finally { - closeConnections(firstConnection, secondConnection, dbFile, firstStmt, secondStmt); - } - } - - protected abstract String getJDBCDriverClassName(); - protected abstract String getConnectionURL(); - protected abstract File getDbFile(); - - private void closeConnections(Connection firstConnection, Connection secondConnection, - File dbFile, Statement firstStmt, Statement secondStmt) { - String failText = null; - try { - if (firstStmt != null) { - firstStmt.execute("drop table tbl1"); - } - } catch (SQLException e) { - failText = e.getLocalizedMessage(); - } - try { - if (firstStmt != null) { - firstStmt.close(); - } - } catch (SQLException e) { - failText = e.getLocalizedMessage(); - } - try { - if (firstConnection != null) { - firstConnection.close(); - } - } catch (SQLException e) { - failText = e.getLocalizedMessage(); - } - try { - if (secondStmt != null) { - secondStmt.close(); - } - } catch (SQLException e) { - failText = e.getLocalizedMessage(); - } - try { - if (secondConnection != null) { - secondConnection.close(); - } - } catch (SQLException e) { - failText = e.getLocalizedMessage(); - } - dbFile.delete(); - assertNull(failText, failText); - } - - /** - * Inserts the values from 'ones' with the values from 'twos' into 'tbl1' - * @param stmt the statement to use for the inserts. - * @param ones the string values to insert into tbl1. - * @param twos the corresponding numerical values to insert into tbl1. - * @throws SQLException in case of a problem during insert. - */ - private void autoCommitInsertSelectTest(Statement stmt, String[] ones, - short[] twos) throws SQLException { - for (int i = 0; i < ones.length; i++) { - stmt.execute("insert into tbl1 values('" + ones[i] + "'," + twos[i] - + ")"); - } - assertAllFromTbl1(stmt, ones, twos); - } - - /** - * Asserts that all values that where added to tbl1 are actually in tbl1. - * @param stmt the statement to use for the select. - * @param ones the string values that where added. - * @param twos the numerical values that where added. - * @throws SQLException in case of a problem during select. - */ - private void assertAllFromTbl1(Statement stmt, String[] ones, short[] twos) - throws SQLException { - ResultSet rs = stmt.executeQuery("select * from tbl1"); - int i = 0; - for (; rs.next(); i++) { - assertTrue(i < ones.length); - assertEquals(ones[i], rs.getString("one")); - assertEquals(twos[i], rs.getShort("two")); - } - assertEquals(i, ones.length); - } - - /** - * Tests the results of an update followed bz a select on a diffrent statement. - * After that the first statement commits its update. and now the second - * statement should also be able to see the changed values in a select. - * @param firstStmt the statement to use for the update and commit. - * @param secondStmt the statement that should be used to check if the commit works - * @param ones the original string values. - * @param onesUpdated the updated string values. - * @param twos the numerical values. - * @throws SQLException in case of a problem during any of the executed commands. - */ - private void updateSelectCommitSelectTest(Statement firstStmt, - Statement secondStmt, String[] ones, String[] onesUpdated, - short[] twos) throws SQLException { - firstStmt.getConnection().setAutoCommit(false); - try { - updateOnes(firstStmt, onesUpdated, twos); - assertAllFromTbl1(secondStmt, ones, twos); - firstStmt.getConnection().commit(); - assertAllFromTbl1(secondStmt, onesUpdated, twos); - } finally { - firstStmt.getConnection().setAutoCommit(true); - } - } - - /** - * Tests if an update followed by a select works. After that a rollback will - * be made and again a select should show that the rollback worked. - * @param firstStmt the statement to use for the update and the rollback - * @param secondStmt the statement to use for checking if the rollback worked as intended. - * @param ones the original string values. - * @param onesUpdated the updated string values. - * @param twos the nomerical values. - * @throws SQLException in case of a problem during any command. - */ - private void updateSelectRollbackSelectTest(Statement firstStmt, - Statement secondStmt, String[] ones, String[] onesUpdated, - short[] twos) throws SQLException { - firstStmt.getConnection().setAutoCommit(false); - try { - updateOnes(firstStmt, onesUpdated, twos); - assertAllFromTbl1(secondStmt, ones, twos); - firstStmt.getConnection().rollback(); - assertAllFromTbl1(secondStmt, ones, twos); - } finally { - firstStmt.getConnection().setAutoCommit(true); - } - } - - /** - * updates the sring values. the original values are stored in 'ones' - * and the updated values in 'ones_updated' - * @param stmt the statement to use for the update. - * @param onesUpdated the new string values. - * @param twos the numerical values. - * @throws SQLException in case of a problem during update. - */ - private void updateOnes(Statement stmt, String[] onesUpdated, short[] twos) - throws SQLException { - for (int i = 0; i < onesUpdated.length; i++) { - stmt.execute("UPDATE tbl1 SET one = '" + onesUpdated[i] - + "' WHERE two = " + twos[i]); - } - } -} diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteJDBCDriverTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteJDBCDriverTest.java deleted file mode 100644 index 8e677a5b8702..000000000000 --- a/core/tests/coretests/src/android/database/sqlite/SQLiteJDBCDriverTest.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright (C) 2008 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 java.io.File; -import java.sql.Connection; -import java.sql.DriverManager; -import java.sql.PreparedStatement; -import java.sql.SQLException; -import java.sql.Statement; -import android.test.suitebuilder.annotation.MediumTest; - -/** - * Minimal test for JDBC driver - */ -public class SQLiteJDBCDriverTest extends AbstractJDBCDriverTest { - - private File dbFile; - - @Override - protected void setUp() throws Exception { - super.setUp(); - dbFile = File.createTempFile("sqliteTestDB", null); - } - - @Override - protected void tearDown() throws Exception { - if(dbFile != null) { - dbFile.delete(); - } - super.tearDown(); - } - - @Override - protected String getConnectionURL() { - return "jdbc:sqlite:/" + dbFile; - } - - @Override - protected File getDbFile() { - return dbFile; - } - - @Override - protected String getJDBCDriverClassName() { - return "SQLite.JDBCDriver"; - } - - // Regression test for (Noser) #255: PreparedStatement.executeUpdate results - // in VM crashing with SIGABRT. - @MediumTest - public void test_connection3() throws Exception { - PreparedStatement prst = null; - Statement st = null; - Connection conn = null; - try { - Class.forName("SQLite.JDBCDriver").newInstance(); - if (dbFile.exists()) { - dbFile.delete(); - } - conn = DriverManager.getConnection("jdbc:sqlite:/" - + dbFile.getPath()); - assertNotNull(conn); - - // create table - st = conn.createStatement(); - String sql = "CREATE TABLE zoo (ZID INTEGER NOT NULL, family VARCHAR (20) NOT NULL, name VARCHAR (20) NOT NULL, PRIMARY KEY(ZID) )"; - st.executeUpdate(sql); - - String update = "update zoo set family = ? where name = ?;"; - prst = conn.prepareStatement(update); - prst.setString(1, "cat"); - prst.setString(2, "Yasha"); - // st = conn.createStatement(); - // st.execute("select * from zoo where family = 'cat'"); - // ResultSet rs = st.getResultSet(); - // assertEquals(0, getCount(rs)); - prst.executeUpdate(); - // st.execute("select * from zoo where family = 'cat'"); - // ResultSet rs1 = st.getResultSet(); - // assertEquals(1, getCount(rs1)); - try { - prst = conn.prepareStatement(""); - prst.execute(); - fail("SQLException is not thrown"); - } catch (SQLException e) { - // expected - } - - try { - conn.prepareStatement(null); - fail("NPE is not thrown"); - } catch (Exception e) { - // expected - } - try { - st = conn.createStatement(); - st.execute("drop table if exists zoo"); - - } catch (SQLException e) { - fail("Couldn't drop table: " + e.getMessage()); - } finally { - try { - st.close(); - conn.close(); - } catch (SQLException ee) { - } - } - } finally { - try { - if (prst != null) { - prst.close(); - } - if (st != null) { - st.close(); - } - } catch (SQLException ee) { - } - } - - } - -} diff --git a/core/tests/coretests/src/android/net/DownloadManagerBaseTest.java b/core/tests/coretests/src/android/net/DownloadManagerBaseTest.java new file mode 100644 index 000000000000..ee0f5f14516d --- /dev/null +++ b/core/tests/coretests/src/android/net/DownloadManagerBaseTest.java @@ -0,0 +1,888 @@ +/* + * 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 android.net; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.database.Cursor; +import android.net.ConnectivityManager; +import android.net.DownloadManager; +import android.net.NetworkInfo; +import android.net.DownloadManager.Query; +import android.net.DownloadManager.Request; +import android.net.wifi.WifiManager; +import android.os.Environment; +import android.os.ParcelFileDescriptor; +import android.os.SystemClock; +import android.os.ParcelFileDescriptor.AutoCloseInputStream; +import android.provider.Settings; +import android.test.InstrumentationTestCase; +import android.util.Log; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.net.URL; +import java.util.concurrent.TimeoutException; +import java.util.Iterator; +import java.util.List; +import java.util.Random; +import java.util.Vector; + +import junit.framework.AssertionFailedError; + +import coretestutils.http.MockResponse; +import coretestutils.http.MockWebServer; + +/** + * Base class for Instrumented tests for the Download Manager. + */ +public class DownloadManagerBaseTest extends InstrumentationTestCase { + + protected DownloadManager mDownloadManager = null; + protected MockWebServer mServer = null; + protected String mFileType = "text/plain"; + protected Context mContext = null; + protected static final int DEFAULT_FILE_SIZE = 130 * 1024; // 130kb + protected static final int FILE_BLOCK_READ_SIZE = 1024 * 1024; + + protected static final String LOG_TAG = "android.net.DownloadManagerBaseTest"; + protected static final int HTTP_OK = 200; + protected static final int HTTP_PARTIAL_CONTENT = 206; + protected static final int HTTP_NOT_FOUND = 404; + protected static final int HTTP_SERVICE_UNAVAILABLE = 503; + protected String DEFAULT_FILENAME = "somefile.txt"; + + protected static final int DEFAULT_MAX_WAIT_TIME = 2 * 60 * 1000; // 2 minutes + protected static final int DEFAULT_WAIT_POLL_TIME = 5 * 1000; // 5 seconds + + protected static final int WAIT_FOR_DOWNLOAD_POLL_TIME = 1 * 1000; // 1 second + protected static final int MAX_WAIT_FOR_DOWNLOAD_TIME = 5 * 60 * 1000; // 5 minutes + + // Just a few popular file types used to return from a download + protected enum DownloadFileType { + PLAINTEXT, + APK, + GIF, + GARBAGE, + UNRECOGNIZED, + ZIP + } + + protected enum DataType { + TEXT, + BINARY + } + + public static class LoggingRng extends Random { + + /** + * Constructor + * + * Creates RNG with self-generated seed value. + */ + public LoggingRng() { + this(SystemClock.uptimeMillis()); + } + + /** + * Constructor + * + * Creats RNG with given initial seed value + + * @param seed The initial seed value + */ + public LoggingRng(long seed) { + super(seed); + Log.i(LOG_TAG, "Seeding RNG with value: " + seed); + } + } + + public static class MultipleDownloadsCompletedReceiver extends BroadcastReceiver { + private volatile int mNumDownloadsCompleted = 0; + + /** + * {@inheritDoc} + */ + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction().equalsIgnoreCase(DownloadManager.ACTION_DOWNLOAD_COMPLETE)) { + ++mNumDownloadsCompleted; + Log.i(LOG_TAG, "MultipleDownloadsCompletedReceiver got intent: " + + intent.getAction() + " --> total count: " + mNumDownloadsCompleted); + } + } + + /** + * Gets the number of times the {@link #onReceive} callback has been called for the + * {@link DownloadManager.ACTION_DOWNLOAD_COMPLETED} action, indicating the number of + * downloads completed thus far. + * + * @return the number of downloads completed so far. + */ + public int numDownloadsCompleted() { + return mNumDownloadsCompleted; + } + } + + public static class WiFiChangedReceiver extends BroadcastReceiver { + private Context mContext = null; + + /** + * Constructor + * + * Sets the current state of WiFi. + * + * @param context The current app {@link Context}. + */ + public WiFiChangedReceiver(Context context) { + mContext = context; + } + + /** + * {@inheritDoc} + */ + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction().equalsIgnoreCase(ConnectivityManager.CONNECTIVITY_ACTION)) { + Log.i(LOG_TAG, "ConnectivityManager state change: " + intent.getAction()); + synchronized (this) { + this.notify(); + } + } + } + + /** + * Gets the current state of WiFi. + * + * @return Returns true if WiFi is on, false otherwise. + */ + public boolean getWiFiIsOn() { + ConnectivityManager connManager = (ConnectivityManager)mContext.getSystemService( + Context.CONNECTIVITY_SERVICE); + NetworkInfo info = connManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI); + return info.isConnected(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void setUp() throws Exception { + mContext = getInstrumentation().getContext(); + mDownloadManager = (DownloadManager)mContext.getSystemService(Context.DOWNLOAD_SERVICE); + mServer = new MockWebServer(); + // Note: callers overriding this should call mServer.play() with the desired port # + } + + /** + * Helper to enqueue a response from the MockWebServer. + * + * @param status The HTTP status code to return for this response + * @param body The body to return in this response + * @return Returns the mock web server response that was queued (which can be modified) + */ + protected MockResponse enqueueResponse(int status, byte[] body) { + return doEnqueueResponse(status).setBody(body); + + } + + /** + * Helper to enqueue a response from the MockWebServer. + * + * @param status The HTTP status code to return for this response + * @param bodyFile The body to return in this response + * @return Returns the mock web server response that was queued (which can be modified) + */ + protected MockResponse enqueueResponse(int status, File bodyFile) { + return doEnqueueResponse(status).setBody(bodyFile); + } + + /** + * Helper for enqueue'ing a response from the MockWebServer. + * + * @param status The HTTP status code to return for this response + * @return Returns the mock web server response that was queued (which can be modified) + */ + protected MockResponse doEnqueueResponse(int status) { + MockResponse response = new MockResponse().setResponseCode(status); + response.addHeader("Content-type", mFileType); + mServer.enqueue(response); + return response; + } + + /** + * Helper to generate a random blob of bytes. + * + * @param size The size of the data to generate + * @param type The type of data to generate: currently, one of {@link DataType.TEXT} or + * {@link DataType.BINARY}. + * @return The random data that is generated. + */ + protected byte[] generateData(int size, DataType type) { + return generateData(size, type, null); + } + + /** + * Helper to generate a random blob of bytes using a given RNG. + * + * @param size The size of the data to generate + * @param type The type of data to generate: currently, one of {@link DataType.TEXT} or + * {@link DataType.BINARY}. + * @param rng (optional) The RNG to use; pass null to use + * @return The random data that is generated. + */ + protected byte[] generateData(int size, DataType type, Random rng) { + int min = Byte.MIN_VALUE; + int max = Byte.MAX_VALUE; + + // Only use chars in the HTTP ASCII printable character range for Text + if (type == DataType.TEXT) { + min = 32; + max = 126; + } + byte[] result = new byte[size]; + Log.i(LOG_TAG, "Generating data of size: " + size); + + if (rng == null) { + rng = new LoggingRng(); + } + + for (int i = 0; i < size; ++i) { + result[i] = (byte) (min + rng.nextInt(max - min + 1)); + } + return result; + } + + /** + * Helper to verify the size of a file. + * + * @param pfd The input file to compare the size of + * @param size The expected size of the file + */ + protected void verifyFileSize(ParcelFileDescriptor pfd, long size) { + assertEquals(pfd.getStatSize(), size); + } + + /** + * Helper to verify the contents of a downloaded file versus a byte[]. + * + * @param actual The file of whose contents to verify + * @param expected The data we expect to find in the aforementioned file + * @throws IOException if there was a problem reading from the file + */ + protected void verifyFileContents(ParcelFileDescriptor actual, byte[] expected) + throws IOException { + AutoCloseInputStream input = new ParcelFileDescriptor.AutoCloseInputStream(actual); + long fileSize = actual.getStatSize(); + + assertTrue(fileSize <= Integer.MAX_VALUE); + assertEquals(expected.length, fileSize); + + byte[] actualData = new byte[expected.length]; + assertEquals(input.read(actualData), fileSize); + compareByteArrays(actualData, expected); + } + + /** + * Helper to compare 2 byte arrays. + * + * @param actual The array whose data we want to verify + * @param expected The array of data we expect to see + */ + protected void compareByteArrays(byte[] actual, byte[] expected) { + assertEquals(actual.length, expected.length); + int length = actual.length; + for (int i = 0; i < length; ++i) { + // assert has a bit of overhead, so only do the assert when the values are not the same + if (actual[i] != expected[i]) { + fail("Byte arrays are not equal."); + } + } + } + + /** + * Verifies the contents of a downloaded file versus the contents of a File. + * + * @param pfd The file whose data we want to verify + * @param file The file containing the data we expect to see in the aforementioned file + * @throws IOException If there was a problem reading either of the two files + */ + protected void verifyFileContents(ParcelFileDescriptor pfd, File file) throws IOException { + byte[] actual = new byte[FILE_BLOCK_READ_SIZE]; + byte[] expected = new byte[FILE_BLOCK_READ_SIZE]; + + AutoCloseInputStream input = new ParcelFileDescriptor.AutoCloseInputStream(pfd); + + assertEquals(file.length(), pfd.getStatSize()); + + DataInputStream inFile = new DataInputStream(new FileInputStream(file)); + int actualRead = 0; + int expectedRead = 0; + + while (((actualRead = input.read(actual)) != -1) && + ((expectedRead = inFile.read(expected)) != -1)) { + assertEquals(actualRead, expectedRead); + compareByteArrays(actual, expected); + } + } + + /** + * Sets the MIME type of file that will be served from the mock server + * + * @param type The MIME type to return from the server + */ + protected void setServerMimeType(DownloadFileType type) { + mFileType = getMimeMapping(type); + } + + /** + * Gets the MIME content string for a given type + * + * @param type The MIME type to return + * @return the String representation of that MIME content type + */ + protected String getMimeMapping(DownloadFileType type) { + switch (type) { + case APK: + return "application/vnd.android.package-archive"; + case GIF: + return "image/gif"; + case ZIP: + return "application/x-zip-compressed"; + case GARBAGE: + return "zip\\pidy/doo/da"; + case UNRECOGNIZED: + return "application/new.undefined.type.of.app"; + } + return "text/plain"; + } + + /** + * Gets the Uri that should be used to access the mock server + * + * @param filename The name of the file to try to retrieve from the mock server + * @return the Uri to use for access the file on the mock server + */ + protected Uri getServerUri(String filename) throws Exception { + URL url = mServer.getUrl("/" + filename); + return Uri.parse(url.toString()); + } + + /** + * Gets the Uri that should be used to access the mock server + * + * @param filename The name of the file to try to retrieve from the mock server + * @return the Uri to use for access the file on the mock server + */ + protected void logDBColumnData(Cursor cursor, String column) { + int index = cursor.getColumnIndex(column); + Log.i(LOG_TAG, "columnName: " + column); + Log.i(LOG_TAG, "columnValue: " + cursor.getString(index)); + } + + /** + * Helper to create and register a new MultipleDownloadCompletedReciever + * + * This is used to track many simultaneous downloads by keeping count of all the downloads + * that have completed. + * + * @return A new receiver that records and can be queried on how many downloads have completed. + */ + protected MultipleDownloadsCompletedReceiver registerNewMultipleDownloadsReceiver() { + MultipleDownloadsCompletedReceiver receiver = new MultipleDownloadsCompletedReceiver(); + mContext.registerReceiver(receiver, new IntentFilter( + DownloadManager.ACTION_DOWNLOAD_COMPLETE)); + return receiver; + } + + /** + * Helper to verify a standard single-file download from the mock server, and clean up after + * verification + * + * Note that this also calls the Download manager's remove, which cleans up the file from cache. + * + * @param requestId The id of the download to remove + * @param fileData The data to verify the file contains + */ + protected void verifyAndCleanupSingleFileDownload(long requestId, byte[] fileData) + throws Exception { + int fileSize = fileData.length; + ParcelFileDescriptor pfd = mDownloadManager.openDownloadedFile(requestId); + Cursor cursor = mDownloadManager.query(new Query().setFilterById(requestId)); + + try { + assertEquals(1, cursor.getCount()); + assertTrue(cursor.moveToFirst()); + + mServer.checkForExceptions(); + + verifyFileSize(pfd, fileSize); + verifyFileContents(pfd, fileData); + } finally { + pfd.close(); + cursor.close(); + mDownloadManager.remove(requestId); + } + } + + /** + * Enables or disables WiFi. + * + * Note: Needs the following permissions: + * android.permission.ACCESS_WIFI_STATE + * android.permission.CHANGE_WIFI_STATE + * @param enable true if it should be enabled, false if it should be disabled + */ + protected void setWiFiStateOn(boolean enable) throws Exception { + WifiManager manager = (WifiManager)mContext.getSystemService(Context.WIFI_SERVICE); + + manager.setWifiEnabled(enable); + + String timeoutMessage = "Timed out waiting for Wifi to be " + + (enable ? "enabled!" : "disabled!"); + + WiFiChangedReceiver receiver = new WiFiChangedReceiver(mContext); + mContext.registerReceiver(receiver, new IntentFilter( + ConnectivityManager.CONNECTIVITY_ACTION)); + + synchronized (receiver) { + long timeoutTime = SystemClock.elapsedRealtime() + DEFAULT_MAX_WAIT_TIME; + boolean timedOut = false; + + while (receiver.getWiFiIsOn() != enable && !timedOut) { + try { + receiver.wait(DEFAULT_MAX_WAIT_TIME); + + if (SystemClock.elapsedRealtime() > timeoutTime) { + timedOut = true; + } + } + catch (InterruptedException e) { + // ignore InterruptedExceptions + } + } + if (timedOut) { + fail(timeoutMessage); + } + } + assertEquals(enable, receiver.getWiFiIsOn()); + } + + /** + * Helper to enables or disables airplane mode. If successful, it also broadcasts an intent + * indicating that the mode has changed. + * + * Note: Needs the following permission: + * android.permission.WRITE_SETTINGS + * @param enable true if airplane mode should be ON, false if it should be OFF + */ + protected void setAirplaneModeOn(boolean enable) throws Exception { + int state = enable ? 1 : 0; + + // Change the system setting + Settings.System.putInt(mContext.getContentResolver(), Settings.System.AIRPLANE_MODE_ON, + state); + + String timeoutMessage = "Timed out waiting for airplane mode to be " + + (enable ? "enabled!" : "disabled!"); + + // wait for airplane mode to change state + int currentWaitTime = 0; + while (Settings.System.getInt(mContext.getContentResolver(), + Settings.System.AIRPLANE_MODE_ON, -1) != state) { + timeoutWait(currentWaitTime, DEFAULT_WAIT_POLL_TIME, DEFAULT_MAX_WAIT_TIME, + timeoutMessage); + } + + // Post the intent + Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); + intent.putExtra("state", true); + mContext.sendBroadcast(intent); + } + + /** + * Helper to create a large file of random data on the SD card. + * + * @param filename (optional) The name of the file to create on the SD card; pass in null to + * use a default temp filename. + * @param type The type of file to create + * @param subdirectory If not null, the subdirectory under the SD card where the file should go + * @return The File that was created + * @throws IOException if there was an error while creating the file. + */ + protected File createFileOnSD(String filename, long fileSize, DataType type, + String subdirectory) throws IOException { + + // Build up the file path and name + String sdPath = Environment.getExternalStorageDirectory().getPath(); + StringBuilder fullPath = new StringBuilder(sdPath); + if (subdirectory != null) { + fullPath.append(File.separatorChar).append(subdirectory); + } + + File file = null; + if (filename == null) { + file = File.createTempFile("DMTEST_", null, new File(fullPath.toString())); + } + else { + fullPath.append(File.separatorChar).append(filename); + file = new File(fullPath.toString()); + file.createNewFile(); + } + + // Fill the file with random data + DataOutputStream output = new DataOutputStream(new FileOutputStream(file)); + final int CHUNK_SIZE = 1000000; // copy random data in 1000000-char chunks + long remaining = fileSize; + int nextChunkSize = CHUNK_SIZE; + byte[] randomData = null; + Random rng = new LoggingRng(); + + try { + while (remaining > 0) { + if (remaining < CHUNK_SIZE) { + nextChunkSize = (int)remaining; + remaining = 0; + } + else { + remaining -= CHUNK_SIZE; + } + + randomData = generateData(nextChunkSize, type, rng); + output.write(randomData); + } + } catch (IOException e) { + Log.e(LOG_TAG, "Error writing to file " + file.getAbsolutePath()); + file.delete(); + throw e; + } finally { + output.close(); + } + return file; + } + + /** + * Helper to wait for a particular download to finish, or else a timeout to occur + * + * @param id The download id to query on (wait for) + */ + protected void waitForDownloadOrTimeout(long id) throws TimeoutException, + InterruptedException { + waitForDownloadOrTimeout(id, WAIT_FOR_DOWNLOAD_POLL_TIME, MAX_WAIT_FOR_DOWNLOAD_TIME); + } + + /** + * Helper to wait for a particular download to finish, or else a timeout to occur + * + * @param id The download id to query on (wait for) + * @param poll The amount of time to wait + * @param timeoutMillis The max time (in ms) to wait for the download(s) to complete + */ + protected void waitForDownloadOrTimeout(long id, long poll, long timeoutMillis) + throws TimeoutException, InterruptedException { + doWaitForDownloadsOrTimeout(new Query().setFilterById(id), poll, timeoutMillis); + } + + /** + * Helper to wait for all downloads to finish, or else a specified timeout to occur + * + * @param poll The amount of time to wait + * @param timeoutMillis The max time (in ms) to wait for the download(s) to complete + */ + protected void waitForDownloadsOrTimeout(long poll, long timeoutMillis) throws TimeoutException, + InterruptedException { + doWaitForDownloadsOrTimeout(new Query(), poll, timeoutMillis); + } + + /** + * Helper to wait for all downloads to finish, or else a timeout to occur, but does not throw + * + * @param id The id of the download to query against + * @param poll The amount of time to wait + * @param timeoutMillis The max time (in ms) to wait for the download(s) to complete + * @return true if download completed successfully (didn't timeout), false otherwise + */ + protected boolean waitForDownloadOrTimeoutNoThrow(long id, long poll, long timeoutMillis) { + try { + doWaitForDownloadsOrTimeout(new Query().setFilterById(id), poll, timeoutMillis); + } catch (TimeoutException e) { + return false; + } + return true; + } + + /** + * Helper function to synchronously wait, or timeout if the maximum threshold has been exceeded. + * + * @param currentTotalWaitTime The total time waited so far + * @param poll The amount of time to wait + * @param maxTimeoutMillis The total wait time threshold; if we've waited more than this long, + * we timeout and fail + * @param timedOutMessage The message to display in the failure message if we timeout + * @return The new total amount of time we've waited so far + * @throws TimeoutException if timed out waiting for SD card to mount + */ + protected int timeoutWait(int currentTotalWaitTime, long poll, long maxTimeoutMillis, + String timedOutMessage) throws TimeoutException { + long now = SystemClock.elapsedRealtime(); + long end = now + poll; + + // if we get InterruptedException's, ignore them and just keep sleeping + while (now < end) { + try { + Thread.sleep(end - now); + } catch (InterruptedException e) { + // ignore interrupted exceptions + } + now = SystemClock.elapsedRealtime(); + } + + currentTotalWaitTime += poll; + if (currentTotalWaitTime > maxTimeoutMillis) { + throw new TimeoutException(timedOutMessage); + } + return currentTotalWaitTime; + } + + /** + * Helper to wait for all downloads to finish, or else a timeout to occur + * + * @param query The query to pass to the download manager + * @param poll The poll time to wait between checks + * @param timeoutMillis The max amount of time (in ms) to wait for the download(s) to complete + */ + protected void doWaitForDownloadsOrTimeout(Query query, long poll, long timeoutMillis) + throws TimeoutException { + int currentWaitTime = 0; + while (true) { + query.setFilterByStatus(DownloadManager.STATUS_PENDING | DownloadManager.STATUS_PAUSED + | DownloadManager.STATUS_RUNNING); + Cursor cursor = mDownloadManager.query(query); + + try { + // If we've finished the downloads then we're done + if (cursor.getCount() == 0) { + break; + } + currentWaitTime = timeoutWait(currentWaitTime, poll, timeoutMillis, + "Timed out waiting for all downloads to finish"); + } finally { + cursor.close(); + } + } + } + + /** + * Synchronously waits for external store to be mounted (eg: SD Card). + * + * @throws InterruptedException if interrupted + * @throws Exception if timed out waiting for SD card to mount + */ + protected void waitForExternalStoreMount() throws Exception { + String extStorageState = Environment.getExternalStorageState(); + int currentWaitTime = 0; + while (!extStorageState.equals(Environment.MEDIA_MOUNTED)) { + Log.i(LOG_TAG, "Waiting for SD card..."); + currentWaitTime = timeoutWait(currentWaitTime, DEFAULT_WAIT_POLL_TIME, + DEFAULT_MAX_WAIT_TIME, "Timed out waiting for SD Card to be ready!"); + extStorageState = Environment.getExternalStorageState(); + } + } + + /** + * Synchronously waits for a download to start. + * + * @param dlRequest the download request id used by Download Manager to track the download. + * @throws Exception if timed out while waiting for SD card to mount + */ + protected void waitForDownloadToStart(long dlRequest) throws Exception { + Cursor cursor = getCursor(dlRequest); + try { + int columnIndex = cursor.getColumnIndex(DownloadManager.COLUMN_STATUS); + int value = cursor.getInt(columnIndex); + int currentWaitTime = 0; + + while (value != DownloadManager.STATUS_RUNNING && + (value != DownloadManager.STATUS_FAILED) && + (value != DownloadManager.STATUS_SUCCESSFUL)) { + Log.i(LOG_TAG, "Waiting for download to start..."); + currentWaitTime = timeoutWait(currentWaitTime, WAIT_FOR_DOWNLOAD_POLL_TIME, + MAX_WAIT_FOR_DOWNLOAD_TIME, "Timed out waiting for download to start!"); + cursor.requery(); + assertTrue(cursor.moveToFirst()); + columnIndex = cursor.getColumnIndex(DownloadManager.COLUMN_STATUS); + value = cursor.getInt(columnIndex); + } + assertFalse("Download failed immediately after start", + value == DownloadManager.STATUS_FAILED); + } finally { + cursor.close(); + } + } + + /** + * Synchronously waits for a file to increase in size (such as to monitor that a download is + * progressing). + * + * @param file The file whose size to track. + * @throws Exception if timed out while waiting for the file to grow in size. + */ + protected void waitForFileToGrow(File file) throws Exception { + int currentWaitTime = 0; + + // File may not even exist yet, so wait until it does (or we timeout) + while (!file.exists()) { + Log.i(LOG_TAG, "Waiting for file to exist..."); + currentWaitTime = timeoutWait(currentWaitTime, WAIT_FOR_DOWNLOAD_POLL_TIME, + MAX_WAIT_FOR_DOWNLOAD_TIME, "Timed out waiting for file to be created."); + } + + // Get original file size... + long originalSize = file.length(); + + while (file.length() <= originalSize) { + Log.i(LOG_TAG, "Waiting for file to be written to..."); + currentWaitTime = timeoutWait(currentWaitTime, WAIT_FOR_DOWNLOAD_POLL_TIME, + MAX_WAIT_FOR_DOWNLOAD_TIME, "Timed out waiting for file to be written to."); + } + } + + /** + * Helper to remove all downloads that are registered with the DL Manager. + * + * Note: This gives us a clean slate b/c it includes downloads that are pending, running, + * paused, or have completed. + */ + protected void removeAllCurrentDownloads() { + Log.i(LOG_TAG, "Removing all current registered downloads..."); + Cursor cursor = mDownloadManager.query(new Query()); + try { + if (cursor.moveToFirst()) { + do { + int index = cursor.getColumnIndex(DownloadManager.COLUMN_ID); + long downloadId = cursor.getLong(index); + + mDownloadManager.remove(downloadId); + } while (cursor.moveToNext()); + } + } finally { + cursor.close(); + } + } + + /** + * Helper to perform a standard enqueue of data to the mock server. + * + * @param body The body to return in the response from the server + */ + protected long doStandardEnqueue(byte[] body) throws Exception { + // Prepare the mock server with a standard response + enqueueResponse(HTTP_OK, body); + return doCommonStandardEnqueue(); + } + + /** + * Helper to perform a standard enqueue of data to the mock server. + * + * @param body The body to return in the response from the server, contained in the file + */ + protected long doStandardEnqueue(File body) throws Exception { + // Prepare the mock server with a standard response + enqueueResponse(HTTP_OK, body); + return doCommonStandardEnqueue(); + } + + /** + * Helper to do the additional steps (setting title and Uri of default filename) when + * doing a standard enqueue request to the server. + */ + protected long doCommonStandardEnqueue() throws Exception { + Uri uri = getServerUri(DEFAULT_FILENAME); + Request request = new Request(uri); + request.setTitle(DEFAULT_FILENAME); + + long dlRequest = mDownloadManager.enqueue(request); + Log.i(LOG_TAG, "request ID: " + dlRequest); + return dlRequest; + } + + /** + * Helper to verify an int value in a Cursor + * + * @param cursor The cursor containing the query results + * @param columnName The name of the column to query + * @param expected The expected int value + */ + protected void verifyInt(Cursor cursor, String columnName, int expected) { + int index = cursor.getColumnIndex(columnName); + int actual = cursor.getInt(index); + assertEquals(expected, actual); + } + + /** + * Helper to verify a String value in a Cursor + * + * @param cursor The cursor containing the query results + * @param columnName The name of the column to query + * @param expected The expected String value + */ + protected void verifyString(Cursor cursor, String columnName, String expected) { + int index = cursor.getColumnIndex(columnName); + String actual = cursor.getString(index); + Log.i(LOG_TAG, ": " + actual); + assertEquals(expected, actual); + } + + /** + * Performs a query based on ID and returns a Cursor for the query. + * + * @param id The id of the download in DL Manager; pass -1 to query all downloads + * @return A cursor for the query results + */ + protected Cursor getCursor(long id) throws Exception { + Query query = new Query(); + if (id != -1) { + query.setFilterById(id); + } + + Cursor cursor = mDownloadManager.query(query); + int currentWaitTime = 0; + + try { + while (!cursor.moveToFirst()) { + Thread.sleep(DEFAULT_WAIT_POLL_TIME); + currentWaitTime += DEFAULT_WAIT_POLL_TIME; + if (currentWaitTime > DEFAULT_MAX_WAIT_TIME) { + fail("timed out waiting for a non-null query result"); + } + cursor.requery(); + } + } catch (Exception e) { + cursor.close(); + throw e; + } + return cursor; + } + +}
\ No newline at end of file diff --git a/core/tests/coretests/src/android/net/DownloadManagerIntegrationTest.java b/core/tests/coretests/src/android/net/DownloadManagerIntegrationTest.java new file mode 100644 index 000000000000..be3cbf729458 --- /dev/null +++ b/core/tests/coretests/src/android/net/DownloadManagerIntegrationTest.java @@ -0,0 +1,381 @@ +/* + * 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 android.net; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.database.Cursor; +import android.net.DownloadManager.Query; +import android.net.DownloadManager.Request; +import android.net.DownloadManagerBaseTest.DataType; +import android.net.DownloadManagerBaseTest.MultipleDownloadsCompletedReceiver; +import android.net.wifi.WifiManager; +import android.os.Environment; +import android.os.ParcelFileDescriptor; +import android.os.SystemClock; +import android.test.InstrumentationTestCase; +import android.test.suitebuilder.annotation.LargeTest; +import android.test.suitebuilder.annotation.MediumTest; +import android.test.suitebuilder.annotation.SmallTest; +import android.util.Log; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.net.URL; +import java.util.Random; + +import junit.framework.AssertionFailedError; + +import coretestutils.http.MockResponse; +import coretestutils.http.MockWebServer; + +/** + * Integration tests of the DownloadManager API. + */ +public class DownloadManagerIntegrationTest extends DownloadManagerBaseTest { + + private static String LOG_TAG = "android.net.DownloadManagerIntegrationTest"; + private static String PROHIBITED_DIRECTORY = "/system"; + protected MultipleDownloadsCompletedReceiver mReceiver = null; + + /** + * {@inheritDoc} + */ + @Override + public void setUp() throws Exception { + super.setUp(); + setWiFiStateOn(true); + mServer.play(); + removeAllCurrentDownloads(); + mReceiver = registerNewMultipleDownloadsReceiver(); + } + + /** + * {@inheritDoc} + */ + @Override + public void tearDown() throws Exception { + super.tearDown(); + setWiFiStateOn(true); + + if (mReceiver != null) { + mContext.unregisterReceiver(mReceiver); + mReceiver = null; + removeAllCurrentDownloads(); + } + } + + /** + * Helper that does the actual basic download verification. + */ + protected void doBasicDownload(byte[] blobData) throws Exception { + long dlRequest = doStandardEnqueue(blobData); + + // wait for the download to complete + waitForDownloadOrTimeout(dlRequest); + + verifyAndCleanupSingleFileDownload(dlRequest, blobData); + assertEquals(1, mReceiver.numDownloadsCompleted()); + } + + /** + * Test a basic download of a binary file 500k in size. + */ + @LargeTest + public void testBasicBinaryDownload() throws Exception { + int fileSize = 500 * 1024; // 500k + byte[] blobData = generateData(fileSize, DataType.BINARY); + + doBasicDownload(blobData); + } + + /** + * Tests the basic downloading of a text file 300000 bytes in size. + */ + @LargeTest + public void testBasicTextDownload() throws Exception { + int fileSize = 300000; + byte[] blobData = generateData(fileSize, DataType.TEXT); + + doBasicDownload(blobData); + } + + /** + * Tests when the server drops the connection after all headers (but before any data send). + */ + @LargeTest + public void testDropConnection_headers() throws Exception { + byte[] blobData = generateData(DEFAULT_FILE_SIZE, DataType.TEXT); + + MockResponse response = enqueueResponse(HTTP_OK, blobData); + response.setCloseConnectionAfterHeader("content-length"); + long dlRequest = doCommonStandardEnqueue(); + + // Download will never complete when header is dropped + boolean success = waitForDownloadOrTimeoutNoThrow(dlRequest, DEFAULT_WAIT_POLL_TIME, + DEFAULT_MAX_WAIT_TIME); + + assertFalse(success); + } + + /** + * Tests that we get an error code when the server drops the connection during a download. + */ + @LargeTest + public void testServerDropConnection_body() throws Exception { + byte[] blobData = generateData(25000, DataType.TEXT); // file size = 25000 bytes + + MockResponse response = enqueueResponse(HTTP_OK, blobData); + response.setCloseConnectionAfterXBytes(15382); + long dlRequest = doCommonStandardEnqueue(); + waitForDownloadOrTimeout(dlRequest); + + Cursor cursor = getCursor(dlRequest); + try { + verifyInt(cursor, DownloadManager.COLUMN_STATUS, DownloadManager.STATUS_FAILED); + verifyInt(cursor, DownloadManager.COLUMN_ERROR_CODE, + DownloadManager.ERROR_CANNOT_RESUME); + } finally { + cursor.close(); + } + // Even tho the server drops the connection, we should still get a completed notification + assertEquals(1, mReceiver.numDownloadsCompleted()); + } + + /** + * Attempts to download several files simultaneously + */ + @LargeTest + public void testMultipleDownloads() throws Exception { + // need to be sure all current downloads have stopped first + removeAllCurrentDownloads(); + int NUM_FILES = 50; + int MAX_FILE_SIZE = 500 * 1024; // 500 kb + + Random r = new LoggingRng(); + for (int i=0; i<NUM_FILES; ++i) { + int size = r.nextInt(MAX_FILE_SIZE); + byte[] blobData = generateData(size, DataType.TEXT); + + Uri uri = getServerUri(DEFAULT_FILENAME); + Request request = new Request(uri); + request.setTitle(String.format("%s--%d", DEFAULT_FILENAME, i)); + + // Prepare the mock server with a standard response + enqueueResponse(HTTP_OK, blobData); + + Log.i(LOG_TAG, "request: " + i); + mDownloadManager.enqueue(request); + } + + waitForDownloadsOrTimeout(WAIT_FOR_DOWNLOAD_POLL_TIME, MAX_WAIT_FOR_DOWNLOAD_TIME); + Cursor cursor = mDownloadManager.query(new Query()); + try { + assertEquals(NUM_FILES, cursor.getCount()); + + if (cursor.moveToFirst()) { + do { + int status = cursor.getInt(cursor.getColumnIndex( + DownloadManager.COLUMN_STATUS)); + String filename = cursor.getString(cursor.getColumnIndex( + DownloadManager.COLUMN_URI)); + String errorString = String.format( + "File %s failed to download successfully. Status code: %d", + filename, status); + assertEquals(errorString, DownloadManager.STATUS_SUCCESSFUL, status); + } while (cursor.moveToNext()); + } + + assertEquals(NUM_FILES, mReceiver.numDownloadsCompleted()); + } finally { + cursor.close(); + } + } + + /** + * Tests trying to download to SD card when the file with same name already exists. + */ + @LargeTest + public void testDownloadToExternal_fileExists() throws Exception { + File existentFile = createFileOnSD(null, 1, DataType.TEXT, null); + byte[] blobData = generateData(DEFAULT_FILE_SIZE, DataType.TEXT); + + // Prepare the mock server with a standard response + enqueueResponse(HTTP_OK, blobData); + + try { + Uri uri = getServerUri(DEFAULT_FILENAME); + Request request = new Request(uri); + + Uri localUri = Uri.fromFile(existentFile); + Log.i(LOG_TAG, "setting localUri to: " + localUri.getPath()); + request.setDestinationUri(localUri); + + long dlRequest = mDownloadManager.enqueue(request); + + // wait for the download to complete + waitForDownloadOrTimeout(dlRequest); + Cursor cursor = getCursor(dlRequest); + + try { + verifyInt(cursor, DownloadManager.COLUMN_STATUS, DownloadManager.STATUS_FAILED); + verifyInt(cursor, DownloadManager.COLUMN_ERROR_CODE, + DownloadManager.ERROR_FILE_ERROR); + } finally { + cursor.close(); + } + } finally { + existentFile.delete(); + } + } + + /** + * Tests trying to download a file to SD card. + */ + @LargeTest + public void testDownloadToExternal() throws Exception { + String localDownloadDirectory = Environment.getExternalStorageDirectory().getPath(); + File downloadedFile = new File(localDownloadDirectory, DEFAULT_FILENAME); + // make sure the file doesn't already exist in the directory + downloadedFile.delete(); + + try { + byte[] blobData = generateData(DEFAULT_FILE_SIZE, DataType.TEXT); + + // Prepare the mock server with a standard response + enqueueResponse(HTTP_OK, blobData); + + Uri uri = getServerUri(DEFAULT_FILENAME); + Request request = new Request(uri); + + Uri localUri = Uri.fromFile(downloadedFile); + Log.i(LOG_TAG, "setting localUri to: " + localUri.getPath()); + request.setDestinationUri(localUri); + + long dlRequest = mDownloadManager.enqueue(request); + + // wait for the download to complete + waitForDownloadOrTimeout(dlRequest); + + verifyAndCleanupSingleFileDownload(dlRequest, blobData); + + assertEquals(1, mReceiver.numDownloadsCompleted()); + } finally { + downloadedFile.delete(); + } + } + + /** + * Tests trying to download a file to the system partition. + */ + @LargeTest + public void testDownloadToProhibitedDirectory() throws Exception { + File downloadedFile = new File(PROHIBITED_DIRECTORY, DEFAULT_FILENAME); + try { + byte[] blobData = generateData(DEFAULT_FILE_SIZE, DataType.TEXT); + + // Prepare the mock server with a standard response + enqueueResponse(HTTP_OK, blobData); + + Uri uri = getServerUri(DEFAULT_FILENAME); + Request request = new Request(uri); + + Uri localUri = Uri.fromFile(downloadedFile); + Log.i(LOG_TAG, "setting localUri to: " + localUri.getPath()); + request.setDestinationUri(localUri); + + try { + mDownloadManager.enqueue(request); + fail("Failed to throw SecurityException when trying to write to /system."); + } catch (SecurityException s) { + assertFalse(downloadedFile.exists()); + } + } finally { + // Just in case file somehow got created, make sure to delete it + downloadedFile.delete(); + } + } + + /** + * Tests that a download set for Wifi does not progress while Wifi is disabled, but resumes + * once Wifi is re-enabled. + */ + @LargeTest + public void testDownloadNoWifi() throws Exception { + long timeout = 60 * 1000; // wait only 60 seconds before giving up + int fileSize = 140 * 1024; // 140k + byte[] blobData = generateData(fileSize, DataType.TEXT); + + setWiFiStateOn(false); + enqueueResponse(HTTP_OK, blobData); + + try { + Uri uri = getServerUri(DEFAULT_FILENAME); + Request request = new Request(uri); + request.setAllowedNetworkTypes(Request.NETWORK_WIFI); + + long dlRequest = mDownloadManager.enqueue(request); + + // wait for the download to complete + boolean success = waitForDownloadOrTimeoutNoThrow(dlRequest, + WAIT_FOR_DOWNLOAD_POLL_TIME, timeout); + assertFalse("Download proceeded without Wifi connection!", success); + + setWiFiStateOn(true); + waitForDownloadOrTimeout(dlRequest); + + assertEquals(1, mReceiver.numDownloadsCompleted()); + } finally { + setWiFiStateOn(true); + } + } + + /** + * Tests trying to download two large files (50M bytes, followed by 60M bytes) + */ + @LargeTest + public void testInsufficientSpaceSingleFiles() throws Exception { + long fileSize1 = 50000000L; + long fileSize2 = 60000000L; + File largeFile1 = createFileOnSD(null, fileSize1, DataType.TEXT, null); + File largeFile2 = createFileOnSD(null, fileSize2, DataType.TEXT, null); + + try { + long dlRequest = doStandardEnqueue(largeFile1); + waitForDownloadOrTimeout(dlRequest); + ParcelFileDescriptor pfd = mDownloadManager.openDownloadedFile(dlRequest); + verifyFileContents(pfd, largeFile1); + verifyFileSize(pfd, largeFile1.length()); + + dlRequest = doStandardEnqueue(largeFile2); + waitForDownloadOrTimeout(dlRequest); + Cursor cursor = getCursor(dlRequest); + try { + verifyInt(cursor, DownloadManager.COLUMN_ERROR_CODE, + DownloadManager.ERROR_INSUFFICIENT_SPACE); + } finally { + cursor.close(); + } + } finally { + largeFile1.delete(); + largeFile2.delete(); + } + } +} diff --git a/core/tests/coretests/src/android/net/DownloadManagerStressTest.java b/core/tests/coretests/src/android/net/DownloadManagerStressTest.java new file mode 100644 index 000000000000..9fa8620b0931 --- /dev/null +++ b/core/tests/coretests/src/android/net/DownloadManagerStressTest.java @@ -0,0 +1,156 @@ +/* + * 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 android.net; + +import java.io.File; +import java.util.Random; + +import android.database.Cursor; +import android.net.DownloadManager.Query; +import android.net.DownloadManager.Request; +import android.os.ParcelFileDescriptor; +import android.test.suitebuilder.annotation.LargeTest; +import android.util.Log; + + +public class DownloadManagerStressTest extends DownloadManagerBaseTest { + private static String LOG_TAG = "android.net.DownloadManagerStressTest"; + + /** + * {@inheritDoc} + */ + @Override + public void setUp() throws Exception { + super.setUp(); + mServer.play(0); + removeAllCurrentDownloads(); + } + + /** + * Attempts to downloading thousands of files simultaneously + */ + public void testDownloadThousands() throws Exception { + int NUM_FILES = 1500; + int MAX_FILE_SIZE = 3000; + long[] reqs = new long[NUM_FILES]; + + // need to be sure all current downloads have stopped first + MultipleDownloadsCompletedReceiver receiver = registerNewMultipleDownloadsReceiver(); + Cursor cursor = null; + try { + Random r = new LoggingRng(); + for (int i = 0; i < NUM_FILES; ++i) { + int size = r.nextInt(MAX_FILE_SIZE); + byte[] blobData = generateData(size, DataType.TEXT); + + Uri uri = getServerUri(DEFAULT_FILENAME); + Request request = new Request(uri); + request.setTitle(String.format("%s--%d", DEFAULT_FILENAME, i)); + + // Prepare the mock server with a standard response + enqueueResponse(HTTP_OK, blobData); + + Log.i(LOG_TAG, "issuing request: " + i); + long reqId = mDownloadManager.enqueue(request); + reqs[i] = reqId; + } + + // wait for the download to complete or timeout + waitForDownloadsOrTimeout(WAIT_FOR_DOWNLOAD_POLL_TIME, MAX_WAIT_FOR_DOWNLOAD_TIME); + cursor = mDownloadManager.query(new Query()); + assertEquals(NUM_FILES, cursor.getCount()); + Log.i(LOG_TAG, "Verified number of downloads in download manager is what we expect."); + while (cursor.moveToNext()) { + int status = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS)); + String filename = cursor.getString(cursor.getColumnIndex( + DownloadManager.COLUMN_URI)); + String errorString = String.format("File %s failed to download successfully. " + + "Status code: %d", filename, status); + assertEquals(errorString, DownloadManager.STATUS_SUCCESSFUL, status); + } + Log.i(LOG_TAG, "Verified each download was successful."); + assertEquals(NUM_FILES, receiver.numDownloadsCompleted()); + Log.i(LOG_TAG, "Verified number of completed downloads in our receiver."); + + // Verify that for each request, we can open the downloaded file + for (int i = 0; i < NUM_FILES; ++i) { + ParcelFileDescriptor pfd = mDownloadManager.openDownloadedFile(reqs[i]); + pfd.close(); + } + Log.i(LOG_TAG, "Verified we can open each file."); + } finally { + if (cursor != null) { + cursor.close(); + } + mContext.unregisterReceiver(receiver); + removeAllCurrentDownloads(); + } + } + + /** + * Tests trying to download a large file (50M bytes). + */ + public void testDownloadLargeFile() throws Exception { + long fileSize = 50000000L; // note: kept relatively small to not exceed /cache dir size + File largeFile = createFileOnSD(null, fileSize, DataType.TEXT, null); + MultipleDownloadsCompletedReceiver receiver = registerNewMultipleDownloadsReceiver(); + + try { + long dlRequest = doStandardEnqueue(largeFile); + + // wait for the download to complete + waitForDownloadOrTimeout(dlRequest); + + ParcelFileDescriptor pfd = mDownloadManager.openDownloadedFile(dlRequest); + verifyFileContents(pfd, largeFile); + verifyFileSize(pfd, largeFile.length()); + + assertEquals(1, receiver.numDownloadsCompleted()); + mContext.unregisterReceiver(receiver); + } catch (Exception e) { + throw e; + } finally { + largeFile.delete(); + } + } + + /** + * Tests trying to download a large file (~300M bytes) when there's not enough space in cache + */ + public void testInsufficientSpace() throws Exception { + long fileSize = 300000000L; + File largeFile = createFileOnSD(null, fileSize, DataType.TEXT, null); + + Cursor cursor = null; + try { + long dlRequest = doStandardEnqueue(largeFile); + + // wait for the download to complete + waitForDownloadOrTimeout(dlRequest); + + cursor = getCursor(dlRequest); + verifyInt(cursor, DownloadManager.COLUMN_STATUS, DownloadManager.STATUS_FAILED); + verifyInt(cursor, DownloadManager.COLUMN_ERROR_CODE, + DownloadManager.ERROR_INSUFFICIENT_SPACE); + } finally { + if (cursor != null) { + cursor.close(); + } + largeFile.delete(); + } + } +} diff --git a/core/tests/coretests/src/android/net/http/HttpsThroughHttpProxyTest.java b/core/tests/coretests/src/android/net/http/HttpsThroughHttpProxyTest.java new file mode 100644 index 000000000000..95aad91a6f10 --- /dev/null +++ b/core/tests/coretests/src/android/net/http/HttpsThroughHttpProxyTest.java @@ -0,0 +1,142 @@ +/* + * 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 android.net.http; + +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.StringWriter; +import java.util.Arrays; +import java.util.List; +import junit.framework.TestCase; +import libcore.javax.net.ssl.TestSSLContext; +import org.apache.http.HttpHost; +import org.apache.http.HttpResponse; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.conn.params.ConnRoutePNames; +import org.apache.http.conn.scheme.Scheme; +import org.apache.http.conn.ssl.AllowAllHostnameVerifier; +import org.apache.http.conn.ssl.SSLSocketFactory; +import org.apache.http.impl.client.DefaultHttpClient; +import tests.http.MockResponse; +import tests.http.MockWebServer; +import tests.http.RecordedRequest; + +public class HttpsThroughHttpProxyTest extends TestCase { + + public void testConnectViaHttps() throws IOException, InterruptedException { + TestSSLContext testSSLContext = TestSSLContext.create(); + + MockWebServer server = new MockWebServer(); + server.useHttps(testSSLContext.serverContext.getSocketFactory(), false); + server.enqueue(new MockResponse() + .setResponseCode(200) + .setBody("this response comes via HTTPS")); + server.play(); + + HttpClient httpClient = new DefaultHttpClient(); + SSLSocketFactory sslSocketFactory = new SSLSocketFactory( + testSSLContext.clientContext.getSocketFactory()); + sslSocketFactory.setHostnameVerifier(new AllowAllHostnameVerifier()); + httpClient.getConnectionManager().getSchemeRegistry() + .register(new Scheme("https", sslSocketFactory, server.getPort())); + + HttpResponse response = httpClient.execute( + new HttpGet("https://localhost:" + server.getPort() + "/foo")); + assertEquals("this response comes via HTTPS", contentToString(response)); + + RecordedRequest request = server.takeRequest(); + assertEquals("GET /foo HTTP/1.1", request.getRequestLine()); + } + + /** + * http://code.google.com/p/android/issues/detail?id=2690 + */ + public void testConnectViaProxy() throws IOException, InterruptedException { + MockWebServer proxy = new MockWebServer(); + MockResponse mockResponse = new MockResponse() + .setResponseCode(200) + .setBody("this response comes via a proxy"); + proxy.enqueue(mockResponse); + proxy.play(); + + HttpClient httpProxyClient = new DefaultHttpClient(); + httpProxyClient.getParams().setParameter( + ConnRoutePNames.DEFAULT_PROXY, new HttpHost("localhost", proxy.getPort())); + + HttpResponse response = httpProxyClient.execute(new HttpGet("http://android.com/foo")); + assertEquals("this response comes via a proxy", contentToString(response)); + + RecordedRequest request = proxy.takeRequest(); + assertEquals("GET http://android.com/foo HTTP/1.1", request.getRequestLine()); + assertContains(request.getHeaders(), "Host: android.com"); + } + + public void testConnectViaHttpProxyToHttps() throws IOException, InterruptedException { + TestSSLContext testSSLContext = TestSSLContext.create(); + + MockWebServer proxy = new MockWebServer(); + proxy.useHttps(testSSLContext.serverContext.getSocketFactory(), true); + MockResponse connectResponse = new MockResponse() + .setResponseCode(200); + connectResponse.getHeaders().clear(); + proxy.enqueue(connectResponse); + proxy.enqueue(new MockResponse() + .setResponseCode(200) + .setBody("this response comes via a secure proxy")); + proxy.play(); + + HttpClient httpProxyClient = new DefaultHttpClient(); + HttpHost proxyHost = new HttpHost("localhost", proxy.getPort()); + httpProxyClient.getParams().setParameter( + ConnRoutePNames.DEFAULT_PROXY, proxyHost); + SSLSocketFactory sslSocketFactory = new SSLSocketFactory( + testSSLContext.clientContext.getSocketFactory()); + sslSocketFactory.setHostnameVerifier(new AllowAllHostnameVerifier()); + httpProxyClient.getConnectionManager().getSchemeRegistry() + .register(new Scheme("https", sslSocketFactory, 443)); + + HttpResponse response = httpProxyClient.execute(new HttpGet("https://android.com/foo")); + assertEquals("this response comes via a secure proxy", contentToString(response)); + + RecordedRequest connect = proxy.takeRequest(); + assertEquals("Connect line failure on proxy " + proxyHost.toHostString(), + "CONNECT android.com:443 HTTP/1.1", connect.getRequestLine()); + assertContains(connect.getHeaders(), "Host: android.com"); + + RecordedRequest get = proxy.takeRequest(); + assertEquals("GET /foo HTTP/1.1", get.getRequestLine()); + assertContains(get.getHeaders(), "Host: android.com"); + } + + private void assertContains(List<String> headers, String header) { + assertTrue(headers.toString(), headers.contains(header)); + } + + private String contentToString(HttpResponse response) throws IOException { + StringWriter writer = new StringWriter(); + char[] buffer = new char[1024]; + Reader reader = new InputStreamReader(response.getEntity().getContent()); + int length; + while ((length = reader.read(buffer)) != -1) { + writer.write(buffer, 0, length); + } + reader.close(); + return writer.toString(); + } +} diff --git a/core/tests/coretests/src/android/os/FileObserverTest.java b/core/tests/coretests/src/android/os/FileObserverTest.java index ca4e0d657c46..93e27af44170 100644 --- a/core/tests/coretests/src/android/os/FileObserverTest.java +++ b/core/tests/coretests/src/android/os/FileObserverTest.java @@ -19,9 +19,8 @@ package android.os; import com.google.android.collect.Lists; import com.google.android.collect.Maps; -import android.os.FileObserver; import android.test.AndroidTestCase; -import android.test.suitebuilder.annotation.LargeTest; +import android.test.suitebuilder.annotation.MediumTest; import android.util.Log; import java.io.File; @@ -69,7 +68,7 @@ public class FileObserverTest extends AndroidTestCase { } } - @LargeTest + @MediumTest public void testRun() throws Exception { // make file changes and wait for them assertTrue(mTestFile.exists()); diff --git a/core/tests/coretests/src/android/os/HierarchicalStateMachineTest.java b/core/tests/coretests/src/android/os/HierarchicalStateMachineTest.java index 89b3fb61ddd2..68209876319a 100644 --- a/core/tests/coretests/src/android/os/HierarchicalStateMachineTest.java +++ b/core/tests/coretests/src/android/os/HierarchicalStateMachineTest.java @@ -16,23 +16,15 @@ package android.os; -import junit.framework.TestCase; +import com.android.internal.util.HierarchicalState; +import com.android.internal.util.HierarchicalStateMachine; +import com.android.internal.util.ProcessedMessages; -import android.os.Debug; -import android.os.HandlerThread; -import android.os.Looper; -import android.os.Message; -import android.os.SystemClock; +import android.test.suitebuilder.annotation.MediumTest; import android.test.suitebuilder.annotation.SmallTest; - import android.util.Log; -import com.android.internal.util.HierarchicalStateMachine; -import com.android.internal.util.HierarchicalState; -import com.android.internal.util.ProcessedMessages; - -import java.util.ArrayList; -import java.util.Arrays; +import junit.framework.TestCase; /** * Test for HierarchicalStateMachine. @@ -74,15 +66,15 @@ public class HierarchicalStateMachineTest extends TestCase { if (isQuit(message)) { mQuitCount += 1; if (mQuitCount > 2) { - // Returning false to actually quit - return false; + // Returning NOT_HANDLED to actually quit + return NOT_HANDLED; } else { // Do NOT quit - return true; + return HANDLED; } } else { // All other message are handled - return true; + return HANDLED; } } } @@ -172,12 +164,18 @@ public class HierarchicalStateMachineTest extends TestCase { class S1 extends HierarchicalState { @Override protected void enter() { + // Test that message is HSM_INIT_CMD + assertEquals(HSM_INIT_CMD, getCurrentMessage().what); + // Test that a transition in enter and the initial state works mS1EnterCount += 1; transitionTo(mS2); Log.d(TAG, "S1.enter"); } @Override protected void exit() { + // Test that message is HSM_INIT_CMD + assertEquals(HSM_INIT_CMD, getCurrentMessage().what); + mS1ExitCount += 1; Log.d(TAG, "S1.exit"); } @@ -185,10 +183,16 @@ public class HierarchicalStateMachineTest extends TestCase { class S2 extends HierarchicalState { @Override protected void enter() { + // Test that message is HSM_INIT_CMD + assertEquals(HSM_INIT_CMD, getCurrentMessage().what); + mS2EnterCount += 1; Log.d(TAG, "S2.enter"); } @Override protected void exit() { + // Test that message is TEST_CMD_1 + assertEquals(TEST_CMD_1, getCurrentMessage().what); + // Test transition in exit work mS2ExitCount += 1; transitionTo(mS4); @@ -196,10 +200,10 @@ public class HierarchicalStateMachineTest extends TestCase { } @Override protected boolean processMessage(Message message) { // Start a transition to S3 but it will be - // changed to a transition to S4 + // changed to a transition to S4 in exit transitionTo(mS3); Log.d(TAG, "S2.processMessage"); - return true; + return HANDLED; } } @@ -264,7 +268,7 @@ public class HierarchicalStateMachineTest extends TestCase { } synchronized (smEnterExitTranstionToTest) { - smEnterExitTranstionToTest.sendMessage(1); + smEnterExitTranstionToTest.sendMessage(TEST_CMD_1); try { // wait for the messages to be handled @@ -321,7 +325,7 @@ public class HierarchicalStateMachineTest extends TestCase { if (message.what == TEST_CMD_6) { transitionToHaltingState(); } - return true; + return HANDLED; } } @@ -415,7 +419,7 @@ public class HierarchicalStateMachineTest extends TestCase { assertEquals(1, mExitCount); transitionToHaltingState(); } - return true; + return HANDLED; } @Override protected void exit() { @@ -437,7 +441,7 @@ public class HierarchicalStateMachineTest extends TestCase { private int mExitCount; } - @SmallTest + @MediumTest public void testStateMachine1() throws Exception { StateMachine1 sm1 = new StateMachine1("sm1"); sm1.start(); @@ -510,7 +514,7 @@ public class HierarchicalStateMachineTest extends TestCase { if (message.what == TEST_CMD_2) { transitionTo(mS2); } - return true; + return HANDLED; } @Override protected void exit() { @@ -523,7 +527,7 @@ public class HierarchicalStateMachineTest extends TestCase { if (message.what == TEST_CMD_2) { transitionToHaltingState(); } - return true; + return HANDLED; } } @@ -542,7 +546,7 @@ public class HierarchicalStateMachineTest extends TestCase { private boolean mDidExit = false; } - @SmallTest + @MediumTest public void testStateMachine2() throws Exception { StateMachine2 sm2 = new StateMachine2("sm2"); sm2.start(); @@ -612,13 +616,13 @@ public class HierarchicalStateMachineTest extends TestCase { if (message.what == TEST_CMD_2) { transitionToHaltingState(); } - return true; + return HANDLED; } } class ChildState extends HierarchicalState { @Override protected boolean processMessage(Message message) { - return false; + return NOT_HANDLED; } } @@ -634,7 +638,7 @@ public class HierarchicalStateMachineTest extends TestCase { private ChildState mChildState = new ChildState(); } - @SmallTest + @MediumTest public void testStateMachine3() throws Exception { StateMachine3 sm3 = new StateMachine3("sm3"); sm3.start(); @@ -697,20 +701,20 @@ public class HierarchicalStateMachineTest extends TestCase { if (message.what == TEST_CMD_2) { transitionToHaltingState(); } - return true; + return HANDLED; } } class ChildState1 extends HierarchicalState { @Override protected boolean processMessage(Message message) { transitionTo(mChildState2); - return true; + return HANDLED; } } class ChildState2 extends HierarchicalState { @Override protected boolean processMessage(Message message) { - return false; + return NOT_HANDLED; } } @@ -727,7 +731,7 @@ public class HierarchicalStateMachineTest extends TestCase { private ChildState2 mChildState2 = new ChildState2(); } - @SmallTest + @MediumTest public void testStateMachine4() throws Exception { StateMachine4 sm4 = new StateMachine4("sm4"); sm4.start(); @@ -794,7 +798,7 @@ public class HierarchicalStateMachineTest extends TestCase { mParentState1EnterCount += 1; } @Override protected boolean processMessage(Message message) { - return true; + return HANDLED; } @Override protected void exit() { mParentState1ExitCount += 1; @@ -822,7 +826,7 @@ public class HierarchicalStateMachineTest extends TestCase { assertEquals(0, mChildState5ExitCount); transitionTo(mChildState2); - return true; + return HANDLED; } @Override protected void exit() { mChildState1ExitCount += 1; @@ -850,7 +854,7 @@ public class HierarchicalStateMachineTest extends TestCase { assertEquals(0, mChildState5ExitCount); transitionTo(mChildState5); - return true; + return HANDLED; } @Override protected void exit() { mChildState2ExitCount += 1; @@ -878,7 +882,7 @@ public class HierarchicalStateMachineTest extends TestCase { assertEquals(1, mChildState5ExitCount); transitionToHaltingState(); - return true; + return HANDLED; } @Override protected void exit() { mParentState2ExitCount += 1; @@ -906,7 +910,7 @@ public class HierarchicalStateMachineTest extends TestCase { assertEquals(1, mChildState5ExitCount); transitionTo(mChildState4); - return true; + return HANDLED; } @Override protected void exit() { mChildState3ExitCount += 1; @@ -934,7 +938,7 @@ public class HierarchicalStateMachineTest extends TestCase { assertEquals(1, mChildState5ExitCount); transitionTo(mParentState2); - return true; + return HANDLED; } @Override protected void exit() { mChildState4ExitCount += 1; @@ -962,7 +966,7 @@ public class HierarchicalStateMachineTest extends TestCase { assertEquals(0, mChildState5ExitCount); transitionTo(mChildState3); - return true; + return HANDLED; } @Override protected void exit() { mChildState5ExitCount += 1; @@ -1001,7 +1005,7 @@ public class HierarchicalStateMachineTest extends TestCase { private int mChildState5ExitCount = 0; } - @SmallTest + @MediumTest public void testStateMachine5() throws Exception { StateMachine5 sm5 = new StateMachine5("sm5"); sm5.start(); @@ -1108,7 +1112,7 @@ public class HierarchicalStateMachineTest extends TestCase { mArrivalTimeMsg2 = SystemClock.elapsedRealtime(); transitionToHaltingState(); } - return true; + return HANDLED; } @Override protected void exit() { @@ -1129,7 +1133,7 @@ public class HierarchicalStateMachineTest extends TestCase { private long mArrivalTimeMsg2; } - @SmallTest + @MediumTest public void testStateMachine6() throws Exception { long sentTimeMsg2; final int DELAY_TIME = 250; @@ -1190,7 +1194,7 @@ public class HierarchicalStateMachineTest extends TestCase { class S1 extends HierarchicalState { @Override protected boolean processMessage(Message message) { transitionTo(mS2); - return true; + return HANDLED; } @Override protected void exit() { sendMessage(TEST_CMD_2); @@ -1216,7 +1220,7 @@ public class HierarchicalStateMachineTest extends TestCase { if (mMsgCount == 2) { transitionToHaltingState(); } - return true; + return HANDLED; } @Override protected void exit() { @@ -1239,7 +1243,7 @@ public class HierarchicalStateMachineTest extends TestCase { private long mArrivalTimeMsg3; } - @SmallTest + @MediumTest public void testStateMachine7() throws Exception { long sentTimeMsg2; final int SM7_DELAY_FUDGE = 20; @@ -1300,7 +1304,7 @@ public class HierarchicalStateMachineTest extends TestCase { if (message.what == TEST_CMD_2) { transitionToHaltingState(); } - return false; + return NOT_HANDLED; } } @@ -1369,7 +1373,7 @@ public class HierarchicalStateMachineTest extends TestCase { if (message.what == TEST_CMD_4) { transitionToHaltingState(); } - return true; + return HANDLED; } } @@ -1391,7 +1395,7 @@ public class HierarchicalStateMachineTest extends TestCase { private static int sharedCounter = 0; private static Object waitObject = new Object(); - @SmallTest + @MediumTest public void testStateMachineSharedThread() throws Exception { if (DBG) Log.d(TAG, "testStateMachineSharedThread E"); @@ -1436,7 +1440,7 @@ public class HierarchicalStateMachineTest extends TestCase { if (DBG) Log.d(TAG, "testStateMachineSharedThread X"); } - @SmallTest + @MediumTest public void testHsm1() throws Exception { if (DBG) Log.d(TAG, "testHsm1 E"); @@ -1563,10 +1567,10 @@ class Hsm1 extends HierarchicalStateMachine { if (message.what == CMD_1) { // Transition to ourself to show that enter/exit is called transitionTo(mS1); - return true; + return HANDLED; } else { // Let parent process all other messages - return false; + return NOT_HANDLED; } } @Override protected void exit() { @@ -1618,7 +1622,7 @@ class Hsm1 extends HierarchicalStateMachine { transitionToHaltingState(); break; } - return true; + return HANDLED; } @Override protected void exit() { Log.d(TAG, "P2.exit"); diff --git a/core/tests/coretests/src/android/os/PerformanceCollectorTest.java b/core/tests/coretests/src/android/os/PerformanceCollectorTest.java index a382239e7869..7533c84673de 100644 --- a/core/tests/coretests/src/android/os/PerformanceCollectorTest.java +++ b/core/tests/coretests/src/android/os/PerformanceCollectorTest.java @@ -16,11 +16,8 @@ package android.os; -import android.os.Bundle; -import android.os.Parcelable; -import android.os.PerformanceCollector; -import android.os.Process; import android.os.PerformanceCollector.PerformanceResultsWriter; +import android.test.suitebuilder.annotation.MediumTest; import android.test.suitebuilder.annotation.SmallTest; import java.lang.reflect.Field; @@ -56,7 +53,7 @@ public class PerformanceCollectorTest extends TestCase { assertEquals(2, snapshot.size()); } - @SmallTest + @MediumTest public void testEndSnapshotNoWriter() throws Exception { mPerfCollector.beginSnapshot("testEndSnapshotNoWriter"); workForRandomLongPeriod(); @@ -116,7 +113,7 @@ public class PerformanceCollectorTest extends TestCase { assertEquals(2, snapshot.size()); } - @SmallTest + @MediumTest public void testEndSnapshot() throws Exception { MockPerformanceResultsWriter writer = new MockPerformanceResultsWriter(); mPerfCollector.setPerformanceResultsWriter(writer); @@ -232,7 +229,7 @@ public class PerformanceCollectorTest extends TestCase { assertEquals("Hello World", results.getString("testAddMeasurementStringNonEmpty")); } - @SmallTest + @MediumTest public void testSimpleSequence() throws Exception { MockPerformanceResultsWriter writer = new MockPerformanceResultsWriter(); mPerfCollector.setPerformanceResultsWriter(writer); @@ -264,7 +261,7 @@ public class PerformanceCollectorTest extends TestCase { verifyTimingBundle(timing, labels); } - @SmallTest + @MediumTest public void testLongSequence() throws Exception { MockPerformanceResultsWriter writer = new MockPerformanceResultsWriter(); mPerfCollector.setPerformanceResultsWriter(writer); @@ -350,7 +347,7 @@ public class PerformanceCollectorTest extends TestCase { * Verify that snapshotting and timing do not interfere w/ each other, * by staggering calls to snapshot and timing functions. */ - @SmallTest + @MediumTest public void testOutOfOrderSequence() { MockPerformanceResultsWriter writer = new MockPerformanceResultsWriter(); mPerfCollector.setPerformanceResultsWriter(writer); diff --git a/core/tests/coretests/src/android/os/PowerManagerTest.java b/core/tests/coretests/src/android/os/PowerManagerTest.java index e089b3e0768e..9893c161fec8 100644 --- a/core/tests/coretests/src/android/os/PowerManagerTest.java +++ b/core/tests/coretests/src/android/os/PowerManagerTest.java @@ -17,9 +17,8 @@ package android.os; import android.content.Context; -import android.os.PowerManager; import android.test.AndroidTestCase; -import android.test.suitebuilder.annotation.MediumTest; +import android.test.suitebuilder.annotation.SmallTest; public class PowerManagerTest extends AndroidTestCase { @@ -39,7 +38,7 @@ public class PowerManagerTest extends AndroidTestCase { * * @throws Exception */ - @MediumTest + @SmallTest public void testPreconditions() throws Exception { assertNotNull(mPm); } @@ -49,7 +48,7 @@ public class PowerManagerTest extends AndroidTestCase { * * @throws Exception */ - @MediumTest + @SmallTest public void testNewWakeLock() throws Exception { PowerManager.WakeLock wl = mPm.newWakeLock(PowerManager.FULL_WAKE_LOCK, "FULL_WAKE_LOCK"); doTestWakeLock(wl); @@ -74,7 +73,7 @@ public class PowerManagerTest extends AndroidTestCase { * * @throws Exception */ - @MediumTest + @SmallTest public void testBadNewWakeLock() throws Exception { final int badFlags = PowerManager.SCREEN_BRIGHT_WAKE_LOCK diff --git a/core/tests/coretests/src/android/os/storage/StorageListener.java b/core/tests/coretests/src/android/os/storage/StorageListener.java index d6dae2227cda..6a26b88d5c44 100644 --- a/core/tests/coretests/src/android/os/storage/StorageListener.java +++ b/core/tests/coretests/src/android/os/storage/StorageListener.java @@ -21,21 +21,24 @@ import android.util.Log; public class StorageListener extends StorageEventListener { private static final boolean localLOGV = true; - public static final String TAG="StorageListener"; + public static final String TAG = "StorageListener"; - String oldState; - String newState; - String path; + private String mTargetState; private boolean doneFlag = false; + + public StorageListener(String targetState) { + mTargetState = targetState; + } + @Override public void onStorageStateChanged(String path, String oldState, String newState) { if (localLOGV) Log.i(TAG, "Storage state changed from " + oldState + " to " + newState); + synchronized (this) { - this.oldState = oldState; - this.newState = newState; - this.path = path; - doneFlag = true; - notifyAll(); + if (mTargetState.equals(newState)) { + doneFlag = true; + notifyAll(); + } } } diff --git a/core/tests/coretests/src/android/pim/vcard/ContentValuesVerifier.java b/core/tests/coretests/src/android/pim/vcard/ContentValuesVerifier.java deleted file mode 100644 index b9e9875cd9a7..000000000000 --- a/core/tests/coretests/src/android/pim/vcard/ContentValuesVerifier.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright (C) 2009 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.pim.vcard; - -import android.pim.vcard.VCardConfig; -import android.pim.vcard.VCardEntry; -import android.pim.vcard.VCardEntryConstructor; -import android.pim.vcard.VCardEntryHandler; -import android.pim.vcard.VCardParser; -import android.pim.vcard.VCardParser_V21; -import android.pim.vcard.VCardParser_V30; -import android.pim.vcard.exception.VCardException; -import android.test.AndroidTestCase; - -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.List; - -/* package */ class ContentValuesVerifier implements VCardEntryHandler { - private AndroidTestCase mTestCase; - private List<ContentValuesVerifierElem> mContentValuesVerifierElemList = - new ArrayList<ContentValuesVerifierElem>(); - private int mIndex; - - public ContentValuesVerifierElem addElem(AndroidTestCase androidTestCase) { - mTestCase = androidTestCase; - ContentValuesVerifierElem importVerifier = new ContentValuesVerifierElem(androidTestCase); - mContentValuesVerifierElemList.add(importVerifier); - return importVerifier; - } - - public void verify(int resId, int vCardType) throws IOException, VCardException { - verify(mTestCase.getContext().getResources().openRawResource(resId), vCardType); - } - - public void verify(int resId, int vCardType, final VCardParser vCardParser) - throws IOException, VCardException { - verify(mTestCase.getContext().getResources().openRawResource(resId), - vCardType, vCardParser); - } - - public void verify(InputStream is, int vCardType) throws IOException, VCardException { - final VCardParser vCardParser; - if (VCardConfig.isV30(vCardType)) { - vCardParser = new VCardParser_V30(true); // use StrictParsing - } else { - vCardParser = new VCardParser_V21(); - } - verify(is, vCardType, vCardParser); - } - - public void verify(InputStream is, int vCardType, final VCardParser vCardParser) - throws IOException, VCardException { - VCardEntryConstructor builder = - new VCardEntryConstructor(null, null, false, vCardType, null); - builder.addEntryHandler(this); - try { - vCardParser.parse(is, builder); - } finally { - if (is != null) { - try { - is.close(); - } catch (IOException e) { - } - } - } - } - - public void onStart() { - for (ContentValuesVerifierElem elem : mContentValuesVerifierElemList) { - elem.onParsingStart(); - } - } - - public void onEntryCreated(VCardEntry entry) { - mTestCase.assertTrue(mIndex < mContentValuesVerifierElemList.size()); - mContentValuesVerifierElemList.get(mIndex).onEntryCreated(entry); - mIndex++; - } - - public void onEnd() { - for (ContentValuesVerifierElem elem : mContentValuesVerifierElemList) { - elem.onParsingEnd(); - elem.verifyResolver(); - } - } -} diff --git a/core/tests/coretests/src/android/pim/vcard/VCardExporterTests.java b/core/tests/coretests/src/android/pim/vcard/VCardExporterTests.java index 004a197352d9..2bec46287fae 100644 --- a/core/tests/coretests/src/android/pim/vcard/VCardExporterTests.java +++ b/core/tests/coretests/src/android/pim/vcard/VCardExporterTests.java @@ -17,7 +17,10 @@ package android.pim.vcard; import android.content.ContentValues; -import android.pim.vcard.VCardConfig; +import android.pim.vcard.test_utils.ContactEntry; +import android.pim.vcard.test_utils.PropertyNodesVerifierElem; +import android.pim.vcard.test_utils.PropertyNodesVerifierElem.TypeSet; +import android.pim.vcard.test_utils.VCardTestsBase; import android.provider.ContactsContract.CommonDataKinds.Email; import android.provider.ContactsContract.CommonDataKinds.Event; import android.provider.ContactsContract.CommonDataKinds.Im; @@ -31,8 +34,6 @@ import android.provider.ContactsContract.CommonDataKinds.StructuredName; import android.provider.ContactsContract.CommonDataKinds.StructuredPostal; import android.provider.ContactsContract.CommonDataKinds.Website; -import android.pim.vcard.PropertyNodesVerifierElem.TypeSet; - import java.util.Arrays; /** @@ -56,7 +57,6 @@ public class VCardExporterTests extends VCardTestsBase { } private void testStructuredNameBasic(int vcardType) { - final boolean isV30 = VCardConfig.isV30(vcardType); mVerifier.initForExportTest(vcardType); mVerifier.addInputEntry().addContentValues(StructuredName.CONTENT_ITEM_TYPE) .put(StructuredName.FAMILY_NAME, "AppropriateFamilyName") @@ -64,28 +64,15 @@ public class VCardExporterTests extends VCardTestsBase { .put(StructuredName.MIDDLE_NAME, "AppropriateMiddleName") .put(StructuredName.PREFIX, "AppropriatePrefix") .put(StructuredName.SUFFIX, "AppropriateSuffix") - .put(StructuredName.PHONETIC_FAMILY_NAME, "AppropriatePhoneticFamily") - .put(StructuredName.PHONETIC_GIVEN_NAME, "AppropriatePhoneticGiven") - .put(StructuredName.PHONETIC_MIDDLE_NAME, "AppropriatePhoneticMiddle"); + .put(StructuredName.DISPLAY_NAME, "DISPLAY NAME"); - PropertyNodesVerifierElem elem = mVerifier.addPropertyNodesVerifierElem() + mVerifier.addPropertyNodesVerifierElem() .addExpectedNodeWithOrder("N", "AppropriateFamilyName;AppropriateGivenName;AppropriateMiddleName;" + "AppropriatePrefix;AppropriateSuffix", Arrays.asList("AppropriateFamilyName", "AppropriateGivenName", "AppropriateMiddleName", "AppropriatePrefix", "AppropriateSuffix")) - .addExpectedNodeWithOrder("FN", - "AppropriatePrefix AppropriateGivenName " - + "AppropriateMiddleName AppropriateFamilyName AppropriateSuffix") - .addExpectedNode("X-PHONETIC-FIRST-NAME", "AppropriatePhoneticGiven") - .addExpectedNode("X-PHONETIC-MIDDLE-NAME", "AppropriatePhoneticMiddle") - .addExpectedNode("X-PHONETIC-LAST-NAME", "AppropriatePhoneticFamily"); - - if (isV30) { - elem.addExpectedNode("SORT-STRING", - "AppropriatePhoneticGiven AppropriatePhoneticMiddle " - + "AppropriatePhoneticFamily"); - } + .addExpectedNodeWithOrder("FN", "DISPLAY NAME"); } public void testStructuredNameBasicV21() { @@ -96,6 +83,10 @@ public class VCardExporterTests extends VCardTestsBase { testStructuredNameBasic(V30); } + public void testStructuredNameBasicV40() { + testStructuredNameBasic(V40); + } + /** * Test that only "primary" StructuredName is emitted, so that our vCard file * will not confuse the external importer, assuming there may be some importer @@ -103,18 +94,15 @@ public class VCardExporterTests extends VCardTestsBase { * Note that more than one "N", "FN", etc. properties are acceptable in vCard spec. */ private void testStructuredNameUsePrimaryCommon(int vcardType) { - final boolean isV30 = (vcardType == V30); mVerifier.initForExportTest(vcardType); - ContactEntry entry = mVerifier.addInputEntry(); + final ContactEntry entry = mVerifier.addInputEntry(); entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE) .put(StructuredName.FAMILY_NAME, "DoNotEmitFamilyName1") .put(StructuredName.GIVEN_NAME, "DoNotEmitGivenName1") .put(StructuredName.MIDDLE_NAME, "DoNotEmitMiddleName1") .put(StructuredName.PREFIX, "DoNotEmitPrefix1") .put(StructuredName.SUFFIX, "DoNotEmitSuffix1") - .put(StructuredName.PHONETIC_FAMILY_NAME, "DoNotEmitPhoneticFamily1") - .put(StructuredName.PHONETIC_GIVEN_NAME, "DoNotEmitPhoneticGiven1") - .put(StructuredName.PHONETIC_MIDDLE_NAME, "DoNotEmitPhoneticMiddle1"); + .put(StructuredName.DISPLAY_NAME, "DoNotEmitDisplayName1"); // With "IS_PRIMARY=1". This is what we should use. entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE) @@ -123,41 +111,30 @@ public class VCardExporterTests extends VCardTestsBase { .put(StructuredName.MIDDLE_NAME, "AppropriateMiddleName") .put(StructuredName.PREFIX, "AppropriatePrefix") .put(StructuredName.SUFFIX, "AppropriateSuffix") - .put(StructuredName.PHONETIC_FAMILY_NAME, "AppropriatePhoneticFamily") - .put(StructuredName.PHONETIC_GIVEN_NAME, "AppropriatePhoneticGiven") - .put(StructuredName.PHONETIC_MIDDLE_NAME, "AppropriatePhoneticMiddle") + .put(StructuredName.DISPLAY_NAME, "AppropriateDisplayName") .put(StructuredName.IS_PRIMARY, 1); // With "IS_PRIMARY=1", but we should ignore this time, since this is second, not first. + // vCard 2.1 does not specify anything about the number of N properties. We choose not + // emitting this property. + // vCard 3.0 does (There must be one N property) + // vCard 4.0 (rev13) does (cardinality (0, 1)). entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE) .put(StructuredName.FAMILY_NAME, "DoNotEmitFamilyName2") .put(StructuredName.GIVEN_NAME, "DoNotEmitGivenName2") .put(StructuredName.MIDDLE_NAME, "DoNotEmitMiddleName2") .put(StructuredName.PREFIX, "DoNotEmitPrefix2") .put(StructuredName.SUFFIX, "DoNotEmitSuffix2") - .put(StructuredName.PHONETIC_FAMILY_NAME, "DoNotEmitPhoneticFamily2") - .put(StructuredName.PHONETIC_GIVEN_NAME, "DoNotEmitPhoneticGiven2") - .put(StructuredName.PHONETIC_MIDDLE_NAME, "DoNotEmitPhoneticMiddle2") + .put(StructuredName.DISPLAY_NAME, "DoNotEmitDisplayName2") .put(StructuredName.IS_PRIMARY, 1); - PropertyNodesVerifierElem elem = mVerifier.addPropertyNodesVerifierElem() + mVerifier.addPropertyNodesVerifierElem() .addExpectedNodeWithOrder("N", "AppropriateFamilyName;AppropriateGivenName;AppropriateMiddleName;" + "AppropriatePrefix;AppropriateSuffix", Arrays.asList("AppropriateFamilyName", "AppropriateGivenName", "AppropriateMiddleName", "AppropriatePrefix", "AppropriateSuffix")) - .addExpectedNodeWithOrder("FN", - "AppropriatePrefix AppropriateGivenName " - + "AppropriateMiddleName AppropriateFamilyName AppropriateSuffix") - .addExpectedNode("X-PHONETIC-FIRST-NAME", "AppropriatePhoneticGiven") - .addExpectedNode("X-PHONETIC-MIDDLE-NAME", "AppropriatePhoneticMiddle") - .addExpectedNode("X-PHONETIC-LAST-NAME", "AppropriatePhoneticFamily"); - - if (isV30) { - elem.addExpectedNode("SORT-STRING", - "AppropriatePhoneticGiven AppropriatePhoneticMiddle " - + "AppropriatePhoneticFamily"); - } + .addExpectedNodeWithOrder("FN", "AppropriateDisplayName"); } public void testStructuredNameUsePrimaryV21() { @@ -168,14 +145,152 @@ public class VCardExporterTests extends VCardTestsBase { testStructuredNameUsePrimaryCommon(V30); } + public void testStructuredNameUsePrimaryV40() { + testStructuredNameUsePrimaryCommon(V40); + } + /** * Tests that only "super primary" StructuredName is emitted. * See also the comment in {@link #testStructuredNameUsePrimaryCommon(int)}. */ private void testStructuredNameUseSuperPrimaryCommon(int vcardType) { - final boolean isV30 = (vcardType == V30); mVerifier.initForExportTest(vcardType); - ContactEntry entry = mVerifier.addInputEntry(); + final ContactEntry entry = mVerifier.addInputEntry(); + entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE) + .put(StructuredName.FAMILY_NAME, "DoNotEmitFamilyName1") + .put(StructuredName.GIVEN_NAME, "DoNotEmitGivenName1") + .put(StructuredName.MIDDLE_NAME, "DoNotEmitMiddleName1") + .put(StructuredName.PREFIX, "DoNotEmitPrefix1") + .put(StructuredName.SUFFIX, "DoNotEmitSuffix1") + .put(StructuredName.DISPLAY_NAME, "DoNotEmitDisplay1"); + + // With "IS_PRIMARY=1", but we should ignore this time. + entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE) + .put(StructuredName.FAMILY_NAME, "DoNotEmitFamilyName2") + .put(StructuredName.GIVEN_NAME, "DoNotEmitGivenName2") + .put(StructuredName.MIDDLE_NAME, "DoNotEmitMiddleName2") + .put(StructuredName.PREFIX, "DoNotEmitPrefix2") + .put(StructuredName.SUFFIX, "DoNotEmitSuffix2") + .put(StructuredName.DISPLAY_NAME, "DoNotEmitDisplay2") + .put(StructuredName.IS_PRIMARY, 1); + + // With "IS_SUPER_PRIMARY=1". This is what we should use. + entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE) + .put(StructuredName.FAMILY_NAME, "AppropriateFamilyName") + .put(StructuredName.GIVEN_NAME, "AppropriateGivenName") + .put(StructuredName.MIDDLE_NAME, "AppropriateMiddleName") + .put(StructuredName.PREFIX, "AppropriatePrefix") + .put(StructuredName.SUFFIX, "AppropriateSuffix") + .put(StructuredName.DISPLAY_NAME, "AppropriateDisplayName") + .put(StructuredName.IS_SUPER_PRIMARY, 1); + + entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE) + .put(StructuredName.FAMILY_NAME, "DoNotEmitFamilyName3") + .put(StructuredName.GIVEN_NAME, "DoNotEmitGivenName3") + .put(StructuredName.MIDDLE_NAME, "DoNotEmitMiddleName3") + .put(StructuredName.PREFIX, "DoNotEmitPrefix3") + .put(StructuredName.SUFFIX, "DoNotEmitSuffix3") + .put(StructuredName.DISPLAY_NAME, "DoNotEmitDisplay3") + .put(StructuredName.IS_PRIMARY, 1); + + final PropertyNodesVerifierElem elem = mVerifier.addPropertyNodesVerifierElem(); + elem.addExpectedNodeWithOrder("N", + "AppropriateFamilyName;AppropriateGivenName;AppropriateMiddleName;" + + "AppropriatePrefix;AppropriateSuffix", + Arrays.asList("AppropriateFamilyName", "AppropriateGivenName", + "AppropriateMiddleName", "AppropriatePrefix", "AppropriateSuffix")); + + elem.addExpectedNodeWithOrder("FN", "AppropriateDisplayName"); + } + + public void testStructuredNameUseSuperPrimaryV21() { + testStructuredNameUseSuperPrimaryCommon(V21); + } + + public void testStructuredNameUseSuperPrimaryV30() { + testStructuredNameUseSuperPrimaryCommon(V30); + } + + public void testStructuredNameUseSuperPrimaryV40() { + testStructuredNameUseSuperPrimaryCommon(V40); + } + + /** + * Tests phonetic names field are handled correctly. + * + * vCard 2.1 does not have any field corresponding to them. + * vCard 3.0 has SORT-STRING property, which does not support multiple values inside it. + * vCard 4.0 (rev13) has SORT-AS parameter, which has three values (family, given, middle) + * inside it. + */ + private void testStructuredNamePhoneticNameCommon(int vcardType) { + mVerifier.initForExportTest(vcardType); + final ContactEntry entry = mVerifier.addInputEntry(); + entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE) + .put(StructuredName.FAMILY_NAME, "AppropriateFamilyName") + .put(StructuredName.GIVEN_NAME, "AppropriateGivenName") + .put(StructuredName.MIDDLE_NAME, "AppropriateMiddleName") + .put(StructuredName.PREFIX, "AppropriatePrefix") + .put(StructuredName.SUFFIX, "AppropriateSuffix") + .put(StructuredName.DISPLAY_NAME, "AppropriateDisplayName") + .put(StructuredName.PHONETIC_FAMILY_NAME, "AppropriatePhoneticFamily") + .put(StructuredName.PHONETIC_GIVEN_NAME, "AppropriatePhoneticGiven") + .put(StructuredName.PHONETIC_MIDDLE_NAME, "AppropriatePhoneticMiddle"); + + final PropertyNodesVerifierElem elem = mVerifier.addPropertyNodesVerifierElem(); + if (VCardConfig.isVersion40(vcardType)) { + final ContentValues contentValues = new ContentValues(); + contentValues.put("SORT-AS", + "AppropriateFamilyName;AppropriateGivenName;AppropriateMiddleName"); + // vCard 4.0 (rev13) now uses SORT-AS parameter, which is not compatible with + // either 2.1 nor 3.0. + elem.addExpectedNodeWithOrder("N", + "AppropriateFamilyName;AppropriateGivenName;AppropriateMiddleName;" + + "AppropriatePrefix;AppropriateSuffix", + Arrays.asList("AppropriateFamilyName", "AppropriateGivenName", + "AppropriateMiddleName", "AppropriatePrefix", "AppropriateSuffix"), + contentValues); + } else { + elem.addExpectedNodeWithOrder("N", + "AppropriateFamilyName;AppropriateGivenName;AppropriateMiddleName;" + + "AppropriatePrefix;AppropriateSuffix", + Arrays.asList("AppropriateFamilyName", "AppropriateGivenName", + "AppropriateMiddleName", "AppropriatePrefix", "AppropriateSuffix")); + if (VCardConfig.isVersion30(vcardType)) { + elem.addExpectedNode("SORT-STRING", + "AppropriatePhoneticGiven AppropriatePhoneticMiddle" + + " AppropriatePhoneticFamily"); + } + } + + elem.addExpectedNodeWithOrder("FN", "AppropriateDisplayName") + .addExpectedNode("X-PHONETIC-FIRST-NAME", "AppropriatePhoneticGiven") + .addExpectedNode("X-PHONETIC-MIDDLE-NAME", "AppropriatePhoneticMiddle") + .addExpectedNode("X-PHONETIC-LAST-NAME", "AppropriatePhoneticFamily"); + } + + public void testStructuredNamePhoneticNameV21() { + testStructuredNamePhoneticNameCommon(V21); + } + + public void testStructuredNamePhoneticNameV30() { + testStructuredNamePhoneticNameCommon(V30); + } + + public void testStructuredNamePhoneticNameV40() { + testStructuredNamePhoneticNameCommon(V40); + } + + // TODO: need to add test cases confirming escaping, empty values, etc. + + /** + * Confirms all the other sides of the handling is correctly interpreted at one time. + * + * A kind of regression test for StructuredName handling. + */ + private void testStructuredNameComplicatedCommon(int vcardType) { + mVerifier.initForExportTest(vcardType); + final ContactEntry entry = mVerifier.addInputEntry(); entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE) .put(StructuredName.FAMILY_NAME, "DoNotEmitFamilyName1") .put(StructuredName.GIVEN_NAME, "DoNotEmitGivenName1") @@ -221,32 +336,50 @@ public class VCardExporterTests extends VCardTestsBase { .put(StructuredName.PHONETIC_MIDDLE_NAME, "DoNotEmitPhoneticMiddle3") .put(StructuredName.IS_PRIMARY, 1); - PropertyNodesVerifierElem elem = mVerifier.addPropertyNodesVerifierElem() - .addExpectedNodeWithOrder("N", - "AppropriateFamilyName;AppropriateGivenName;AppropriateMiddleName;" - + "AppropriatePrefix;AppropriateSuffix", - Arrays.asList("AppropriateFamilyName", "AppropriateGivenName", - "AppropriateMiddleName", "AppropriatePrefix", "AppropriateSuffix")) - .addExpectedNodeWithOrder("FN", - "AppropriatePrefix AppropriateGivenName " - + "AppropriateMiddleName AppropriateFamilyName AppropriateSuffix") - .addExpectedNode("X-PHONETIC-FIRST-NAME", "AppropriatePhoneticGiven") - .addExpectedNode("X-PHONETIC-MIDDLE-NAME", "AppropriatePhoneticMiddle") - .addExpectedNode("X-PHONETIC-LAST-NAME", "AppropriatePhoneticFamily"); - - if (isV30) { - elem.addExpectedNode("SORT-STRING", - "AppropriatePhoneticGiven AppropriatePhoneticMiddle" - + " AppropriatePhoneticFamily"); + final PropertyNodesVerifierElem elem = mVerifier.addPropertyNodesVerifierElem(); + if (VCardConfig.isVersion40(vcardType)) { + final ContentValues contentValues = new ContentValues(); + contentValues.put("SORT-AS", + "AppropriateFamilyName;AppropriateGivenName;AppropriateMiddleName"); + // vCard 4.0 (rev13) now uses SORT-AS parameter, which is not compatible with + // either 2.1 nor 3.0. + elem.addExpectedNodeWithOrder("N", + "AppropriateFamilyName;AppropriateGivenName;AppropriateMiddleName;" + + "AppropriatePrefix;AppropriateSuffix", + Arrays.asList("AppropriateFamilyName", "AppropriateGivenName", + "AppropriateMiddleName", "AppropriatePrefix", "AppropriateSuffix"), + contentValues); + } else { + elem.addExpectedNodeWithOrder("N", + "AppropriateFamilyName;AppropriateGivenName;AppropriateMiddleName;" + + "AppropriatePrefix;AppropriateSuffix", + Arrays.asList("AppropriateFamilyName", "AppropriateGivenName", + "AppropriateMiddleName", "AppropriatePrefix", "AppropriateSuffix")); + if (VCardConfig.isVersion30(vcardType)) { + elem.addExpectedNode("SORT-STRING", + "AppropriatePhoneticGiven AppropriatePhoneticMiddle" + + " AppropriatePhoneticFamily"); + } } + + elem.addExpectedNodeWithOrder("FN", + "AppropriatePrefix AppropriateGivenName " + + "AppropriateMiddleName AppropriateFamilyName AppropriateSuffix") + .addExpectedNode("X-PHONETIC-FIRST-NAME", "AppropriatePhoneticGiven") + .addExpectedNode("X-PHONETIC-MIDDLE-NAME", "AppropriatePhoneticMiddle") + .addExpectedNode("X-PHONETIC-LAST-NAME", "AppropriatePhoneticFamily"); } - public void testStructuredNameUseSuperPrimaryV21() { - testStructuredNameUseSuperPrimaryCommon(V21); + public void testStructuredNameComplicatedV21() { + testStructuredNameComplicatedCommon(V21); } - public void testStructuredNameUseSuperPrimaryV30() { - testStructuredNameUseSuperPrimaryCommon(V30); + public void testStructuredNameComplicatedV30() { + testStructuredNameComplicatedCommon(V30); + } + + public void testStructuredNameComplicatedV40() { + testStructuredNameComplicatedCommon(V40); } public void testNickNameV30() { @@ -258,6 +391,15 @@ public class VCardExporterTests extends VCardTestsBase { .addExpectedNodeWithOrder("NICKNAME", "Nicky"); } + public void testNickNameV40() { + mVerifier.initForExportTest(V40); + mVerifier.addInputEntry().addContentValues(Nickname.CONTENT_ITEM_TYPE) + .put(Nickname.NAME, "Nicky"); + + mVerifier.addPropertyNodesVerifierElemWithEmptyName() + .addExpectedNodeWithOrder("NICKNAME", "Nicky"); + } + private void testPhoneBasicCommon(int vcardType) { mVerifier.initForExportTest(vcardType); mVerifier.addInputEntry().addContentValues(Phone.CONTENT_ITEM_TYPE) @@ -275,6 +417,20 @@ public class VCardExporterTests extends VCardTestsBase { testPhoneBasicCommon(V30); } + public void testPhoneBasicV40() { + testPhoneBasicCommon(V40); + } + + public void testPhoneRefrainFormatting() { + mVerifier.initForExportTest(V21 | VCardConfig.FLAG_REFRAIN_PHONE_NUMBER_FORMATTING); + mVerifier.addInputEntry().addContentValues(Phone.CONTENT_ITEM_TYPE) + .put(Phone.NUMBER, "1234567890(abcdefghijklmnopqrstuvwxyz)") + .put(Phone.TYPE, Phone.TYPE_HOME); + mVerifier.addPropertyNodesVerifierElemWithEmptyName() + .addExpectedNode("TEL", "1234567890(abcdefghijklmnopqrstuvwxyz)", + new TypeSet("HOME")); + } + /** * Tests that vCard composer emits corresponding type param which we expect. */ @@ -357,6 +513,10 @@ public class VCardExporterTests extends VCardTestsBase { testPhoneVariousTypeSupport(V30); } + public void testPhoneVariousTypeSupportV40() { + testPhoneVariousTypeSupport(V40); + } + /** * Tests that "PREF"s are emitted appropriately. */ @@ -393,6 +553,10 @@ public class VCardExporterTests extends VCardTestsBase { testPhonePrefHandlingCommon(V30); } + public void testPhonePrefHandlingV40() { + testPhonePrefHandlingCommon(V40); + } + private void testMiscPhoneTypeHandling(int vcardType) { mVerifier.initForExportTest(vcardType); ContactEntry entry = mVerifier.addInputEntry(); @@ -428,14 +592,26 @@ public class VCardExporterTests extends VCardTestsBase { .put(Phone.TYPE, Phone.TYPE_CUSTOM) .put(Phone.LABEL, "invalid"); PropertyNodesVerifierElem elem = mVerifier.addPropertyNodesVerifierElemWithEmptyName(); - elem.addExpectedNode("TEL", "1", new TypeSet("MODEM")) - .addExpectedNode("TEL", "2", new TypeSet("MSG")) - .addExpectedNode("TEL", "3", new TypeSet("BBS")) - .addExpectedNode("TEL", "4", new TypeSet("VIDEO")) - .addExpectedNode("TEL", "5", new TypeSet("VOICE")) - .addExpectedNode("TEL", "6", new TypeSet("CELL")) - .addExpectedNode("TEL", "7", new TypeSet("CELL")) - .addExpectedNode("TEL", "8", new TypeSet("X-invalid")); + if (VCardConfig.isVersion30(vcardType) || VCardConfig.isVersion40(vcardType)) { + // vCard 3.0 accepts "invalid". Also stop using toUpper() + elem.addExpectedNode("TEL", "1", new TypeSet("Modem")) + .addExpectedNode("TEL", "2", new TypeSet("MSG")) + .addExpectedNode("TEL", "3", new TypeSet("BBS")) + .addExpectedNode("TEL", "4", new TypeSet("VIDEO")) + .addExpectedNode("TEL", "5", new TypeSet("VOICE")) + .addExpectedNode("TEL", "6", new TypeSet("CELL")) + .addExpectedNode("TEL", "7", new TypeSet("CELL")) + .addExpectedNode("TEL", "8", new TypeSet("invalid")); + } else { + elem.addExpectedNode("TEL", "1", new TypeSet("MODEM")) + .addExpectedNode("TEL", "2", new TypeSet("MSG")) + .addExpectedNode("TEL", "3", new TypeSet("BBS")) + .addExpectedNode("TEL", "4", new TypeSet("VIDEO")) + .addExpectedNode("TEL", "5", new TypeSet("VOICE")) + .addExpectedNode("TEL", "6", new TypeSet("CELL")) + .addExpectedNode("TEL", "7", new TypeSet("CELL")) + .addExpectedNode("TEL", "8", new TypeSet("X-invalid")); + } } public void testPhoneTypeHandlingV21() { @@ -446,6 +622,10 @@ public class VCardExporterTests extends VCardTestsBase { testMiscPhoneTypeHandling(V30); } + public void testPhoneTypeHandlingV40() { + testMiscPhoneTypeHandling(V40); + } + private void testEmailBasicCommon(int vcardType) { mVerifier.initForExportTest(vcardType); mVerifier.addInputEntry().addContentValues(Email.CONTENT_ITEM_TYPE) @@ -462,6 +642,10 @@ public class VCardExporterTests extends VCardTestsBase { testEmailBasicCommon(V30); } + public void testEmailBasicV40() { + testEmailBasicCommon(V40); + } + private void testEmailVariousTypeSupportCommon(int vcardType) { mVerifier.initForExportTest(vcardType); ContactEntry entry = mVerifier.addInputEntry(); @@ -492,6 +676,10 @@ public class VCardExporterTests extends VCardTestsBase { testEmailVariousTypeSupportCommon(V30); } + public void testEmailVariousTypeSupportV40() { + testEmailVariousTypeSupportCommon(V40); + } + private void testEmailPrefHandlingCommon(int vcardType) { mVerifier.initForExportTest(vcardType); ContactEntry entry = mVerifier.addInputEntry(); @@ -516,6 +704,10 @@ public class VCardExporterTests extends VCardTestsBase { testEmailPrefHandlingCommon(V30); } + public void testEmailPrefHandlingV40() { + testEmailPrefHandlingCommon(V40); + } + private void testPostalAddressCommon(int vcardType) { mVerifier.initForExportTest(vcardType); mVerifier.addInputEntry().addContentValues(StructuredPostal.CONTENT_ITEM_TYPE) @@ -547,6 +739,10 @@ public class VCardExporterTests extends VCardTestsBase { testPostalAddressCommon(V30); } + public void testPostalAddressV40() { + testPostalAddressCommon(V40); + } + private void testPostalAddressNonNeighborhood(int vcardType) { mVerifier.initForExportTest(vcardType); mVerifier.addInputEntry().addContentValues(StructuredPostal.CONTENT_ITEM_TYPE) @@ -564,6 +760,10 @@ public class VCardExporterTests extends VCardTestsBase { testPostalAddressNonNeighborhood(V30); } + public void testPostalAddressNonNeighborhoodV40() { + testPostalAddressNonNeighborhood(V40); + } + private void testPostalAddressNonCity(int vcardType) { mVerifier.initForExportTest(vcardType); mVerifier.addInputEntry().addContentValues(StructuredPostal.CONTENT_ITEM_TYPE) @@ -581,6 +781,10 @@ public class VCardExporterTests extends VCardTestsBase { testPostalAddressNonCity(V30); } + public void testPostalAddressNonCityV40() { + testPostalAddressNonCity(V40); + } + private void testPostalOnlyWithFormattedAddressCommon(int vcardType) { mVerifier.initForExportTest(vcardType); mVerifier.addInputEntry().addContentValues(StructuredPostal.CONTENT_ITEM_TYPE) @@ -601,6 +805,10 @@ public class VCardExporterTests extends VCardTestsBase { testPostalOnlyWithFormattedAddressCommon(V30); } + public void testPostalOnlyWithFormattedAddressV40() { + testPostalOnlyWithFormattedAddressCommon(V40); + } + /** * Tests that the vCard composer honors formatted data when it is available * even when it is partial. @@ -626,6 +834,10 @@ public class VCardExporterTests extends VCardTestsBase { testPostalWithBothStructuredAndFormattedCommon(V30); } + public void testPostalWithBothStructuredAndFormattedV40() { + testPostalWithBothStructuredAndFormattedCommon(V40); + } + private void testOrganizationCommon(int vcardType) { mVerifier.initForExportTest(vcardType); ContactEntry entry = mVerifier.addInputEntry(); @@ -663,6 +875,10 @@ public class VCardExporterTests extends VCardTestsBase { testOrganizationCommon(V30); } + public void testOrganizationV40() { + testOrganizationCommon(V40); + } + private void testImVariousTypeSupportCommon(int vcardType) { mVerifier.initForExportTest(vcardType); ContactEntry entry = mVerifier.addInputEntry(); @@ -715,6 +931,10 @@ public class VCardExporterTests extends VCardTestsBase { testImVariousTypeSupportCommon(V30); } + public void testImBasicV40() { + testImVariousTypeSupportCommon(V40); + } + private void testImPrefHandlingCommon(int vcardType) { mVerifier.initForExportTest(vcardType); ContactEntry entry = mVerifier.addInputEntry(); @@ -740,6 +960,10 @@ public class VCardExporterTests extends VCardTestsBase { testImPrefHandlingCommon(V30); } + public void testImPrefHandlingV40() { + testImPrefHandlingCommon(V40); + } + private void testWebsiteCommon(int vcardType) { mVerifier.initForExportTest(vcardType); ContactEntry entry = mVerifier.addInputEntry(); @@ -764,6 +988,10 @@ public class VCardExporterTests extends VCardTestsBase { testWebsiteCommon(V30); } + public void testWebsiteV40() { + testWebsiteCommon(V40); + } + private String getAndroidPropValue(final String mimeType, String value, Integer type) { return getAndroidPropValue(mimeType, value, type, null); } @@ -817,6 +1045,10 @@ public class VCardExporterTests extends VCardTestsBase { testEventCommon(V30); } + public void testEventV40() { + testEventCommon(V40); + } + private void testNoteCommon(int vcardType) { mVerifier.initForExportTest(vcardType); ContactEntry entry = mVerifier.addInputEntry(); @@ -838,8 +1070,13 @@ public class VCardExporterTests extends VCardTestsBase { testNoteCommon(V30); } + public void testNoteV40() { + testNoteCommon(V40); + } + private void testPhotoCommon(int vcardType) { - final boolean isV30 = vcardType == V30; + final boolean useB = + (VCardConfig.isVersion30(vcardType) || VCardConfig.isVersion40(vcardType)); mVerifier.initForExportTest(vcardType); ContactEntry entry = mVerifier.addInputEntry(); entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE) @@ -848,7 +1085,7 @@ public class VCardExporterTests extends VCardTestsBase { .put(Photo.PHOTO, sPhotoByteArray); ContentValues contentValuesForPhoto = new ContentValues(); - contentValuesForPhoto.put("ENCODING", (isV30 ? "b" : "BASE64")); + contentValuesForPhoto.put("ENCODING", (useB ? "b" : "BASE64")); mVerifier.addPropertyNodesVerifierElem() .addExpectedNode("FN", "PhotoTest") .addExpectedNode("N", "PhotoTest;;;;", @@ -865,6 +1102,10 @@ public class VCardExporterTests extends VCardTestsBase { testPhotoCommon(V30); } + public void testPhotoV40() { + testPhotoCommon(V40); + } + private void testRelationCommon(int vcardType) { mVerifier.initForExportTest(vcardType); mVerifier.addInputEntry().addContentValues(Relation.CONTENT_ITEM_TYPE) @@ -935,18 +1176,22 @@ public class VCardExporterTests extends VCardTestsBase { entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE) .put(StructuredName.IS_PRIMARY, 1); // Empty name. Should be ignored. entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE) - .put(StructuredName.FAMILY_NAME, "family1"); // Not primary. Should be ignored. + .put(StructuredName.FAMILY_NAME, "family1") // Not primary. Should be ignored. + .put(StructuredName.DISPLAY_NAME, "display"); entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE) .put(StructuredName.IS_PRIMARY, 1) - .put(StructuredName.FAMILY_NAME, "family2"); // This entry is what we want. + .put(StructuredName.FAMILY_NAME, "family2") // This entry is what we want. + .put(StructuredName.DISPLAY_NAME, "display"); entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE) .put(StructuredName.IS_PRIMARY, 1) - .put(StructuredName.FAMILY_NAME, "family3"); + .put(StructuredName.FAMILY_NAME, "family3") + .put(StructuredName.DISPLAY_NAME, "display"); entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE) - .put(StructuredName.FAMILY_NAME, "family4"); + .put(StructuredName.FAMILY_NAME, "family4") + .put(StructuredName.DISPLAY_NAME, "display"); mVerifier.addPropertyNodesVerifierElem() .addExpectedNode("N", Arrays.asList("family2", "", "", "", "")) - .addExpectedNode("FN", "family2"); + .addExpectedNode("FN", "display"); } public void testPickUpNonEmptyContentValuesV21() { @@ -956,4 +1201,23 @@ public class VCardExporterTests extends VCardTestsBase { public void testPickUpNonEmptyContentValuesV30() { testPickUpNonEmptyContentValuesCommon(V30); } + + public void testPickUpNonEmptyContentValuesV40() { + testPickUpNonEmptyContentValuesCommon(V40); + } + + public void testUseMultiByteTypeV30() { + mVerifier.initForExportTest(V30); + final ContactEntry entry = mVerifier.addInputEntry(); + entry.addContentValues(Phone.CONTENT_ITEM_TYPE) + .put(Phone.TYPE, Phone.TYPE_CUSTOM) + .put(Phone.LABEL, "\u96FB\u8A71") + .put(Phone.NUMBER, "1"); + mVerifier.addLineVerifierElem() + .addExpected("N:") + .addExpected("FN:") + .addExpected("TEL;TYPE=\u96FB\u8A71:1"); + mVerifier.addPropertyNodesVerifierElemWithEmptyName() + .addExpectedNode("TEL", "1", new TypeSet("\u96FB\u8A71")); + } } diff --git a/core/tests/coretests/src/android/pim/vcard/VCardImporterTests.java b/core/tests/coretests/src/android/pim/vcard/VCardImporterTests.java index 21f22540c697..09e2914420da 100644 --- a/core/tests/coretests/src/android/pim/vcard/VCardImporterTests.java +++ b/core/tests/coretests/src/android/pim/vcard/VCardImporterTests.java @@ -15,11 +15,17 @@ */ package android.pim.vcard; +import com.android.frameworks.coretests.R; + import android.content.ContentValues; -import android.pim.vcard.VCardConfig; -import android.provider.ContactsContract.Data; +import android.pim.vcard.test_utils.PropertyNodesVerifierElem.TypeSet; +import android.pim.vcard.test_utils.VCardTestsBase; +import android.pim.vcard.test_utils.ContentValuesVerifier; +import android.pim.vcard.test_utils.ContentValuesVerifierElem; import android.provider.ContactsContract.CommonDataKinds.Email; import android.provider.ContactsContract.CommonDataKinds.Event; +import android.provider.ContactsContract.CommonDataKinds.Im; +import android.provider.ContactsContract.CommonDataKinds.Nickname; import android.provider.ContactsContract.CommonDataKinds.Note; import android.provider.ContactsContract.CommonDataKinds.Organization; import android.provider.ContactsContract.CommonDataKinds.Phone; @@ -27,9 +33,7 @@ import android.provider.ContactsContract.CommonDataKinds.Photo; import android.provider.ContactsContract.CommonDataKinds.StructuredName; import android.provider.ContactsContract.CommonDataKinds.StructuredPostal; import android.provider.ContactsContract.CommonDataKinds.Website; - -import com.android.frameworks.coretests.R; -import android.pim.vcard.PropertyNodesVerifierElem.TypeSet; +import android.provider.ContactsContract.Data; import java.util.Arrays; @@ -405,12 +409,12 @@ public class VCardImporterTests extends VCardTestsBase { public void testV21SimpleCase1_Parsing() { mVerifier.initForImportTest(V21, R.raw.v21_simple_1); - mVerifier.addPropertyNodesVerifierElem() + mVerifier.addPropertyNodesVerifierElemWithoutVersion() // no "VERSION:2.1" line. .addExpectedNodeWithOrder("N", "Ando;Roid;", Arrays.asList("Ando", "Roid", "")); } public void testV21SimpleCase1_Type_Generic() { - mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8, R.raw.v21_simple_1); + mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_GENERIC, R.raw.v21_simple_1); mVerifier.addContentValuesVerifierElem() .addExpected(StructuredName.CONTENT_ITEM_TYPE) .put(StructuredName.FAMILY_NAME, "Ando") @@ -419,7 +423,7 @@ public class VCardImporterTests extends VCardTestsBase { } public void testV21SimpleCase1_Type_Japanese() { - mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS, R.raw.v21_simple_1); + mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE, R.raw.v21_simple_1); mVerifier.addContentValuesVerifierElem() .addExpected(StructuredName.CONTENT_ITEM_TYPE) .put(StructuredName.FAMILY_NAME, "Ando") @@ -431,7 +435,7 @@ public class VCardImporterTests extends VCardTestsBase { } public void testV21SimpleCase2() { - mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS, R.raw.v21_simple_2); + mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE, R.raw.v21_simple_2); mVerifier.addContentValuesVerifierElem() .addExpected(StructuredName.CONTENT_ITEM_TYPE) .put(StructuredName.DISPLAY_NAME, "Ando Roid"); @@ -454,7 +458,6 @@ public class VCardImporterTests extends VCardTestsBase { public void testV21BackslashCase_Parsing() { mVerifier.initForImportTest(V21, R.raw.v21_backslash); mVerifier.addPropertyNodesVerifierElem() - .addExpectedNodeWithOrder("VERSION", "2.1") .addExpectedNodeWithOrder("N", ";A;B\\;C\\;;D;:E;\\\\;", Arrays.asList("", "A;B\\", "C\\;", "D", ":E", "\\\\", "")) .addExpectedNodeWithOrder("FN", "A;B\\C\\;D:E\\\\"); @@ -549,11 +552,11 @@ public class VCardImporterTests extends VCardTestsBase { public void testV21ComplicatedCase_Parsing() { mVerifier.initForImportTest(V21, R.raw.v21_complicated); mVerifier.addPropertyNodesVerifierElem() - .addExpectedNodeWithOrder("VERSION", "2.1") .addExpectedNodeWithOrder("N", "Gump;Forrest;Hoge;Pos;Tao", Arrays.asList("Gump", "Forrest", "Hoge", "Pos", "Tao")) .addExpectedNodeWithOrder("FN", "Joe Due") - .addExpectedNodeWithOrder("ORG", "Gump Shrimp Co.;Sales Dept.;Manager;Fish keeper", + .addExpectedNodeWithOrder("ORG", + "Gump Shrimp Co.;Sales Dept.;Manager;Fish keeper", Arrays.asList("Gump Shrimp Co.", "Sales Dept.;Manager", "Fish keeper")) .addExpectedNodeWithOrder("ROLE", "Fish Cake Keeper!") .addExpectedNodeWithOrder("TITLE", "Shrimp Man") @@ -583,7 +586,8 @@ public class VCardImporterTests extends VCardTestsBase { .addExpectedNodeWithOrder("EMAIL", "forrestgump@walladalla.com", new TypeSet("PREF", "INTERNET")) .addExpectedNodeWithOrder("EMAIL", "cell@example.com", new TypeSet("CELL")) - .addExpectedNodeWithOrder("NOTE", "The following note is the example from RFC 2045.") + .addExpectedNodeWithOrder("NOTE", + "The following note is the example from RFC 2045.") .addExpectedNodeWithOrder("NOTE", "Now's the time for all folk to come to the aid of their country.", null, null, mContentValuesForQP, null, null) @@ -677,12 +681,24 @@ public class VCardImporterTests extends VCardTestsBase { .put(Website.TYPE, Website.TYPE_HOMEPAGE); } + public void testInvalidMultipleLineV21() { + mVerifier.initForImportTest(V21, R.raw.v21_invalid_multiple_line); + ContentValuesVerifierElem elem = mVerifier.addContentValuesVerifierElem(); + elem.addExpected(StructuredName.CONTENT_ITEM_TYPE) + .put(StructuredName.GIVEN_NAME, "Omega") + .put(StructuredName.DISPLAY_NAME, "Omega"); + elem.addExpected(Email.CONTENT_ITEM_TYPE) + .put(Email.TYPE, Email.TYPE_CUSTOM) + .put(Email.LABEL, "INTERNET") + .put(Email.ADDRESS, "\"Omega\" <omega@example.com>"); + } + public void testV30Simple_Parsing() { mVerifier.initForImportTest(V30, R.raw.v30_simple); mVerifier.addPropertyNodesVerifierElem() - .addExpectedNodeWithOrder("VERSION", "3.0") .addExpectedNodeWithOrder("FN", "And Roid") - .addExpectedNodeWithOrder("N", "And;Roid;;;", Arrays.asList("And", "Roid", "", "", "")) + .addExpectedNodeWithOrder("N", "And;Roid;;;", + Arrays.asList("And", "Roid", "", "", "")) .addExpectedNodeWithOrder("ORG", "Open;Handset; Alliance", Arrays.asList("Open", "Handset", " Alliance")) .addExpectedNodeWithOrder("SORT-STRING", "android") @@ -717,10 +733,8 @@ public class VCardImporterTests extends VCardTestsBase { // Though Japanese careers append ";;;;" at the end of the value of "SOUND", // vCard 2.1/3.0 specification does not allow multiple values. // Do not need to handle it as multiple values. - mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS, - R.raw.v21_japanese_1); + mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE, R.raw.v21_japanese_1); mVerifier.addPropertyNodesVerifierElem() - .addExpectedNodeWithOrder("VERSION", "2.1", null, null, null, null, null) .addExpectedNodeWithOrder("N", "\u5B89\u85E4\u30ED\u30A4\u30C9;;;;", Arrays.asList("\u5B89\u85E4\u30ED\u30A4\u30C9", "", "", "", ""), null, mContentValuesForSJis, null, null) @@ -752,37 +766,35 @@ public class VCardImporterTests extends VCardTestsBase { /** * Verifies vCard with Japanese can be parsed correctly with - * {@link android.pim.vcard.VCardConfig#VCARD_TYPE_V21_GENERIC_UTF8}. + * {@link com.android.vcard.VCardConfig#VCARD_TYPE_V21_GENERIC}. */ public void testV21Japanese1_Type_Generic_Utf8() { testV21Japanese1Common( - R.raw.v21_japanese_1, VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8, false); + R.raw.v21_japanese_1, VCardConfig.VCARD_TYPE_V21_GENERIC, false); } /** * Verifies vCard with Japanese can be parsed correctly with - * {@link android.pim.vcard.VCardConfig#VCARD_TYPE_V21_JAPANESE_SJIS}. + * {@link com.android.vcard.VCardConfig#VCARD_TYPE_V21_JAPANESE}. */ public void testV21Japanese1_Type_Japanese_Sjis() { testV21Japanese1Common( - R.raw.v21_japanese_1, VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS, true); + R.raw.v21_japanese_1, VCardConfig.VCARD_TYPE_V21_JAPANESE, true); } /** * Verifies vCard with Japanese can be parsed correctly with - * {@link android.pim.vcard.VCardConfig#VCARD_TYPE_V21_JAPANESE_UTF8}. + * {@link com.android.vcard.VCardConfig#VCARD_TYPE_V21_JAPANESE}. * since vCard 2.1 specifies the charset of each line if it contains non-Ascii. */ public void testV21Japanese1_Type_Japanese_Utf8() { testV21Japanese1Common( - R.raw.v21_japanese_1, VCardConfig.VCARD_TYPE_V21_JAPANESE_UTF8, true); + R.raw.v21_japanese_1, VCardConfig.VCARD_TYPE_V21_JAPANESE, true); } public void testV21Japanese2_Parsing() { - mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS, - R.raw.v21_japanese_2); + mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE, R.raw.v21_japanese_2); mVerifier.addPropertyNodesVerifierElem() - .addExpectedNodeWithOrder("VERSION", "2.1") .addExpectedNodeWithOrder("N", "\u5B89\u85E4;\u30ED\u30A4\u30C9\u0031;;;", Arrays.asList("\u5B89\u85E4", "\u30ED\u30A4\u30C9\u0031", "", "", ""), @@ -838,10 +850,8 @@ public class VCardImporterTests extends VCardTestsBase { } public void testV21MultipleEntryCase_Parse() { - mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS, - R.raw.v21_multiple_entry); + mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE, R.raw.v21_multiple_entry); mVerifier.addPropertyNodesVerifierElem() - .addExpectedNodeWithOrder("VERSION", "2.1") .addExpectedNodeWithOrder("N", "\u5B89\u85E4\u30ED\u30A4\u30C9\u0033;;;;", Arrays.asList("\u5B89\u85E4\u30ED\u30A4\u30C9\u0033", "", "", "", ""), null, mContentValuesForSJis, null, null) @@ -854,7 +864,6 @@ public class VCardImporterTests extends VCardTestsBase { .addExpectedNodeWithOrder("TEL", "11", new TypeSet("X-NEC-SCHOOL")) .addExpectedNodeWithOrder("TEL", "12", new TypeSet("FAX", "HOME")); mVerifier.addPropertyNodesVerifierElem() - .addExpectedNodeWithOrder("VERSION", "2.1") .addExpectedNodeWithOrder("N", "\u5B89\u85E4\u30ED\u30A4\u30C9\u0034;;;;", Arrays.asList("\u5B89\u85E4\u30ED\u30A4\u30C9\u0034", "", "", "", ""), null, mContentValuesForSJis, null, null) @@ -867,7 +876,6 @@ public class VCardImporterTests extends VCardTestsBase { .addExpectedNodeWithOrder("TEL", "15", new TypeSet("X-NEC-FAMILY")) .addExpectedNodeWithOrder("TEL", "16", new TypeSet("X-NEC-GIRL")); mVerifier.addPropertyNodesVerifierElem() - .addExpectedNodeWithOrder("VERSION", "2.1") .addExpectedNodeWithOrder("N", "\u5B89\u85E4\u30ED\u30A4\u30C9\u0035;;;;", Arrays.asList("\u5B89\u85E4\u30ED\u30A4\u30C9\u0035", "", "", "", ""), null, mContentValuesForSJis, null, null) @@ -882,8 +890,7 @@ public class VCardImporterTests extends VCardTestsBase { } public void testV21MultipleEntryCase() { - mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS, - R.raw.v21_multiple_entry); + mVerifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE, R.raw.v21_multiple_entry); ContentValuesVerifierElem elem = mVerifier.addContentValuesVerifierElem(); elem.addExpected(StructuredName.CONTENT_ITEM_TYPE) .put(StructuredName.FAMILY_NAME, "\u5B89\u85E4\u30ED\u30A4\u30C9\u0033") @@ -957,7 +964,6 @@ public class VCardImporterTests extends VCardTestsBase { ContentValues contentValuesForValue = new ContentValues(); contentValuesForValue.put("VALUE", "DATE"); mVerifier.addPropertyNodesVerifierElem() - .addExpectedNodeWithOrder("VERSION", "2.1") .addExpectedNodeWithOrder("N", Arrays.asList("Example", "", "", "", "")) .addExpectedNodeWithOrder("FN", "Example") .addExpectedNodeWithOrder("ANNIVERSARY", "20091010", contentValuesForValue) @@ -974,6 +980,9 @@ public class VCardImporterTests extends VCardTestsBase { elem.addExpected(StructuredName.CONTENT_ITEM_TYPE) .put(StructuredName.FAMILY_NAME, "Example") .put(StructuredName.DISPLAY_NAME, "Example"); + elem.addExpected(Event.CONTENT_ITEM_TYPE) + .put(Event.TYPE, Event.TYPE_ANNIVERSARY) + .put(Event.START_DATE, "20091010"); } public void testTolerateInvalidCommentLikeLineV21() { @@ -988,16 +997,15 @@ public class VCardImporterTests extends VCardTestsBase { } public void testPagerV30_Parse() { - mVerifier.initForImportTest(V30, R.raw.v30_comma_separated); + mVerifier.initForImportTest(V30, R.raw.v30_pager); mVerifier.addPropertyNodesVerifierElem() - .addExpectedNodeWithOrder("VERSION", "3.0") .addExpectedNodeWithOrder("N", Arrays.asList("F", "G", "M", "", "")) .addExpectedNodeWithOrder("TEL", "6101231234@pagersample.com", new TypeSet("WORK", "MSG", "PAGER")); } public void testPagerV30() { - mVerifier.initForImportTest(V30, R.raw.v30_comma_separated); + mVerifier.initForImportTest(V30, R.raw.v30_pager); ContentValuesVerifierElem elem = mVerifier.addContentValuesVerifierElem(); elem.addExpected(StructuredName.CONTENT_ITEM_TYPE) .put(StructuredName.FAMILY_NAME, "F") @@ -1008,4 +1016,93 @@ public class VCardImporterTests extends VCardTestsBase { .put(Phone.TYPE, Phone.TYPE_PAGER) .put(Phone.NUMBER, "6101231234@pagersample.com"); } + + public void testMultiBytePropV30_Parse() { + mVerifier.initForImportTest(V30, R.raw.v30_multibyte_param); + mVerifier.addPropertyNodesVerifierElem() + .addExpectedNodeWithOrder("N", Arrays.asList("F", "G", "M", "", "")) + .addExpectedNodeWithOrder("TEL", "1", new TypeSet("\u8D39")); + } + + public void testMultiBytePropV30() { + mVerifier.initForImportTest(V30, R.raw.v30_multibyte_param); + final ContentValuesVerifierElem elem = mVerifier.addContentValuesVerifierElem(); + elem.addExpected(StructuredName.CONTENT_ITEM_TYPE) + .put(StructuredName.FAMILY_NAME, "F") + .put(StructuredName.MIDDLE_NAME, "M") + .put(StructuredName.GIVEN_NAME, "G") + .put(StructuredName.DISPLAY_NAME, "G M F"); + elem.addExpected(Phone.CONTENT_ITEM_TYPE) + .put(Phone.TYPE, Phone.TYPE_CUSTOM) + .put(Phone.LABEL, "\u8D39") + .put(Phone.NUMBER, "1"); + } + + public void testCommaSeparatedParamsV30_Parse() { + mVerifier.initForImportTest(V30, R.raw.v30_comma_separated); + mVerifier.addPropertyNodesVerifierElem() + .addExpectedNodeWithOrder("N", Arrays.asList("F", "G", "M", "", ""), + new TypeSet("PREF", "HOME")) + .addExpectedNodeWithOrder("TEL", "1", + new TypeSet("COMMA,SEPARATED:INSIDE.DQUOTE", "PREF")); + } + + public void testSortAsV40_Parse() { + mVerifier.initForImportTest(V40, R.raw.v40_sort_as); + + final ContentValues contentValuesForSortAsN = new ContentValues(); + contentValuesForSortAsN.put("SORT-AS", + "\u3042\u3093\u3069\u3046;\u308D\u3044\u3069"); + final ContentValues contentValuesForSortAsOrg = new ContentValues(); + contentValuesForSortAsOrg.put("SORT-AS", + "\u3050\u30FC\u3050\u308B;\u3051\u3093\u3055\u304F\u3076\u3082\u3093"); + + mVerifier.addPropertyNodesVerifierElem() + .addExpectedNodeWithOrder("FN", "\u5B89\u85E4\u0020\u30ED\u30A4\u30C9") + .addExpectedNodeWithOrder("N", + Arrays.asList("\u5B89\u85E4", "\u30ED\u30A4\u30C9", "", "", ""), + contentValuesForSortAsN) + .addExpectedNodeWithOrder("ORG", + Arrays.asList("\u30B0\u30FC\u30B0\u30EB", "\u691C\u7D22\u90E8\u9580"), + contentValuesForSortAsOrg, new TypeSet("WORK")); + } + + public void testSortAsV40() { + mVerifier.initForImportTest(V40, R.raw.v40_sort_as); + final ContentValuesVerifierElem elem = mVerifier.addContentValuesVerifierElem(); + elem.addExpected(StructuredName.CONTENT_ITEM_TYPE) + .put(StructuredName.FAMILY_NAME, "\u5B89\u85E4") + .put(StructuredName.GIVEN_NAME, "\u30ED\u30A4\u30C9") + .put(StructuredName.DISPLAY_NAME, "\u5B89\u85E4\u0020\u30ED\u30A4\u30C9") + .put(StructuredName.PHONETIC_FAMILY_NAME, "\u3042\u3093\u3069\u3046") + .put(StructuredName.PHONETIC_GIVEN_NAME, + "\u308D\u3044\u3069"); + elem.addExpected(Organization.CONTENT_ITEM_TYPE) + .put(Organization.TYPE, Organization.TYPE_WORK) + .put(Organization.COMPANY, "\u30B0\u30FC\u30B0\u30EB") + .put(Organization.DEPARTMENT, "\u691C\u7D22\u90E8\u9580") + .put(Organization.PHONETIC_NAME, + "\u3050\u30FC\u3050\u308B\u3051\u3093\u3055\u304F\u3076\u3082\u3093"); + } + + public void testIMV21_Parse() { + mVerifier.initForImportTest(V21, R.raw.v21_im); + mVerifier.addPropertyNodesVerifierElem() + .addExpectedNodeWithOrder("X-ANDROID-CUSTOM", + Arrays.asList("vnd.android.cursor.item/nickname", "Nick", "1", + "", "", "", "", "", "", "", "", "", "", "", "", "")) // 13 + .addExpectedNodeWithOrder("X-GOOGLE-TALK", "hhh@gmail.com"); + } + + public void testIMV21() { + mVerifier.initForImportTest(V21, R.raw.v21_im); + final ContentValuesVerifierElem elem = mVerifier.addContentValuesVerifierElem(); + elem.addExpected(Nickname.CONTENT_ITEM_TYPE) + .put(Nickname.NAME, "Nick") + .put(Nickname.TYPE, "1"); + elem.addExpected(Im.CONTENT_ITEM_TYPE) + .put(Im.PROTOCOL, Im.PROTOCOL_GOOGLE_TALK) + .put(Im.TYPE, Im.TYPE_HOME) + .put(Im.DATA, "hhh@gmail.com"); + } } diff --git a/core/tests/coretests/src/android/pim/vcard/VCardJapanizationTests.java b/core/tests/coretests/src/android/pim/vcard/VCardJapanizationTests.java index 5b60342c872b..8a99419dfa11 100644 --- a/core/tests/coretests/src/android/pim/vcard/VCardJapanizationTests.java +++ b/core/tests/coretests/src/android/pim/vcard/VCardJapanizationTests.java @@ -13,19 +13,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package android.pim.vcard; import android.content.ContentValues; -import android.pim.vcard.VCardConfig; +import android.pim.vcard.test_utils.ContactEntry; +import android.pim.vcard.test_utils.ContentValuesBuilder; +import android.pim.vcard.test_utils.PropertyNodesVerifierElem; +import android.pim.vcard.test_utils.PropertyNodesVerifierElem.TypeSet; +import android.pim.vcard.test_utils.VCardTestsBase; import android.provider.ContactsContract.CommonDataKinds.Nickname; import android.provider.ContactsContract.CommonDataKinds.Note; import android.provider.ContactsContract.CommonDataKinds.Phone; import android.provider.ContactsContract.CommonDataKinds.StructuredName; import android.provider.ContactsContract.CommonDataKinds.StructuredPostal; -import android.pim.vcard.PropertyNodesVerifierElem.TypeSet; - import java.util.Arrays; public class VCardJapanizationTests extends VCardTestsBase { @@ -39,7 +40,7 @@ public class VCardJapanizationTests extends VCardTestsBase { .put(StructuredName.PREFIX, "Dr.") .put(StructuredName.SUFFIX, "Ph.D"); ContentValues contentValues = - (VCardConfig.isV30(vcardType) ? null : mContentValuesForQPAndUtf8); + (VCardConfig.isVersion21(vcardType) ? mContentValuesForQPAndUtf8 : null); mVerifier.addPropertyNodesVerifierElem() .addExpectedNode("FN", "Dr. \u3075\u308B\u3069 B \u3091\u308A\u304B Ph.D", contentValues) @@ -50,15 +51,15 @@ public class VCardJapanizationTests extends VCardTestsBase { } public void testNameUtf8V21() { - testNameUtf8Common(VCardConfig.VCARD_TYPE_V21_JAPANESE_UTF8); + testNameUtf8Common(VCardConfig.VCARD_TYPE_V21_JAPANESE); } public void testNameUtf8V30() { - testNameUtf8Common(VCardConfig.VCARD_TYPE_V30_JAPANESE_UTF8); + testNameUtf8Common(VCardConfig.VCARD_TYPE_V30_JAPANESE); } public void testNameShiftJis() { - mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_V30_JAPANESE_SJIS); + mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_V30_JAPANESE, "Shift_JIS"); ContactEntry entry = mVerifier.addInputEntry(); entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE) .put(StructuredName.FAMILY_NAME, "\u3075\u308B\u3069") @@ -80,7 +81,7 @@ public class VCardJapanizationTests extends VCardTestsBase { * DoCoMo phones require all name elements should be in "family name" field. */ public void testNameDoCoMo() { - mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO); + mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO, "Shift_JIS"); ContactEntry entry = mVerifier.addInputEntry(); entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE) .put(StructuredName.FAMILY_NAME, "\u3075\u308B\u3069") @@ -105,8 +106,8 @@ public class VCardJapanizationTests extends VCardTestsBase { .addExpectedNode("X-DCM-HMN-MODE", ""); } - private void testPhoneticNameCommon(int vcardType) { - mVerifier.initForExportTest(vcardType); + private void testPhoneticNameCommon(int vcardType, String charset) { + mVerifier.initForExportTest(vcardType, charset); ContactEntry entry = mVerifier.addInputEntry(); entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE) .put(StructuredName.PHONETIC_FAMILY_NAME, "\u3084\u307E\u3060") @@ -114,10 +115,10 @@ public class VCardJapanizationTests extends VCardTestsBase { .put(StructuredName.PHONETIC_GIVEN_NAME, "\u305F\u308D\u3046"); final ContentValues contentValues = - (VCardConfig.usesShiftJis(vcardType) ? - (VCardConfig.isV30(vcardType) ? mContentValuesForSJis : - mContentValuesForQPAndSJis) : - (VCardConfig.isV30(vcardType) ? null : mContentValuesForQPAndUtf8)); + ("SHIFT_JIS".equalsIgnoreCase(charset) ? + (VCardConfig.isVersion21(vcardType) ? mContentValuesForQPAndSJis : + mContentValuesForSJis) : + (VCardConfig.isVersion21(vcardType) ? mContentValuesForQPAndUtf8 : null)); PropertyNodesVerifierElem elem = mVerifier.addPropertyNodesVerifierElemWithEmptyName(); elem.addExpectedNode("X-PHONETIC-LAST-NAME", "\u3084\u307E\u3060", contentValues) @@ -126,7 +127,7 @@ public class VCardJapanizationTests extends VCardTestsBase { contentValues) .addExpectedNode("X-PHONETIC-FIRST-NAME", "\u305F\u308D\u3046", contentValues); - if (VCardConfig.isV30(vcardType)) { + if (!VCardConfig.isVersion21(vcardType)) { elem.addExpectedNode("SORT-STRING", "\u3084\u307E\u3060 \u30DF\u30C9\u30EB\u30CD\u30FC\u30E0 \u305F\u308D\u3046", contentValues); @@ -142,23 +143,23 @@ public class VCardJapanizationTests extends VCardTestsBase { } public void testPhoneticNameForJapaneseV21Utf8() { - testPhoneticNameCommon(VCardConfig.VCARD_TYPE_V21_JAPANESE_UTF8); + testPhoneticNameCommon(VCardConfig.VCARD_TYPE_V21_JAPANESE, null); } public void testPhoneticNameForJapaneseV21Sjis() { - testPhoneticNameCommon(VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS); + testPhoneticNameCommon(VCardConfig.VCARD_TYPE_V21_JAPANESE, "Shift_JIS"); } public void testPhoneticNameForJapaneseV30Utf8() { - testPhoneticNameCommon(VCardConfig.VCARD_TYPE_V30_JAPANESE_UTF8); + testPhoneticNameCommon(VCardConfig.VCARD_TYPE_V30_JAPANESE, null); } public void testPhoneticNameForJapaneseV30SJis() { - testPhoneticNameCommon(VCardConfig.VCARD_TYPE_V30_JAPANESE_SJIS); + testPhoneticNameCommon(VCardConfig.VCARD_TYPE_V30_JAPANESE, "Shift_JIS"); } public void testPhoneticNameForMobileV21_1() { - mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE_MOBILE); + mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE_MOBILE, "Shift_JIS"); ContactEntry entry = mVerifier.addInputEntry(); entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE) .put(StructuredName.PHONETIC_FAMILY_NAME, "\u3084\u307E\u3060") @@ -182,7 +183,7 @@ public class VCardJapanizationTests extends VCardTestsBase { } public void testPhoneticNameForMobileV21_2() { - mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE_MOBILE); + mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_V21_JAPANESE_MOBILE, "Shift_JIS"); ContactEntry entry = mVerifier.addInputEntry(); entry.addContentValues(StructuredName.CONTENT_ITEM_TYPE) .put(StructuredName.PHONETIC_FAMILY_NAME, "\u3084\u307E\u3060") @@ -198,8 +199,8 @@ public class VCardJapanizationTests extends VCardTestsBase { .put(StructuredName.DISPLAY_NAME, "\uFF94\uFF8F\uFF80\uFF9E \uFF80\uFF9B\uFF73"); } - private void testPostalAddressWithJapaneseCommon(int vcardType) { - mVerifier.initForExportTest(vcardType); + private void testPostalAddressWithJapaneseCommon(int vcardType, String charset) { + mVerifier.initForExportTest(vcardType, charset); ContactEntry entry = mVerifier.addInputEntry(); entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE) .put(StructuredPostal.POBOX, "\u79C1\u66F8\u7BB107") @@ -214,11 +215,11 @@ public class VCardJapanizationTests extends VCardTestsBase { .put(StructuredPostal.TYPE, StructuredPostal.TYPE_CUSTOM) .put(StructuredPostal.LABEL, "\u304A\u3082\u3061\u304B\u3048\u308A"); - ContentValues contentValues = (VCardConfig.usesShiftJis(vcardType) ? - (VCardConfig.isV30(vcardType) ? mContentValuesForSJis : - mContentValuesForQPAndSJis) : - (VCardConfig.isV30(vcardType) ? mContentValuesForUtf8 : - mContentValuesForQPAndUtf8)); + ContentValues contentValues = ("UTF-8".equalsIgnoreCase(charset) ? + (VCardConfig.isVersion21(vcardType) ? mContentValuesForQPAndSJis : + mContentValuesForSJis) : + (VCardConfig.isVersion21(vcardType) ? mContentValuesForQPAndUtf8 : + mContentValuesForUtf8)); PropertyNodesVerifierElem elem = mVerifier.addPropertyNodesVerifierElemWithEmptyName(); // LABEL must be ignored in vCard 2.1. As for vCard 3.0, the current behavior is @@ -240,7 +241,7 @@ public class VCardJapanizationTests extends VCardTestsBase { .put(StructuredPostal.TYPE, StructuredPostal.TYPE_HOME); } public void testPostalAddresswithJapaneseV21() { - testPostalAddressWithJapaneseCommon(VCardConfig.VCARD_TYPE_V21_JAPANESE_SJIS); + testPostalAddressWithJapaneseCommon(VCardConfig.VCARD_TYPE_V21_JAPANESE, "Shift_JIS"); } /** @@ -248,7 +249,7 @@ public class VCardJapanizationTests extends VCardTestsBase { * Prefered type must (should?) be: HOME > WORK > OTHER > CUSTOM */ public void testPostalAdrressForDoCoMo_1() { - mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO); + mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO, "Shift_JIS"); ContactEntry entry = mVerifier.addInputEntry(); entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE) .put(StructuredPostal.TYPE, StructuredPostal.TYPE_WORK) @@ -276,7 +277,7 @@ public class VCardJapanizationTests extends VCardTestsBase { } public void testPostalAdrressForDoCoMo_2() { - mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO); + mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO, "Shift_JIS"); ContactEntry entry = mVerifier.addInputEntry(); entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE) .put(StructuredPostal.TYPE, StructuredPostal.TYPE_OTHER) @@ -301,7 +302,7 @@ public class VCardJapanizationTests extends VCardTestsBase { } public void testPostalAdrressForDoCoMo_3() { - mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO); + mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO, "Shift_JIS"); ContactEntry entry = mVerifier.addInputEntry(); entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE) .put(StructuredPostal.TYPE, StructuredPostal.TYPE_CUSTOM) @@ -329,7 +330,7 @@ public class VCardJapanizationTests extends VCardTestsBase { * Verifies the vCard exporter tolerates null TYPE. */ public void testPostalAdrressForDoCoMo_4() { - mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO); + mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO, "Shift_JIS"); ContactEntry entry = mVerifier.addInputEntry(); entry.addContentValues(StructuredPostal.CONTENT_ITEM_TYPE) .put(StructuredPostal.POBOX, "1"); @@ -371,15 +372,15 @@ public class VCardJapanizationTests extends VCardTestsBase { } public void testJapanesePhoneNumberV21_1() { - testJapanesePhoneNumberCommon(VCardConfig.VCARD_TYPE_V21_JAPANESE_UTF8); + testJapanesePhoneNumberCommon(VCardConfig.VCARD_TYPE_V21_JAPANESE); } public void testJapanesePhoneNumberV30() { - testJapanesePhoneNumberCommon(VCardConfig.VCARD_TYPE_V30_JAPANESE_UTF8); + testJapanesePhoneNumberCommon(VCardConfig.VCARD_TYPE_V30_JAPANESE); } public void testJapanesePhoneNumberDoCoMo() { - mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO); + mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO, "Shift_JIS"); ContactEntry entry = mVerifier.addInputEntry(); entry.addContentValues(Phone.CONTENT_ITEM_TYPE) .put(Phone.NUMBER, "0312341234") @@ -399,7 +400,7 @@ public class VCardJapanizationTests extends VCardTestsBase { } public void testNoteDoCoMo() { - mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO); + mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_DOCOMO, "Shift_JIS"); ContactEntry entry = mVerifier.addInputEntry(); entry.addContentValues(Note.CONTENT_ITEM_TYPE) .put(Note.NOTE, "note1"); @@ -421,7 +422,7 @@ public class VCardJapanizationTests extends VCardTestsBase { } public void testAndroidCustomV21() { - mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8); + mVerifier.initForExportTest(VCardConfig.VCARD_TYPE_V21_GENERIC); mVerifier.addInputEntry().addContentValues(Nickname.CONTENT_ITEM_TYPE) .put(Nickname.NAME, "\u304D\u3083\u30FC\u30A8\u30C3\u30C1\u30FC"); mVerifier.addPropertyNodesVerifierElemWithEmptyName() diff --git a/core/tests/coretests/src/android/pim/vcard/VCardTestRunner.java b/core/tests/coretests/src/android/pim/vcard/VCardTestRunner.java new file mode 100644 index 000000000000..a3a4393ef395 --- /dev/null +++ b/core/tests/coretests/src/android/pim/vcard/VCardTestRunner.java @@ -0,0 +1,42 @@ +/* + * 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 android.pim.vcard; + +import android.test.InstrumentationTestRunner; +import android.test.InstrumentationTestSuite; + +import junit.framework.TestSuite; + +/** + * Usage: adb shell am instrument -w com.android.vcard.tests/.VCardTestRunnerTestRunner + */ +public class VCardTestRunner extends InstrumentationTestRunner { + @Override + public TestSuite getAllTests() { + TestSuite suite = new InstrumentationTestSuite(this); + suite.addTestSuite(VCardUtilsTests.class); + suite.addTestSuite(VCardTestUtilsTests.class); + suite.addTestSuite(VCardImporterTests.class); + suite.addTestSuite(VCardExporterTests.class); + suite.addTestSuite(VCardJapanizationTests.class); + return suite; + } + + @Override + public ClassLoader getLoader() { + return VCardTestRunner.class.getClassLoader(); + } +}
\ No newline at end of file diff --git a/core/tests/coretests/src/android/pim/vcard/VCardTestUtilsTests.java b/core/tests/coretests/src/android/pim/vcard/VCardTestUtilsTests.java new file mode 100644 index 000000000000..3ff5cf7a5158 --- /dev/null +++ b/core/tests/coretests/src/android/pim/vcard/VCardTestUtilsTests.java @@ -0,0 +1,98 @@ +/* + * 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 android.pim.vcard; + +import com.android.frameworks.coretests.R; + +import android.pim.vcard.test_utils.VCardVerifier; +import android.provider.ContactsContract.CommonDataKinds.StructuredName; +import android.test.AndroidTestCase; + +import junit.framework.AssertionFailedError; +import junit.framework.TestCase; + +import java.util.Arrays; + +/** + * Tests confirming utilities for vCard tests work fine. + * + * Now that the foundation classes for vCard test cases became too complicated to + * rely on without testing itself. + */ +public class VCardTestUtilsTests extends AndroidTestCase { + public void testShouldFailAtPropertyNodeVerification() { + boolean failureDetected = false; + try { + final VCardVerifier verifier = new VCardVerifier(this); + verifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_GENERIC, R.raw.v21_backslash); + verifier.addPropertyNodesVerifierElem() + .addExpectedNodeWithOrder("N", ";A;B\\;C\\;;D;:E;\\\\;--", // wrong + Arrays.asList("", "A;B\\", "C\\;", "D", ":E", "\\\\", "")) + .addExpectedNodeWithOrder("FN", "A;B\\C\\;D:E\\\\"); + verifier.verify(); + } catch (AssertionFailedError e) { + failureDetected = true; + } + if (!failureDetected) { + TestCase.fail("Test case that should fail actually succeeded."); + } + } + + public void testShouldFailAtContentValueVerification() { + boolean failureDetected = false; + try { + final VCardVerifier verifier = new VCardVerifier(this); + verifier.initForImportTest(VCardConfig.VCARD_TYPE_V21_GENERIC, R.raw.v21_backslash); + verifier.addContentValuesVerifierElem() + .addExpected(StructuredName.CONTENT_ITEM_TYPE) + .put(StructuredName.GIVEN_NAME, "A;B\\") + .put(StructuredName.MIDDLE_NAME, "C\\;") + .put(StructuredName.PREFIX, "D") + .put(StructuredName.SUFFIX, ":E"); + // DISPLAY_NAME is missing. + verifier.verify(); + } catch (AssertionFailedError e) { + failureDetected = true; + } + if (!failureDetected) { + TestCase.fail("Test case that should fail actually succeeded."); + } + } + + public void testShouldFailAtLineVerification() { + boolean failureDetected = false; + try { + final VCardVerifier verifier = new VCardVerifier(this); + verifier.initForExportTest(VCardConfig.VCARD_TYPE_V30_GENERIC); + verifier.addInputEntry().addContentValues(StructuredName.CONTENT_ITEM_TYPE) + .put(StructuredName.FAMILY_NAME, "\\") + .put(StructuredName.GIVEN_NAME, ";") + .put(StructuredName.MIDDLE_NAME, ",") + .put(StructuredName.PREFIX, "\n") + .put(StructuredName.DISPLAY_NAME, "[<{Unescaped:Asciis}>]"); + verifier.addLineVerifierElem() + .addExpected("TEL:1") // wrong + .addExpected("FN:[<{Unescaped:Asciis}>]"); + verifier.verify(); + } catch (AssertionFailedError e) { + failureDetected = true; + } + if (!failureDetected) { + TestCase.fail("Test case that should fail actually succeeded."); + } + } + +} diff --git a/core/tests/coretests/src/android/pim/vcard/VCardTestsBase.java b/core/tests/coretests/src/android/pim/vcard/VCardTestsBase.java deleted file mode 100644 index 0857e0c6e3ad..000000000000 --- a/core/tests/coretests/src/android/pim/vcard/VCardTestsBase.java +++ /dev/null @@ -1,169 +0,0 @@ -/* - * Copyright (C) 2009 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.pim.vcard; - -import android.content.ContentProvider; -import android.content.ContentProviderOperation; -import android.content.ContentProviderResult; -import android.content.ContentValues; -import android.content.EntityIterator; -import android.content.res.AssetFileDescriptor; -import android.database.Cursor; -import android.database.CursorWindow; -import android.database.IBulkCursor; -import android.database.IContentObserver; -import android.net.Uri; -import android.os.IBinder; -import android.os.ParcelFileDescriptor; -import android.os.RemoteException; -import android.pim.vcard.VCardConfig; -import android.test.AndroidTestCase; -import android.util.Log; - -import java.util.ArrayList; - -/** - * Almost a dead copy of android.test.mock.MockContentProvider, but different in that this - * class extends ContentProvider, not implementing IContentProvider, - * so that MockContentResolver is able to accept this class :( - */ -class MockContentProvider extends ContentProvider { - @Override - public boolean onCreate() { - return true; - } - - @Override - public int bulkInsert(Uri url, ContentValues[] initialValues) { - throw new UnsupportedOperationException("unimplemented mock method"); - } - - @SuppressWarnings("unused") - public IBulkCursor bulkQuery(Uri url, String[] projection, String selection, - String[] selectionArgs, String sortOrder, IContentObserver observer, - CursorWindow window) throws RemoteException { - throw new UnsupportedOperationException("unimplemented mock method"); - } - - @Override - @SuppressWarnings("unused") - public int delete(Uri url, String selection, String[] selectionArgs) { - throw new UnsupportedOperationException("unimplemented mock method"); - } - - @Override - public String getType(Uri url) { - throw new UnsupportedOperationException("unimplemented mock method"); - } - - @Override - public Uri insert(Uri url, ContentValues initialValues) { - throw new UnsupportedOperationException("unimplemented mock method"); - } - - @Override - public ParcelFileDescriptor openFile(Uri url, String mode) { - throw new UnsupportedOperationException("unimplemented mock method"); - } - - @Override - public AssetFileDescriptor openAssetFile(Uri uri, String mode) { - throw new UnsupportedOperationException("unimplemented mock method"); - } - - @Override - public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations) { - throw new UnsupportedOperationException("unimplemented mock method"); - } - - @Override - public Cursor query(Uri url, String[] projection, String selection, String[] selectionArgs, - String sortOrder) { - throw new UnsupportedOperationException("unimplemented mock method"); - } - - @Override - public int update(Uri url, ContentValues values, String selection, String[] selectionArgs) { - throw new UnsupportedOperationException("unimplemented mock method"); - } - - public IBinder asBinder() { - throw new UnsupportedOperationException("unimplemented mock method"); - } -} - -/** - * BaseClass for vCard unit tests with utility classes. - * Please do not add each unit test here. - */ -/* package */ class VCardTestsBase extends AndroidTestCase { - public static final int V21 = VCardConfig.VCARD_TYPE_V21_GENERIC_UTF8; - public static final int V30 = VCardConfig.VCARD_TYPE_V30_GENERIC_UTF8; - - // Do not modify these during tests. - protected final ContentValues mContentValuesForQP; - protected final ContentValues mContentValuesForSJis; - protected final ContentValues mContentValuesForUtf8; - protected final ContentValues mContentValuesForQPAndSJis; - protected final ContentValues mContentValuesForQPAndUtf8; - protected final ContentValues mContentValuesForBase64V21; - protected final ContentValues mContentValuesForBase64V30; - - protected VCardVerifier mVerifier; - private boolean mSkipVerification; - - public VCardTestsBase() { - super(); - mContentValuesForQP = new ContentValues(); - mContentValuesForQP.put("ENCODING", "QUOTED-PRINTABLE"); - mContentValuesForSJis = new ContentValues(); - mContentValuesForSJis.put("CHARSET", "SHIFT_JIS"); - mContentValuesForUtf8 = new ContentValues(); - mContentValuesForUtf8.put("CHARSET", "UTF-8"); - mContentValuesForQPAndSJis = new ContentValues(); - mContentValuesForQPAndSJis.put("ENCODING", "QUOTED-PRINTABLE"); - mContentValuesForQPAndSJis.put("CHARSET", "SHIFT_JIS"); - mContentValuesForQPAndUtf8 = new ContentValues(); - mContentValuesForQPAndUtf8.put("ENCODING", "QUOTED-PRINTABLE"); - mContentValuesForQPAndUtf8.put("CHARSET", "UTF-8"); - mContentValuesForBase64V21 = new ContentValues(); - mContentValuesForBase64V21.put("ENCODING", "BASE64"); - mContentValuesForBase64V30 = new ContentValues(); - mContentValuesForBase64V30.put("ENCODING", "b"); - } - - @Override - public void testAndroidTestCaseSetupProperly() { - super.testAndroidTestCaseSetupProperly(); - mSkipVerification = true; - } - - @Override - public void setUp() throws Exception{ - super.setUp(); - mVerifier = new VCardVerifier(this); - mSkipVerification = false; - } - - @Override - public void tearDown() throws Exception { - super.tearDown(); - if (!mSkipVerification) { - mVerifier.verify(); - } - } -} diff --git a/core/tests/coretests/src/android/pim/vcard/VCardUtilsTests.java b/core/tests/coretests/src/android/pim/vcard/VCardUtilsTests.java index 59299f9dff35..251fab212768 100644 --- a/core/tests/coretests/src/android/pim/vcard/VCardUtilsTests.java +++ b/core/tests/coretests/src/android/pim/vcard/VCardUtilsTests.java @@ -13,10 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package android.pim.vcard; -import android.pim.vcard.VCardUtils; +import android.text.TextUtils; import junit.framework.TestCase; @@ -82,4 +81,38 @@ public class VCardUtilsTests extends TestCase { assertFalse(VCardUtils.containsOnlyAlphaDigitHyphen(String.valueOf((char)i))); } } + + public void testToStringAvailableAsV30ParamValue() { + // Smoke tests. + assertEquals("HOME", VCardUtils.toStringAsV30ParamValue("HOME")); + assertEquals("TEL", VCardUtils.toStringAsV30ParamValue("TEL")); + assertEquals("PAGER", VCardUtils.toStringAsV30ParamValue("PAGER")); + + assertTrue(TextUtils.isEmpty(VCardUtils.toStringAsV30ParamValue(""))); + assertTrue(TextUtils.isEmpty(VCardUtils.toStringAsV30ParamValue(null))); + assertTrue(TextUtils.isEmpty(VCardUtils.toStringAsV30ParamValue(" \t"))); + + // non-Ascii must be allowed + assertEquals("\u4E8B\u52D9\u6240", + VCardUtils.toStringAsV30ParamValue("\u4E8B\u52D9\u6240")); + // Reported as bug report. + assertEquals("\u8D39", VCardUtils.toStringAsV30ParamValue("\u8D39")); + assertEquals("\"comma,separated\"", + VCardUtils.toStringAsV30ParamValue("comma,separated")); + assertEquals("\"colon:aware\"", + VCardUtils.toStringAsV30ParamValue("colon:aware")); + // CTL characters. + assertEquals("CTLExample", + VCardUtils.toStringAsV30ParamValue("CTL\u0001Example")); + assertTrue(TextUtils.isEmpty( + VCardUtils.toStringAsV30ParamValue("\u0001\u0002\u0003"))); + // DQUOTE must be removed. + assertEquals("quoted", + VCardUtils.toStringAsV30ParamValue("\"quoted\"")); + // DQUOTE must be removed basically, but we should detect a space, which + // require us to use DQUOTE again. + // Right-side has one more illegal dquote to test quote-handle code thoroughly. + assertEquals("\"Already quoted\"", + VCardUtils.toStringAsV30ParamValue("\"Already quoted\"\"")); + } } diff --git a/core/tests/coretests/src/android/pim/vcard/VCardVerifier.java b/core/tests/coretests/src/android/pim/vcard/VCardVerifier.java deleted file mode 100644 index bfc315829c2d..000000000000 --- a/core/tests/coretests/src/android/pim/vcard/VCardVerifier.java +++ /dev/null @@ -1,306 +0,0 @@ -/* - * Copyright (C) 2009 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.pim.vcard; - -import android.content.ContentProvider; -import android.content.ContentResolver; -import android.content.Context; -import android.content.EntityIterator; -import android.net.Uri; -import android.pim.vcard.VCardComposer; -import android.pim.vcard.VCardConfig; -import android.pim.vcard.VCardEntryConstructor; -import android.pim.vcard.VCardInterpreter; -import android.pim.vcard.VCardInterpreterCollection; -import android.pim.vcard.VCardParser; -import android.pim.vcard.VCardParser_V21; -import android.pim.vcard.VCardParser_V30; -import android.pim.vcard.exception.VCardException; -import android.test.AndroidTestCase; -import android.test.mock.MockContext; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.lang.reflect.Method; -import java.util.Arrays; - -/* package */ class CustomMockContext extends MockContext { - final ContentResolver mResolver; - public CustomMockContext(ContentResolver resolver) { - mResolver = resolver; - } - - @Override - public ContentResolver getContentResolver() { - return mResolver; - } -} - -/* package */ class VCardVerifier { - private class VCardVerifierInternal implements VCardComposer.OneEntryHandler { - public boolean onInit(Context context) { - return true; - } - public boolean onEntryCreated(String vcard) { - verifyOneVCard(vcard); - return true; - } - public void onTerminate() { - } - } - - private final AndroidTestCase mTestCase; - private final VCardVerifierInternal mVCardVerifierInternal; - private int mVCardType; - private boolean mIsV30; - private boolean mIsDoCoMo; - - // Only one of them must be non-empty. - private ExportTestResolver mExportTestResolver; - private InputStream mInputStream; - - // To allow duplication, use list instead of set. - // When null, we don't need to do the verification. - private PropertyNodesVerifier mPropertyNodesVerifier; - private LineVerifier mLineVerifier; - private ContentValuesVerifier mContentValuesVerifier; - private boolean mInitialized; - private boolean mVerified = false; - - public VCardVerifier(AndroidTestCase androidTestCase) { - mTestCase = androidTestCase; - mVCardVerifierInternal = new VCardVerifierInternal(); - mExportTestResolver = null; - mInputStream = null; - mInitialized = false; - mVerified = false; - } - - public void initForExportTest(int vcardType) { - if (mInitialized) { - mTestCase.fail("Already initialized"); - } - mExportTestResolver = new ExportTestResolver(mTestCase); - mVCardType = vcardType; - mIsV30 = VCardConfig.isV30(vcardType); - mIsDoCoMo = VCardConfig.isDoCoMo(vcardType); - mInitialized = true; - } - - public void initForImportTest(int vcardType, int resId) { - if (mInitialized) { - mTestCase.fail("Already initialized"); - } - mVCardType = vcardType; - mIsV30 = VCardConfig.isV30(vcardType); - mIsDoCoMo = VCardConfig.isDoCoMo(vcardType); - setInputResourceId(resId); - mInitialized = true; - } - - private void setInputResourceId(int resId) { - InputStream inputStream = mTestCase.getContext().getResources().openRawResource(resId); - if (inputStream == null) { - mTestCase.fail("Wrong resId: " + resId); - } - setInputStream(inputStream); - } - - private void setInputStream(InputStream inputStream) { - if (mExportTestResolver != null) { - mTestCase.fail("addInputEntry() is called."); - } else if (mInputStream != null) { - mTestCase.fail("InputStream is already set"); - } - mInputStream = inputStream; - } - - public ContactEntry addInputEntry() { - if (!mInitialized) { - mTestCase.fail("Not initialized"); - } - if (mInputStream != null) { - mTestCase.fail("setInputStream is called"); - } - return mExportTestResolver.addInputContactEntry(); - } - - public PropertyNodesVerifierElem addPropertyNodesVerifierElem() { - if (!mInitialized) { - mTestCase.fail("Not initialized"); - } - if (mPropertyNodesVerifier == null) { - mPropertyNodesVerifier = new PropertyNodesVerifier(mTestCase); - } - PropertyNodesVerifierElem elem = - mPropertyNodesVerifier.addPropertyNodesVerifierElem(); - elem.addExpectedNodeWithOrder("VERSION", (mIsV30 ? "3.0" : "2.1")); - - return elem; - } - - public PropertyNodesVerifierElem addPropertyNodesVerifierElemWithEmptyName() { - if (!mInitialized) { - mTestCase.fail("Not initialized"); - } - PropertyNodesVerifierElem elem = addPropertyNodesVerifierElem(); - if (mIsV30) { - elem.addExpectedNodeWithOrder("N", "").addExpectedNodeWithOrder("FN", ""); - } else if (mIsDoCoMo) { - elem.addExpectedNodeWithOrder("N", ""); - } - return elem; - } - - public LineVerifierElem addLineVerifierElem() { - if (!mInitialized) { - mTestCase.fail("Not initialized"); - } - if (mLineVerifier == null) { - mLineVerifier = new LineVerifier(mTestCase, mVCardType); - } - return mLineVerifier.addLineVerifierElem(); - } - - public ContentValuesVerifierElem addContentValuesVerifierElem() { - if (!mInitialized) { - mTestCase.fail("Not initialized"); - } - if (mContentValuesVerifier == null) { - mContentValuesVerifier = new ContentValuesVerifier(); - } - - return mContentValuesVerifier.addElem(mTestCase); - } - - private void verifyOneVCard(final String vcard) { - // Log.d("@@@", vcard); - final VCardInterpreter builder; - if (mContentValuesVerifier != null) { - final VNodeBuilder vnodeBuilder = mPropertyNodesVerifier; - final VCardEntryConstructor vcardDataBuilder = - new VCardEntryConstructor(mVCardType); - vcardDataBuilder.addEntryHandler(mContentValuesVerifier); - if (mPropertyNodesVerifier != null) { - builder = new VCardInterpreterCollection(Arrays.asList( - mPropertyNodesVerifier, vcardDataBuilder)); - } else { - builder = vnodeBuilder; - } - } else { - if (mPropertyNodesVerifier != null) { - builder = mPropertyNodesVerifier; - } else { - return; - } - } - - final VCardParser parser = - (mIsV30 ? new VCardParser_V30(true) : new VCardParser_V21()); - InputStream is = null; - try { - String charset = - (VCardConfig.usesShiftJis(mVCardType) ? "SHIFT_JIS" : "UTF-8"); - is = new ByteArrayInputStream(vcard.getBytes(charset)); - mTestCase.assertEquals(true, parser.parse(is, null, builder)); - } catch (IOException e) { - mTestCase.fail("Unexpected IOException: " + e.getMessage()); - } catch (VCardException e) { - mTestCase.fail("Unexpected VCardException: " + e.getMessage()); - } finally { - if (is != null) { - try { - is.close(); - } catch (IOException e) { - } - } - } - } - - public void verify() { - if (!mInitialized) { - mTestCase.fail("Not initialized."); - } - if (mVerified) { - mTestCase.fail("verify() was called twice."); - } - if (mInputStream != null) { - try { - verifyForImportTest(); - } catch (IOException e) { - mTestCase.fail("IOException was thrown: " + e.getMessage()); - } catch (VCardException e) { - mTestCase.fail("VCardException was thrown: " + e.getMessage()); - } - } else if (mExportTestResolver != null){ - verifyForExportTest(); - } else { - mTestCase.fail("No input is determined"); - } - mVerified = true; - } - - private void verifyForImportTest() throws IOException, VCardException { - if (mLineVerifier != null) { - mTestCase.fail("Not supported now."); - } - if (mContentValuesVerifier != null) { - mContentValuesVerifier.verify(mInputStream, mVCardType); - } - } - - public static EntityIterator mockGetEntityIteratorMethod( - final ContentResolver resolver, - final Uri uri, final String selection, - final String[] selectionArgs, final String sortOrder) { - final ContentProvider provider = - resolver.acquireContentProviderClient(uri).getLocalContentProvider(); - return ((ExportTestProvider)provider).queryEntities( - uri, selection, selectionArgs, sortOrder); - } - - private Method getMockGetEntityIteratorMethod() - throws SecurityException, NoSuchMethodException { - return this.getClass().getMethod("mockGetEntityIteratorMethod", - ContentResolver.class, Uri.class, String.class, String[].class, String.class); - } - - private void verifyForExportTest() { - final VCardComposer composer = - new VCardComposer(new CustomMockContext(mExportTestResolver), mVCardType); - composer.addHandler(mLineVerifier); - composer.addHandler(mVCardVerifierInternal); - if (!composer.init(VCardComposer.CONTACTS_TEST_CONTENT_URI, null, null, null)) { - mTestCase.fail("init() failed. Reason: " + composer.getErrorReason()); - } - mTestCase.assertFalse(composer.isAfterLast()); - try { - while (!composer.isAfterLast()) { - try { - final Method mockGetEntityIteratorMethod = getMockGetEntityIteratorMethod(); - mTestCase.assertTrue( - composer.createOneEntry(getMockGetEntityIteratorMethod())); - } catch (Exception e) { - e.printStackTrace(); - mTestCase.fail(); - } - } - } finally { - composer.terminate(); - } - } -} diff --git a/core/tests/coretests/src/android/pim/vcard/test_utils/ContactEntry.java b/core/tests/coretests/src/android/pim/vcard/test_utils/ContactEntry.java new file mode 100644 index 000000000000..843750e789b3 --- /dev/null +++ b/core/tests/coretests/src/android/pim/vcard/test_utils/ContactEntry.java @@ -0,0 +1,43 @@ +/* + * 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 android.pim.vcard.test_utils; + +import android.content.ContentValues; +import android.provider.ContactsContract.Data; + +import java.util.ArrayList; +import java.util.List; + +/** + * <p> + * The class representing one contact, which should contain multiple ContentValues like + * StructuredName, Email, etc. + * </p> + */ +public final class ContactEntry { + private final List<ContentValues> mContentValuesList = new ArrayList<ContentValues>(); + + public ContentValuesBuilder addContentValues(String mimeType) { + ContentValues contentValues = new ContentValues(); + contentValues.put(Data.MIMETYPE, mimeType); + mContentValuesList.add(contentValues); + return new ContentValuesBuilder(contentValues); + } + + public List<ContentValues> getList() { + return mContentValuesList; + } +}
\ No newline at end of file diff --git a/core/tests/coretests/src/android/pim/vcard/ContentValuesBuilder.java b/core/tests/coretests/src/android/pim/vcard/test_utils/ContentValuesBuilder.java index b3c0773903fa..bdcccf98e6d9 100644 --- a/core/tests/coretests/src/android/pim/vcard/ContentValuesBuilder.java +++ b/core/tests/coretests/src/android/pim/vcard/test_utils/ContentValuesBuilder.java @@ -13,8 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -package android.pim.vcard; +package android.pim.vcard.test_utils; import android.content.ContentValues; @@ -22,7 +21,7 @@ import android.content.ContentValues; * ContentValues-like class which enables users to chain put() methods and restricts * the other methods. */ -/* package */ class ContentValuesBuilder { +public class ContentValuesBuilder { private final ContentValues mContentValues; public ContentValuesBuilder(final ContentValues contentValues) { @@ -34,6 +33,7 @@ import android.content.ContentValues; return this; } + /* public ContentValuesBuilder put(String key, Byte value) { mContentValues.put(key, value); return this; @@ -42,13 +42,14 @@ import android.content.ContentValues; public ContentValuesBuilder put(String key, Short value) { mContentValues.put(key, value); return this; - } + }*/ public ContentValuesBuilder put(String key, Integer value) { mContentValues.put(key, value); return this; } + /* public ContentValuesBuilder put(String key, Long value) { mContentValues.put(key, value); return this; @@ -67,7 +68,7 @@ import android.content.ContentValues; public ContentValuesBuilder put(String key, Boolean value) { mContentValues.put(key, value); return this; - } + }*/ public ContentValuesBuilder put(String key, byte[] value) { mContentValues.put(key, value); diff --git a/core/tests/coretests/src/android/pim/vcard/test_utils/ContentValuesVerifier.java b/core/tests/coretests/src/android/pim/vcard/test_utils/ContentValuesVerifier.java new file mode 100644 index 000000000000..d0613c65c256 --- /dev/null +++ b/core/tests/coretests/src/android/pim/vcard/test_utils/ContentValuesVerifier.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2009 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.pim.vcard.test_utils; + +import android.pim.vcard.VCardEntry; +import android.pim.vcard.VCardEntryHandler; +import android.test.AndroidTestCase; + +import java.util.ArrayList; +import java.util.List; + +public class ContentValuesVerifier implements VCardEntryHandler { + private List<ContentValuesVerifierElem> mContentValuesVerifierElemList = + new ArrayList<ContentValuesVerifierElem>(); + private int mIndex; + + public ContentValuesVerifierElem addElem(AndroidTestCase androidTestCase) { + ContentValuesVerifierElem elem = new ContentValuesVerifierElem(androidTestCase); + mContentValuesVerifierElemList.add(elem); + return elem; + } + + public void onStart() { + for (ContentValuesVerifierElem elem : mContentValuesVerifierElemList) { + elem.onParsingStart(); + } + } + + public void onEntryCreated(VCardEntry entry) { + AndroidTestCase.assertTrue(mIndex < mContentValuesVerifierElemList.size()); + mContentValuesVerifierElemList.get(mIndex).onEntryCreated(entry); + mIndex++; + } + + public void onEnd() { + for (ContentValuesVerifierElem elem : mContentValuesVerifierElemList) { + elem.onParsingEnd(); + elem.verifyResolver(); + } + } +} diff --git a/core/tests/coretests/src/android/pim/vcard/ContentValuesVerifierElem.java b/core/tests/coretests/src/android/pim/vcard/test_utils/ContentValuesVerifierElem.java index 2edbb36d4220..03c18e1f0c69 100644 --- a/core/tests/coretests/src/android/pim/vcard/ContentValuesVerifierElem.java +++ b/core/tests/coretests/src/android/pim/vcard/test_utils/ContentValuesVerifierElem.java @@ -13,17 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.pim.vcard; +package android.pim.vcard.test_utils; import android.content.ContentValues; -import android.pim.vcard.VCardConfig; import android.pim.vcard.VCardEntry; import android.pim.vcard.VCardEntryCommitter; import android.pim.vcard.VCardEntryConstructor; import android.pim.vcard.VCardEntryHandler; import android.pim.vcard.VCardParser; -import android.pim.vcard.VCardParser_V21; -import android.pim.vcard.VCardParser_V30; +import android.pim.vcard.VCardUtils; import android.pim.vcard.exception.VCardException; import android.provider.ContactsContract.Data; import android.test.AndroidTestCase; @@ -31,7 +29,7 @@ import android.test.AndroidTestCase; import java.io.IOException; import java.io.InputStream; -/* package */ class ContentValuesVerifierElem { +public class ContentValuesVerifierElem { private final AndroidTestCase mTestCase; private final ImportTestResolver mResolver; private final VCardEntryHandler mHandler; @@ -49,20 +47,14 @@ import java.io.InputStream; return new ContentValuesBuilder(contentValues); } - public void verify(int resId, int vCardType) + public void verify(int resId, int vcardType) throws IOException, VCardException { - verify(mTestCase.getContext().getResources().openRawResource(resId), vCardType); + verify(mTestCase.getContext().getResources().openRawResource(resId), vcardType); } - public void verify(InputStream is, int vCardType) throws IOException, VCardException { - final VCardParser vCardParser; - if (VCardConfig.isV30(vCardType)) { - vCardParser = new VCardParser_V30(true); // use StrictParsing - } else { - vCardParser = new VCardParser_V21(); - } - VCardEntryConstructor builder = - new VCardEntryConstructor(null, null, false, vCardType, null); + public void verify(InputStream is, int vcardType) throws IOException, VCardException { + final VCardParser vCardParser = VCardUtils.getAppropriateParser(vcardType); + final VCardEntryConstructor builder = new VCardEntryConstructor(vcardType, null); builder.addEntryHandler(mHandler); try { vCardParser.parse(is, builder); diff --git a/core/tests/coretests/src/android/pim/vcard/ExportTestResolver.java b/core/tests/coretests/src/android/pim/vcard/test_utils/ExportTestProvider.java index 5968e83647d1..e095258ff592 100644 --- a/core/tests/coretests/src/android/pim/vcard/ExportTestResolver.java +++ b/core/tests/coretests/src/android/pim/vcard/test_utils/ExportTestProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009 The Android Open Source Project + * 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 @@ -13,7 +13,7 @@ * License for the specific language governing permissions and limitations under * the License. */ -package android.pim.vcard; +package android.pim.vcard.test_utils; import android.content.ContentResolver; import android.content.ContentValues; @@ -21,10 +21,12 @@ import android.content.Entity; import android.content.EntityIterator; import android.database.Cursor; import android.net.Uri; +import android.pim.vcard.VCardComposer; import android.provider.ContactsContract.Contacts; import android.provider.ContactsContract.Data; import android.provider.ContactsContract.RawContacts; -import android.test.mock.MockContentResolver; +import android.test.AndroidTestCase; +import android.test.mock.MockContentProvider; import android.test.mock.MockCursor; import junit.framework.TestCase; @@ -33,78 +35,44 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; -/* package */ public class ExportTestResolver extends MockContentResolver { - ExportTestProvider mProvider; - public ExportTestResolver(TestCase testCase) { - mProvider = new ExportTestProvider(testCase); - addProvider(VCardComposer.VCARD_TEST_AUTHORITY, mProvider); - addProvider(RawContacts.CONTENT_URI.getAuthority(), mProvider); - } - - public ContactEntry addInputContactEntry() { - return mProvider.buildInputEntry(); - } -} +public class ExportTestProvider extends MockContentProvider { + final private ArrayList<ContactEntry> mContactEntryList = new ArrayList<ContactEntry>(); -/* package */ class MockEntityIterator implements EntityIterator { - List<Entity> mEntityList; - Iterator<Entity> mIterator; + private static class MockEntityIterator implements EntityIterator { + List<Entity> mEntityList; + Iterator<Entity> mIterator; - public MockEntityIterator(List<ContentValues> contentValuesList) { - mEntityList = new ArrayList<Entity>(); - Entity entity = new Entity(new ContentValues()); - for (ContentValues contentValues : contentValuesList) { - entity.addSubValue(Data.CONTENT_URI, contentValues); + public MockEntityIterator(List<ContentValues> contentValuesList) { + mEntityList = new ArrayList<Entity>(); + Entity entity = new Entity(new ContentValues()); + for (ContentValues contentValues : contentValuesList) { + entity.addSubValue(Data.CONTENT_URI, contentValues); + } + mEntityList.add(entity); + mIterator = mEntityList.iterator(); } - mEntityList.add(entity); - mIterator = mEntityList.iterator(); - } - public boolean hasNext() { - return mIterator.hasNext(); - } - - public Entity next() { - return mIterator.next(); - } - - public void remove() { - throw new UnsupportedOperationException("remove not supported"); - } + public boolean hasNext() { + return mIterator.hasNext(); + } - public void reset() { - mIterator = mEntityList.iterator(); - } + public Entity next() { + return mIterator.next(); + } - public void close() { - } -} + public void remove() { + throw new UnsupportedOperationException("remove not supported"); + } -/** - * Represents one contact, which should contain multiple ContentValues like - * StructuredName, Email, etc. - */ -/* package */ class ContactEntry { - private final List<ContentValues> mContentValuesList = new ArrayList<ContentValues>(); - - public ContentValuesBuilder addContentValues(String mimeType) { - ContentValues contentValues = new ContentValues(); - contentValues.put(Data.MIMETYPE, mimeType); - mContentValuesList.add(contentValues); - return new ContentValuesBuilder(contentValues); - } + public void reset() { + mIterator = mEntityList.iterator(); + } - public List<ContentValues> getList() { - return mContentValuesList; + public void close() { + } } -} - -/* package */ class ExportTestProvider extends MockContentProvider { - final private TestCase mTestCase; - final private ArrayList<ContactEntry> mContactEntryList = new ArrayList<ContactEntry>(); - public ExportTestProvider(TestCase testCase) { - mTestCase = testCase; + public ExportTestProvider(AndroidTestCase androidTestCase) { } public ContactEntry buildInputEntry() { @@ -121,27 +89,17 @@ import java.util.List; * We still keep using this method since we don't have a propeer way to know * which value in the ContentValue corresponds to the entry in Contacts database. * </p> - * <p> - * Detail: - * There's an easy way to know which index "family name" corresponds to, via - * {@link android.provider.ContactsContract}. - * FAMILY_NAME equals DATA3, so the corresponding index - * for "family name" should be 2 (note that index is 0-origin). - * However, we cannot know what the index 2 corresponds to; it may be "family name", - * "label" for now, but may be the other some column in the future. We don't have - * convenient way to know the original data structure. - * </p> */ public EntityIterator queryEntities(Uri uri, String selection, String[] selectionArgs, String sortOrder) { - mTestCase.assertTrue(uri != null); - mTestCase.assertTrue(ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())); + TestCase.assertTrue(uri != null); + TestCase.assertTrue(ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())); final String authority = uri.getAuthority(); - mTestCase.assertTrue(RawContacts.CONTENT_URI.getAuthority().equals(authority)); - mTestCase.assertTrue((Data.CONTACT_ID + "=?").equals(selection)); - mTestCase.assertEquals(1, selectionArgs.length); + TestCase.assertTrue(RawContacts.CONTENT_URI.getAuthority().equals(authority)); + TestCase.assertTrue((Data.CONTACT_ID + "=?").equals(selection)); + TestCase.assertEquals(1, selectionArgs.length); final int id = Integer.parseInt(selectionArgs[0]); - mTestCase.assertTrue(id >= 0 && id < mContactEntryList.size()); + TestCase.assertTrue(id >= 0 && id < mContactEntryList.size()); return new MockEntityIterator(mContactEntryList.get(id).getList()); } @@ -149,11 +107,11 @@ import java.util.List; @Override public Cursor query(Uri uri,String[] projection, String selection, String[] selectionArgs, String sortOrder) { - mTestCase.assertTrue(VCardComposer.CONTACTS_TEST_CONTENT_URI.equals(uri)); + TestCase.assertTrue(VCardComposer.CONTACTS_TEST_CONTENT_URI.equals(uri)); // In this test, following arguments are not supported. - mTestCase.assertNull(selection); - mTestCase.assertNull(selectionArgs); - mTestCase.assertNull(sortOrder); + TestCase.assertNull(selection); + TestCase.assertNull(selectionArgs); + TestCase.assertNull(sortOrder); return new MockCursor() { int mCurrentPosition = -1; @@ -191,14 +149,14 @@ import java.util.List; @Override public int getColumnIndex(String columnName) { - mTestCase.assertEquals(Contacts._ID, columnName); + TestCase.assertEquals(Contacts._ID, columnName); return 0; } @Override public int getInt(int columnIndex) { - mTestCase.assertEquals(0, columnIndex); - mTestCase.assertTrue(mCurrentPosition >= 0 + TestCase.assertEquals(0, columnIndex); + TestCase.assertTrue(mCurrentPosition >= 0 && mCurrentPosition < mContactEntryList.size()); return mCurrentPosition; } @@ -213,4 +171,4 @@ import java.util.List; } }; } -} +}
\ No newline at end of file diff --git a/core/tests/coretests/src/android/pim/vcard/test_utils/ExportTestResolver.java b/core/tests/coretests/src/android/pim/vcard/test_utils/ExportTestResolver.java new file mode 100644 index 000000000000..606d91afaa34 --- /dev/null +++ b/core/tests/coretests/src/android/pim/vcard/test_utils/ExportTestResolver.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2009 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.pim.vcard.test_utils; + +import android.pim.vcard.VCardComposer; +import android.provider.ContactsContract.RawContacts; +import android.test.AndroidTestCase; +import android.test.mock.MockContentResolver; + +public class ExportTestResolver extends MockContentResolver { + private final ExportTestProvider mProvider; + public ExportTestResolver(AndroidTestCase androidTestCase) { + mProvider = new ExportTestProvider(androidTestCase); + addProvider(VCardComposer.VCARD_TEST_AUTHORITY, mProvider); + addProvider(RawContacts.CONTENT_URI.getAuthority(), mProvider); + } + + public ContactEntry addInputContactEntry() { + return mProvider.buildInputEntry(); + } + + public ExportTestProvider getProvider() { + return mProvider; + } +} diff --git a/core/tests/coretests/src/android/pim/vcard/ImportTestResolver.java b/core/tests/coretests/src/android/pim/vcard/test_utils/ImportTestProvider.java index c3f6f798ba7c..df89491afab5 100644 --- a/core/tests/coretests/src/android/pim/vcard/ImportTestResolver.java +++ b/core/tests/coretests/src/android/pim/vcard/test_utils/ImportTestProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009 The Android Open Source Project + * 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. @@ -13,14 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.pim.vcard; +package android.pim.vcard.test_utils; import android.content.ContentProviderOperation; import android.content.ContentProviderResult; import android.content.ContentValues; import android.net.Uri; -import android.provider.ContactsContract.Data; -import android.provider.ContactsContract.RawContacts; import android.provider.ContactsContract.CommonDataKinds.Email; import android.provider.ContactsContract.CommonDataKinds.Event; import android.provider.ContactsContract.CommonDataKinds.GroupMembership; @@ -34,7 +32,10 @@ import android.provider.ContactsContract.CommonDataKinds.Relation; import android.provider.ContactsContract.CommonDataKinds.StructuredName; import android.provider.ContactsContract.CommonDataKinds.StructuredPostal; import android.provider.ContactsContract.CommonDataKinds.Website; -import android.test.mock.MockContentResolver; +import android.provider.ContactsContract.Data; +import android.provider.ContactsContract.RawContacts; +import android.test.AndroidTestCase; +import android.test.mock.MockContentProvider; import android.text.TextUtils; import junit.framework.TestCase; @@ -45,43 +46,12 @@ import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; import java.util.SortedMap; import java.util.TreeMap; -import java.util.Map.Entry; - -/* package */ class ImportTestResolver extends MockContentResolver { - final ImportTestProvider mProvider; - - public ImportTestResolver(TestCase testCase) { - mProvider = new ImportTestProvider(testCase); - } - - @Override - public ContentProviderResult[] applyBatch(String authority, - ArrayList<ContentProviderOperation> operations) { - equalsString(authority, RawContacts.CONTENT_URI.toString()); - return mProvider.applyBatch(operations); - } - public void addExpectedContentValues(ContentValues expectedContentValues) { - mProvider.addExpectedContentValues(expectedContentValues); - } - - public void verify() { - mProvider.verify(); - } - - private static boolean equalsString(String a, String b) { - if (a == null || a.length() == 0) { - return b == null || b.length() == 0; - } else { - return a.equals(b); - } - } -} - -/* package */ class ImportTestProvider extends MockContentProvider { +public class ImportTestProvider extends MockContentProvider { private static final Set<String> sKnownMimeTypeSet = new HashSet<String>(Arrays.asList(StructuredName.CONTENT_ITEM_TYPE, Nickname.CONTENT_ITEM_TYPE, Phone.CONTENT_ITEM_TYPE, @@ -94,10 +64,7 @@ import java.util.Map.Entry; final Map<String, Collection<ContentValues>> mMimeTypeToExpectedContentValues; - private final TestCase mTestCase; - - public ImportTestProvider(TestCase testCase) { - mTestCase = testCase; + public ImportTestProvider(AndroidTestCase androidTestCase) { mMimeTypeToExpectedContentValues = new HashMap<String, Collection<ContentValues>>(); for (String acceptanbleMimeType : sKnownMimeTypeSet) { @@ -112,7 +79,7 @@ import java.util.Map.Entry; public void addExpectedContentValues(ContentValues expectedContentValues) { final String mimeType = expectedContentValues.getAsString(Data.MIMETYPE); if (!sKnownMimeTypeSet.contains(mimeType)) { - mTestCase.fail(String.format( + TestCase.fail(String.format( "Unknow MimeType %s in the test code. Test code should be broken.", mimeType)); } @@ -126,7 +93,7 @@ import java.util.Map.Entry; public ContentProviderResult[] applyBatch( ArrayList<ContentProviderOperation> operations) { if (operations == null) { - mTestCase.fail("There is no operation."); + TestCase.fail("There is no operation."); } final int size = operations.size(); @@ -147,12 +114,12 @@ import java.util.Map.Entry; fakeResultArray, i); final Uri uri = operation.getUri(); if (uri.equals(RawContacts.CONTENT_URI)) { - mTestCase.assertNull(actualContentValues.get(RawContacts.ACCOUNT_NAME)); - mTestCase.assertNull(actualContentValues.get(RawContacts.ACCOUNT_TYPE)); + TestCase.assertNull(actualContentValues.get(RawContacts.ACCOUNT_NAME)); + TestCase.assertNull(actualContentValues.get(RawContacts.ACCOUNT_TYPE)); } else if (uri.equals(Data.CONTENT_URI)) { final String mimeType = actualContentValues.getAsString(Data.MIMETYPE); if (!sKnownMimeTypeSet.contains(mimeType)) { - mTestCase.fail(String.format( + TestCase.fail(String.format( "Unknown MimeType %s. Probably added after developing this test", mimeType)); } @@ -184,7 +151,7 @@ import java.util.Map.Entry; final Collection<ContentValues> contentValuesCollection = mMimeTypeToExpectedContentValues.get(mimeType); if (contentValuesCollection.isEmpty()) { - mTestCase.fail("ContentValues for MimeType " + mimeType + TestCase.fail("ContentValues for MimeType " + mimeType + " is not expected at all (" + actualContentValues + ")"); } boolean checked = false; @@ -196,23 +163,25 @@ import java.util.Map.Entry; + convertToEasilyReadableString(actualContentValues));*/ if (equalsForContentValues(expectedContentValues, actualContentValues)) { - mTestCase.assertTrue(contentValuesCollection.remove(expectedContentValues)); + TestCase.assertTrue(contentValuesCollection.remove(expectedContentValues)); checked = true; break; } } if (!checked) { final StringBuilder builder = new StringBuilder(); + builder.append("\n"); builder.append("Unexpected: "); builder.append(convertToEasilyReadableString(actualContentValues)); - builder.append("\nExpected: "); + builder.append("\n"); + builder.append("Expected : "); for (ContentValues expectedContentValues : contentValuesCollection) { builder.append(convertToEasilyReadableString(expectedContentValues)); } - mTestCase.fail(builder.toString()); + TestCase.fail(builder.toString()); } } else { - mTestCase.fail("Unexpected Uri has come: " + uri); + TestCase.fail("Unexpected Uri has come: " + uri); } } // for (int i = 0; i < size; i++) { return fakeResultArray; @@ -231,7 +200,7 @@ import java.util.Map.Entry; final String failMsg = "There is(are) remaining expected ContentValues instance(s): \n" + builder.toString(); - mTestCase.fail(failMsg); + TestCase.fail(failMsg); } } @@ -251,7 +220,7 @@ import java.util.Map.Entry; if (Data.MIMETYPE.equals(key)) { mimeTypeValue = valueString; } else { - mTestCase.assertNotNull(key); + TestCase.assertNotNull(key); sortedMap.put(key, valueString); } } @@ -272,7 +241,7 @@ import java.util.Map.Entry; } private static boolean equalsForContentValues( - ContentValues expected, ContentValues actual) { + final ContentValues expected, final ContentValues actual) { if (expected == actual) { return true; } else if (expected == null || actual == null || expected.size() != actual.size()) { @@ -285,15 +254,21 @@ import java.util.Map.Entry; if (!actual.containsKey(key)) { return false; } + // Type mismatch usuall happens as importer doesn't care the type of each value. + // For example, variable type might be Integer when importing the type of TEL, + // while variable type would be String when importing the type of RELATION. + final Object actualValue = actual.get(key); if (value instanceof byte[]) { - Object actualValue = actual.get(key); if (!Arrays.equals((byte[])value, (byte[])actualValue)) { + byte[] e = (byte[])value; + byte[] a = (byte[])actualValue; return false; } - } else if (!value.equals(actual.get(key))) { - return false; + } else if (!value.equals(actualValue) && + !value.toString().equals(actualValue.toString())) { + return false; } } return true; } -} +}
\ No newline at end of file diff --git a/core/tests/coretests/src/android/pim/vcard/test_utils/ImportTestResolver.java b/core/tests/coretests/src/android/pim/vcard/test_utils/ImportTestResolver.java new file mode 100644 index 000000000000..1822afefe71f --- /dev/null +++ b/core/tests/coretests/src/android/pim/vcard/test_utils/ImportTestResolver.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2009 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.pim.vcard.test_utils; + +import android.content.ContentProviderOperation; +import android.content.ContentProviderResult; +import android.content.ContentValues; +import android.provider.ContactsContract.RawContacts; +import android.test.AndroidTestCase; +import android.test.mock.MockContentResolver; + +import java.util.ArrayList; + +public class ImportTestResolver extends MockContentResolver { + private final ImportTestProvider mProvider; + + public ImportTestResolver(AndroidTestCase androidTestCase) { + mProvider = new ImportTestProvider(androidTestCase); + } + + @Override + public ContentProviderResult[] applyBatch(String authority, + ArrayList<ContentProviderOperation> operations) { + equalsString(authority, RawContacts.CONTENT_URI.toString()); + return mProvider.applyBatch(operations); + } + + public void addExpectedContentValues(ContentValues expectedContentValues) { + mProvider.addExpectedContentValues(expectedContentValues); + } + + public void verify() { + mProvider.verify(); + } + + private static boolean equalsString(String a, String b) { + if (a == null || a.length() == 0) { + return b == null || b.length() == 0; + } else { + return a.equals(b); + } + } +} diff --git a/core/tests/coretests/src/android/pim/vcard/LineVerifier.java b/core/tests/coretests/src/android/pim/vcard/test_utils/LineVerifier.java index cef15fd762c0..e314ac5798b1 100644 --- a/core/tests/coretests/src/android/pim/vcard/LineVerifier.java +++ b/core/tests/coretests/src/android/pim/vcard/test_utils/LineVerifier.java @@ -13,39 +13,40 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.pim.vcard; +package android.pim.vcard.test_utils; import android.content.Context; import android.pim.vcard.VCardComposer; +import android.test.AndroidTestCase; import junit.framework.TestCase; import java.util.ArrayList; -class LineVerifier implements VCardComposer.OneEntryHandler { - private final TestCase mTestCase; +public class LineVerifier implements VCardComposer.OneEntryHandler { + private final AndroidTestCase mAndroidTestCase; private final ArrayList<LineVerifierElem> mLineVerifierElemList; private int mVCardType; private int index; - public LineVerifier(TestCase testCase, int vcardType) { - mTestCase = testCase; + public LineVerifier(AndroidTestCase androidTestCase, int vcardType) { + mAndroidTestCase = androidTestCase; mLineVerifierElemList = new ArrayList<LineVerifierElem>(); mVCardType = vcardType; } public LineVerifierElem addLineVerifierElem() { - LineVerifierElem lineVerifier = new LineVerifierElem(mTestCase, mVCardType); + LineVerifierElem lineVerifier = new LineVerifierElem(mAndroidTestCase, mVCardType); mLineVerifierElemList.add(lineVerifier); return lineVerifier; } public void verify(String vcard) { if (index >= mLineVerifierElemList.size()) { - mTestCase.fail("Insufficient number of LineVerifier (" + index + ")"); + TestCase.fail("Insufficient number of LineVerifier (" + index + ")"); } - LineVerifierElem lineVerifier = mLineVerifierElemList.get(index); + final LineVerifierElem lineVerifier = mLineVerifierElemList.get(index); lineVerifier.verify(vcard); index++; diff --git a/core/tests/coretests/src/android/pim/vcard/LineVerifierElem.java b/core/tests/coretests/src/android/pim/vcard/test_utils/LineVerifierElem.java index b23b29be8a6a..e23a9cded618 100644 --- a/core/tests/coretests/src/android/pim/vcard/LineVerifierElem.java +++ b/core/tests/coretests/src/android/pim/vcard/test_utils/LineVerifierElem.java @@ -13,9 +13,10 @@ * License for the specific language governing permissions and limitations under * the License. */ -package android.pim.vcard; +package android.pim.vcard.test_utils; import android.pim.vcard.VCardConfig; +import android.test.AndroidTestCase; import android.text.TextUtils; import junit.framework.TestCase; @@ -23,14 +24,12 @@ import junit.framework.TestCase; import java.util.ArrayList; import java.util.List; -class LineVerifierElem { - private final TestCase mTestCase; +public class LineVerifierElem { private final List<String> mExpectedLineList = new ArrayList<String>(); - private final boolean mIsV30; + private final int mVCardType; - public LineVerifierElem(TestCase testCase, int vcardType) { - mTestCase = testCase; - mIsV30 = VCardConfig.isV30(vcardType); + public LineVerifierElem(AndroidTestCase androidTestCase, int vcardType) { + mVCardType = vcardType; } public LineVerifierElem addExpected(final String line) { @@ -55,21 +54,23 @@ class LineVerifierElem { if ("BEGIN:VCARD".equalsIgnoreCase(line)) { if (beginExists) { - mTestCase.fail("Multiple \"BEGIN:VCARD\" line found"); + TestCase.fail("Multiple \"BEGIN:VCARD\" line found"); } else { beginExists = true; continue; } } else if ("END:VCARD".equalsIgnoreCase(line)) { if (endExists) { - mTestCase.fail("Multiple \"END:VCARD\" line found"); + TestCase.fail("Multiple \"END:VCARD\" line found"); } else { endExists = true; continue; } - } else if ((mIsV30 ? "VERSION:3.0" : "VERSION:2.1").equalsIgnoreCase(line)) { + } else if ((VCardConfig.isVersion21(mVCardType) ? "VERSION:2.1" : + (VCardConfig.isVersion30(mVCardType) ? "VERSION:3.0" : + "VERSION:4.0")).equalsIgnoreCase(line)) { if (versionExists) { - mTestCase.fail("Multiple VERSION line + found"); + TestCase.fail("Multiple VERSION line + found"); } else { versionExists = true; continue; @@ -77,18 +78,16 @@ class LineVerifierElem { } if (!beginExists) { - mTestCase.fail("Property other than BEGIN came before BEGIN property: " - + line); + TestCase.fail("Property other than BEGIN came before BEGIN property: " + line); } else if (endExists) { - mTestCase.fail("Property other than END came after END property: " - + line); + TestCase.fail("Property other than END came after END property: " + line); } final int index = mExpectedLineList.indexOf(line); if (index >= 0) { mExpectedLineList.remove(index); } else { - mTestCase.fail("Unexpected line: " + line); + TestCase.fail("Unexpected line: " + line); } } @@ -99,7 +98,7 @@ class LineVerifierElem { buffer.append("\n"); } - mTestCase.fail("Expected line(s) not found:" + buffer.toString()); + TestCase.fail("Expected line(s) not found:" + buffer.toString()); } } } diff --git a/core/tests/coretests/src/android/pim/vcard/PropertyNode.java b/core/tests/coretests/src/android/pim/vcard/test_utils/PropertyNode.java index 2c1f6d28de98..de7ad8eaa64e 100644 --- a/core/tests/coretests/src/android/pim/vcard/PropertyNode.java +++ b/core/tests/coretests/src/android/pim/vcard/test_utils/PropertyNode.java @@ -13,11 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.pim.vcard; +package android.pim.vcard.test_utils; import android.content.ContentValues; import android.pim.vcard.VCardEntry; -import android.util.Log; import java.util.ArrayList; import java.util.Arrays; @@ -26,12 +25,18 @@ import java.util.List; import java.util.Set; /** + * <p> + * The class representing one property (e.g. "N;ENCODING=UTF-8:family:given:middle:prefix:suffix"). + * </p> + * <p> * Previously used in main vCard handling code but now exists only for testing. - * + * </p> + * <p> * Especially useful for testing parser code (VCardParser), since all properties can be * checked via this class unlike {@link VCardEntry}, which only emits the result of * interpretation of the content of each vCard. We cannot know whether vCard parser or - * ContactStruct is wrong withouth this class. + * {@link VCardEntry} is wrong without this class. + * </p> */ public class PropertyNode { public String propName; @@ -43,7 +48,8 @@ public class PropertyNode { */ public byte[] propValue_bytes; - /** param store: key=paramType, value=paramValue + /** + * param store: key=paramType, value=paramValue * Note that currently PropertyNode class does not support multiple param-values * defined in vCard 3.0 (See also RFC 2426). multiple-values are stored as * one String value like "A,B", not ["A", "B"]... diff --git a/core/tests/coretests/src/android/pim/vcard/test_utils/PropertyNodesVerifier.java b/core/tests/coretests/src/android/pim/vcard/test_utils/PropertyNodesVerifier.java new file mode 100644 index 000000000000..422928494594 --- /dev/null +++ b/core/tests/coretests/src/android/pim/vcard/test_utils/PropertyNodesVerifier.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2009 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.pim.vcard.test_utils; + +import android.pim.vcard.VCardParser; +import android.pim.vcard.VCardUtils; +import android.pim.vcard.exception.VCardException; +import android.test.AndroidTestCase; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +public class PropertyNodesVerifier extends VNodeBuilder { + private final List<PropertyNodesVerifierElem> mPropertyNodesVerifierElemList; + private final AndroidTestCase mAndroidTestCase; + private int mIndex; + + public PropertyNodesVerifier(AndroidTestCase testCase) { + super(); + mPropertyNodesVerifierElemList = new ArrayList<PropertyNodesVerifierElem>(); + mAndroidTestCase = testCase; + } + + public PropertyNodesVerifierElem addPropertyNodesVerifierElem() { + PropertyNodesVerifierElem elem = new PropertyNodesVerifierElem(mAndroidTestCase); + mPropertyNodesVerifierElemList.add(elem); + return elem; + } + + public void verify(int resId, int vcardType) throws IOException, VCardException { + verify(mAndroidTestCase.getContext().getResources().openRawResource(resId), vcardType); + } + + public void verify(int resId, int vcardType, final VCardParser parser) + throws IOException, VCardException { + verify(mAndroidTestCase.getContext().getResources().openRawResource(resId), + vcardType, parser); + } + + public void verify(InputStream is, int vcardType) throws IOException, VCardException { + final VCardParser parser = VCardUtils.getAppropriateParser(vcardType); + verify(is, vcardType, parser); + } + + public void verify(InputStream is, int vcardType, final VCardParser parser) + throws IOException, VCardException { + try { + parser.parse(is, this); + } finally { + if (is != null) { + try { + is.close(); + } catch (IOException e) { + } + } + } + } + + @Override + public void endEntry() { + super.endEntry(); + AndroidTestCase.assertTrue(mIndex < mPropertyNodesVerifierElemList.size()); + AndroidTestCase.assertTrue(mIndex < vNodeList.size()); + mPropertyNodesVerifierElemList.get(mIndex).verify(vNodeList.get(mIndex)); + mIndex++; + } +} diff --git a/core/tests/coretests/src/android/pim/vcard/PropertyNodesVerifier.java b/core/tests/coretests/src/android/pim/vcard/test_utils/PropertyNodesVerifierElem.java index cfdd0743fa5c..44c6abc9d0ae 100644 --- a/core/tests/coretests/src/android/pim/vcard/PropertyNodesVerifier.java +++ b/core/tests/coretests/src/android/pim/vcard/test_utils/PropertyNodesVerifierElem.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009 The Android Open Source Project + * 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. @@ -13,87 +13,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.pim.vcard; +package android.pim.vcard.test_utils; import android.content.ContentValues; -import android.pim.vcard.VCardConfig; -import android.pim.vcard.VCardParser; -import android.pim.vcard.VCardParser_V21; -import android.pim.vcard.VCardParser_V30; -import android.pim.vcard.exception.VCardException; import android.test.AndroidTestCase; import junit.framework.TestCase; -import java.io.IOException; -import java.io.InputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; -/* package */ class PropertyNodesVerifier extends VNodeBuilder { - private final List<PropertyNodesVerifierElem> mPropertyNodesVerifierElemList; - private final AndroidTestCase mAndroidTestCase; - private int mIndex; - - public PropertyNodesVerifier(AndroidTestCase testCase) { - mPropertyNodesVerifierElemList = new ArrayList<PropertyNodesVerifierElem>(); - mAndroidTestCase = testCase; - } - - public PropertyNodesVerifierElem addPropertyNodesVerifierElem() { - PropertyNodesVerifierElem elem = new PropertyNodesVerifierElem(mAndroidTestCase); - mPropertyNodesVerifierElemList.add(elem); - return elem; - } - - public void verify(int resId, int vCardType) - throws IOException, VCardException { - verify(mAndroidTestCase.getContext().getResources().openRawResource(resId), vCardType); - } - - public void verify(int resId, int vCardType, final VCardParser vCardParser) - throws IOException, VCardException { - verify(mAndroidTestCase.getContext().getResources().openRawResource(resId), - vCardType, vCardParser); - } - - public void verify(InputStream is, int vCardType) throws IOException, VCardException { - final VCardParser vCardParser; - if (VCardConfig.isV30(vCardType)) { - vCardParser = new VCardParser_V30(true); // Use StrictParsing. - } else { - vCardParser = new VCardParser_V21(); - } - verify(is, vCardType, vCardParser); - } - - public void verify(InputStream is, int vCardType, final VCardParser vCardParser) - throws IOException, VCardException { - try { - vCardParser.parse(is, this); - } finally { - if (is != null) { - try { - is.close(); - } catch (IOException e) { - } - } - } - } - - @Override - public void endEntry() { - super.endEntry(); - mAndroidTestCase.assertTrue(mIndex < mPropertyNodesVerifierElemList.size()); - mAndroidTestCase.assertTrue(mIndex < vNodeList.size()); - mPropertyNodesVerifierElemList.get(mIndex).verify(vNodeList.get(mIndex)); - mIndex++; - } -} - /** * Utility class which verifies input VNode. * @@ -102,7 +34,7 @@ import java.util.List; * If the node does not exist in the "ordered list", the class refers to * "unorderd expected property set" and checks the node is expected somewhere. */ -/* package */ class PropertyNodesVerifierElem { +public class PropertyNodesVerifierElem { public static class TypeSet extends HashSet<String> { public TypeSet(String ... array) { super(Arrays.asList(array)); @@ -119,12 +51,10 @@ import java.util.List; // Intentionally use ArrayList instead of Set, assuming there may be more than one // exactly same objects. private final ArrayList<PropertyNode> mUnorderedNodeList; - private final TestCase mTestCase; - public PropertyNodesVerifierElem(TestCase testCase) { + public PropertyNodesVerifierElem(AndroidTestCase androidTestCase) { mOrderedNodeMap = new HashMap<String, List<PropertyNode>>(); mUnorderedNodeList = new ArrayList<PropertyNode>(); - mTestCase = testCase; } // WithOrder @@ -170,6 +100,12 @@ import java.util.List; paramMap_TYPE, null); } + public PropertyNodesVerifierElem addExpectedNodeWithOrder(String propName, + List<String> propValueList, ContentValues paramMap, TypeSet paramMap_TYPE) { + return addExpectedNodeWithOrder(propName, null, propValueList, null, paramMap, + paramMap_TYPE, null); + } + public PropertyNodesVerifierElem addExpectedNodeWithOrder(String propName, String propValue, ContentValues paramMap, TypeSet paramMap_TYPE) { return addExpectedNodeWithOrder(propName, propValue, null, null, @@ -183,12 +119,18 @@ import java.util.List; } public PropertyNodesVerifierElem addExpectedNodeWithOrder(String propName, String propValue, + List<String> propValueList, ContentValues paramMap) { + return addExpectedNodeWithOrder(propName, propValue, propValueList, null, paramMap, + null, null); + } + + public PropertyNodesVerifierElem addExpectedNodeWithOrder(String propName, String propValue, List<String> propValueList, byte[] propValue_bytes, ContentValues paramMap, TypeSet paramMap_TYPE, GroupSet propGroupSet) { if (propValue == null && propValueList != null) { propValue = concatinateListWithSemiColon(propValueList); } - PropertyNode propertyNode = new PropertyNode(propName, + final PropertyNode propertyNode = new PropertyNode(propName, propValue, propValueList, propValue_bytes, paramMap, paramMap_TYPE, propGroupSet); List<PropertyNode> expectedNodeList = mOrderedNodeMap.get(propName); @@ -267,8 +209,9 @@ import java.util.List; for (PropertyNode actualNode : vnode.propList) { verifyNode(actualNode.propName, actualNode); } + if (!mOrderedNodeMap.isEmpty() || !mUnorderedNodeList.isEmpty()) { - List<String> expectedProps = new ArrayList<String>(); + final List<String> expectedProps = new ArrayList<String>(); for (List<PropertyNode> nodes : mOrderedNodeMap.values()) { for (PropertyNode node : nodes) { if (!expectedProps.contains(node.propName)) { @@ -281,18 +224,19 @@ import java.util.List; expectedProps.add(node.propName); } } - mTestCase.fail("Expected property " + Arrays.toString(expectedProps.toArray()) + TestCase.fail("Expected property " + Arrays.toString(expectedProps.toArray()) + " was not found."); } } private void verifyNode(final String propName, final PropertyNode actualNode) { - List<PropertyNode> expectedNodeList = mOrderedNodeMap.get(propName); + final List<PropertyNode> expectedNodeList = mOrderedNodeMap.get(propName); final int size = (expectedNodeList != null ? expectedNodeList.size() : 0); if (size > 0) { for (int i = 0; i < size; i++) { - PropertyNode expectedNode = expectedNodeList.get(i); - List<PropertyNode> expectedButDifferentValueList = new ArrayList<PropertyNode>(); + final PropertyNode expectedNode = expectedNodeList.get(i); + final List<PropertyNode> expectedButDifferentValueList = + new ArrayList<PropertyNode>(); if (expectedNode.propName.equals(propName)) { if (expectedNode.equals(actualNode)) { expectedNodeList.remove(i); @@ -318,7 +262,7 @@ import java.util.List; expectedButDifferentValueList); } else { // There's no expected node with same propName. - mTestCase.fail("Unexpected property \"" + propName + "\" exists."); + TestCase.fail("Unexpected property \"" + propName + "\" exists."); } } } else { @@ -333,7 +277,7 @@ import java.util.List; expectedButDifferentValueList); } else { // There's no expected node with same propName. - mTestCase.fail("Unexpected property \"" + propName + "\" exists."); + TestCase.fail("Unexpected property \"" + propName + "\" exists."); } } } @@ -379,7 +323,7 @@ import java.util.List; builder.append(expectedNode.toString()); builder.append("\n"); } - mTestCase.fail("Property \"" + propName + "\" has wrong value.\n" + TestCase.fail("Property \"" + propName + "\" has wrong value.\n" + builder.toString() + " actual: " + actualNode.toString()); } diff --git a/core/tests/coretests/src/android/pim/vcard/test_utils/VCardTestsBase.java b/core/tests/coretests/src/android/pim/vcard/test_utils/VCardTestsBase.java new file mode 100644 index 000000000000..e7413ab29534 --- /dev/null +++ b/core/tests/coretests/src/android/pim/vcard/test_utils/VCardTestsBase.java @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2009 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.pim.vcard.test_utils; + +import android.content.ContentValues; +import android.pim.vcard.VCardConfig; +import android.test.AndroidTestCase; + +/** + * BaseClass for vCard unit tests with utility classes. + * Please do not add each unit test here. + */ +public class VCardTestsBase extends AndroidTestCase { + public static final int V21 = VCardConfig.VCARD_TYPE_V21_GENERIC; + public static final int V30 = VCardConfig.VCARD_TYPE_V30_GENERIC; + public static final int V40 = VCardConfig.VCARD_TYPE_V40_GENERIC; + + // Do not modify these during tests. + protected final ContentValues mContentValuesForQP; + protected final ContentValues mContentValuesForSJis; + protected final ContentValues mContentValuesForUtf8; + protected final ContentValues mContentValuesForQPAndSJis; + protected final ContentValues mContentValuesForQPAndUtf8; + protected final ContentValues mContentValuesForBase64V21; + protected final ContentValues mContentValuesForBase64V30; + + protected VCardVerifier mVerifier; + private boolean mSkipVerification; + + public VCardTestsBase() { + super(); + // Not using constants in vCard code since it may be wrong. + mContentValuesForQP = new ContentValues(); + mContentValuesForQP.put("ENCODING", "QUOTED-PRINTABLE"); + mContentValuesForSJis = new ContentValues(); + mContentValuesForSJis.put("CHARSET", "SHIFT_JIS"); + mContentValuesForUtf8 = new ContentValues(); + mContentValuesForUtf8.put("CHARSET", "UTF-8"); + mContentValuesForQPAndSJis = new ContentValues(); + mContentValuesForQPAndSJis.put("ENCODING", "QUOTED-PRINTABLE"); + mContentValuesForQPAndSJis.put("CHARSET", "SHIFT_JIS"); + mContentValuesForQPAndUtf8 = new ContentValues(); + mContentValuesForQPAndUtf8.put("ENCODING", "QUOTED-PRINTABLE"); + mContentValuesForQPAndUtf8.put("CHARSET", "UTF-8"); + mContentValuesForBase64V21 = new ContentValues(); + mContentValuesForBase64V21.put("ENCODING", "BASE64"); + mContentValuesForBase64V30 = new ContentValues(); + mContentValuesForBase64V30.put("ENCODING", "b"); + } + + @Override + public void testAndroidTestCaseSetupProperly() { + super.testAndroidTestCaseSetupProperly(); + mSkipVerification = true; + } + + @Override + public void setUp() throws Exception{ + super.setUp(); + mVerifier = new VCardVerifier(this); + mSkipVerification = false; + } + + @Override + public void tearDown() throws Exception { + super.tearDown(); + if (!mSkipVerification) { + mVerifier.verify(); + } + } +} diff --git a/core/tests/coretests/src/android/pim/vcard/test_utils/VCardVerifier.java b/core/tests/coretests/src/android/pim/vcard/test_utils/VCardVerifier.java new file mode 100644 index 000000000000..7379a5b947e0 --- /dev/null +++ b/core/tests/coretests/src/android/pim/vcard/test_utils/VCardVerifier.java @@ -0,0 +1,370 @@ +/* + * Copyright (C) 2009 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.pim.vcard.test_utils; + +import android.content.ContentResolver; +import android.content.Context; +import android.content.EntityIterator; +import android.net.Uri; +import android.pim.vcard.VCardComposer; +import android.pim.vcard.VCardConfig; +import android.pim.vcard.VCardEntryConstructor; +import android.pim.vcard.VCardInterpreter; +import android.pim.vcard.VCardInterpreterCollection; +import android.pim.vcard.VCardParser; +import android.pim.vcard.VCardUtils; +import android.pim.vcard.exception.VCardException; +import android.test.AndroidTestCase; +import android.test.mock.MockContext; +import android.text.TextUtils; +import android.util.Log; + +import junit.framework.TestCase; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Method; +import java.util.Arrays; + +/** + * <p> + * The class lets users checks that given expected vCard data are same as given actual vCard data. + * Able to verify both vCard importer/exporter. + * </p> + * <p> + * First a user has to initialize the object by calling either + * {@link #initForImportTest(int, int)} or {@link #initForExportTest(int)}. + * "Round trip test" (import -> export -> import, or export -> import -> export) is not supported. + * </p> + */ +public class VCardVerifier { + private static final String LOG_TAG = "VCardVerifier"; + + private static class CustomMockContext extends MockContext { + final ContentResolver mResolver; + public CustomMockContext(ContentResolver resolver) { + mResolver = resolver; + } + + @Override + public ContentResolver getContentResolver() { + return mResolver; + } + } + + private class VCardVerifierInternal implements VCardComposer.OneEntryHandler { + public boolean onInit(Context context) { + return true; + } + public boolean onEntryCreated(String vcard) { + verifyOneVCardForExport(vcard); + return true; + } + public void onTerminate() { + } + } + + private final AndroidTestCase mAndroidTestCase; + private final VCardVerifierInternal mVCardVerifierInternal; + private int mVCardType; + private boolean mIsDoCoMo; + + // Only one of them must be non-empty. + private ExportTestResolver mExportTestResolver; + private InputStream mInputStream; + + // To allow duplication, use list instead of set. + // When null, we don't need to do the verification. + private PropertyNodesVerifier mPropertyNodesVerifier; + private LineVerifier mLineVerifier; + private ContentValuesVerifier mContentValuesVerifier; + private boolean mInitialized; + private boolean mVerified = false; + private String mCharset; + + // Called by VCardTestsBase + public VCardVerifier(AndroidTestCase androidTestCase) { + mAndroidTestCase = androidTestCase; + mVCardVerifierInternal = new VCardVerifierInternal(); + mExportTestResolver = null; + mInputStream = null; + mInitialized = false; + mVerified = false; + } + + // Should be called at the beginning of each import test. + public void initForImportTest(int vcardType, int resId) { + if (mInitialized) { + AndroidTestCase.fail("Already initialized"); + } + mVCardType = vcardType; + mIsDoCoMo = VCardConfig.isDoCoMo(vcardType); + setInputResourceId(resId); + mInitialized = true; + } + + // Should be called at the beginning of each export test. + public void initForExportTest(int vcardType) { + initForExportTest(vcardType, "UTF-8"); + } + + public void initForExportTest(int vcardType, String charset) { + if (mInitialized) { + AndroidTestCase.fail("Already initialized"); + } + mExportTestResolver = new ExportTestResolver(mAndroidTestCase); + mVCardType = vcardType; + mIsDoCoMo = VCardConfig.isDoCoMo(vcardType); + mInitialized = true; + if (TextUtils.isEmpty(charset)) { + mCharset = "UTF-8"; + } else { + mCharset = charset; + } + } + + private void setInputResourceId(int resId) { + InputStream inputStream = mAndroidTestCase.getContext().getResources().openRawResource(resId); + if (inputStream == null) { + AndroidTestCase.fail("Wrong resId: " + resId); + } + setInputStream(inputStream); + } + + private void setInputStream(InputStream inputStream) { + if (mExportTestResolver != null) { + AndroidTestCase.fail("addInputEntry() is called."); + } else if (mInputStream != null) { + AndroidTestCase.fail("InputStream is already set"); + } + mInputStream = inputStream; + } + + public ContactEntry addInputEntry() { + if (!mInitialized) { + AndroidTestCase.fail("Not initialized"); + } + if (mInputStream != null) { + AndroidTestCase.fail("setInputStream is called"); + } + return mExportTestResolver.addInputContactEntry(); + } + + public PropertyNodesVerifierElem addPropertyNodesVerifierElemWithoutVersion() { + if (!mInitialized) { + AndroidTestCase.fail("Not initialized"); + } + if (mPropertyNodesVerifier == null) { + mPropertyNodesVerifier = new PropertyNodesVerifier(mAndroidTestCase); + } + return mPropertyNodesVerifier.addPropertyNodesVerifierElem(); + } + + public PropertyNodesVerifierElem addPropertyNodesVerifierElem() { + final PropertyNodesVerifierElem elem = addPropertyNodesVerifierElemWithoutVersion(); + final String versionString; + if (VCardConfig.isVersion21(mVCardType)) { + versionString = "2.1"; + } else if (VCardConfig.isVersion30(mVCardType)) { + versionString = "3.0"; + } else if (VCardConfig.isVersion40(mVCardType)) { + versionString = "4.0"; + } else { + throw new RuntimeException("Unexpected vcard type during a unit test"); + } + elem.addExpectedNodeWithOrder("VERSION", versionString); + + return elem; + } + + public PropertyNodesVerifierElem addPropertyNodesVerifierElemWithEmptyName() { + if (!mInitialized) { + AndroidTestCase.fail("Not initialized"); + } + final PropertyNodesVerifierElem elem = addPropertyNodesVerifierElem(); + if (VCardConfig.isVersion40(mVCardType)) { + elem.addExpectedNodeWithOrder("FN", ""); + } else if (VCardConfig.isVersion30(mVCardType)) { + elem.addExpectedNodeWithOrder("N", ""); + elem.addExpectedNodeWithOrder("FN", ""); + } else if (mIsDoCoMo) { + elem.addExpectedNodeWithOrder("N", ""); + } + return elem; + } + + public LineVerifierElem addLineVerifierElem() { + if (!mInitialized) { + AndroidTestCase.fail("Not initialized"); + } + if (mLineVerifier == null) { + mLineVerifier = new LineVerifier(mAndroidTestCase, mVCardType); + } + return mLineVerifier.addLineVerifierElem(); + } + + public ContentValuesVerifierElem addContentValuesVerifierElem() { + if (!mInitialized) { + AndroidTestCase.fail("Not initialized"); + } + if (mContentValuesVerifier == null) { + mContentValuesVerifier = new ContentValuesVerifier(); + } + + return mContentValuesVerifier.addElem(mAndroidTestCase); + } + + /** + * Sets up sub-verifiers correctly and try parse given vCard as InputStream. + * Errors around InputStream must be handled outside this method. + * + * Used both from {@link #verifyForImportTest()} and from {@link #verifyForExportTest()}. + */ + private void verifyWithInputStream(InputStream is) throws IOException { + final VCardInterpreter interpreter; + if (mContentValuesVerifier != null) { + final VCardEntryConstructor constructor = new VCardEntryConstructor(mVCardType); + constructor.addEntryHandler(mContentValuesVerifier); + if (mPropertyNodesVerifier != null) { + interpreter = new VCardInterpreterCollection(Arrays.asList( + mPropertyNodesVerifier, constructor)); + } else { + interpreter = constructor; + } + } else { + if (mPropertyNodesVerifier != null) { + interpreter = mPropertyNodesVerifier; + } else { + interpreter = null; + } + } + + try { + // Note: we must not specify charset toward vCard parsers. This code checks whether + // those parsers are able to encode given binary without any extra information for + // charset. + final VCardParser parser = VCardUtils.getAppropriateParser(mVCardType); + parser.parse(is, interpreter); + } catch (VCardException e) { + AndroidTestCase.fail("Unexpected VCardException: " + e.getMessage()); + } + } + + private void verifyOneVCardForExport(final String vcard) { + Log.d(LOG_TAG, vcard); + InputStream is = null; + try { + is = new ByteArrayInputStream(vcard.getBytes(mCharset)); + verifyWithInputStream(is); + } catch (IOException e) { + AndroidTestCase.fail("Unexpected IOException: " + e.getMessage()); + } finally { + if (is != null) { + try { + is.close(); + } catch (IOException e) { + AndroidTestCase.fail("Unexpected IOException: " + e.getMessage()); + } + } + } + } + + public void verify() { + if (!mInitialized) { + TestCase.fail("Not initialized."); + } + if (mVerified) { + TestCase.fail("verify() was called twice."); + } + + if (mInputStream != null) { + if (mExportTestResolver != null){ + TestCase.fail("There are two input sources."); + } + verifyForImportTest(); + } else if (mExportTestResolver != null){ + verifyForExportTest(); + } else { + TestCase.fail("No input is determined"); + } + mVerified = true; + } + + private void verifyForImportTest() { + if (mLineVerifier != null) { + AndroidTestCase.fail("Not supported now."); + } + + try { + verifyWithInputStream(mInputStream); + } catch (IOException e) { + AndroidTestCase.fail("IOException was thrown: " + e.getMessage()); + } finally { + if (mInputStream != null) { + try { + mInputStream.close(); + } catch (IOException e) { + } + } + } + } + + public static EntityIterator mockGetEntityIteratorMethod( + final ContentResolver resolver, + final Uri uri, final String selection, + final String[] selectionArgs, final String sortOrder) { + if (ExportTestResolver.class.equals(resolver.getClass())) { + return ((ExportTestResolver)resolver).getProvider().queryEntities( + uri, selection, selectionArgs, sortOrder); + } + + Log.e(LOG_TAG, "Unexpected provider given."); + return null; + } + + private Method getMockGetEntityIteratorMethod() + throws SecurityException, NoSuchMethodException { + return this.getClass().getMethod("mockGetEntityIteratorMethod", + ContentResolver.class, Uri.class, String.class, String[].class, String.class); + } + + private void verifyForExportTest() { + final VCardComposer composer = + new VCardComposer(new CustomMockContext(mExportTestResolver), mVCardType, mCharset); + composer.addHandler(mLineVerifier); + composer.addHandler(mVCardVerifierInternal); + if (!composer.init(VCardComposer.CONTACTS_TEST_CONTENT_URI, null, null, null)) { + AndroidTestCase.fail("init() failed. Reason: " + composer.getErrorReason()); + } + AndroidTestCase.assertFalse(composer.isAfterLast()); + try { + while (!composer.isAfterLast()) { + try { + final Method mockGetEntityIteratorMethod = getMockGetEntityIteratorMethod(); + AndroidTestCase.assertNotNull(mockGetEntityIteratorMethod); + AndroidTestCase.assertTrue( + composer.createOneEntry(mockGetEntityIteratorMethod)); + } catch (Exception e) { + e.printStackTrace(); + AndroidTestCase.fail(e.toString()); + } + } + } finally { + composer.terminate(); + } + } +} diff --git a/core/tests/coretests/src/android/pim/vcard/VNode.java b/core/tests/coretests/src/android/pim/vcard/test_utils/VNode.java index 79f10dc92e19..b890e2c2be60 100644 --- a/core/tests/coretests/src/android/pim/vcard/VNode.java +++ b/core/tests/coretests/src/android/pim/vcard/test_utils/VNode.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.pim.vcard; +package android.pim.vcard.test_utils; import java.util.ArrayList; diff --git a/core/tests/coretests/src/android/pim/vcard/VNodeBuilder.java b/core/tests/coretests/src/android/pim/vcard/test_utils/VNodeBuilder.java index 0e6c3259b5fa..883703407352 100644 --- a/core/tests/coretests/src/android/pim/vcard/VNodeBuilder.java +++ b/core/tests/coretests/src/android/pim/vcard/test_utils/VNodeBuilder.java @@ -13,18 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package android.pim.vcard; +package android.pim.vcard.test_utils; import android.content.ContentValues; -import android.pim.vcard.VCardInterpreter; import android.pim.vcard.VCardConfig; +import android.pim.vcard.VCardInterpreter; +import android.pim.vcard.VCardUtils; +import android.util.Base64; import android.util.CharsetUtils; import android.util.Log; -import org.apache.commons.codec.DecoderException; -import org.apache.commons.codec.binary.Base64; -import org.apache.commons.codec.net.QuotedPrintableCodec; - import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; import java.nio.charset.Charset; @@ -32,34 +30,29 @@ import java.util.ArrayList; import java.util.List; /** - * Store the parse result to custom datastruct: VNode, PropertyNode + * <p> + * The class storing the parse result to custom datastruct: + * {@link VNode}, and {@link PropertyNode}. * Maybe several vcard instance, so use vNodeList to store. - * VNode: standy by a vcard instance. - * PropertyNode: standy by a property line of a card. - * - * Previously used in main vCard handling code but now exists only for testing. + * </p> + * <p> + * This is called VNode, not VCardNode, since it was used for expressing vCalendar (iCal). + * </p> */ public class VNodeBuilder implements VCardInterpreter { static private String LOG_TAG = "VNodeBuilder"; - /** - * If there's no other information available, this class uses this charset for encoding - * byte arrays. - */ - static public String TARGET_CHARSET = "UTF-8"; - - /** type=VNode */ public List<VNode> vNodeList = new ArrayList<VNode>(); private int mNodeListPos = 0; private VNode mCurrentVNode; private PropertyNode mCurrentPropNode; private String mCurrentParamType; - + /** * The charset using which VParser parses the text. */ private String mSourceCharset; - + /** * The charset with which byte array is encoded to String. */ @@ -68,27 +61,15 @@ public class VNodeBuilder implements VCardInterpreter { private boolean mStrictLineBreakParsing; public VNodeBuilder() { - this(VCardConfig.DEFAULT_CHARSET, TARGET_CHARSET, false); + this(VCardConfig.DEFAULT_IMPORT_CHARSET, false); } - public VNodeBuilder(String charset, boolean strictLineBreakParsing) { - this(null, charset, strictLineBreakParsing); - } - - /** - * @hide sourceCharset is temporal. - */ - public VNodeBuilder(String sourceCharset, String targetCharset, - boolean strictLineBreakParsing) { - if (sourceCharset != null) { - mSourceCharset = sourceCharset; - } else { - mSourceCharset = VCardConfig.DEFAULT_CHARSET; - } + public VNodeBuilder(String targetCharset, boolean strictLineBreakParsing) { + mSourceCharset = VCardConfig.DEFAULT_INTERMEDIATE_CHARSET; if (targetCharset != null) { mTargetCharset = targetCharset; } else { - mTargetCharset = TARGET_CHARSET; + mTargetCharset = VCardConfig.DEFAULT_IMPORT_CHARSET; } mStrictLineBreakParsing = strictLineBreakParsing; } @@ -149,7 +130,6 @@ public class VNodeBuilder implements VCardInterpreter { mCurrentPropNode.propName = name; } - // Used only in VCard. public void propertyGroup(String group) { mCurrentPropNode.propGroupSet.add(group); } @@ -159,6 +139,12 @@ public class VNodeBuilder implements VCardInterpreter { } public void propertyParamValue(String value) { + if (!VCardUtils.containsOnlyAlphaDigitHyphen(value)) { + value = VCardUtils.convertStringCharset(value, + VCardConfig.DEFAULT_INTERMEDIATE_CHARSET, + VCardConfig.DEFAULT_IMPORT_CHARSET); + } + if (mCurrentParamType == null || mCurrentParamType.equalsIgnoreCase("TYPE")) { mCurrentPropNode.paramMap_TYPE.add(value); @@ -192,71 +178,11 @@ public class VNodeBuilder implements VCardInterpreter { encoding = encoding.toUpperCase(); if (encoding.equals("BASE64") || encoding.equals("B")) { // Assume BASE64 is used only when the number of values is 1. - mCurrentPropNode.propValue_bytes = - Base64.decodeBase64(value.getBytes()); + mCurrentPropNode.propValue_bytes = Base64.decode(value.getBytes(), Base64.NO_WRAP); return value; } else if (encoding.equals("QUOTED-PRINTABLE")) { - String quotedPrintable = value - .replaceAll("= ", " ").replaceAll("=\t", "\t"); - String[] lines; - if (mStrictLineBreakParsing) { - lines = quotedPrintable.split("\r\n"); - } else { - StringBuilder builder = new StringBuilder(); - int length = quotedPrintable.length(); - ArrayList<String> list = new ArrayList<String>(); - for (int i = 0; i < length; i++) { - char ch = quotedPrintable.charAt(i); - if (ch == '\n') { - list.add(builder.toString()); - builder = new StringBuilder(); - } else if (ch == '\r') { - list.add(builder.toString()); - builder = new StringBuilder(); - if (i < length - 1) { - char nextCh = quotedPrintable.charAt(i + 1); - if (nextCh == '\n') { - i++; - } - } - } else { - builder.append(ch); - } - } - String finalLine = builder.toString(); - if (finalLine.length() > 0) { - list.add(finalLine); - } - lines = list.toArray(new String[0]); - } - StringBuilder builder = new StringBuilder(); - for (String line : lines) { - if (line.endsWith("=")) { - line = line.substring(0, line.length() - 1); - } - builder.append(line); - } - byte[] bytes; - try { - bytes = builder.toString().getBytes(mSourceCharset); - } catch (UnsupportedEncodingException e1) { - Log.e(LOG_TAG, "Failed to encode: charset=" + mSourceCharset); - bytes = builder.toString().getBytes(); - } - - try { - bytes = QuotedPrintableCodec.decodeQuotedPrintable(bytes); - } catch (DecoderException e) { - Log.e(LOG_TAG, "Failed to decode quoted-printable: " + e); - return ""; - } - - try { - return new String(bytes, targetCharset); - } catch (UnsupportedEncodingException e) { - Log.e(LOG_TAG, "Failed to encode: charset=" + targetCharset); - return new String(bytes); - } + return VCardUtils.parseQuotedPrintable( + value, mStrictLineBreakParsing, mSourceCharset, targetCharset); } // Unknown encoding. Fall back to default. } @@ -309,6 +235,6 @@ public class VNodeBuilder implements VCardInterpreter { } public String getResult(){ - return null; + throw new RuntimeException("Not supported"); } } diff --git a/core/tests/coretests/src/android/text/HtmlTest.java b/core/tests/coretests/src/android/text/HtmlTest.java index c07d212bedc3..b2298f7bfa83 100644 --- a/core/tests/coretests/src/android/text/HtmlTest.java +++ b/core/tests/coretests/src/android/text/HtmlTest.java @@ -19,7 +19,6 @@ package android.text; import android.content.res.ColorStateList; import android.content.res.Resources; import android.graphics.Typeface; -import android.test.suitebuilder.annotation.MediumTest; import android.test.suitebuilder.annotation.SmallTest; import android.text.style.ForegroundColorSpan; import android.text.style.QuoteSpan; @@ -36,7 +35,7 @@ import junit.framework.TestCase; public class HtmlTest extends TestCase { - @MediumTest + @SmallTest public void testSingleTagOnWhileString() { Spanned spanned = Html.fromHtml("<b>hello</b>"); Object[] spans = spanned.getSpans(-1, 100, Object.class); @@ -46,7 +45,7 @@ public class HtmlTest extends TestCase { assertEquals(5, spanned.getSpanEnd(span)); } - @MediumTest + @SmallTest public void testEmptyFontTag() { Spanned spanned = Html.fromHtml("Hello <font color=\"#ff00ff00\"></font>"); Object[] spans = spanned.getSpans(0, 100, Object.class); @@ -54,7 +53,7 @@ public class HtmlTest extends TestCase { } /** Tests that the parser can handle mal-formed HTML. */ - @MediumTest + @SmallTest public void testBadHtml() { Spanned spanned = Html.fromHtml("Hello <b>b<i>bi</b>i</i>"); Object[] spans = spanned.getSpans(0, 100, Object.class); @@ -69,13 +68,13 @@ public class HtmlTest extends TestCase { assertEquals(10, spanned.getSpanEnd(spans[2])); } - @MediumTest + @SmallTest public void testSymbols() { String spanned = Html.fromHtml("© > <").toString(); assertEquals("\u00a9 > <", spanned); } - @MediumTest + @SmallTest public void testColor() throws Exception { Spanned s; ForegroundColorSpan[] colors; @@ -95,7 +94,7 @@ public class HtmlTest extends TestCase { assertEquals(0, colors.length); } - @MediumTest + @SmallTest public void testResourceColor() throws Exception { ColorStateList c = Resources.getSystem().getColorStateList(android.R.color.primary_text_dark); diff --git a/core/tests/coretests/src/android/text/PackedIntVectorTest.java b/core/tests/coretests/src/android/text/PackedIntVectorTest.java new file mode 100644 index 000000000000..1dc683e87937 --- /dev/null +++ b/core/tests/coretests/src/android/text/PackedIntVectorTest.java @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2007 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.text; + +import junit.framework.TestCase; + +/** + * PackedIntVectorTest tests the features of android.util.PackedIntVector. + */ +public class PackedIntVectorTest extends TestCase { + + public void testBasic() throws Exception { + for (int width = 0; width < 10; width++) { + PackedIntVector p = new PackedIntVector(width); + int[] ins = new int[width]; + + for (int height = width * 2; height < width * 4; height++) { + assertEquals(p.width(), width); + + // Test adding rows. + + for (int i = 0; i < height; i++) { + int at; + + if (i % 2 == 0) { + at = i; + } else { + at = p.size() - i; + } + + for (int j = 0; j < width; j++) { + ins[j] = i + j; + } + + if (i == height / 2) { + p.insertAt(at, null); + } else { + p.insertAt(at, ins); + } + + assertEquals(p.size(), i + 1); + + for (int j = 0; j < width; j++) { + if (i == height / 2) { + assertEquals(0, p.getValue(at, j)); + } else { + assertEquals(p.getValue(at, j), i + j); + } + } + } + + // Test setting values. + + for (int i = 0; i < height; i++) { + for (int j = 0; j < width; j++) { + p.setValue(i, j, i * j); + + assertEquals(p.getValue(i, j), i * j); + } + } + + // Test offsetting values. + + for (int j = 0; j < width; j++) { + p.adjustValuesBelow(j * 2, j, j + 27); + } + + for (int i = 0; i < height; i++) { + for (int j = 0; j < width; j++) { + int expect = i * j; + + if (i >= j * 2) { + expect += j + 27; + } + + assertEquals(p.getValue(i, j), expect); + } + } + + for (int j = 0; j < width; j++) { + p.adjustValuesBelow(j, j, j * j + 14); + } + + for (int i = 0; i < height; i++) { + for (int j = 0; j < width; j++) { + int expect = i * j; + + if (i >= j * 2) { + expect += j + 27; + } + if (i >= j) { + expect += j * j + 14; + } + + assertEquals(p.getValue(i, j), expect); + } + } + + // Test undoing offsets. + + for (int j = 0; j < width; j++) { + p.adjustValuesBelow(j * 2, j, -(j + 27)); + p.adjustValuesBelow(j, j, -(j * j + 14)); + } + + for (int i = 0; i < height; i++) { + for (int j = 0; j < width; j++) { + assertEquals(p.getValue(i, j), i * j); + } + } + + // Test deleting rows. + + while (p.size() > 0) { + int osize = p.size(); + int del = osize / 3; + + if (del == 0) { + del = 1; + } + + int at = (osize - del) / 2; + p.deleteAt(at, del); + + assertEquals(p.size(), osize - del); + + for (int i = 0; i < at; i++) { + for (int j = 0; j < width; j++) { + assertEquals(p.getValue(i, j), i * j); + } + } + + for (int i = at; i < p.size(); i++) { + for (int j = 0; j < width; j++) { + assertEquals(p.getValue(i, j), (i + height - p.size()) * j); + } + } + } + + assertEquals(0, p.size()); + } + } + } +} diff --git a/core/tests/coretests/src/android/text/TextUtilsTest.java b/core/tests/coretests/src/android/text/TextUtilsTest.java index 5b427be23dc7..1beba53a1e29 100644 --- a/core/tests/coretests/src/android/text/TextUtilsTest.java +++ b/core/tests/coretests/src/android/text/TextUtilsTest.java @@ -26,6 +26,8 @@ import android.text.SpannedString; import android.text.TextPaint; import android.text.TextUtils; import android.text.style.StyleSpan; +import android.text.util.Rfc822Token; +import android.text.util.Rfc822Tokenizer; import android.test.MoreAsserts; import com.android.common.Rfc822Validator; @@ -269,6 +271,24 @@ public class TextUtilsTest extends TestCase { } } + @SmallTest + public void testRfc822TokenizerFullAddress() { + Rfc822Token[] tokens = Rfc822Tokenizer.tokenize("Foo Bar (something) <foo@google.com>"); + assertNotNull(tokens); + assertEquals(1, tokens.length); + assertEquals("foo@google.com", tokens[0].getAddress()); + assertEquals("Foo Bar", tokens[0].getName()); + assertEquals("something",tokens[0].getComment()); + } + + @SmallTest + public void testRfc822TokenizeItemWithError() { + Rfc822Token[] tokens = Rfc822Tokenizer.tokenize("\"Foo Bar\\"); + assertNotNull(tokens); + assertEquals(1, tokens.length); + assertEquals("Foo Bar", tokens[0].getAddress()); + } + @LargeTest public void testEllipsize() { CharSequence s1 = "The quick brown fox jumps over \u00FEhe lazy dog."; @@ -329,6 +349,26 @@ public class TextUtilsTest extends TestCase { } } + @SmallTest + public void testDelimitedStringContains() { + assertFalse(TextUtils.delimitedStringContains("", ',', null)); + assertFalse(TextUtils.delimitedStringContains(null, ',', "")); + // Whole match + assertTrue(TextUtils.delimitedStringContains("gps", ',', "gps")); + // At beginning. + assertTrue(TextUtils.delimitedStringContains("gps,gpsx,network,mock", ',', "gps")); + assertTrue(TextUtils.delimitedStringContains("gps,network,mock", ',', "gps")); + // In middle, both without, before & after a false match. + assertTrue(TextUtils.delimitedStringContains("network,gps,mock", ',', "gps")); + assertTrue(TextUtils.delimitedStringContains("network,gps,gpsx,mock", ',', "gps")); + assertTrue(TextUtils.delimitedStringContains("network,gpsx,gps,mock", ',', "gps")); + // At the end. + assertTrue(TextUtils.delimitedStringContains("network,mock,gps", ',', "gps")); + assertTrue(TextUtils.delimitedStringContains("network,mock,gpsx,gps", ',', "gps")); + // Not present (but with a false match) + assertFalse(TextUtils.delimitedStringContains("network,mock,gpsx", ',', "gps")); + } + /** * CharSequence wrapper for testing the cases where text is copied into * a char array instead of working from a String or a Spanned. diff --git a/core/tests/coretests/src/android/text/util/LinkifyTest.java b/core/tests/coretests/src/android/text/util/LinkifyTest.java index 99c6501f817d..444eb0c6a8d8 100644 --- a/core/tests/coretests/src/android/text/util/LinkifyTest.java +++ b/core/tests/coretests/src/android/text/util/LinkifyTest.java @@ -17,10 +17,8 @@ package android.text.util; import android.test.AndroidTestCase; -import android.test.suitebuilder.annotation.MediumTest; import android.test.suitebuilder.annotation.SmallTest; import android.text.method.LinkMovementMethod; -import android.text.util.Linkify; import android.widget.TextView; /** @@ -39,7 +37,7 @@ public class LinkifyTest extends AndroidTestCase { assertTrue(tv.getUrls().length == 0); } - @MediumTest + @SmallTest public void testNormal() throws Exception { TextView tv; diff --git a/core/tests/coretests/src/android/util/MonthDisplayHelperTest.java b/core/tests/coretests/src/android/util/MonthDisplayHelperTest.java index 5207ad918943..55da665c7b34 100644 --- a/core/tests/coretests/src/android/util/MonthDisplayHelperTest.java +++ b/core/tests/coretests/src/android/util/MonthDisplayHelperTest.java @@ -16,13 +16,11 @@ package android.util; -import junit.framework.TestCase; -import junit.framework.Test; -import junit.framework.TestSuite; +import android.test.suitebuilder.annotation.SmallTest; import java.util.Calendar; -import android.test.suitebuilder.annotation.SmallTest; -import android.test.suitebuilder.annotation.MediumTest; + +import junit.framework.TestCase; /** * Unit tests for {@link MonthDisplayHelper}. @@ -42,7 +40,7 @@ public class MonthDisplayHelperTest extends TestCase { new MonthDisplayHelper(2007, Calendar.SEPTEMBER).getFirstDayOfMonth()); } - @MediumTest + @SmallTest public void testNumberOfDaysInCurrentMonth() { assertEquals(30, new MonthDisplayHelper(2007, Calendar.SEPTEMBER).getNumberOfDaysInMonth()); diff --git a/core/tests/coretests/src/android/view/FocusFinderTest.java b/core/tests/coretests/src/android/view/FocusFinderTest.java index 186689fb57b1..6f1dd7c40184 100644 --- a/core/tests/coretests/src/android/view/FocusFinderTest.java +++ b/core/tests/coretests/src/android/view/FocusFinderTest.java @@ -18,7 +18,6 @@ package android.view; import android.graphics.Rect; import android.test.AndroidTestCase; -import android.test.suitebuilder.annotation.MediumTest; import android.test.suitebuilder.annotation.SmallTest; public class FocusFinderTest extends AndroidTestCase { @@ -239,7 +238,7 @@ public class FocusFinderTest extends AndroidTestCase { * A non-candidate (even a much closer one) is always a worse choice * than a real candidate. */ - @MediumTest + @SmallTest public void testSomeCandidateBetterThanNonCandidate() { Rect src = new Rect(0, 0, 50, 50); // (left, top, right, bottom) diff --git a/core/tests/coretests/src/android/view/VelocityTest.java b/core/tests/coretests/src/android/view/VelocityTest.java new file mode 100644 index 000000000000..fb28e1e30f0a --- /dev/null +++ b/core/tests/coretests/src/android/view/VelocityTest.java @@ -0,0 +1,285 @@ +/* + * 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.frameworktest.view; + +import junit.framework.Assert; + +import android.test.InstrumentationTestCase; +import android.test.suitebuilder.annotation.MediumTest; +import android.view.MotionEvent; +import android.view.VelocityTracker; +import android.view.animation.AccelerateInterpolator; +import android.view.animation.DecelerateInterpolator; +import android.view.animation.Interpolator; +import android.view.animation.LinearInterpolator; + +/** + * Exercises {@link android.view.VelocityTracker} to compute correct velocity.<br> + * To launch this test, use :<br> + * <code>./development/testrunner/runtest.py framework -c com.android.frameworktest.view.VelocityTest</code> + */ +public class VelocityTest extends InstrumentationTestCase { + + @MediumTest + public void testInitialCondiditions() { + VelocityTracker vt = VelocityTracker.obtain(); + assertNotNull(vt); + vt.recycle(); + } + + /** + * Test that {@link android.view.VelocityTracker}.clear() clears + * the previous values after a call to computeCurrentVelocity() + */ + @MediumTest + public void testClear() { + long t = System.currentTimeMillis(); + VelocityTracker vt = VelocityTracker.obtain(); + drag(vt, 100, 200, 100, 200, 10, t, 300); + vt.computeCurrentVelocity(1); + assertFalse("Velocity should not be null", vt.getXVelocity() == 0.0f); + assertFalse("Velocity should not be null", vt.getYVelocity() == 0.0f); + vt.clear(); + vt.computeCurrentVelocity(1); + assertEquals(0.0f, vt.getXVelocity()); + assertEquals(0.0f, vt.getYVelocity()); + vt.recycle(); + } + + @MediumTest + public void testDragAcceleration () { + long t = System.currentTimeMillis(); + VelocityTracker vt = VelocityTracker.obtain(); + drag(vt, 100, 200, 100, 200, 15, t, 400, new AccelerateInterpolator()); + vt.computeCurrentVelocity(1000); + assertGreater(250.0f, vt.getXVelocity()); + assertGreater(250.0f, vt.getYVelocity()); + vt.recycle(); + } + + @MediumTest + public void testDragDeceleration () { + long t = System.currentTimeMillis(); + VelocityTracker vt = VelocityTracker.obtain(); + drag(vt, 100, 200, 100, 200, 15, t, 400, new DecelerateInterpolator()); + vt.computeCurrentVelocity(1000); + assertLower(250.0f, vt.getXVelocity()); + assertLower(250.0f, vt.getYVelocity()); + vt.recycle(); + } + + @MediumTest + public void testDragLinearHorizontal() { + long t = System.currentTimeMillis(); + VelocityTracker vt = VelocityTracker.obtain(); + // 100px in 400ms => 250px/s + drag(vt, 100, 200, 200, 200, 15, t, 400); + vt.computeCurrentVelocity(1000); + assertEquals(0.0f, vt.getYVelocity()); + assertEqualFuzzy(250.0f, vt.getXVelocity(), 4f); + vt.recycle(); + } + + @MediumTest + public void testDragLinearVertical() { + long t = System.currentTimeMillis(); + VelocityTracker vt = VelocityTracker.obtain(); + // 100px in 400ms => 250px/s + drag(vt, 200, 200, 100, 200, 15, t, 400); + vt.computeCurrentVelocity(1000); + assertEquals(0.0f, vt.getXVelocity()); + assertEqualFuzzy(250.0f, vt.getYVelocity(), 4f); + vt.recycle(); + } + + /** + * Test dragging with two points only + * (velocity must be an exact value) + */ + @MediumTest + public void testDragWith2Points () { + long t = System.currentTimeMillis(); + VelocityTracker vt = VelocityTracker.obtain(); + // 100px, 2 steps, 100ms => 1000px/s + drag(vt, 100, 200, 100, 200, 2, t, 100); + vt.computeCurrentVelocity(1000); + assertEquals(1000.0f, vt.getXVelocity()); + assertEquals(1000.0f, vt.getYVelocity()); + vt.recycle(); + } + + /** + * Velocity is independent of the number of points used during + * the same interval + */ + @MediumTest + public void testStabilityInNbPoints () { + long t = System.currentTimeMillis(); + VelocityTracker vt = VelocityTracker.obtain(); + drag(vt, 100, 200, 100, 200, 10, t, 400); // 10 steps over 400ms + vt.computeCurrentVelocity(1); + float firstX = vt.getXVelocity(); + float firstY = vt.getYVelocity(); + vt.clear(); + drag(vt, 100, 200, 100, 200, 20, t, 400); // 20 steps over 400ms + vt.computeCurrentVelocity(1); + float secondX = vt.getXVelocity(); + float secondY = vt.getYVelocity(); + assertEqualFuzzy(firstX, secondX, 0.1f); + assertEqualFuzzy(firstY, secondY, 0.1f); + vt.recycle(); + } + + /** + * Velocity is independent of the time when the events occurs, + * it only depends on delays between the events. + */ + @MediumTest + public void testStabilityInTime () { + long t = System.currentTimeMillis(); + VelocityTracker vt = VelocityTracker.obtain(); + drag(vt, 100, 200, 100, 200, 10, t, 400); + vt.computeCurrentVelocity(1); + float firstX = vt.getXVelocity(); + float firstY = vt.getYVelocity(); + vt.clear(); + drag(vt, 100, 200, 100, 200, 10, t + 3600*1000, 400); // on hour later + vt.computeCurrentVelocity(1); + float secondX = vt.getXVelocity(); + float secondY = vt.getYVelocity(); + assertEqualFuzzy(firstX, secondX, 0.1f); + assertEqualFuzzy(firstY, secondY, 0.1f); + vt.recycle(); + } + + /** + * Velocity is independent of the position of the events, + * it only depends on their relative distance. + */ + @MediumTest + public void testStabilityInSpace () { + long t = System.currentTimeMillis(); + VelocityTracker vt = VelocityTracker.obtain(); + drag(vt, 100, 200, 100, 200, 10, t, 400); + vt.computeCurrentVelocity(1); + float firstX = vt.getXVelocity(); + float firstY = vt.getYVelocity(); + vt.clear(); + drag(vt, 200, 300, 200, 300, 10, t, 400); // 100px further + vt.computeCurrentVelocity(1); + float secondX = vt.getXVelocity(); + float secondY = vt.getYVelocity(); + assertEqualFuzzy(firstX, secondX, 0.1f); + assertEqualFuzzy(firstY, secondY, 0.1f); + vt.recycle(); + } + + /** + * Test that calls to {@link android.view.VelocityTracker}.computeCurrentVelocity() + * will output same values when using the same data. + */ + @MediumTest + public void testStabilityOfComputation() { + long t = System.currentTimeMillis(); + VelocityTracker vt = VelocityTracker.obtain(); + drag(vt, 100, 200, 100, 200, 10, t, 300); + vt.computeCurrentVelocity(1); + float firstX = vt.getXVelocity(); + float firstY = vt.getYVelocity(); + vt.computeCurrentVelocity(1); + float secondX = vt.getXVelocity(); + float secondY = vt.getYVelocity(); + assertEquals(firstX, secondX); + assertEquals(firstY, secondY); + vt.recycle(); + } + + /** + * Test the units parameter of {@link android.view.VelocityTracker}.computeCurrentVelocity() + */ + @MediumTest + public void testStabilityOfUnits() { + long t = System.currentTimeMillis(); + VelocityTracker vt = VelocityTracker.obtain(); + drag(vt, 100, 200, 100, 200, 10, t, 300); + vt.computeCurrentVelocity(1); + float firstX = vt.getXVelocity(); + float firstY = vt.getYVelocity(); + vt.computeCurrentVelocity(1000); + float secondX = vt.getXVelocity(); + float secondY = vt.getYVelocity(); + assertEqualFuzzy(firstX, secondX / 1000.0f, 0.1f); + assertEqualFuzzy(firstY, secondY / 1000.0f, 0.1f); + vt.recycle(); + } + + /** + * Simulate a drag by giving directly MotionEvents to + * the VelocityTracker using a linear interpolator + */ + private void drag(VelocityTracker vt, int startX, int endX, int startY, int endY, int steps, + long startime, int duration) { + drag(vt, startX, endX, startY, endY, steps, startime, duration, new LinearInterpolator()); + } + + /** + * Simulate a drag by giving directly MotionEvents to + * the VelocityTracker using a given interpolator + */ + private void drag(VelocityTracker vt, int startX, int endX, int startY, int endY, int steps, + long startime, int duration, Interpolator interpolator) { + addMotionEvent(vt, startX, startY, startime, MotionEvent.ACTION_DOWN); + float dt = duration / (float)steps; + int distX = endX - startX; + int distY = endY - startY; + for (int i=1; i<steps-1; i++) { + float ii = interpolator.getInterpolation(i / (float)steps); + int x = (int) (startX + distX * ii); + int y = (int) (startY + distY * ii); + long time = startime + (int) (i * dt); + addMotionEvent(vt, x, y, time, MotionEvent.ACTION_MOVE); + } + addMotionEvent(vt, endX, endY, startime + duration, MotionEvent.ACTION_UP); + } + + private void addMotionEvent(VelocityTracker vt, int x, int y, long time, int action) { + MotionEvent me = MotionEvent.obtain(time, time, action, x, y, 0); + vt.addMovement(me); + me.recycle(); + } + + /** + * Float imprecision of the average computations and filtering + * (removing last MotionEvent for N > 3) implies that tests + * accepts some approximated values. + */ + private void assertEqualFuzzy(float expected, float actual, float threshold) { + boolean fuzzyEqual = actual >= expected - threshold && actual <= expected + threshold; + Assert.assertTrue("Expected: <"+expected+"> but was: <"+actual+ + "> while accepting a variation of: <"+threshold+">", fuzzyEqual); + } + + private void assertGreater(float minExpected, float actual) { + Assert.assertTrue("Expected: minimum <"+minExpected+"> but was: <"+actual+">", + actual > minExpected); + } + + private void assertLower(float maxExpected, float actual) { + Assert.assertTrue("Expected: maximum <"+maxExpected+"> but was: <"+actual+">", + actual < maxExpected); + } +} diff --git a/core/tests/coretests/src/android/view/accessibility/RecycleAccessibilityEventTest.java b/core/tests/coretests/src/android/view/accessibility/RecycleAccessibilityEventTest.java index df8d8362a6b2..bbf1696eb25b 100644 --- a/core/tests/coretests/src/android/view/accessibility/RecycleAccessibilityEventTest.java +++ b/core/tests/coretests/src/android/view/accessibility/RecycleAccessibilityEventTest.java @@ -14,8 +14,7 @@ package android.view.accessibility; -import android.test.suitebuilder.annotation.MediumTest; -import android.view.accessibility.AccessibilityEvent; +import android.test.suitebuilder.annotation.SmallTest; import junit.framework.TestCase; @@ -39,7 +38,7 @@ public class RecycleAccessibilityEventTest extends TestCase { /** * If an {@link AccessibilityEvent} is marshaled/unmarshaled correctly */ - @MediumTest + @SmallTest public void testAccessibilityEventViewTextChangedType() { AccessibilityEvent first = AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED); diff --git a/core/tests/coretests/src/android/widget/RadioGroupPreCheckedTest.java b/core/tests/coretests/src/android/widget/RadioGroupPreCheckedTest.java index 855caaec95a8..1ab362835bbe 100644 --- a/core/tests/coretests/src/android/widget/RadioGroupPreCheckedTest.java +++ b/core/tests/coretests/src/android/widget/RadioGroupPreCheckedTest.java @@ -16,23 +16,22 @@ package android.widget; -import android.test.TouchUtils; -import android.widget.RadioButton; -import android.widget.RadioGroup; import com.android.frameworks.coretests.R; import android.test.ActivityInstrumentationTestCase2; +import android.test.TouchUtils; import android.test.suitebuilder.annotation.LargeTest; +import android.test.suitebuilder.annotation.MediumTest; /** * Exercises {@link android.widget.RadioGroup}'s check feature. */ public class RadioGroupPreCheckedTest extends ActivityInstrumentationTestCase2<RadioGroupActivity> { public RadioGroupPreCheckedTest() { - super("com.android.frameworks.coretests", RadioGroupActivity.class); + super(RadioGroupActivity.class); } - @LargeTest + @MediumTest public void testRadioButtonPreChecked() throws Exception { final RadioGroupActivity activity = getActivity(); diff --git a/core/tests/coretests/src/android/widget/expandablelistview/ExpandableListWithHeadersTest.java b/core/tests/coretests/src/android/widget/expandablelistview/ExpandableListWithHeadersTest.java index 64a0fff8e63b..c74c8539659f 100644 --- a/core/tests/coretests/src/android/widget/expandablelistview/ExpandableListWithHeadersTest.java +++ b/core/tests/coretests/src/android/widget/expandablelistview/ExpandableListWithHeadersTest.java @@ -62,20 +62,20 @@ public class ExpandableListWithHeadersTest extends assertTrue(mExpandableListView.isGroupExpanded(0)); } - @MediumTest + @LargeTest public void testContextMenus() { ExpandableListTester tester = new ExpandableListTester(mExpandableListView, this); tester.testContextMenus(); } - @MediumTest + @LargeTest public void testConvertionBetweenFlatAndPacked() { ExpandableListTester tester = new ExpandableListTester(mExpandableListView, this); tester.testConvertionBetweenFlatAndPackedOnGroups(); tester.testConvertionBetweenFlatAndPackedOnChildren(); } - @MediumTest + @LargeTest public void testSelectedPosition() { ExpandableListTester tester = new ExpandableListTester(mExpandableListView, this); tester.testSelectedPositionOnGroups(); diff --git a/core/tests/coretests/src/android/widget/gridview/touch/GridTouchStackFromBottomManyTest.java b/core/tests/coretests/src/android/widget/gridview/touch/GridTouchStackFromBottomManyTest.java index f8e6ae7a0317..9a8d3074890b 100644 --- a/core/tests/coretests/src/android/widget/gridview/touch/GridTouchStackFromBottomManyTest.java +++ b/core/tests/coretests/src/android/widget/gridview/touch/GridTouchStackFromBottomManyTest.java @@ -67,7 +67,7 @@ public class GridTouchStackFromBottomManyTest extends ActivityInstrumentationTes mGridView.getListPaddingTop(), firstChild.getTop()); } - @MediumTest + @LargeTest public void testScrollToBottom() { TouchUtils.scrollToBottom(this, mGridView); diff --git a/core/tests/coretests/src/android/widget/listview/arrowscroll/ListInterleaveFocusablesTest.java b/core/tests/coretests/src/android/widget/listview/arrowscroll/ListInterleaveFocusablesTest.java index 6238dab6a2fd..ec8ab7e77357 100644 --- a/core/tests/coretests/src/android/widget/listview/arrowscroll/ListInterleaveFocusablesTest.java +++ b/core/tests/coretests/src/android/widget/listview/arrowscroll/ListInterleaveFocusablesTest.java @@ -16,22 +16,20 @@ package android.widget.listview.arrowscroll; -import android.test.ActivityInstrumentationTestCase; -import android.test.suitebuilder.annotation.LargeTest; +import android.test.ActivityInstrumentationTestCase2; import android.test.suitebuilder.annotation.MediumTest; -import android.widget.ListView; +import android.util.ListUtil; import android.view.KeyEvent; import android.view.View; - +import android.widget.ListView; import android.widget.listview.ListInterleaveFocusables; -import android.util.ListUtil; -public class ListInterleaveFocusablesTest extends ActivityInstrumentationTestCase<ListInterleaveFocusables> { +public class ListInterleaveFocusablesTest extends ActivityInstrumentationTestCase2<ListInterleaveFocusables> { private ListView mListView; private ListUtil mListUtil; public ListInterleaveFocusablesTest() { - super("com.android.frameworks.coretests", ListInterleaveFocusables.class); + super(ListInterleaveFocusables.class); } @Override @@ -42,7 +40,7 @@ public class ListInterleaveFocusablesTest extends ActivityInstrumentationTestCas mListUtil = new ListUtil(mListView, getInstrumentation()); } - @LargeTest + @MediumTest public void testPreconditions() { assertEquals(7, mListView.getChildCount()); assertTrue(mListView.getChildAt(1).isFocusable()); diff --git a/core/tests/coretests/src/android/widget/listview/arrowscroll/ListOfItemsTallerThanScreenTest.java b/core/tests/coretests/src/android/widget/listview/arrowscroll/ListOfItemsTallerThanScreenTest.java index 5960942282a9..6805b728f540 100644 --- a/core/tests/coretests/src/android/widget/listview/arrowscroll/ListOfItemsTallerThanScreenTest.java +++ b/core/tests/coretests/src/android/widget/listview/arrowscroll/ListOfItemsTallerThanScreenTest.java @@ -16,16 +16,15 @@ package android.widget.listview.arrowscroll; -import android.test.ActivityInstrumentationTestCase; -import android.test.suitebuilder.annotation.LargeTest; +import android.test.ActivityInstrumentationTestCase2; import android.test.suitebuilder.annotation.MediumTest; -import android.widget.ListView; -import android.view.View; import android.view.KeyEvent; +import android.view.View; +import android.widget.ListView; import android.widget.listview.ListOfItemsTallerThanScreen; public class ListOfItemsTallerThanScreenTest - extends ActivityInstrumentationTestCase<ListOfItemsTallerThanScreen> { + extends ActivityInstrumentationTestCase2<ListOfItemsTallerThanScreen> { private ListView mListView; private ListOfItemsTallerThanScreen mActivity; @@ -38,7 +37,7 @@ public class ListOfItemsTallerThanScreenTest } public ListOfItemsTallerThanScreenTest() { - super("com.android.frameworks.coretests", ListOfItemsTallerThanScreen.class); + super(ListOfItemsTallerThanScreen.class); } @MediumTest @@ -126,7 +125,7 @@ public class ListOfItemsTallerThanScreenTest 1, mListView.getChildCount()); } - @LargeTest + @MediumTest public void testScrollDownToLastItem() { final int numItems = mListView.getAdapter().getCount(); diff --git a/core/tests/coretests/src/android/widget/listview/arrowscroll/ListOfShortShortTallShortShortTest.java b/core/tests/coretests/src/android/widget/listview/arrowscroll/ListOfShortShortTallShortShortTest.java index a5d4906b0c7d..5aa27b267025 100644 --- a/core/tests/coretests/src/android/widget/listview/arrowscroll/ListOfShortShortTallShortShortTest.java +++ b/core/tests/coretests/src/android/widget/listview/arrowscroll/ListOfShortShortTallShortShortTest.java @@ -16,20 +16,19 @@ package android.widget.listview.arrowscroll; -import android.test.ActivityInstrumentationTestCase; -import android.test.suitebuilder.annotation.LargeTest; +import android.test.ActivityInstrumentationTestCase2; import android.test.suitebuilder.annotation.MediumTest; +import android.util.ListUtil; import android.view.KeyEvent; import android.widget.ListView; import android.widget.listview.ListOfShortShortTallShortShort; -import android.util.ListUtil; -public class ListOfShortShortTallShortShortTest extends ActivityInstrumentationTestCase<ListOfShortShortTallShortShort> { +public class ListOfShortShortTallShortShortTest extends ActivityInstrumentationTestCase2<ListOfShortShortTallShortShort> { private ListView mListView; private ListUtil mListUtil; public ListOfShortShortTallShortShortTest() { - super("com.android.frameworks.coretests", ListOfShortShortTallShortShort.class); + super(ListOfShortShortTallShortShort.class); } @Override @@ -68,7 +67,7 @@ public class ListOfShortShortTallShortShortTest extends ActivityInstrumentationT 1, mListView.getChildCount()); } - @LargeTest + @MediumTest public void testFadeInTwoBottomItems() { // put 2nd item selected sendKeys(KeyEvent.KEYCODE_DPAD_DOWN); @@ -89,7 +88,7 @@ public class ListOfShortShortTallShortShortTest extends ActivityInstrumentationT mListView.getChildAt(2).getBottom() >= mListUtil.getListBottom()); } - @LargeTest + @MediumTest public void testFadeOutBottomTwoItems() throws Exception { mListUtil.arrowScrollToSelectedPosition(4); @@ -110,7 +109,7 @@ public class ListOfShortShortTallShortShortTest extends ActivityInstrumentationT 1, mListView.getChildCount()); } - @LargeTest + @MediumTest public void testFadeInTopTwoItems() throws Exception { mListUtil.arrowScrollToSelectedPosition(4); diff --git a/core/tests/coretests/src/android/widget/listview/arrowscroll/ListWithScreenOfNoSelectablesTest.java b/core/tests/coretests/src/android/widget/listview/arrowscroll/ListWithScreenOfNoSelectablesTest.java index 80716501d503..b68631a84b1f 100644 --- a/core/tests/coretests/src/android/widget/listview/arrowscroll/ListWithScreenOfNoSelectablesTest.java +++ b/core/tests/coretests/src/android/widget/listview/arrowscroll/ListWithScreenOfNoSelectablesTest.java @@ -16,21 +16,19 @@ package android.widget.listview.arrowscroll; -import android.widget.listview.ListWithScreenOfNoSelectables; - -import android.test.ActivityInstrumentationTestCase; -import android.test.suitebuilder.annotation.LargeTest; +import android.test.ActivityInstrumentationTestCase2; import android.test.suitebuilder.annotation.MediumTest; import android.view.KeyEvent; import android.view.View; import android.widget.ListView; +import android.widget.listview.ListWithScreenOfNoSelectables; -public class ListWithScreenOfNoSelectablesTest extends ActivityInstrumentationTestCase<ListWithScreenOfNoSelectables> { +public class ListWithScreenOfNoSelectablesTest extends ActivityInstrumentationTestCase2<ListWithScreenOfNoSelectables> { private ListView mListView; public ListWithScreenOfNoSelectablesTest() { - super("com.android.frameworks.coretests", ListWithScreenOfNoSelectables.class); + super(ListWithScreenOfNoSelectables.class); } @Override @@ -56,7 +54,7 @@ public class ListWithScreenOfNoSelectablesTest extends ActivityInstrumentationTe @MediumTest public void testGoFromSelectedViewExistsToNoSelectedViewExists() { - // go down untile first (and only selectable) item is off screen + // go down until first (and only selectable) item is off screen View first = mListView.getChildAt(0); while (first.getParent() != null) { sendKeys(KeyEvent.KEYCODE_DPAD_DOWN); @@ -67,7 +65,7 @@ public class ListWithScreenOfNoSelectablesTest extends ActivityInstrumentationTe assertNull("selected view", mListView.getSelectedView()); } - @LargeTest + @MediumTest public void testPanDownAcrossUnselectableChildrenToBottom() { final int lastPosition = mListView.getCount() - 1; final int maxDowns = 20; diff --git a/core/tests/coretests/src/com/android/internal/http/multipart/MultipartTest.java b/core/tests/coretests/src/com/android/internal/http/multipart/MultipartTest.java new file mode 100644 index 000000000000..32e13a7ac7f8 --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/http/multipart/MultipartTest.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2008 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.internal.http.multipart; + +import junit.framework.TestCase; +import org.apache.http.Header; +import org.apache.http.util.EncodingUtils; + +import java.io.BufferedWriter; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileWriter; + +public class MultipartTest extends TestCase { + + public void testParts() throws Exception { + StringBuffer filebuffer = new StringBuffer(); + String filepartStr = "this is file part"; + filebuffer.append(filepartStr); + File upload = File.createTempFile("Multipart", "test"); + + FileWriter outFile = new FileWriter(upload); + BufferedWriter out = new BufferedWriter(outFile); + try { + out.write(filebuffer.toString()); + out.flush(); + } finally { + out.close(); + } + + Part[] parts = new Part[3]; + parts[0] = new StringPart("stringpart", "PART1!!"); + parts[1] = new FilePart(upload.getName(), upload); + parts[2] = new StringPart("stringpart", "PART2!!"); + + MultipartEntity me = new MultipartEntity(parts); + ByteArrayOutputStream os = new ByteArrayOutputStream(); + me.writeTo(os); + Header h = me.getContentType(); + String boundry = EncodingUtils.getAsciiString(me.getMultipartBoundary()); + StringBuffer contentType = new StringBuffer("multipart/form-data"); + contentType.append("; boundary="); + contentType.append(boundry); + assertEquals("Multipart content type error", contentType.toString(), h.getValue()); + final String CRLF = "\r\n"; + StringBuffer output = new StringBuffer(); + + output.append("--"); + output.append(boundry); + output.append(CRLF); + + output.append("Content-Disposition: form-data; name=\"stringpart\""); + output.append(CRLF); + output.append("Content-Type: text/plain; charset=US-ASCII"); + output.append(CRLF); + output.append("Content-Transfer-Encoding: 8bit"); + output.append(CRLF); + output.append(CRLF); + output.append("PART1!!"); + output.append(CRLF); + + output.append("--"); + output.append(boundry); + output.append(CRLF); + + output.append("Content-Disposition: form-data; name=\""); + output.append(upload.getName()); + output.append("\"; filename=\""); + output.append(upload.getName()); + output.append("\""); + + output.append(CRLF); + output.append("Content-Type: application/octet-stream; charset=ISO-8859-1"); + output.append(CRLF); + output.append("Content-Transfer-Encoding: binary"); + output.append(CRLF); + output.append(CRLF); + output.append(filepartStr); + output.append(CRLF); + + output.append("--"); + output.append(boundry); + output.append(CRLF); + + output.append("Content-Disposition: form-data; name=\"stringpart\""); + output.append(CRLF); + output.append("Content-Type: text/plain; charset=US-ASCII"); + output.append(CRLF); + output.append("Content-Transfer-Encoding: 8bit"); + output.append(CRLF); + output.append(CRLF); + output.append("PART2!!"); + output.append(CRLF); + + output.append("--"); + output.append(boundry); + output.append("--"); + output.append(CRLF); + // System.out.print(output.toString()); + assertEquals("Multipart content error", output.toString(), os.toString()); + + // System.out.print(os.toString()); + } +} diff --git a/core/tests/coretests/src/com/android/internal/os/LoggingPrintStreamTest.java b/core/tests/coretests/src/com/android/internal/os/LoggingPrintStreamTest.java new file mode 100644 index 000000000000..4d016d14c6e4 --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/os/LoggingPrintStreamTest.java @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2008 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.internal.os; + +import junit.framework.TestCase; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public class LoggingPrintStreamTest extends TestCase { + + TestPrintStream out = new TestPrintStream(); + + public void testPrintException() { + @SuppressWarnings("ThrowableInstanceNeverThrown") + Throwable t = new Throwable("Ignore me."); + + StringWriter sout = new StringWriter(); + t.printStackTrace(new PrintWriter(sout)); + + t.printStackTrace(out); + // t.printStackTrace(); + + String[] lines = sout.toString().split("\\n"); + assertEquals(Arrays.asList(lines), out.lines); + } + + public void testPrintObject() { + Object o = new Object(); + out.print(4); + out.print(o); + out.print(2); + out.flush(); + assertEquals(Arrays.asList("4" + o + "2"), out.lines); + } + + public void testPrintlnObject() { + Object o = new Object(); + out.print(4); + out.println(o); + out.print(2); + out.flush(); + assertEquals(Arrays.asList("4" + o, "2"), out.lines); + } + + public void testPrintf() { + out.printf("Name: %s\nEmployer: %s", "Bob", "Google"); + assertEquals(Arrays.asList("Name: Bob"), out.lines); + out.flush(); + assertEquals(Arrays.asList("Name: Bob", "Employer: Google"), out.lines); + } + + public void testPrintInt() { + out.print(4); + out.print(2); + assertTrue(out.lines.isEmpty()); + out.flush(); + assertEquals(Collections.singletonList("42"), out.lines); + } + + public void testPrintlnInt() { + out.println(4); + out.println(2); + assertEquals(Arrays.asList("4", "2"), out.lines); + } + + public void testPrintCharArray() { + out.print("Foo\nBar\nTee".toCharArray()); + assertEquals(Arrays.asList("Foo", "Bar"), out.lines); + out.flush(); + assertEquals(Arrays.asList("Foo", "Bar", "Tee"), out.lines); + } + + public void testPrintString() { + out.print("Foo\nBar\nTee"); + assertEquals(Arrays.asList("Foo", "Bar"), out.lines); + out.flush(); + assertEquals(Arrays.asList("Foo", "Bar", "Tee"), out.lines); + } + + public void testPrintlnCharArray() { + out.println("Foo\nBar\nTee".toCharArray()); + assertEquals(Arrays.asList("Foo", "Bar", "Tee"), out.lines); + } + + public void testPrintlnString() { + out.println("Foo\nBar\nTee"); + assertEquals(Arrays.asList("Foo", "Bar", "Tee"), out.lines); + } + + public void testPrintlnStringWithBufferedData() { + out.print(5); + out.println("Foo\nBar\nTee"); + assertEquals(Arrays.asList("5Foo", "Bar", "Tee"), out.lines); + } + + public void testAppend() { + out.append("Foo\n") + .append('4') + .append('\n') + .append("Bar", 1, 2) + .append('\n'); + assertEquals(Arrays.asList("Foo", "4", "a"), out.lines); + } + + public void testMultiByteCharactersSpanningBuffers() throws Exception { + // assume 3*1000 bytes won't fit in LoggingPrintStream's internal buffer + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < 1000; i++) { + builder.append("\u20AC"); // a Euro character; 3 bytes in UTF-8 + } + String expected = builder.toString(); + + out.write(expected.getBytes("UTF-8")); + out.flush(); + assertEquals(Arrays.asList(expected), out.lines); + } + + public void testWriteOneByteAtATimeMultibyteCharacters() throws Exception { + String expected = " \u20AC \u20AC \u20AC \u20AC "; + for (byte b : expected.getBytes()) { + out.write(b); + } + out.flush(); + assertEquals(Arrays.asList(expected), out.lines); + } + + public void testWriteByteArrayAtATimeMultibyteCharacters() throws Exception { + String expected = " \u20AC \u20AC \u20AC \u20AC "; + out.write(expected.getBytes()); + out.flush(); + assertEquals(Arrays.asList(expected), out.lines); + } + + public void testWriteWithOffsetsMultibyteCharacters() throws Exception { + String expected = " \u20AC \u20AC \u20AC \u20AC "; + byte[] bytes = expected.getBytes(); + int i = 0; + while (i < bytes.length - 5) { + out.write(bytes, i, 5); + i += 5; + } + out.write(bytes, i, bytes.length - i); + out.flush(); + assertEquals(Arrays.asList(expected), out.lines); + } + + public void testWriteFlushesOnNewlines() throws Exception { + String a = " \u20AC \u20AC "; + String b = " \u20AC \u20AC "; + String c = " "; + String toWrite = a + "\n" + b + "\n" + c; + out.write(toWrite.getBytes()); + out.flush(); + assertEquals(Arrays.asList(a, b, c), out.lines); + } + + static class TestPrintStream extends LoggingPrintStream { + + final List<String> lines = new ArrayList<String>(); + + protected void log(String line) { + lines.add(line); + } + } +} diff --git a/core/tests/hosttests/src/android/content/pm/PackageManagerHostTestUtils.java b/core/tests/hosttests/src/android/content/pm/PackageManagerHostTestUtils.java index 91cbe2fc328f..fed39c949523 100644 --- a/core/tests/hosttests/src/android/content/pm/PackageManagerHostTestUtils.java +++ b/core/tests/hosttests/src/android/content/pm/PackageManagerHostTestUtils.java @@ -37,6 +37,9 @@ import java.io.IOException; import java.io.StringReader; import java.lang.Runtime; import java.lang.Process; +import java.util.Hashtable; +import java.util.Map; +import java.util.Map.Entry; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -117,12 +120,33 @@ public class PackageManagerHostTestUtils extends Assert { /** * Helper method to run tests and return the listener that collected the results. + * + * For the optional params, pass null to use the default values. + * @param pkgName Android application package for tests + * @param className (optional) The class containing the method to test + * @param methodName (optional) The method in the class of which to test + * @param runnerName (optional) The name of the TestRunner of the test on the device to be run + * @param params (optional) Any additional parameters to pass into the Test Runner * @return the {@link CollectingTestRunListener} */ - private CollectingTestRunListener doRunTests(String pkgName) throws IOException { - RemoteAndroidTestRunner testRunner = new RemoteAndroidTestRunner( - pkgName, mDevice); + private CollectingTestRunListener doRunTests(String pkgName, String className, String + methodName, String runnerName, Map<String, String> params) throws IOException { + + RemoteAndroidTestRunner testRunner = new RemoteAndroidTestRunner(pkgName, runnerName, + mDevice); + + if (className != null && methodName != null) { + testRunner.setMethodName(className, methodName); + } + + // Add in any additional args to pass into the test + if (params != null) { + for (Entry<String, String> argPair : params.entrySet()) { + testRunner.addInstrumentationArg(argPair.getKey(), argPair.getValue()); + } + } + CollectingTestRunListener listener = new CollectingTestRunListener(); testRunner.run(listener); return listener; @@ -132,10 +156,27 @@ public class PackageManagerHostTestUtils extends Assert { * Runs the specified packages tests, and returns whether all tests passed or not. * * @param pkgName Android application package for tests + * @param className The class containing the method to test + * @param methodName The method in the class of which to test + * @param runnerName The name of the TestRunner of the test on the device to be run + * @param params Any additional parameters to pass into the Test Runner + * @return true if test passed, false otherwise. + */ + public boolean runDeviceTestsDidAllTestsPass(String pkgName, String className, + String methodName, String runnerName, Map<String, String> params) throws IOException { + CollectingTestRunListener listener = doRunTests(pkgName, className, methodName, + runnerName, params); + return listener.didAllTestsPass(); + } + + /** + * Runs the specified packages tests, and returns whether all tests passed or not. + * + * @param pkgName Android application package for tests * @return true if every test passed, false otherwise. */ public boolean runDeviceTestsDidAllTestsPass(String pkgName) throws IOException { - CollectingTestRunListener listener = doRunTests(pkgName); + CollectingTestRunListener listener = doRunTests(pkgName, null, null, null, null); return listener.didAllTestsPass(); } @@ -452,7 +493,7 @@ public class PackageManagerHostTestUtils extends Assert { } // For collecting results from running device tests - private static class CollectingTestRunListener implements ITestRunListener { + public static class CollectingTestRunListener implements ITestRunListener { private boolean mAllTestsPassed = true; private String mTestRunErrorMessage = null; diff --git a/core/tests/hosttests/src/android/net/DownloadManagerHostTests.java b/core/tests/hosttests/src/android/net/DownloadManagerHostTests.java new file mode 100644 index 000000000000..ed280c916ec4 --- /dev/null +++ b/core/tests/hosttests/src/android/net/DownloadManagerHostTests.java @@ -0,0 +1,201 @@ +/* + * 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 android.net; + +import android.content.pm.PackageManagerHostTestUtils; +import android.content.pm.PackageManagerHostTestUtils.CollectingTestRunListener; + +import com.android.ddmlib.IDevice; +import com.android.ddmlib.IShellOutputReceiver; +import com.android.ddmlib.Log; +import com.android.ddmlib.MultiLineReceiver; +import com.android.ddmlib.SyncService; +import com.android.ddmlib.SyncService.ISyncProgressMonitor; +import com.android.ddmlib.SyncService.SyncResult; +import com.android.hosttest.DeviceTestCase; +import com.android.hosttest.DeviceTestSuite; + +import java.io.File; +import java.io.IOException; +import java.util.Hashtable; + +import junit.framework.Test; + +/** + * Host-based tests of the DownloadManager API. (Uses a device-based app to actually invoke the + * various tests.) + */ +public class DownloadManagerHostTests extends DeviceTestCase { + protected PackageManagerHostTestUtils mPMUtils = null; + + private static final String LOG_TAG = "android.net.DownloadManagerHostTests"; + private static final String FILE_DOWNLOAD_APK = "DownloadManagerTestApp.apk"; + private static final String FILE_DOWNLOAD_PKG = "com.android.frameworks.downloadmanagertests"; + private static final String FILE_DOWNLOAD_CLASS = + "com.android.frameworks.downloadmanagertests.DownloadManagerTestApp"; + private static final String DOWNLOAD_TEST_RUNNER_NAME = + "com.android.frameworks.downloadmanagertests.DownloadManagerTestRunner"; + + // Extra parameters to pass to the TestRunner + private static final String EXTERNAL_DOWNLOAD_URI_KEY = "external_download_uri"; + // Note: External environment variable ANDROID_TEST_EXTERNAL_URI must be set to point to the + // external URI under which the files downloaded by the tests can be found. Note that the Uri + // must be accessible by the device during a test run. + private static String EXTERNAL_DOWNLOAD_URI_VALUE = null; + + Hashtable<String, String> mExtraParams = null; + + public static Test suite() { + return new DeviceTestSuite(DownloadManagerHostTests.class); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + // ensure apk path has been set before test is run + assertNotNull(getTestAppPath()); + mPMUtils = new PackageManagerHostTestUtils(getDevice()); + EXTERNAL_DOWNLOAD_URI_VALUE = System.getenv("ANDROID_TEST_EXTERNAL_URI"); + assertNotNull(EXTERNAL_DOWNLOAD_URI_VALUE); + mExtraParams = getExtraParams(); + } + + /** + * Helper function to get extra params that can be used to pass into the helper app. + */ + protected Hashtable<String, String> getExtraParams() { + Hashtable<String, String> extraParams = new Hashtable<String, String>(); + extraParams.put(EXTERNAL_DOWNLOAD_URI_KEY, EXTERNAL_DOWNLOAD_URI_VALUE); + return extraParams; + } + + /** + * Tests that a large download over WiFi + * @throws Exception if the test failed at any point + */ + public void testLargeDownloadOverWiFi() throws Exception { + mPMUtils.installAppAndVerifyExistsOnDevice(String.format("%s%s%s", getTestAppPath(), + File.separator, FILE_DOWNLOAD_APK), FILE_DOWNLOAD_PKG, true); + + boolean testPassed = mPMUtils.runDeviceTestsDidAllTestsPass(FILE_DOWNLOAD_PKG, + FILE_DOWNLOAD_CLASS, "runLargeDownloadOverWiFi", DOWNLOAD_TEST_RUNNER_NAME, + mExtraParams); + + assertTrue("Failed to install large file over WiFi in < 10 minutes!", testPassed); + } + + /** + * Spawns a device-based function to initiate a download on the device, reboots the device, + * then waits and verifies the download succeeded. + * + * @throws Exception if the test failed at any point + */ + public void testDownloadManagerSingleReboot() throws Exception { + mPMUtils.installAppAndVerifyExistsOnDevice(String.format("%s%s%s", getTestAppPath(), + File.separator, FILE_DOWNLOAD_APK), FILE_DOWNLOAD_PKG, true); + + boolean testPassed = mPMUtils.runDeviceTestsDidAllTestsPass(FILE_DOWNLOAD_PKG, + FILE_DOWNLOAD_CLASS, "initiateDownload", DOWNLOAD_TEST_RUNNER_NAME, + mExtraParams); + + assertTrue("Failed to initiate download properly!", testPassed); + mPMUtils.rebootDevice(); + testPassed = mPMUtils.runDeviceTestsDidAllTestsPass(FILE_DOWNLOAD_PKG, + FILE_DOWNLOAD_CLASS, "verifyFileDownloadSucceeded", DOWNLOAD_TEST_RUNNER_NAME, + mExtraParams); + assertTrue("Failed to verify initiated download completed properyly!", testPassed); + } + + /** + * Spawns a device-based function to initiate a download on the device, reboots the device three + * times (using different intervals), then waits and verifies the download succeeded. + * + * @throws Exception if the test failed at any point + */ + public void testDownloadManagerMultipleReboots() throws Exception { + mPMUtils.installAppAndVerifyExistsOnDevice(String.format("%s%s%s", getTestAppPath(), + File.separator, FILE_DOWNLOAD_APK), FILE_DOWNLOAD_PKG, true); + + boolean testPassed = mPMUtils.runDeviceTestsDidAllTestsPass(FILE_DOWNLOAD_PKG, + FILE_DOWNLOAD_CLASS, "initiateDownload", DOWNLOAD_TEST_RUNNER_NAME, + mExtraParams); + + assertTrue("Failed to initiate download properly!", testPassed); + Thread.sleep(5000); + + // Do 3 random reboots - after 13, 9, and 19 seconds + Log.i(LOG_TAG, "First reboot..."); + mPMUtils.rebootDevice(); + Thread.sleep(13000); + Log.i(LOG_TAG, "Second reboot..."); + mPMUtils.rebootDevice(); + Thread.sleep(9000); + Log.i(LOG_TAG, "Third reboot..."); + mPMUtils.rebootDevice(); + Thread.sleep(19000); + testPassed = mPMUtils.runDeviceTestsDidAllTestsPass(FILE_DOWNLOAD_PKG, + FILE_DOWNLOAD_CLASS, "verifyFileDownloadSucceeded", DOWNLOAD_TEST_RUNNER_NAME, + mExtraParams); + assertTrue("Failed to verify initiated download completed properyly!", testPassed); + } + + /** + * Spawns a device-based function to test download while WiFi is enabled/disabled multiple times + * during the download. + * + * @throws Exception if the test failed at any point + */ + public void testDownloadMultipleWiFiEnableDisable() throws Exception { + mPMUtils.installAppAndVerifyExistsOnDevice(String.format("%s%s%s", getTestAppPath(), + File.separator, FILE_DOWNLOAD_APK), FILE_DOWNLOAD_PKG, true); + + boolean testPassed = mPMUtils.runDeviceTestsDidAllTestsPass(FILE_DOWNLOAD_PKG, + FILE_DOWNLOAD_CLASS, "runDownloadMultipleWiFiEnableDisable", + DOWNLOAD_TEST_RUNNER_NAME, mExtraParams); + assertTrue(testPassed); + } + + /** + * Spawns a device-based function to test switching on/off both airplane mode and WiFi + * + * @throws Exception if the test failed at any point + */ + public void testDownloadMultipleSwitching() throws Exception { + mPMUtils.installAppAndVerifyExistsOnDevice(String.format("%s%s%s", getTestAppPath(), + File.separator, FILE_DOWNLOAD_APK), FILE_DOWNLOAD_PKG, true); + + boolean testPassed = mPMUtils.runDeviceTestsDidAllTestsPass(FILE_DOWNLOAD_PKG, + FILE_DOWNLOAD_CLASS, "runDownloadMultipleSwitching", + DOWNLOAD_TEST_RUNNER_NAME, mExtraParams); + assertTrue(testPassed); + } + + /** + * Spawns a device-based function to test switching on/off airplane mode multiple times + * + * @throws Exception if the test failed at any point + */ + public void testDownloadMultipleAirplaneModeEnableDisable() throws Exception { + mPMUtils.installAppAndVerifyExistsOnDevice(String.format("%s%s%s", getTestAppPath(), + File.separator, FILE_DOWNLOAD_APK), FILE_DOWNLOAD_PKG, true); + + boolean testPassed = mPMUtils.runDeviceTestsDidAllTestsPass(FILE_DOWNLOAD_PKG, + FILE_DOWNLOAD_CLASS, "runDownloadMultipleAirplaneModeEnableDisable", + DOWNLOAD_TEST_RUNNER_NAME, mExtraParams); + assertTrue(testPassed); + } +} diff --git a/core/tests/hosttests/test-apps/DownloadManagerTestApp/Android.mk b/core/tests/hosttests/test-apps/DownloadManagerTestApp/Android.mk new file mode 100644 index 000000000000..576765c742b0 --- /dev/null +++ b/core/tests/hosttests/test-apps/DownloadManagerTestApp/Android.mk @@ -0,0 +1,29 @@ +# 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. + +LOCAL_PATH:= $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := tests + +LOCAL_SRC_FILES := $(call all-java-files-under, src) \ + ../../../coretests/src/android/net/DownloadManagerBaseTest.java + +LOCAL_STATIC_JAVA_LIBRARIES := android-common frameworks-core-util-lib +LOCAL_SDK_VERSION := current + +LOCAL_PACKAGE_NAME := DownloadManagerTestApp + +include $(BUILD_PACKAGE) diff --git a/core/tests/hosttests/test-apps/DownloadManagerTestApp/AndroidManifest.xml b/core/tests/hosttests/test-apps/DownloadManagerTestApp/AndroidManifest.xml new file mode 100644 index 000000000000..3f2be3cab6e6 --- /dev/null +++ b/core/tests/hosttests/test-apps/DownloadManagerTestApp/AndroidManifest.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.frameworks.downloadmanagertests"> + + <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> + <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> + <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" /> + <uses-permission android:name="android.permission.INTERNET" /> + <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> + <uses-permission android:name="android.permission.WRITE_SETTINGS" /> + + <application android:label="DownloadManagerTestApp"> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation + android:name=".DownloadManagerTestRunner" + android:targetPackage="com.android.frameworks.downloadmanagertests" + android:label="Frameworks Download Manager Test App" /> + +</manifest> diff --git a/core/tests/hosttests/test-apps/DownloadManagerTestApp/src/com/android/frameworks/DownloadManagerTestApp.java b/core/tests/hosttests/test-apps/DownloadManagerTestApp/src/com/android/frameworks/DownloadManagerTestApp.java new file mode 100644 index 000000000000..ef8135398583 --- /dev/null +++ b/core/tests/hosttests/test-apps/DownloadManagerTestApp/src/com/android/frameworks/DownloadManagerTestApp.java @@ -0,0 +1,463 @@ +/* + * 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.frameworks.downloadmanagertests; + +import android.content.Context; +import android.content.Intent; +import android.database.Cursor; +import android.net.DownloadManager; +import android.net.DownloadManager.Query; +import android.net.DownloadManager.Request; +import android.net.DownloadManagerBaseTest; +import android.net.Uri; +import android.os.Environment; +import android.os.ParcelFileDescriptor; +import android.provider.Settings; +import android.test.suitebuilder.annotation.LargeTest; +import android.util.Log; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.FileWriter; + +import coretestutils.http.MockResponse; +import coretestutils.http.MockWebServer; +import coretestutils.http.RecordedRequest; + +/** + * Class to test downloading files from a real (not mock) external server. + */ +public class DownloadManagerTestApp extends DownloadManagerBaseTest { + protected static String DOWNLOAD_STARTED_FLAG = "DMTEST_DOWNLOAD_STARTED"; + protected static String LOG_TAG = + "com.android.frameworks.downloadmanagertests.DownloadManagerTestApp"; + + protected static String DOWNLOAD_500K_FILENAME = "External541kb.apk"; + protected static long DOWNLOAD_500K_FILESIZE = 570927; + protected static String DOWNLOAD_1MB_FILENAME = "External1mb.apk"; + protected static long DOWNLOAD_1MB_FILESIZE = 1041262; + protected static String DOWNLOAD_10MB_FILENAME = "External10mb.apk"; + protected static long DOWNLOAD_10MB_FILESIZE = 10258741; + + // Values to be obtained from TestRunner + private String externalDownloadUriValue = null; + + /** + * {@inheritDoc } + */ + @Override + public void setUp() throws Exception { + super.setUp(); + DownloadManagerTestRunner mRunner = (DownloadManagerTestRunner)getInstrumentation(); + externalDownloadUriValue = mRunner.externalDownloadUriValue; + assertNotNull(externalDownloadUriValue); + + if (!externalDownloadUriValue.endsWith("/")) { + externalDownloadUriValue += "/"; + } + } + + /** + * Gets the external URL of the file to download + * + * @return the Uri of the external file to download + */ + private Uri getExternalFileUri(String file) { + return Uri.parse(externalDownloadUriValue + file); + } + + /** + * Gets the path to the file that flags that a download has started. The file contains the + * DownloadManager id of the download being trackted between reboot sessions. + * + * @return The path of the file tracking that a download has started + * @throws InterruptedException if interrupted + * @throws Exception if timed out while waiting for SD card to mount + */ + protected String getDownloadStartedFilePath() { + String path = Environment.getExternalStorageDirectory().getPath(); + return path + File.separatorChar + DOWNLOAD_STARTED_FLAG; + } + + /** + * Common setup steps for downloads. + * + * Note that these are not included in setUp, so that individual tests can control their own + * state between reboots, etc. + */ + protected void doCommonDownloadSetup() throws Exception { + setWiFiStateOn(true); + setAirplaneModeOn(false); + waitForExternalStoreMount(); + removeAllCurrentDownloads(); + } + + /** + * Initiates a download. + * + * Queues up a download to the download manager, and saves the DownloadManager's assigned + * download ID for this download to a file. + * + * @throws Exception if unsuccessful + */ + public void initiateDownload() throws Exception { + String filename = DOWNLOAD_1MB_FILENAME; + mContext.deleteFile(DOWNLOAD_STARTED_FLAG); + FileOutputStream fileOutput = mContext.openFileOutput(DOWNLOAD_STARTED_FLAG, 0); + DataOutputStream outputFile = null; + doCommonDownloadSetup(); + + try { + long dlRequest = -1; + + // Make sure there are no pending downloads currently going on + removeAllCurrentDownloads(); + + Uri remoteUri = getExternalFileUri(filename); + Request request = new Request(remoteUri); + + dlRequest = mDownloadManager.enqueue(request); + waitForDownloadToStart(dlRequest); + assertTrue(dlRequest != -1); + + // Store ID of download for later retrieval + outputFile = new DataOutputStream(fileOutput); + outputFile.writeLong(dlRequest); + } finally { + if (outputFile != null) { + outputFile.flush(); + outputFile.close(); + } + } + } + + /** + * Waits for a previously-initiated download and verifies it has completed successfully. + * + * @throws Exception if unsuccessful + */ + public void verifyFileDownloadSucceeded() throws Exception { + String filename = DOWNLOAD_1MB_FILENAME; + long filesize = DOWNLOAD_1MB_FILESIZE; + long dlRequest = -1; + boolean rebootMarkerValid = false; + DataInputStream dataInputFile = null; + + setWiFiStateOn(true); + setAirplaneModeOn(false); + + try { + FileInputStream inFile = mContext.openFileInput(DOWNLOAD_STARTED_FLAG); + dataInputFile = new DataInputStream(inFile); + dlRequest = dataInputFile.readLong(); + } catch (Exception e) { + // The file was't valid so we just leave the flag false + Log.i(LOG_TAG, "Unable to determine initial download id."); + throw e; + } finally { + if (dataInputFile != null) { + dataInputFile.close(); + } + mContext.deleteFile(DOWNLOAD_STARTED_FLAG); + } + + assertTrue(dlRequest != -1); + Cursor cursor = getCursor(dlRequest); + ParcelFileDescriptor pfd = null; + try { + assertTrue("Unable to query last initiated download!", cursor.moveToFirst()); + + int columnIndex = cursor.getColumnIndex(DownloadManager.COLUMN_STATUS); + int status = cursor.getInt(columnIndex); + int currentWaitTime = 0; + + // Wait until the download finishes + waitForDownloadOrTimeout(dlRequest); + + Log.i(LOG_TAG, "Verifying download information..."); + // Verify specific info about the file (size, name, etc)... + pfd = mDownloadManager.openDownloadedFile(dlRequest); + verifyFileSize(pfd, filesize); + } catch (Exception e) { + Log.i(LOG_TAG, "error: " + e.toString()); + throw e; + } finally { + // Clean up... + cursor.close(); + mDownloadManager.remove(dlRequest); + if (pfd != null) { + pfd.close(); + } + } + } + + /** + * Tests downloading a large file over WiFi (~10 Mb). + * + * @throws Exception if unsuccessful + */ + public void runLargeDownloadOverWiFi() throws Exception { + String filename = DOWNLOAD_10MB_FILENAME; + long filesize = DOWNLOAD_10MB_FILESIZE; + long dlRequest = -1; + doCommonDownloadSetup(); + + // Make sure there are no pending downloads currently going on + removeAllCurrentDownloads(); + + Uri remoteUri = getExternalFileUri(filename); + Request request = new Request(remoteUri); + request.setMediaType(getMimeMapping(DownloadFileType.APK)); + + dlRequest = mDownloadManager.enqueue(request); + + // Rather large file, so wait up to 15 mins... + waitForDownloadOrTimeout(dlRequest, WAIT_FOR_DOWNLOAD_POLL_TIME, 15 * 60 * 1000); + + Cursor cursor = getCursor(dlRequest); + ParcelFileDescriptor pfd = null; + try { + Log.i(LOG_TAG, "Verifying download information..."); + // Verify specific info about the file (size, name, etc)... + pfd = mDownloadManager.openDownloadedFile(dlRequest); + verifyFileSize(pfd, filesize); + } finally { + if (pfd != null) { + pfd.close(); + } + mDownloadManager.remove(dlRequest); + cursor.close(); + } + } + + /** + * Tests that downloads resume when switching back and forth from having connectivity to + * having no connectivity using both WiFi and airplane mode. + * + * Note: Device has no mobile access when running this test. + * + * @throws Exception if unsuccessful + */ + public void runDownloadMultipleSwitching() throws Exception { + String filename = DOWNLOAD_500K_FILENAME; + long filesize = DOWNLOAD_500K_FILESIZE; + doCommonDownloadSetup(); + + String localDownloadDirectory = Environment.getExternalStorageDirectory().getPath(); + File downloadedFile = new File(localDownloadDirectory, filename); + + long dlRequest = -1; + try { + downloadedFile.delete(); + + // Make sure there are no pending downloads currently going on + removeAllCurrentDownloads(); + + Uri remoteUri = getExternalFileUri(filename); + Request request = new Request(remoteUri); + + // Local destination of downloaded file + Uri localUri = Uri.fromFile(downloadedFile); + Log.i(LOG_TAG, "setting localUri to: " + localUri.getPath()); + request.setDestinationUri(localUri); + + request.setAllowedNetworkTypes(Request.NETWORK_MOBILE | Request.NETWORK_WIFI); + + dlRequest = mDownloadManager.enqueue(request); + waitForDownloadToStart(dlRequest); + // make sure we're starting to download some data... + waitForFileToGrow(downloadedFile); + + // download disable + setWiFiStateOn(false); + + // download disable + Log.i(LOG_TAG, "Turning on airplane mode..."); + setAirplaneModeOn(true); + Thread.sleep(30 * 1000); // wait 30 secs + + // download disable + setWiFiStateOn(true); + Thread.sleep(30 * 1000); // wait 30 secs + + // download enable + Log.i(LOG_TAG, "Turning off airplane mode..."); + setAirplaneModeOn(false); + Thread.sleep(5 * 1000); // wait 5 seconds + + // download disable + Log.i(LOG_TAG, "Turning off WiFi..."); + setWiFiStateOn(false); + Thread.sleep(30 * 1000); // wait 30 secs + + // finally, turn WiFi back on and finish up the download + Log.i(LOG_TAG, "Turning on WiFi..."); + setWiFiStateOn(true); + Log.i(LOG_TAG, "Waiting up to 3 minutes for download to complete..."); + waitForDownloadsOrTimeout(dlRequest, 3 * 60 * 1000); + ParcelFileDescriptor pfd = mDownloadManager.openDownloadedFile(dlRequest); + verifyFileSize(pfd, filesize); + } finally { + Log.i(LOG_TAG, "Cleaning up files..."); + if (dlRequest != -1) { + mDownloadManager.remove(dlRequest); + } + downloadedFile.delete(); + } + } + + /** + * Tests that downloads resume when switching on/off WiFi at various intervals. + * + * Note: Device has no mobile access when running this test. + * + * @throws Exception if unsuccessful + */ + public void runDownloadMultipleWiFiEnableDisable() throws Exception { + String filename = DOWNLOAD_500K_FILENAME; + long filesize = DOWNLOAD_500K_FILESIZE; + doCommonDownloadSetup(); + + String localDownloadDirectory = Environment.getExternalStorageDirectory().getPath(); + File downloadedFile = new File(localDownloadDirectory, filename); + long dlRequest = -1; + try { + downloadedFile.delete(); + + // Make sure there are no pending downloads currently going on + removeAllCurrentDownloads(); + + Uri remoteUri = getExternalFileUri(filename); + Request request = new Request(remoteUri); + + // Local destination of downloaded file + Uri localUri = Uri.fromFile(downloadedFile); + Log.i(LOG_TAG, "setting localUri to: " + localUri.getPath()); + request.setDestinationUri(localUri); + + request.setAllowedNetworkTypes(Request.NETWORK_WIFI); + + dlRequest = mDownloadManager.enqueue(request); + waitForDownloadToStart(dlRequest); + // are we making any progress? + waitForFileToGrow(downloadedFile); + + // download disable + Log.i(LOG_TAG, "Turning off WiFi..."); + setWiFiStateOn(false); + Thread.sleep(40 * 1000); // wait 40 seconds + + // enable download... + Log.i(LOG_TAG, "Turning on WiFi again..."); + setWiFiStateOn(true); + waitForFileToGrow(downloadedFile); + + // download disable + Log.i(LOG_TAG, "Turning off WiFi..."); + setWiFiStateOn(false); + Thread.sleep(20 * 1000); // wait 20 seconds + + // enable download... + Log.i(LOG_TAG, "Turning on WiFi again..."); + setWiFiStateOn(true); + + Log.i(LOG_TAG, "Waiting up to 3 minutes for download to complete..."); + waitForDownloadsOrTimeout(dlRequest, 3 * 60 * 1000); + ParcelFileDescriptor pfd = mDownloadManager.openDownloadedFile(dlRequest); + verifyFileSize(pfd, filesize); + } finally { + Log.i(LOG_TAG, "Cleaning up files..."); + if (dlRequest != -1) { + mDownloadManager.remove(dlRequest); + } + downloadedFile.delete(); + } + } + + /** + * Tests that downloads resume when switching on/off Airplane mode numerous times at + * various intervals. + * + * Note: Device has no mobile access when running this test. + * + * @throws Exception if unsuccessful + */ + public void runDownloadMultipleAirplaneModeEnableDisable() throws Exception { + String filename = DOWNLOAD_500K_FILENAME; + long filesize = DOWNLOAD_500K_FILESIZE; + // make sure WiFi is enabled, and airplane mode is not on + doCommonDownloadSetup(); + + String localDownloadDirectory = Environment.getExternalStorageDirectory().getPath(); + File downloadedFile = new File(localDownloadDirectory, filename); + long dlRequest = -1; + try { + downloadedFile.delete(); + + // Make sure there are no pending downloads currently going on + removeAllCurrentDownloads(); + + Uri remoteUri = getExternalFileUri(filename); + Request request = new Request(remoteUri); + + // Local destination of downloaded file + Uri localUri = Uri.fromFile(downloadedFile); + Log.i(LOG_TAG, "setting localUri to: " + localUri.getPath()); + request.setDestinationUri(localUri); + + request.setAllowedNetworkTypes(Request.NETWORK_WIFI); + + dlRequest = mDownloadManager.enqueue(request); + waitForDownloadToStart(dlRequest); + // are we making any progress? + waitForFileToGrow(downloadedFile); + + // download disable + Log.i(LOG_TAG, "Turning on Airplane mode..."); + setAirplaneModeOn(true); + Thread.sleep(60 * 1000); // wait 1 minute + + // download enable + Log.i(LOG_TAG, "Turning off Airplane mode..."); + setAirplaneModeOn(false); + // make sure we're starting to download some data... + waitForFileToGrow(downloadedFile); + + // reenable the connection to start up the download again + Log.i(LOG_TAG, "Turning on Airplane mode again..."); + setAirplaneModeOn(true); + Thread.sleep(20 * 1000); // wait 20 seconds + + // Finish up the download... + Log.i(LOG_TAG, "Turning off Airplane mode again..."); + setAirplaneModeOn(false); + + Log.i(LOG_TAG, "Waiting up to 3 minutes for donwload to complete..."); + waitForDownloadsOrTimeout(dlRequest, 180 * 1000); // wait up to 3 mins before timeout + ParcelFileDescriptor pfd = mDownloadManager.openDownloadedFile(dlRequest); + verifyFileSize(pfd, filesize); + } finally { + Log.i(LOG_TAG, "Cleaning up files..."); + if (dlRequest != -1) { + mDownloadManager.remove(dlRequest); + } + downloadedFile.delete(); + } + } +} diff --git a/core/tests/hosttests/test-apps/DownloadManagerTestApp/src/com/android/frameworks/DownloadManagerTestRunner.java b/core/tests/hosttests/test-apps/DownloadManagerTestApp/src/com/android/frameworks/DownloadManagerTestRunner.java new file mode 100644 index 000000000000..0f166195b4be --- /dev/null +++ b/core/tests/hosttests/test-apps/DownloadManagerTestApp/src/com/android/frameworks/DownloadManagerTestRunner.java @@ -0,0 +1,63 @@ +/* + * 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.frameworks.downloadmanagertests; + +import android.os.Bundle; +import android.test.InstrumentationTestRunner; +import android.test.InstrumentationTestSuite; +import android.util.Log; + +import com.android.frameworks.downloadmanagertests.DownloadManagerTestApp; + +import junit.framework.TestSuite; + +/** + * Instrumentation Test Runner for all download manager tests. + * + * To run the download manager tests: + * + * adb shell am instrument -e external_download_1mb_uri <uri> external_download_500k_uri <uri> \ + * -w com.android.frameworks.downloadmanagertests/.DownloadManagerTestRunner + */ + +public class DownloadManagerTestRunner extends InstrumentationTestRunner { + private static final String EXTERNAL_DOWNLOAD_URI_KEY = "external_download_uri"; + public String externalDownloadUriValue = null; + + @Override + public TestSuite getAllTests() { + TestSuite suite = new InstrumentationTestSuite(this); + suite.addTestSuite(DownloadManagerTestApp.class); + return suite; + } + + @Override + public ClassLoader getLoader() { + return DownloadManagerTestRunner.class.getClassLoader(); + } + + @Override + public void onCreate(Bundle icicle) { + // Extract the extra params passed in from the bundle... + String externalDownloadUri = (String) icicle.get(EXTERNAL_DOWNLOAD_URI_KEY); + if (externalDownloadUri != null) { + externalDownloadUriValue = externalDownloadUri; + } + super.onCreate(icicle); + } + +} diff --git a/core/tests/utillib/Android.mk b/core/tests/utillib/Android.mk new file mode 100644 index 000000000000..299ea5a2aeec --- /dev/null +++ b/core/tests/utillib/Android.mk @@ -0,0 +1,27 @@ +# 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. + +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := $(call all-subdir-java-files) + +LOCAL_MODULE := frameworks-core-util-lib + +include $(BUILD_STATIC_JAVA_LIBRARY) + +# Build the test APKs using their own makefiles +include $(call all-makefiles-under,$(LOCAL_PATH)) + diff --git a/core/tests/utillib/src/coretestutils/http/MockResponse.java b/core/tests/utillib/src/coretestutils/http/MockResponse.java new file mode 100644 index 000000000000..5b03e5fe9019 --- /dev/null +++ b/core/tests/utillib/src/coretestutils/http/MockResponse.java @@ -0,0 +1,239 @@ +/* + * 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 coretestutils.http; + +import static coretestutils.http.MockWebServer.ASCII; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import android.util.Log; + +/** + * A scripted response to be replayed by the mock web server. + */ +public class MockResponse { + private static final byte[] EMPTY_BODY = new byte[0]; + static final String LOG_TAG = "coretestutils.http.MockResponse"; + + private String status = "HTTP/1.1 200 OK"; + private Map<String, String> headers = new HashMap<String, String>(); + private byte[] body = EMPTY_BODY; + private boolean closeConnectionAfter = false; + private String closeConnectionAfterHeader = null; + private int closeConnectionAfterXBytes = -1; + private int pauseConnectionAfterXBytes = -1; + private File bodyExternalFile = null; + + public MockResponse() { + addHeader("Content-Length", 0); + } + + /** + * Returns the HTTP response line, such as "HTTP/1.1 200 OK". + */ + public String getStatus() { + return status; + } + + public MockResponse setResponseCode(int code) { + this.status = "HTTP/1.1 " + code + " OK"; + return this; + } + + /** + * Returns the HTTP headers, such as "Content-Length: 0". + */ + public List<String> getHeaders() { + List<String> headerStrings = new ArrayList<String>(); + for (String header : headers.keySet()) { + headerStrings.add(header + ": " + headers.get(header)); + } + return headerStrings; + } + + public MockResponse addHeader(String header, String value) { + headers.put(header.toLowerCase(), value); + return this; + } + + public MockResponse addHeader(String header, long value) { + return addHeader(header, Long.toString(value)); + } + + public MockResponse removeHeader(String header) { + headers.remove(header.toLowerCase()); + return this; + } + + /** + * Returns true if the body should come from an external file, false otherwise. + */ + private boolean bodyIsExternal() { + return bodyExternalFile != null; + } + + /** + * Returns an input stream containing the raw HTTP payload. + */ + public InputStream getBody() { + if (bodyIsExternal()) { + try { + return new FileInputStream(bodyExternalFile); + } catch (FileNotFoundException e) { + Log.e(LOG_TAG, "File not found: " + bodyExternalFile.getAbsolutePath()); + } + } + return new ByteArrayInputStream(this.body); + } + + public MockResponse setBody(File body) { + addHeader("Content-Length", body.length()); + this.bodyExternalFile = body; + return this; + } + + public MockResponse setBody(byte[] body) { + addHeader("Content-Length", body.length); + this.body = body; + return this; + } + + public MockResponse setBody(String body) { + try { + return setBody(body.getBytes(ASCII)); + } catch (UnsupportedEncodingException e) { + throw new AssertionError(); + } + } + + /** + * Sets the body as chunked. + * + * Currently chunked body is not supported for external files as bodies. + */ + public MockResponse setChunkedBody(byte[] body, int maxChunkSize) throws IOException { + addHeader("Transfer-encoding", "chunked"); + + ByteArrayOutputStream bytesOut = new ByteArrayOutputStream(); + int pos = 0; + while (pos < body.length) { + int chunkSize = Math.min(body.length - pos, maxChunkSize); + bytesOut.write(Integer.toHexString(chunkSize).getBytes(ASCII)); + bytesOut.write("\r\n".getBytes(ASCII)); + bytesOut.write(body, pos, chunkSize); + bytesOut.write("\r\n".getBytes(ASCII)); + pos += chunkSize; + } + bytesOut.write("0\r\n".getBytes(ASCII)); + this.body = bytesOut.toByteArray(); + return this; + } + + public MockResponse setChunkedBody(String body, int maxChunkSize) throws IOException { + return setChunkedBody(body.getBytes(ASCII), maxChunkSize); + } + + @Override public String toString() { + return status; + } + + public boolean shouldCloseConnectionAfter() { + return closeConnectionAfter; + } + + public MockResponse setCloseConnectionAfter(boolean closeConnectionAfter) { + this.closeConnectionAfter = closeConnectionAfter; + return this; + } + + /** + * Sets the header after which sending the server should close the connection. + */ + public MockResponse setCloseConnectionAfterHeader(String header) { + closeConnectionAfterHeader = header; + setCloseConnectionAfter(true); + return this; + } + + /** + * Returns the header after which sending the server should close the connection. + */ + public String getCloseConnectionAfterHeader() { + return closeConnectionAfterHeader; + } + + /** + * Sets the number of bytes in the body to send before which the server should close the + * connection. Set to -1 to unset and send the entire body (default). + */ + public MockResponse setCloseConnectionAfterXBytes(int position) { + closeConnectionAfterXBytes = position; + setCloseConnectionAfter(true); + return this; + } + + /** + * Returns the number of bytes in the body to send before which the server should close the + * connection. Returns -1 if the entire body should be sent (default). + */ + public int getCloseConnectionAfterXBytes() { + return closeConnectionAfterXBytes; + } + + /** + * Sets the number of bytes in the body to send before which the server should pause the + * connection (stalls in sending data). Only one pause per response is supported. + * Set to -1 to unset pausing (default). + */ + public MockResponse setPauseConnectionAfterXBytes(int position) { + pauseConnectionAfterXBytes = position; + return this; + } + + /** + * Returns the number of bytes in the body to send before which the server should pause the + * connection (stalls in sending data). (Returns -1 if it should not pause). + */ + public int getPauseConnectionAfterXBytes() { + return pauseConnectionAfterXBytes; + } + + /** + * Returns true if this response is flagged to pause the connection mid-stream, false otherwise + */ + public boolean getShouldPause() { + return (pauseConnectionAfterXBytes != -1); + } + + /** + * Returns true if this response is flagged to close the connection mid-stream, false otherwise + */ + public boolean getShouldClose() { + return (closeConnectionAfterXBytes != -1); + } +} diff --git a/core/tests/utillib/src/coretestutils/http/MockWebServer.java b/core/tests/utillib/src/coretestutils/http/MockWebServer.java new file mode 100644 index 000000000000..c329ffa518c1 --- /dev/null +++ b/core/tests/utillib/src/coretestutils/http/MockWebServer.java @@ -0,0 +1,426 @@ +/* + * 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 coretestutils.http; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.MalformedURLException; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.URL; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import android.util.Log; + +/** + * A scriptable web server. Callers supply canned responses and the server + * replays them upon request in sequence. + * + * TODO: merge with the version from libcore/support/src/tests/java once it's in. + */ +public final class MockWebServer { + static final String ASCII = "US-ASCII"; + static final String LOG_TAG = "coretestutils.http.MockWebServer"; + + private final BlockingQueue<RecordedRequest> requestQueue + = new LinkedBlockingQueue<RecordedRequest>(); + private final BlockingQueue<MockResponse> responseQueue + = new LinkedBlockingQueue<MockResponse>(); + private int bodyLimit = Integer.MAX_VALUE; + private final ExecutorService executor = Executors.newCachedThreadPool(); + // keep Futures around so we can rethrow any exceptions thrown by Callables + private final Queue<Future<?>> futures = new LinkedList<Future<?>>(); + private final Object downloadPauseLock = new Object(); + // global flag to signal when downloads should resume on the server + private volatile boolean downloadResume = false; + + private int port = -1; + + public int getPort() { + if (port == -1) { + throw new IllegalStateException("Cannot retrieve port before calling play()"); + } + return port; + } + + /** + * Returns a URL for connecting to this server. + * + * @param path the request path, such as "/". + */ + public URL getUrl(String path) throws MalformedURLException { + return new URL("http://localhost:" + getPort() + path); + } + + /** + * Sets the number of bytes of the POST body to keep in memory to the given + * limit. + */ + public void setBodyLimit(int maxBodyLength) { + this.bodyLimit = maxBodyLength; + } + + public void enqueue(MockResponse response) { + responseQueue.add(response); + } + + /** + * Awaits the next HTTP request, removes it, and returns it. Callers should + * use this to verify the request sent was as intended. + */ + public RecordedRequest takeRequest() throws InterruptedException { + return requestQueue.take(); + } + + public RecordedRequest takeRequestWithTimeout(long timeoutMillis) throws InterruptedException { + return requestQueue.poll(timeoutMillis, TimeUnit.MILLISECONDS); + } + + public List<RecordedRequest> drainRequests() { + List<RecordedRequest> requests = new ArrayList<RecordedRequest>(); + requestQueue.drainTo(requests); + return requests; + } + + /** + * Starts the server, serves all enqueued requests, and shuts the server + * down using the default (server-assigned) port. + */ + public void play() throws IOException { + play(0); + } + + /** + * Starts the server, serves all enqueued requests, and shuts the server + * down. + * + * @param port The port number to use to listen to connections on; pass in 0 to have the + * server automatically assign a free port + */ + public void play(int portNumber) throws IOException { + final ServerSocket ss = new ServerSocket(portNumber); + ss.setReuseAddress(true); + port = ss.getLocalPort(); + submitCallable(new Callable<Void>() { + public Void call() throws Exception { + int count = 0; + while (true) { + if (count > 0 && responseQueue.isEmpty()) { + ss.close(); + executor.shutdown(); + return null; + } + + serveConnection(ss.accept()); + count++; + } + } + }); + } + + private void serveConnection(final Socket s) { + submitCallable(new Callable<Void>() { + public Void call() throws Exception { + InputStream in = new BufferedInputStream(s.getInputStream()); + OutputStream out = new BufferedOutputStream(s.getOutputStream()); + + int sequenceNumber = 0; + while (true) { + RecordedRequest request = readRequest(in, sequenceNumber); + if (request == null) { + if (sequenceNumber == 0) { + throw new IllegalStateException("Connection without any request!"); + } else { + break; + } + } + requestQueue.add(request); + MockResponse response = computeResponse(request); + writeResponse(out, response); + if (response.shouldCloseConnectionAfter()) { + break; + } + sequenceNumber++; + } + + in.close(); + out.close(); + return null; + } + }); + } + + private void submitCallable(Callable<?> callable) { + Future<?> future = executor.submit(callable); + futures.add(future); + } + + /** + * Check for and raise any exceptions that have been thrown by child threads. Will not block on + * children still running. + * @throws ExecutionException for the first child thread that threw an exception + */ + public void checkForExceptions() throws ExecutionException, InterruptedException { + final int originalSize = futures.size(); + for (int i = 0; i < originalSize; i++) { + Future<?> future = futures.remove(); + try { + future.get(0, TimeUnit.SECONDS); + } catch (TimeoutException e) { + futures.add(future); // still running + } + } + } + + /** + * @param sequenceNumber the index of this request on this connection. + */ + private RecordedRequest readRequest(InputStream in, int sequenceNumber) throws IOException { + String request = readAsciiUntilCrlf(in); + if (request.equals("")) { + return null; // end of data; no more requests + } + + List<String> headers = new ArrayList<String>(); + int contentLength = -1; + boolean chunked = false; + String header; + while (!(header = readAsciiUntilCrlf(in)).equals("")) { + headers.add(header); + String lowercaseHeader = header.toLowerCase(); + if (contentLength == -1 && lowercaseHeader.startsWith("content-length:")) { + contentLength = Integer.parseInt(header.substring(15).trim()); + } + if (lowercaseHeader.startsWith("transfer-encoding:") && + lowercaseHeader.substring(18).trim().equals("chunked")) { + chunked = true; + } + } + + boolean hasBody = false; + TruncatingOutputStream requestBody = new TruncatingOutputStream(); + List<Integer> chunkSizes = new ArrayList<Integer>(); + if (contentLength != -1) { + hasBody = true; + transfer(contentLength, in, requestBody); + } else if (chunked) { + hasBody = true; + while (true) { + int chunkSize = Integer.parseInt(readAsciiUntilCrlf(in).trim(), 16); + if (chunkSize == 0) { + readEmptyLine(in); + break; + } + chunkSizes.add(chunkSize); + transfer(chunkSize, in, requestBody); + readEmptyLine(in); + } + } + + if (request.startsWith("GET ")) { + if (hasBody) { + throw new IllegalArgumentException("GET requests should not have a body!"); + } + } else if (request.startsWith("POST ")) { + if (!hasBody) { + throw new IllegalArgumentException("POST requests must have a body!"); + } + } else { + throw new UnsupportedOperationException("Unexpected method: " + request); + } + + return new RecordedRequest(request, headers, chunkSizes, + requestBody.numBytesReceived, requestBody.toByteArray(), sequenceNumber); + } + + /** + * Returns a response to satisfy {@code request}. + */ + private MockResponse computeResponse(RecordedRequest request) throws InterruptedException { + if (responseQueue.isEmpty()) { + throw new IllegalStateException("Unexpected request: " + request); + } + return responseQueue.take(); + } + + private void writeResponse(OutputStream out, MockResponse response) throws IOException { + out.write((response.getStatus() + "\r\n").getBytes(ASCII)); + boolean doCloseConnectionAfterHeader = (response.getCloseConnectionAfterHeader() != null); + + // Send headers + String closeConnectionAfterHeader = response.getCloseConnectionAfterHeader(); + for (String header : response.getHeaders()) { + out.write((header + "\r\n").getBytes(ASCII)); + + if (doCloseConnectionAfterHeader && header.startsWith(closeConnectionAfterHeader)) { + Log.i(LOG_TAG, "Closing connection after header" + header); + break; + } + } + + // Send actual body data + if (!doCloseConnectionAfterHeader) { + out.write(("\r\n").getBytes(ASCII)); + + InputStream body = response.getBody(); + final int READ_BLOCK_SIZE = 10000; // process blocks this size + byte[] currentBlock = new byte[READ_BLOCK_SIZE]; + int currentBlockSize = 0; + int writtenSoFar = 0; + + boolean shouldPause = response.getShouldPause(); + boolean shouldClose = response.getShouldClose(); + int pause = response.getPauseConnectionAfterXBytes(); + int close = response.getCloseConnectionAfterXBytes(); + + // Don't bother pausing if it's set to pause -after- the connection should be dropped + if (shouldPause && shouldClose && (pause > close)) { + shouldPause = false; + } + + // Process each block we read in... + while ((currentBlockSize = body.read(currentBlock)) != -1) { + int startIndex = 0; + int writeLength = currentBlockSize; + + // handle the case of pausing + if (shouldPause && (writtenSoFar + currentBlockSize >= pause)) { + writeLength = pause - writtenSoFar; + out.write(currentBlock, 0, writeLength); + out.flush(); + writtenSoFar += writeLength; + + // now pause... + try { + Log.i(LOG_TAG, "Pausing connection after " + pause + " bytes"); + // Wait until someone tells us to resume sending... + synchronized(downloadPauseLock) { + while (!downloadResume) { + downloadPauseLock.wait(); + } + // reset resume back to false + downloadResume = false; + } + } catch (InterruptedException e) { + Log.e(LOG_TAG, "Server was interrupted during pause in download."); + } + + startIndex = writeLength; + writeLength = currentBlockSize - writeLength; + } + + // handle the case of closing the connection + if (shouldClose && (writtenSoFar + writeLength > close)) { + writeLength = close - writtenSoFar; + out.write(currentBlock, startIndex, writeLength); + writtenSoFar += writeLength; + Log.i(LOG_TAG, "Closing connection after " + close + " bytes"); + break; + } + out.write(currentBlock, startIndex, writeLength); + writtenSoFar += writeLength; + } + } + out.flush(); + } + + /** + * Transfer bytes from {@code in} to {@code out} until either {@code length} + * bytes have been transferred or {@code in} is exhausted. + */ + private void transfer(int length, InputStream in, OutputStream out) throws IOException { + byte[] buffer = new byte[1024]; + while (length > 0) { + int count = in.read(buffer, 0, Math.min(buffer.length, length)); + if (count == -1) { + return; + } + out.write(buffer, 0, count); + length -= count; + } + } + + /** + * Returns the text from {@code in} until the next "\r\n", or null if + * {@code in} is exhausted. + */ + private String readAsciiUntilCrlf(InputStream in) throws IOException { + StringBuilder builder = new StringBuilder(); + while (true) { + int c = in.read(); + if (c == '\n' && builder.length() > 0 && builder.charAt(builder.length() - 1) == '\r') { + builder.deleteCharAt(builder.length() - 1); + return builder.toString(); + } else if (c == -1) { + return builder.toString(); + } else { + builder.append((char) c); + } + } + } + + private void readEmptyLine(InputStream in) throws IOException { + String line = readAsciiUntilCrlf(in); + if (!line.equals("")) { + throw new IllegalStateException("Expected empty but was: " + line); + } + } + + /** + * An output stream that drops data after bodyLimit bytes. + */ + private class TruncatingOutputStream extends ByteArrayOutputStream { + private int numBytesReceived = 0; + @Override public void write(byte[] buffer, int offset, int len) { + numBytesReceived += len; + super.write(buffer, offset, Math.min(len, bodyLimit - count)); + } + @Override public void write(int oneByte) { + numBytesReceived++; + if (count < bodyLimit) { + super.write(oneByte); + } + } + } + + /** + * Trigger the server to resume sending the download + */ + public void doResumeDownload() { + synchronized (downloadPauseLock) { + downloadResume = true; + downloadPauseLock.notifyAll(); + } + } +} diff --git a/core/tests/utillib/src/coretestutils/http/RecordedRequest.java b/core/tests/utillib/src/coretestutils/http/RecordedRequest.java new file mode 100644 index 000000000000..293ff80dc6e9 --- /dev/null +++ b/core/tests/utillib/src/coretestutils/http/RecordedRequest.java @@ -0,0 +1,93 @@ +/* + * 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 coretestutils.http; + +import java.util.List; + +/** + * An HTTP request that came into the mock web server. + */ +public final class RecordedRequest { + private final String requestLine; + private final List<String> headers; + private final List<Integer> chunkSizes; + private final int bodySize; + private final byte[] body; + private final int sequenceNumber; + + RecordedRequest(String requestLine, List<String> headers, List<Integer> chunkSizes, + int bodySize, byte[] body, int sequenceNumber) { + this.requestLine = requestLine; + this.headers = headers; + this.chunkSizes = chunkSizes; + this.bodySize = bodySize; + this.body = body; + this.sequenceNumber = sequenceNumber; + } + + public String getRequestLine() { + return requestLine; + } + + public List<String> getHeaders() { + return headers; + } + + /** + * Returns the sizes of the chunks of this request's body, or an empty list + * if the request's body was empty or unchunked. + */ + public List<Integer> getChunkSizes() { + return chunkSizes; + } + + /** + * Returns the total size of the body of this POST request (before + * truncation). + */ + public int getBodySize() { + return bodySize; + } + + /** + * Returns the body of this POST request. This may be truncated. + */ + public byte[] getBody() { + return body; + } + + /** + * Returns the index of this request on its HTTP connection. Since a single + * HTTP connection may serve multiple requests, each request is assigned its + * own sequence number. + */ + public int getSequenceNumber() { + return sequenceNumber; + } + + @Override public String toString() { + return requestLine; + } + + public String getMethod() { + return getRequestLine().split(" ")[0]; + } + + public String getPath() { + return getRequestLine().split(" ")[1]; + } +} |
