diff options
Diffstat (limited to 'core/java')
258 files changed, 26128 insertions, 8205 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/java/android/view/InputDevice.aidl b/core/java/android/view/InputDevice.aidl new file mode 100644 index 000000000000..dbc40c131642 --- /dev/null +++ b/core/java/android/view/InputDevice.aidl @@ -0,0 +1,20 @@ +/* //device/java/android/android.view.InputDevice.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.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/java/android/view/InputEvent.aidl b/core/java/android/view/InputEvent.aidl new file mode 100644 index 000000000000..61acaaf1fa8d --- /dev/null +++ b/core/java/android/view/InputEvent.aidl @@ -0,0 +1,20 @@ +/* //device/java/android/android.view.InputEvent.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.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 */  | 
