Merge change 6503 into donut

* changes:
  Hide soft keyboard in SearchDialog.dismiss()
diff --git a/api/current.xml b/api/current.xml
index d97a0f4..a396c4d 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -496,6 +496,17 @@
  visibility="public"
 >
 </field>
+<field name="GLOBAL_SEARCH"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;android.permission.GLOBAL_SEARCH&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="HARDWARE_TEST"
  type="java.lang.String"
  transient="false"
@@ -25945,6 +25956,17 @@
  visibility="public"
 >
 </method>
+<method name="getPathPermissions"
+ return="android.content.pm.PathPermission[]"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="getReadPermission"
  return="java.lang.String"
  abstract="false"
@@ -26113,6 +26135,19 @@
 <parameter name="sortOrder" type="java.lang.String">
 </parameter>
 </method>
+<method name="setPathPermissions"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="true"
+ deprecated="not deprecated"
+ visibility="protected"
+>
+<parameter name="permissions" type="android.content.pm.PathPermission[]">
+</parameter>
+</method>
 <method name="setReadPermission"
  return="void"
  abstract="false"
@@ -37714,6 +37749,73 @@
 >
 </field>
 </class>
+<class name="PathPermission"
+ extends="android.os.PatternMatcher"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="PathPermission"
+ type="android.content.pm.PathPermission"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="pattern" type="java.lang.String">
+</parameter>
+<parameter name="type" type="int">
+</parameter>
+<parameter name="readPermission" type="java.lang.String">
+</parameter>
+<parameter name="writePermission" type="java.lang.String">
+</parameter>
+</constructor>
+<constructor name="PathPermission"
+ type="android.content.pm.PathPermission"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="src" type="android.os.Parcel">
+</parameter>
+</constructor>
+<method name="getReadPermission"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<method name="getWritePermission"
+ return="java.lang.String"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
+<field name="CREATOR"
+ type="android.os.Parcelable.Creator"
+ transient="false"
+ volatile="false"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+</class>
 <class name="PermissionGroupInfo"
  extends="android.content.pm.PackageItemInfo"
  abstract="false"
@@ -38043,6 +38145,17 @@
  visibility="public"
 >
 </field>
+<field name="pathPermissions"
+ type="android.content.pm.PathPermission[]"
+ transient="false"
+ volatile="false"
+ value="null"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="readPermission"
  type="java.lang.String"
  transient="false"
diff --git a/camera/libcameraservice/CameraService.cpp b/camera/libcameraservice/CameraService.cpp
index 022fe5a..e4b6791 100644
--- a/camera/libcameraservice/CameraService.cpp
+++ b/camera/libcameraservice/CameraService.cpp
@@ -504,8 +504,7 @@
     }
 
     // start recording mode
-    ret = mHardware->startRecording(recordingCallback,
-                                    mCameraService.get());
+    ret = mHardware->startRecording(recordingCallback, mCameraService.get());
     if (ret != NO_ERROR) {
         LOGE("mHardware->startRecording() failed with status %d", ret);
     }
@@ -798,7 +797,7 @@
 }
 
 // recording callback
-void CameraService::Client::recordingCallback(const sp<IMemory>& mem, void* user)
+void CameraService::Client::recordingCallback(nsecs_t timestamp, const sp<IMemory>& mem, void* user)
 {
     LOGV("recordingCallback");
     sp<Client> client = getClientFromCookie(user);
@@ -806,7 +805,7 @@
         return;
     }
     // The strong pointer guarantees the client will exist, but no lock is held.
-    client->postRecordingFrame(mem);
+    client->postRecordingFrame(timestamp, mem);
 }
 
 // take a picture - image is returned in callback
@@ -1072,14 +1071,14 @@
     mCameraClient->dataCallback(CAMERA_MSG_PREVIEW_FRAME, frame);
 }
 
-void CameraService::Client::postRecordingFrame(const sp<IMemory>& frame)
+void CameraService::Client::postRecordingFrame(nsecs_t timestamp, const sp<IMemory>& frame)
 {
     LOGV("postRecordingFrame");
     if (frame == 0) {
         LOGW("frame is a null pointer");
         return;
     }
-    mCameraClient->dataCallback(CAMERA_MSG_VIDEO_FRAME, frame);
+    mCameraClient->dataCallbackTimestamp(timestamp, CAMERA_MSG_VIDEO_FRAME, frame);
 }
 
 void CameraService::Client::postPreviewFrame(const sp<IMemory>& mem)
diff --git a/camera/libcameraservice/CameraService.h b/camera/libcameraservice/CameraService.h
index 0f07673..ea93789 100644
--- a/camera/libcameraservice/CameraService.h
+++ b/camera/libcameraservice/CameraService.h
@@ -132,7 +132,7 @@
 
                     status_t    checkPid();
 
-        static      void        recordingCallback(const sp<IMemory>& mem, void* user);
+        static      void        recordingCallback(nsecs_t timestamp, const sp<IMemory>& mem, void* user);
         static      void        previewCallback(const sp<IMemory>& mem, void* user);
         static      void        shutterCallback(void *user);
         static      void        yuvPictureCallback(const sp<IMemory>& mem, void* user);
@@ -144,7 +144,7 @@
                     void        postRaw(const sp<IMemory>& mem);
                     void        postJpeg(const sp<IMemory>& mem);
                     void        postPreviewFrame(const sp<IMemory>& mem);
-                    void        postRecordingFrame(const sp<IMemory>& frame);
+                    void        postRecordingFrame(nsecs_t timestamp, const sp<IMemory>& frame);
                     void        copyFrameAndPostCopiedFrame(sp<IMemoryHeap> heap, size_t offset, size_t size);
                     void        postError(status_t error);
                     void        postAutoFocus(bool focused);
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index df50f77..6c2560d 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -857,7 +857,7 @@
             if (dialogState != null) {
                 // Calling onRestoreInstanceState() below will invoke dispatchOnCreate
                 // so tell createDialog() not to do it, otherwise we get an exception
-                final Dialog dialog = createDialog(dialogId, false);
+                final Dialog dialog = createDialog(dialogId, dialogState);
                 mManagedDialogs.put(dialogId, dialog);
                 onPrepareDialog(dialogId, dialog);
                 dialog.onRestoreInstanceState(dialogState);
@@ -865,13 +865,13 @@
         }
     }
 
-    private Dialog createDialog(Integer dialogId, boolean dispatchOnCreate) {
+    private Dialog createDialog(Integer dialogId, Bundle state) {
         final Dialog dialog = onCreateDialog(dialogId);
         if (dialog == null) {
             throw new IllegalArgumentException("Activity#onCreateDialog did "
                     + "not create a dialog for id " + dialogId);
         }
-        if (dispatchOnCreate) dialog.dispatchOnCreate(null);
+        dialog.dispatchOnCreate(state);
         return dialog;
     }
 
@@ -2407,7 +2407,7 @@
         }
         Dialog dialog = mManagedDialogs.get(id);
         if (dialog == null) {
-            dialog = createDialog(id, true);
+            dialog = createDialog(id, null);
             mManagedDialogs.put(id, dialog);
         }
         
diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java
index 222fe75..2b165fc 100644
--- a/core/java/android/app/Dialog.java
+++ b/core/java/android/app/Dialog.java
@@ -292,8 +292,10 @@
     // internal method to make sure mcreated is set properly without requiring
     // users to call through to super in onCreate
     void dispatchOnCreate(Bundle savedInstanceState) {
-        onCreate(savedInstanceState);
-        mCreated = true;
+        if (!mCreated) {
+            onCreate(savedInstanceState);
+            mCreated = true;
+        }
     }
 
     /**
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index 5cc5730..6b50405 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -17,6 +17,7 @@
 package android.content;
 
 import android.content.pm.PackageManager;
+import android.content.pm.PathPermission;
 import android.content.pm.ProviderInfo;
 import android.content.res.AssetFileDescriptor;
 import android.content.res.Configuration;
@@ -29,6 +30,7 @@
 import android.net.Uri;
 import android.os.Binder;
 import android.os.ParcelFileDescriptor;
+import android.os.Process;
 
 import java.io.File;
 import java.io.FileNotFoundException;
@@ -65,8 +67,10 @@
  */
 public abstract class ContentProvider implements ComponentCallbacks {
     private Context mContext = null;
+    private int mMyUid;
     private String mReadPermission;
     private String mWritePermission;
+    private PathPermission[] mPathPermissions;
 
     private Transport mTransport = new Transport();
 
@@ -108,24 +112,20 @@
         public IBulkCursor bulkQuery(Uri uri, String[] projection,
                 String selection, String[] selectionArgs, String sortOrder,
                 IContentObserver observer, CursorWindow window) {
-            checkReadPermission(uri);
+            enforceReadPermission(uri);
             Cursor cursor = ContentProvider.this.query(uri, projection,
                     selection, selectionArgs, sortOrder);
             if (cursor == null) {
                 return null;
             }
-            String wperm = getWritePermission();
             return new CursorToBulkCursorAdaptor(cursor, observer,
                     ContentProvider.this.getClass().getName(),
-                    wperm == null ||
-                    getContext().checkCallingOrSelfPermission(getWritePermission())
-                            == PackageManager.PERMISSION_GRANTED,
-                    window);
+                    hasWritePermission(uri), window);
         }
 
         public Cursor query(Uri uri, String[] projection,
                 String selection, String[] selectionArgs, String sortOrder) {
-            checkReadPermission(uri);
+            enforceReadPermission(uri);
             return ContentProvider.this.query(uri, projection, selection,
                     selectionArgs, sortOrder);
         }
@@ -136,55 +136,84 @@
 
 
         public Uri insert(Uri uri, ContentValues initialValues) {
-            checkWritePermission(uri);
+            enforceWritePermission(uri);
             return ContentProvider.this.insert(uri, initialValues);
         }
 
         public int bulkInsert(Uri uri, ContentValues[] initialValues) {
-            checkWritePermission(uri);
+            enforceWritePermission(uri);
             return ContentProvider.this.bulkInsert(uri, initialValues);
         }
 
         public int delete(Uri uri, String selection, String[] selectionArgs) {
-            checkWritePermission(uri);
+            enforceWritePermission(uri);
             return ContentProvider.this.delete(uri, selection, selectionArgs);
         }
 
         public int update(Uri uri, ContentValues values, String selection,
                 String[] selectionArgs) {
-            checkWritePermission(uri);
+            enforceWritePermission(uri);
             return ContentProvider.this.update(uri, values, selection, selectionArgs);
         }
 
         public ParcelFileDescriptor openFile(Uri uri, String mode)
                 throws FileNotFoundException {
-            if (mode != null && mode.startsWith("rw")) checkWritePermission(uri);
-            else checkReadPermission(uri);
+            if (mode != null && mode.startsWith("rw")) enforceWritePermission(uri);
+            else enforceReadPermission(uri);
             return ContentProvider.this.openFile(uri, mode);
         }
 
         public AssetFileDescriptor openAssetFile(Uri uri, String mode)
                 throws FileNotFoundException {
-            if (mode != null && mode.startsWith("rw")) checkWritePermission(uri);
-            else checkReadPermission(uri);
+            if (mode != null && mode.startsWith("rw")) enforceWritePermission(uri);
+            else enforceReadPermission(uri);
             return ContentProvider.this.openAssetFile(uri, mode);
         }
 
         public ISyncAdapter getSyncAdapter() {
-            checkWritePermission(null);
+            enforceWritePermission(null);
             SyncAdapter sa = ContentProvider.this.getSyncAdapter();
             return sa != null ? sa.getISyncAdapter() : null;
         }
 
-        private void checkReadPermission(Uri uri) {
+        private void enforceReadPermission(Uri uri) {
+            final int uid = Binder.getCallingUid();
+            if (uid == mMyUid) {
+                return;
+            }
+            
+            final Context context = getContext();
             final String rperm = getReadPermission();
             final int pid = Binder.getCallingPid();
-            final int uid = Binder.getCallingUid();
-            if (getContext().checkUriPermission(uri, rperm, null, pid, uid,
+            if (rperm == null
+                    || context.checkPermission(rperm, pid, uid)
+                    == PackageManager.PERMISSION_GRANTED) {
+                return;
+            }
+            
+            PathPermission[] pps = getPathPermissions();
+            if (pps != null) {
+                final String path = uri.getPath();
+                int i = pps.length;
+                while (i > 0) {
+                    i--;
+                    final PathPermission pp = pps[i];
+                    final String pprperm = pp.getReadPermission();
+                    if (pprperm != null && pp.match(path)) {
+                        if (context.checkPermission(pprperm, pid, uid)
+                                == PackageManager.PERMISSION_GRANTED) {
+                            return;
+                        }
+                    }
+                }
+            }
+            
+            if (context.checkUriPermission(uri, pid, uid,
                     Intent.FLAG_GRANT_READ_URI_PERMISSION)
                     == PackageManager.PERMISSION_GRANTED) {
                 return;
             }
+            
             String msg = "Permission Denial: reading "
                     + ContentProvider.this.getClass().getName()
                     + " uri " + uri + " from pid=" + Binder.getCallingPid()
@@ -193,20 +222,57 @@
             throw new SecurityException(msg);
         }
 
-        private void checkWritePermission(Uri uri) {
+        private boolean hasWritePermission(Uri uri) {
+            final int uid = Binder.getCallingUid();
+            if (uid == mMyUid) {
+                return true;
+            }
+            
+            final Context context = getContext();
             final String wperm = getWritePermission();
             final int pid = Binder.getCallingPid();
-            final int uid = Binder.getCallingUid();
-            if (getContext().checkUriPermission(uri, null, wperm, pid, uid,
+            if (wperm == null
+                    || context.checkPermission(wperm, pid, uid)
+                    == PackageManager.PERMISSION_GRANTED) {
+                return true;
+            }
+            
+            PathPermission[] pps = getPathPermissions();
+            if (pps != null) {
+                final String path = uri.getPath();
+                int i = pps.length;
+                while (i > 0) {
+                    i--;
+                    final PathPermission pp = pps[i];
+                    final String ppwperm = pp.getWritePermission();
+                    if (ppwperm != null && pp.match(path)) {
+                        if (context.checkPermission(ppwperm, pid, uid)
+                                == PackageManager.PERMISSION_GRANTED) {
+                            return true;
+                        }
+                    }
+                }
+            }
+            
+            if (context.checkUriPermission(uri, pid, uid,
                     Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
                     == PackageManager.PERMISSION_GRANTED) {
+                return true;
+            }
+            
+            return false;
+        }
+        
+        private void enforceWritePermission(Uri uri) {
+            if (hasWritePermission(uri)) {
                 return;
             }
+            
             String msg = "Permission Denial: writing "
                     + ContentProvider.this.getClass().getName()
                     + " uri " + uri + " from pid=" + Binder.getCallingPid()
                     + ", uid=" + Binder.getCallingUid()
-                    + " requires " + wperm;
+                    + " requires " + getWritePermission();
             throw new SecurityException(msg);
         }
     }
@@ -266,6 +332,28 @@
     }
 
     /**
+     * Change the path-based permission required to read and/or write data in
+     * the content provider.  This is normally set for you from its manifest
+     * information when the provider is first created.
+     *
+     * @param permissions Array of path permission descriptions.
+     */
+    protected final void setPathPermissions(PathPermission[] permissions) {
+        mPathPermissions = permissions;
+    }
+
+    /**
+     * Return the path-based permissions required for read and/or write access to
+     * this content provider.  This method can be called from multiple
+     * threads, as described in
+     * <a href="{@docRoot}guide/topics/fundamentals.html#procthread">Application Fundamentals:
+     * Processes and Threads</a>.
+     */
+    public final PathPermission[] getPathPermissions() {
+        return mPathPermissions;
+    }
+
+    /**
      * Called when the provider is being started.
      *
      * @return true if the provider was successfully loaded, false otherwise
@@ -600,9 +688,11 @@
          */
         if (mContext == null) {
             mContext = context;
+            mMyUid = Process.myUid();
             if (info != null) {
                 setReadPermission(info.readPermission);
                 setWritePermission(info.writePermission);
+                setPathPermissions(info.pathPermissions);
             }
             ContentProvider.this.onCreate();
         }
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index bcf95b6..0d00f21 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -173,7 +173,7 @@
      * {@hide}
      */
     public static final int ANY_DENSITY = -1;
-    private static final int[] ANY_DENSITIES_ARRAY = { ANY_DENSITY };
+    static final int[] ANY_DENSITIES_ARRAY = { ANY_DENSITY };
 
     /**
      * Flags associated with the application.  Any combination of
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 558b0c3..0e2deed 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -945,15 +945,25 @@
                         >= android.os.Build.VERSION_CODES.CUR_DEVELOPMENT)) {
             pkg.applicationInfo.flags |= ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS;
         }
-        
+        int densities[] = null;
         int size = pkg.supportsDensityList.size();
         if (size > 0) {
-            int densities[] = pkg.supportsDensities = new int[size];
+            densities = pkg.supportsDensities = new int[size];
             List<Integer> densityList = pkg.supportsDensityList;
             for (int i = 0; i < size; i++) {
                 densities[i] = densityList.get(i);
             }
         }
+        /**
+         * TODO: enable this before code freeze. b/1967935
+         * *
+        if ((densities == null || densities.length == 0)
+                && (pkg.applicationInfo.targetSdkVersion
+                        >= android.os.Build.VERSION_CODES.CUR_DEVELOPMENT)) {
+            pkg.supportsDensities = ApplicationInfo.ANY_DENSITIES_ARRAY;
+        }
+         */
+
         return pkg;
     }
 
@@ -1908,6 +1918,7 @@
                         outInfo.metaData, outError)) == null) {
                     return false;
                 }
+                
             } else if (parser.getName().equals("grant-uri-permission")) {
                 TypedArray sa = res.obtainAttributes(attrs,
                         com.android.internal.R.styleable.AndroidManifestGrantUriPermission);
@@ -1931,7 +1942,7 @@
                 if (str != null) {
                     pa = new PatternMatcher(str, PatternMatcher.PATTERN_SIMPLE_GLOB);
                 }
-
+                
                 sa.recycle();
 
                 if (pa != null) {
@@ -1946,6 +1957,101 @@
                         outInfo.info.uriPermissionPatterns = newp;
                     }
                     outInfo.info.grantUriPermissions = true;
+                } else {
+                    if (!RIGID_PARSER) {
+                        Log.w(TAG, "Problem in package " + mArchiveSourcePath + ":");
+                        Log.w(TAG, "No path, pathPrefix, or pathPattern for <path-permission>");
+                        XmlUtils.skipCurrentTag(parser);
+                        continue;
+                    }
+                    outError[0] = "No path, pathPrefix, or pathPattern for <path-permission>";
+                    return false;
+                }
+                XmlUtils.skipCurrentTag(parser);
+
+            } else if (parser.getName().equals("path-permission")) {
+                TypedArray sa = res.obtainAttributes(attrs,
+                        com.android.internal.R.styleable.AndroidManifestPathPermission);
+
+                PathPermission pa = null;
+
+                String permission = sa.getNonResourceString(
+                        com.android.internal.R.styleable.AndroidManifestPathPermission_permission);
+                String readPermission = sa.getNonResourceString(
+                        com.android.internal.R.styleable.AndroidManifestPathPermission_readPermission);
+                if (readPermission == null) {
+                    readPermission = permission;
+                }
+                String writePermission = sa.getNonResourceString(
+                        com.android.internal.R.styleable.AndroidManifestPathPermission_writePermission);
+                if (writePermission == null) {
+                    writePermission = permission;
+                }
+                
+                boolean havePerm = false;
+                if (readPermission != null) {
+                    readPermission = readPermission.intern();
+                    havePerm = true;
+                }
+                if (writePermission != null) {
+                    writePermission = readPermission.intern();
+                    havePerm = true;
+                }
+
+                if (!havePerm) {
+                    if (!RIGID_PARSER) {
+                        Log.w(TAG, "Problem in package " + mArchiveSourcePath + ":");
+                        Log.w(TAG, "No readPermission or writePermssion for <path-permission>");
+                        XmlUtils.skipCurrentTag(parser);
+                        continue;
+                    }
+                    outError[0] = "No readPermission or writePermssion for <path-permission>";
+                    return false;
+                }
+                
+                String path = sa.getNonResourceString(
+                        com.android.internal.R.styleable.AndroidManifestPathPermission_path);
+                if (path != null) {
+                    pa = new PathPermission(path,
+                            PatternMatcher.PATTERN_LITERAL, readPermission, writePermission);
+                }
+
+                path = sa.getNonResourceString(
+                        com.android.internal.R.styleable.AndroidManifestPathPermission_pathPrefix);
+                if (path != null) {
+                    pa = new PathPermission(path,
+                            PatternMatcher.PATTERN_PREFIX, readPermission, writePermission);
+                }
+
+                path = sa.getNonResourceString(
+                        com.android.internal.R.styleable.AndroidManifestPathPermission_pathPattern);
+                if (path != null) {
+                    pa = new PathPermission(path,
+                            PatternMatcher.PATTERN_SIMPLE_GLOB, readPermission, writePermission);
+                }
+
+                sa.recycle();
+
+                if (pa != null) {
+                    if (outInfo.info.pathPermissions == null) {
+                        outInfo.info.pathPermissions = new PathPermission[1];
+                        outInfo.info.pathPermissions[0] = pa;
+                    } else {
+                        final int N = outInfo.info.pathPermissions.length;
+                        PathPermission[] newp = new PathPermission[N+1];
+                        System.arraycopy(outInfo.info.pathPermissions, 0, newp, 0, N);
+                        newp[N] = pa;
+                        outInfo.info.pathPermissions = newp;
+                    }
+                } else {
+                    if (!RIGID_PARSER) {
+                        Log.w(TAG, "Problem in package " + mArchiveSourcePath + ":");
+                        Log.w(TAG, "No path, pathPrefix, or pathPattern for <path-permission>");
+                        XmlUtils.skipCurrentTag(parser);
+                        continue;
+                    }
+                    outError[0] = "No path, pathPrefix, or pathPattern for <path-permission>";
+                    return false;
                 }
                 XmlUtils.skipCurrentTag(parser);
 
diff --git a/core/java/android/content/pm/PathPermission.java b/core/java/android/content/pm/PathPermission.java
new file mode 100644
index 0000000..7e49d7d
--- /dev/null
+++ b/core/java/android/content/pm/PathPermission.java
@@ -0,0 +1,68 @@
+/*
+ * 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.content.pm;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.PatternMatcher;
+
+/**
+ * Description of permissions needed to access a particular path
+ * in a {@link ProviderInfo}.
+ */
+public class PathPermission extends PatternMatcher {
+    private final String mReadPermission;
+    private final String mWritePermission;
+    
+    public PathPermission(String pattern, int type, String readPermission,
+            String writePermission) {
+        super(pattern, type);
+        mReadPermission = readPermission;
+        mWritePermission = writePermission;
+    }
+    
+    public String getReadPermission() {
+        return mReadPermission;
+    }
+    
+    public String getWritePermission() {
+        return mWritePermission;
+    }
+    
+    public void writeToParcel(Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
+        dest.writeString(mReadPermission);
+        dest.writeString(mWritePermission);
+    }
+    
+    public PathPermission(Parcel src) {
+        super(src);
+        mReadPermission = src.readString();
+        mWritePermission = src.readString();
+    }
+    
+    public static final Parcelable.Creator<PathPermission> CREATOR
+            = new Parcelable.Creator<PathPermission>() {
+        public PathPermission createFromParcel(Parcel source) {
+            return new PathPermission(source);
+        }
+
+        public PathPermission[] newArray(int size) {
+            return new PathPermission[size];
+        }
+    };
+}
\ No newline at end of file
diff --git a/core/java/android/content/pm/ProviderInfo.java b/core/java/android/content/pm/ProviderInfo.java
index b67ddf6..d01460e 100644
--- a/core/java/android/content/pm/ProviderInfo.java
+++ b/core/java/android/content/pm/ProviderInfo.java
@@ -28,6 +28,7 @@
  */
 public final class ProviderInfo extends ComponentInfo
         implements Parcelable {
+    
     /** The name provider is published under content:// */
     public String authority = null;
     
@@ -56,6 +57,14 @@
      */
     public PatternMatcher[] uriPermissionPatterns = null;
     
+    /**
+     * If non-null, these are path-specific permissions that are allowed for
+     * accessing the provider.  Any permissions listed here will allow a
+     * holding client to access the provider, and the provider will check
+     * the URI it provides when making calls against the patterns here.
+     */
+    public PathPermission[] pathPermissions = null;
+    
     /** If true, this content provider allows multiple instances of itself
      *  to run in different process.  If false, a single instances is always
      *  run in {@link #processName}. */
@@ -78,6 +87,7 @@
         writePermission = orig.writePermission;
         grantUriPermissions = orig.grantUriPermissions;
         uriPermissionPatterns = orig.uriPermissionPatterns;
+        pathPermissions = orig.pathPermissions;
         multiprocess = orig.multiprocess;
         initOrder = orig.initOrder;
         isSyncable = orig.isSyncable;
@@ -94,6 +104,7 @@
         out.writeString(writePermission);
         out.writeInt(grantUriPermissions ? 1 : 0);
         out.writeTypedArray(uriPermissionPatterns, parcelableFlags);
+        out.writeTypedArray(pathPermissions, parcelableFlags);
         out.writeInt(multiprocess ? 1 : 0);
         out.writeInt(initOrder);
         out.writeInt(isSyncable ? 1 : 0);
@@ -122,6 +133,7 @@
         writePermission = in.readString();
         grantUriPermissions = in.readInt() != 0;
         uriPermissionPatterns = in.createTypedArray(PatternMatcher.CREATOR);
+        pathPermissions = in.createTypedArray(PathPermission.CREATOR);
         multiprocess = in.readInt() != 0;
         initOrder = in.readInt();
         isSyncable = in.readInt() != 0;
diff --git a/core/java/android/content/res/CompatibilityInfo.java b/core/java/android/content/res/CompatibilityInfo.java
index dfe304d..ebe556e 100644
--- a/core/java/android/content/res/CompatibilityInfo.java
+++ b/core/java/android/content/res/CompatibilityInfo.java
@@ -51,18 +51,6 @@
     public static final int DEFAULT_PORTRAIT_HEIGHT = 480;
 
     /**
-     * The x-shift mode that controls the position of the content or the window under
-     * compatibility mode.
-     * {@see getTranslator}
-     * {@see Translator#mShiftMode}
-     */
-    private static final int X_SHIFT_NONE = 0;
-    private static final int X_SHIFT_CONTENT = 1;
-    private static final int X_SHIFT_AND_CLIP_CONTENT = 2;
-    private static final int X_SHIFT_WINDOW = 3;
-
-
-    /**
      *  A compatibility flags
      */
     private int mCompatibilityFlags;
@@ -106,20 +94,6 @@
      */
     public final int appFlags;
     
-    /**
-     * Window size in Compatibility Mode, in real pixels. This is updated by
-     * {@link DisplayMetrics#updateMetrics}.
-     */
-    private int mWidth;
-    private int mHeight;
-    
-    /**
-     * The x offset to center the window content. In X_SHIFT_WINDOW mode, the offset is added
-     * to the window's layout. In X_SHIFT_CONTENT/X_SHIFT_AND_CLIP_CONTENT mode, the offset
-     * is used to translate the Canvas.
-     */
-    private int mXOffset;
-
     public CompatibilityInfo(ApplicationInfo appInfo) {
         appFlags = appInfo.flags;
         
@@ -153,6 +127,7 @@
             applicationScale =
                     DisplayMetrics.DEVICE_DENSITY / (float) DisplayMetrics.DEFAULT_DENSITY;
         }
+
         applicationInvertedScale = 1.0f / applicationScale;
         if (applicationScale != 1.0f) {
             mCompatibilityFlags |= SCALING_REQUIRED;
@@ -181,23 +156,10 @@
     public CompatibilityInfo copy() {
         CompatibilityInfo info = new CompatibilityInfo(appFlags, mCompatibilityFlags,
                 applicationScale, applicationInvertedScale);
-        info.setVisibleRect(mXOffset, mWidth, mHeight);
         return info;
     }
  
     /**
-     * Sets the application's visible rect in compatibility mode.
-     * @param xOffset the application's x offset that is added to center the content.
-     * @param widthPixels the application's width in real pixels on the screen.
-     * @param heightPixels the application's height in real pixels on the screen.
-     */
-    public void setVisibleRect(int xOffset, int widthPixels, int heightPixels) {
-        this.mXOffset = xOffset; 
-        mWidth = widthPixels;
-        mHeight = heightPixels;
-    }
-    
-    /**
      * Sets expandable bit in the compatibility flag.
      */
     public void setExpandable(boolean expandable) {
@@ -222,6 +184,10 @@
         return (mCompatibilityFlags & SCALING_REQUIRED) != 0;
     }
     
+    public boolean supportsScreen() {
+        return (mCompatibilityFlags & CompatibilityInfo.EXPANDABLE) != 0;
+    }
+    
     @Override
     public String toString() {
         return "CompatibilityInfo{scale=" + applicationScale +
@@ -231,21 +197,6 @@
     /**
      * Returns the translator which can translate the coordinates of the window.
      * There are five different types of Translator.
-     * 
-     * 1) {@link CompatibilityInfo#X_SHIFT_AND_CLIP_CONTENT}
-     *   Shift and clip the content of the window at drawing time. Used for activities'
-     *   main window (with no gravity).
-     * 2) {@link CompatibilityInfo#X_SHIFT_CONTENT}
-     *   Shift the content of the window at drawing time. Used for windows that is created by
-     *   an application and expected to be aligned with the application window.
-     * 3) {@link CompatibilityInfo#X_SHIFT_WINDOW}
-     *   Create the window with adjusted x- coordinates. This is typically used 
-     *   in popup window, where it has to be placed relative to main window.
-     * 4) {@link CompatibilityInfo#X_SHIFT_NONE}
-     *   No adjustment required, such as dialog.
-     * 5) Same as X_SHIFT_WINDOW, but no scaling. This is used by {@link SurfaceView}, which
-     *  does not require scaling, but its window's location has to be adjusted.
-     * 
      * @param params the window's parameter
      */
     public Translator getTranslator(WindowManager.LayoutParams params) {
@@ -254,35 +205,11 @@
             if (DBG) Log.d(TAG, "no translation required");
             return null;
         }
-        
-        if ((mCompatibilityFlags & CompatibilityInfo.EXPANDABLE) == 0) {
-            if ((params.flags & WindowManager.LayoutParams.FLAG_NO_COMPATIBILITY_SCALING) != 0) {
-                if (DBG) Log.d(TAG, "translation for surface view selected");
-                return new Translator(X_SHIFT_WINDOW, false, 1.0f, 1.0f);
-            } else {
-                int shiftMode;
-                if (params.gravity == Gravity.NO_GRAVITY) {
-                    // For Regular Application window
-                    shiftMode = X_SHIFT_AND_CLIP_CONTENT;
-                    if (DBG) Log.d(TAG, "shift and clip translator");
-                } else if (params.width == WindowManager.LayoutParams.FILL_PARENT) {
-                    // For Regular Application window
-                    shiftMode = X_SHIFT_CONTENT;
-                    if (DBG) Log.d(TAG, "shift content translator");
-                } else if ((params.gravity & Gravity.LEFT) != 0 && params.x > 0) {
-                    shiftMode = X_SHIFT_WINDOW;
-                    if (DBG) Log.d(TAG, "shift window translator");
-                } else {
-                    shiftMode = X_SHIFT_NONE;
-                    if (DBG) Log.d(TAG, "no content/window translator");
-                }
-                return new Translator(shiftMode);
-            }
-        } else if (isScalingRequired()) {
-            return new Translator();
-        } else {
+        if (!isScalingRequired() ||
+            (params.flags & WindowManager.LayoutParams.FLAG_NO_COMPATIBILITY_SCALING) != 0) {
             return null;
         }
+        return new Translator();
     }
 
     /**
@@ -290,97 +217,48 @@
      * @hide
      */
     public class Translator {
-        final private int mShiftMode;
-        final public boolean scalingRequired;
         final public float applicationScale;
         final public float applicationInvertedScale;
         
         private Rect mContentInsetsBuffer = null;
-        private Rect mVisibleInsets = null;
+        private Rect mVisibleInsetsBuffer = null;
         
-        Translator(int shiftMode, boolean scalingRequired, float applicationScale,
-                float applicationInvertedScale) {
-            mShiftMode = shiftMode;
-            this.scalingRequired = scalingRequired;
+        Translator(float applicationScale, float applicationInvertedScale) {
             this.applicationScale = applicationScale;
             this.applicationInvertedScale = applicationInvertedScale;
         }
 
-        Translator(int shiftMode) {
-            this(shiftMode,
-                    isScalingRequired(),
-                    CompatibilityInfo.this.applicationScale,
-                    CompatibilityInfo.this.applicationInvertedScale);
-        }
-        
         Translator() {
-            this(X_SHIFT_NONE);
+            this(CompatibilityInfo.this.applicationScale,
+                    CompatibilityInfo.this.applicationInvertedScale);
         }
 
         /**
          * Translate the screen rect to the application frame.
          */
         public void translateRectInScreenToAppWinFrame(Rect rect) {
-            if (rect.isEmpty()) return; // skip if the window size is empty.
-            switch (mShiftMode) {
-                case X_SHIFT_AND_CLIP_CONTENT:
-                    rect.intersect(0, 0, mWidth, mHeight);
-                    break;
-                case X_SHIFT_CONTENT:
-                    rect.intersect(0, 0, mWidth + mXOffset, mHeight);
-                    break;
-                case X_SHIFT_WINDOW:
-                case X_SHIFT_NONE:
-                    break;
-            }
-            if (scalingRequired) {
-                rect.scale(applicationInvertedScale);
-            }
+            rect.scale(applicationInvertedScale);
         }
 
         /**
          * Translate the region in window to screen. 
          */
         public void translateRegionInWindowToScreen(Region transparentRegion) {
-            switch (mShiftMode) {
-                case X_SHIFT_AND_CLIP_CONTENT:
-                case X_SHIFT_CONTENT:
-                    transparentRegion.scale(applicationScale);
-                    transparentRegion.translate(mXOffset, 0);
-                    break;
-                case X_SHIFT_WINDOW:
-                case X_SHIFT_NONE:
-                    transparentRegion.scale(applicationScale);
-            }
+            transparentRegion.scale(applicationScale);
         }
 
         /**
          * Apply translation to the canvas that is necessary to draw the content.
          */
         public void translateCanvas(Canvas canvas) {
-            if (mShiftMode == X_SHIFT_CONTENT ||
-                    mShiftMode == X_SHIFT_AND_CLIP_CONTENT) {
-                // TODO: clear outside when rotation is changed.
-
-                // Translate x-offset only when the content is shifted.
-                canvas.translate(mXOffset, 0);
-            }
-            if (scalingRequired) {
-                canvas.scale(applicationScale, applicationScale);
-            }
+            canvas.scale(applicationScale, applicationScale);
         }
 
         /**
          * Translate the motion event captured on screen to the application's window.
          */
         public void translateEventInScreenToAppWindow(MotionEvent event) {
-            if (mShiftMode == X_SHIFT_CONTENT ||
-                    mShiftMode == X_SHIFT_AND_CLIP_CONTENT) {
-                event.translate(-mXOffset, 0);
-            }
-            if (scalingRequired) {
-                event.scale(applicationInvertedScale);
-            }
+            event.scale(applicationInvertedScale);
         }
 
         /**
@@ -388,62 +266,21 @@
          * Screen's view.
          */
         public void translateWindowLayout(WindowManager.LayoutParams params) {
-            switch (mShiftMode) {
-                case X_SHIFT_NONE:
-                case X_SHIFT_AND_CLIP_CONTENT:
-                case X_SHIFT_CONTENT:
-                    params.scale(applicationScale);
-                    break;
-                case X_SHIFT_WINDOW:
-                    params.scale(applicationScale);
-                    params.x += mXOffset;
-                    break;
-            }
+            params.scale(applicationScale);
         }
         
         /**
          * Translate a Rect in application's window to screen.
          */
         public void translateRectInAppWindowToScreen(Rect rect) {
-            // TODO Auto-generated method stub
-            if (scalingRequired) {
-                rect.scale(applicationScale);
-            }
-            switch(mShiftMode) {
-                case X_SHIFT_NONE:
-                case X_SHIFT_WINDOW:
-                    break;
-                case X_SHIFT_CONTENT:
-                case X_SHIFT_AND_CLIP_CONTENT:
-                    rect.offset(mXOffset, 0);
-                    break;
-            }
+            rect.scale(applicationScale);
         }
  
         /**
          * Translate a Rect in screen coordinates into the app window's coordinates.
          */
         public void translateRectInScreenToAppWindow(Rect rect) {
-            switch (mShiftMode) {
-                case X_SHIFT_NONE:
-                case X_SHIFT_WINDOW:
-                    break;
-                case X_SHIFT_CONTENT: {
-                    rect.intersects(mXOffset, 0, rect.right, rect.bottom);
-                    int dx = Math.min(mXOffset, rect.left);
-                    rect.offset(-dx, 0);
-                    break;
-                }
-                case X_SHIFT_AND_CLIP_CONTENT: {
-                    rect.intersects(mXOffset, 0, mWidth + mXOffset, mHeight);
-                    int dx = Math.min(mXOffset, rect.left);
-                    rect.offset(-dx, 0);
-                    break;
-                }
-            }
-            if (scalingRequired) {
-                rect.scale(applicationInvertedScale);
-            }
+            rect.scale(applicationInvertedScale);
         }
 
         /**
@@ -451,19 +288,7 @@
          * @param params
          */
         public void translateLayoutParamsInAppWindowToScreen(LayoutParams params) {
-            if (scalingRequired) {
-                params.scale(applicationScale);
-            }
-            switch (mShiftMode) {
-                // the window location on these mode does not require adjustmenet.
-                case X_SHIFT_NONE:
-                case X_SHIFT_WINDOW:
-                    break;
-                case X_SHIFT_CONTENT:
-                case X_SHIFT_AND_CLIP_CONTENT:
-                    params.x += mXOffset;
-                    break;
-            }
+            params.scale(applicationScale);
         }
 
         /**
@@ -482,10 +307,31 @@
          * the internal buffer for content insets to avoid extra object allocation.
          */
         public Rect getTranslatedVisbileInsets(Rect visibleInsets) {
-            if (mVisibleInsets == null) mVisibleInsets = new Rect();
-            mVisibleInsets.set(visibleInsets);
-            translateRectInAppWindowToScreen(mVisibleInsets);
-            return mVisibleInsets;
+            if (mVisibleInsetsBuffer == null) mVisibleInsetsBuffer = new Rect();
+            mVisibleInsetsBuffer.set(visibleInsets);
+            translateRectInAppWindowToScreen(mVisibleInsetsBuffer);
+            return mVisibleInsetsBuffer;
+        }
+    }
+
+    /**
+     * Returns the frame Rect for applications runs under compatibility mode.
+     *
+     * @param dm the display metrics used to compute the frame size.
+     * @param orientation the orientation of the screen.
+     * @param outRect the output parameter which will contain the result.
+     */
+    public static void updateCompatibleScreenFrame(DisplayMetrics dm, int orientation,
+            Rect outRect) {
+        int width = dm.widthPixels;
+        int portraitHeight = (int) (DEFAULT_PORTRAIT_HEIGHT * dm.density);
+        int portraitWidth = (int) (DEFAULT_PORTRAIT_WIDTH * dm.density);
+        if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
+            int xOffset = (width - portraitHeight) / 2 ;
+            outRect.set(xOffset, 0, xOffset + portraitHeight, portraitWidth);
+        } else {
+            int xOffset = (width - portraitWidth) / 2 ;
+            outRect.set(xOffset, 0, xOffset + portraitWidth, portraitHeight);
         }
     }
 }
diff --git a/core/java/android/speech/tts/TextToSpeech.java b/core/java/android/speech/tts/TextToSpeech.java
index 616b3f1..ed1e4ff6 100644
--- a/core/java/android/speech/tts/TextToSpeech.java
+++ b/core/java/android/speech/tts/TextToSpeech.java
@@ -107,9 +107,7 @@
         public static final int FALLBACK_TTS_DEFAULT_RATE = 100; // 1x
         public static final int FALLBACK_TTS_DEFAULT_PITCH = 100;// 1x
         public static final int FALLBACK_TTS_USE_DEFAULTS = 0; // false
-        public static final String FALLBACK_TTS_DEFAULT_LANG = "eng";
-        public static final String FALLBACK_TTS_DEFAULT_COUNTRY = "";
-        public static final String FALLBACK_TTS_DEFAULT_VARIANT = "";
+        public static final String FALLBACK_TTS_DEFAULT_SYNTH = "com.svox.pico";
 
         // return codes for a TTS engine's check data activity
         public static final int CHECK_VOICE_DATA_PASS = 1;
@@ -608,6 +606,7 @@
                 result = mITts.setLanguage(mCachedParams[Engine.TTS_PARAM_POSITION_LANGUAGE + 1],
                         mCachedParams[Engine.TTS_PARAM_POSITION_COUNTRY + 1],
                         mCachedParams[Engine.TTS_PARAM_POSITION_VARIANT + 1] );
+            } catch (RemoteException e) {
                 // TTS died; restart it.
                 mStarted = false;
                 initTts();
diff --git a/core/java/android/util/DisplayMetrics.java b/core/java/android/util/DisplayMetrics.java
index 4179edb..9071bf0 100644
--- a/core/java/android/util/DisplayMetrics.java
+++ b/core/java/android/util/DisplayMetrics.java
@@ -109,7 +109,6 @@
      */
     public void updateMetrics(CompatibilityInfo compatibilityInfo, int orientation,
             int screenLayout) {
-        int xOffset = 0;
         if (!compatibilityInfo.isConfiguredExpandable()) {
             // Note: this assume that configuration is updated before calling
             // updateMetrics method.
@@ -142,7 +141,6 @@
                 
                 if (defaultWidth < widthPixels) {
                     // content/window's x offset in original pixels
-                    xOffset = ((widthPixels - defaultWidth) / 2);
                     widthPixels = defaultWidth;
                 }
                 if (defaultHeight < heightPixels) {
@@ -154,7 +152,6 @@
                 compatibilityInfo.setExpandable(true);
             }
         }
-        compatibilityInfo.setVisibleRect(xOffset, widthPixels, heightPixels);
         if (compatibilityInfo.isScalingRequired()) {
             float invertedRatio = compatibilityInfo.applicationInvertedScale;
             density *= invertedRatio;
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 45b0f0a7..ff1eb53 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -306,7 +306,7 @@
 
         // Use original size if the app specified the size of the view,
         // and let the flinger to scale up.
-        if (mRequestedWidth <= 0 && mTranslator != null && mTranslator.scalingRequired) {
+        if (mRequestedWidth <= 0 && mTranslator != null) {
             myWidth *= appScale;
             myHeight *= appScale;
         }
diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewRoot.java
index 6f6e2247..6bcb135 100644
--- a/core/java/android/view/ViewRoot.java
+++ b/core/java/android/view/ViewRoot.java
@@ -385,6 +385,7 @@
             if (mView == null) {
                 mView = view;
                 mWindowAttributes.copyFrom(attrs);
+                attrs = mWindowAttributes;
 
                 CompatibilityInfo compatibilityInfo =
                         mView.getContext().getResources().getCompatibilityInfo();
@@ -397,11 +398,14 @@
                 }
                 if (DEBUG_LAYOUT) Log.d(TAG, "WindowLayout in setView:" + attrs);
 
+                if (!compatibilityInfo.supportsScreen()) {
+                    attrs.flags |= WindowManager.LayoutParams.FLAG_COMPATIBLE_WINDOW;
+                }
+
                 mSoftInputMode = attrs.softInputMode;
                 mWindowAttributesChanged = true;
                 mAttachInfo.mRootView = view;
-                mAttachInfo.mScalingRequired =
-                        mTranslator == null ? false : mTranslator.scalingRequired;
+                mAttachInfo.mScalingRequired = mTranslator == null ? false : true;
                 mAttachInfo.mApplicationScale =
                         mTranslator == null ? 1.0f : mTranslator.applicationScale;
                 if (panelParentView != null) {
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index bdb86d7..e96a15b 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -484,11 +484,19 @@
         public static final int FLAG_SHOW_WHEN_LOCKED = 0x00080000;
 
         /** Window flag: special flag to let a window ignore the compatibility scaling.
-         * This is used by SurfaceView to create a window that does not scale the content.
+         * This is used by SurfaceView to pass this info into ViewRoot, and not used
+         * by WindowManager.
          *
          * {@hide} */
         public static final int FLAG_NO_COMPATIBILITY_SCALING = 0x00100000;
 
+        /** Window flag: special flag to limit the size of the window to be
+         * original size ([320x480] x density). Used to create window for applications
+         * running under compatibility mode.
+         *
+         * {@hide} */
+        public static final int FLAG_COMPATIBLE_WINDOW = 0x00200000;
+
         /** Window flag: a special option intended for system dialogs.  When
          * this flag is set, the window will demand focus unconditionally when
          * it is created.
diff --git a/core/jni/android_hardware_Camera.cpp b/core/jni/android_hardware_Camera.cpp
index 77a8a72..57b5aa6 100644
--- a/core/jni/android_hardware_Camera.cpp
+++ b/core/jni/android_hardware_Camera.cpp
@@ -55,6 +55,7 @@
     ~JNICameraContext() { release(); }
     virtual void notify(int32_t msgType, int32_t ext1, int32_t ext2);
     virtual void postData(int32_t msgType, const sp<IMemory>& dataPtr);
+    virtual void postDataTimestamp(nsecs_t timestamp, int32_t msgType, const sp<IMemory>& dataPtr);
     sp<Camera> getCamera() { Mutex::Autolock _l(mLock); return mCamera; }
     void release();
 
@@ -188,6 +189,12 @@
     }
 }
 
+void JNICameraContext::postDataTimestamp(nsecs_t timestamp, int32_t msgType, const sp<IMemory>& dataPtr)
+{
+    // TODO: plumb up to Java. For now, just drop the timestamp
+    postData(msgType, dataPtr);
+}
+
 // connect to camera service
 static void android_hardware_Camera_native_setup(JNIEnv *env, jobject thiz, jobject weak_this)
 {
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 599360f..23967f4 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -995,6 +995,29 @@
         android:description="@string/permdesc_changeBackgroundDataSetting"
         android:label="@string/permlab_changeBackgroundDataSetting" />
 
+    <!-- This permission can be used on content providers to allow the global
+         search system to access their data.  Typically it used when the
+         provider has some permissions protecting it (which global search
+         would not be expected to hold), and added as a read-only permission
+         to the path in the provider where global search queries are
+         performed.  This permission can not be held by regular applications;
+         it is used by applications to protect themselves from everyone else
+         besides global search. -->
+    <permission android:name="android.permission.GLOBAL_SEARCH"
+        android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
+        android:protectionLevel="signatureOrSystem" />
+
+    <!-- Internal permission protecting access to the global search
+         system: ensures that only the system can access the provider
+         to perform queries (since this otherwise provides unrestricted
+         access to a variety of content providers), and to write the
+         search statistics (to keep applications from gaming the source
+         ranking).
+         @hide -->
+    <permission android:name="android.permission.GLOBAL_SEARCH_CONTROL"
+        android:permissionGroup="android.permission-group.SYSTEM_TOOLS"
+        android:protectionLevel="signature" />
+
     <application android:process="system"
                  android:persistent="true"
                  android:hasCode="false"
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 7571e24..12a76ba 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -953,6 +953,20 @@
         <attr name="pathPattern" format="string" />
     </declare-styleable>
     
+    <!-- Attributes that can be supplied in an AndroidManifest.xml
+         <code>path-permission</code> tag, a child of the
+         {@link #AndroidManifestProvider provider} tag, describing a permission
+         that allows access to a specific path in the provider.  This tag can be
+         specified multiple time to supply multiple paths. -->
+    <declare-styleable name="AndroidManifestPathPermission"  parent="AndroidManifestProvider">
+        <attr name="path" />
+        <attr name="pathPrefix" />
+        <attr name="pathPattern" />
+        <attr name="permission" />
+        <attr name="readPermission" />
+        <attr name="writePermission" />
+    </declare-styleable>
+    
     <!-- The <code>service</code> tag declares a
          {@link android.app.Service} class that is available
          as part of the package's application components, implementing
diff --git a/include/ui/Camera.h b/include/ui/Camera.h
index e3544ab..afb07b5 100644
--- a/include/ui/Camera.h
+++ b/include/ui/Camera.h
@@ -18,6 +18,7 @@
 #ifndef ANDROID_HARDWARE_CAMERA_H
 #define ANDROID_HARDWARE_CAMERA_H
 
+#include <utils/Timers.h>
 #include <ui/ICameraClient.h>
 
 namespace android {
@@ -94,6 +95,7 @@
 public:
     virtual void notify(int32_t msgType, int32_t ext1, int32_t ext2) = 0;
     virtual void postData(int32_t msgType, const sp<IMemory>& dataPtr) = 0;
+    virtual void postDataTimestamp(nsecs_t timestamp, int32_t msgType, const sp<IMemory>& dataPtr) = 0;
 };
 
 class Camera : public BnCameraClient, public IBinder::DeathRecipient
@@ -155,6 +157,7 @@
     // ICameraClient interface
     virtual void        notifyCallback(int32_t msgType, int32_t ext, int32_t ext2);
     virtual void        dataCallback(int32_t msgType, const sp<IMemory>& dataPtr);
+    virtual void        dataCallbackTimestamp(nsecs_t timestamp, int32_t msgType, const sp<IMemory>& dataPtr);
 
     sp<ICamera>         remote();
 
diff --git a/include/ui/CameraHardwareInterface.h b/include/ui/CameraHardwareInterface.h
index 73036f0..822b4a8 100644
--- a/include/ui/CameraHardwareInterface.h
+++ b/include/ui/CameraHardwareInterface.h
@@ -28,7 +28,7 @@
 typedef void (*preview_callback)(const sp<IMemory>& mem, void* user);
 
 /** Callback for startRecord() */
-typedef void (*recording_callback)(const sp<IMemory>& mem, void* user);
+typedef void (*recording_callback)(nsecs_t timestamp, const sp<IMemory>& mem, void* user);
 
 /** Callback for takePicture() */
 typedef void (*shutter_callback)(void* user);
diff --git a/include/ui/ICameraClient.h b/include/ui/ICameraClient.h
index c4bdd07..1001c71 100644
--- a/include/ui/ICameraClient.h
+++ b/include/ui/ICameraClient.h
@@ -21,6 +21,7 @@
 #include <utils/IInterface.h>
 #include <utils/Parcel.h>
 #include <utils/IMemory.h>
+#include <utils/Timers.h>
 
 namespace android {
 
@@ -31,7 +32,7 @@
 
     virtual void            notifyCallback(int32_t msgType, int32_t ext1, int32_t ext2) = 0;
     virtual void            dataCallback(int32_t msgType, const sp<IMemory>& data) = 0;
-
+    virtual void            dataCallbackTimestamp(nsecs_t timestamp, int32_t msgType, const sp<IMemory>& data) = 0;
 };
 
 // ----------------------------------------------------------------------------
diff --git a/include/utils/String8.h b/include/utils/String8.h
index c49faf6..ecc5774 100644
--- a/include/utils/String8.h
+++ b/include/utils/String8.h
@@ -29,11 +29,107 @@
 
 // ---------------------------------------------------------------------------
 
+extern "C" {
+
+typedef uint32_t char32_t;
+
+size_t strlen32(const char32_t *);
+size_t strnlen32(const char32_t *, size_t);
+
+/*
+ * Returns the length of "src" when "src" is valid UTF-8 string.
+ * Returns 0 if src is NULL, 0-length string or non UTF-8 string.
+ * This function should be used to determine whether "src" is valid UTF-8
+ * characters with valid unicode codepoints. "src" must be null-terminated.
+ *
+ * If you are going to use other GetUtf... functions defined in this header
+ * with string which may not be valid UTF-8 with valid codepoint (form 0 to
+ * 0x10FFFF), you should use this function before calling others, since the
+ * other functions do not check whether the string is valid UTF-8 or not.
+ *
+ * If you do not care whether "src" is valid UTF-8 or not, you should use
+ * strlen() as usual, which should be much faster.
+ */
+size_t utf8_length(const char *src);
+
+/*
+ * Returns the UTF-32 length of "src".
+ */
+size_t utf32_length(const char *src, size_t src_len);
+
+/*
+ * Returns the UTF-8 length of "src".
+ */
+size_t utf8_length_from_utf32(const char32_t *src, size_t src_len);
+
+/*
+ * Returns the unicode value at "index".
+ * Returns -1 when the index is invalid (equals to or more than "src_len").
+ * If returned value is positive, it is able to be converted to char32_t, which
+ * is unsigned. Then, if "next_index" is not NULL, the next index to be used is
+ * stored in "next_index". "next_index" can be NULL.
+ */
+int32_t utf32_at(const char *src, size_t src_len,
+                 size_t index, size_t *next_index);
+
+/*
+ * Stores a UTF-32 string converted from "src" in "dst", if "dst_length" is not
+ * large enough to store the string, the part of the "src" string is stored
+ * into "dst".
+ * Returns the size actually used for storing the string.
+ * "dst" is not null-terminated when dst_len is fully used (like strncpy).
+ */
+size_t utf8_to_utf32(const char* src, size_t src_len,
+                     char32_t* dst, size_t dst_len);
+
+/*
+ * Stores a UTF-8 string converted from "src" in "dst", if "dst_length" is not
+ * large enough to store the string, the part of the "src" string is stored
+ * into "dst" as much as possible. See the examples for more detail.
+ * Returns the size actually used for storing the string.
+ * dst" is not null-terminated when dst_len is fully used (like strncpy).
+ *
+ * Example 1
+ * "src" == \u3042\u3044 (\xE3\x81\x82\xE3\x81\x84)
+ * "src_len" == 2
+ * "dst_len" >= 7
+ * ->
+ * Returned value == 6
+ * "dst" becomes \xE3\x81\x82\xE3\x81\x84\0
+ * (note that "dst" is null-terminated)
+ *
+ * Example 2
+ * "src" == \u3042\u3044 (\xE3\x81\x82\xE3\x81\x84)
+ * "src_len" == 2
+ * "dst_len" == 5
+ * ->
+ * Returned value == 3
+ * "dst" becomes \xE3\x81\x82\0
+ * (note that "dst" is null-terminated, but \u3044 is not stored in "dst"
+ * since "dst" does not have enough size to store the character)
+ *
+ * Example 3
+ * "src" == \u3042\u3044 (\xE3\x81\x82\xE3\x81\x84)
+ * "src_len" == 2
+ * "dst_len" == 6
+ * ->
+ * Returned value == 6
+ * "dst" becomes \xE3\x81\x82\xE3\x81\x84
+ * (note that "dst" is NOT null-terminated, like strncpy)
+ */
+size_t utf32_to_utf8(const char32_t* src, size_t src_len,
+                     char* dst, size_t dst_len);
+
+}
+
+// ---------------------------------------------------------------------------
+
 namespace android {
 
 class TextOutput;
 
-//! This is a string holding UTF-8 characters.
+//! This is a string holding UTF-8 characters. Does not allow the value more
+// than 0x10FFFF, which is not valid unicode codepoint.
 class String8
 {
 public:
@@ -45,7 +141,8 @@
     explicit                    String8(const String16& o);
     explicit                    String8(const char16_t* o);
     explicit                    String8(const char16_t* o, size_t numChars);
-    
+    explicit                    String8(const char32_t* o);
+    explicit                    String8(const char32_t* o, size_t numChars);
                                 ~String8();
     
     inline  const char*         string() const;
@@ -59,11 +156,20 @@
             status_t            setTo(const char* other);
             status_t            setTo(const char* other, size_t numChars);
             status_t            setTo(const char16_t* other, size_t numChars);
-    
+            status_t            setTo(const char32_t* other,
+                                      size_t length);
+
             status_t            append(const String8& other);
             status_t            append(const char* other);
             status_t            append(const char* other, size_t numChars);
 
+            // Note that this function takes O(N) time to calculate the value.
+            // No cache value is stored.
+            size_t              getUtf32Length() const;
+            int32_t             getUtf32At(size_t index,
+                                           size_t *next_index) const;
+            size_t              getUtf32(char32_t* dst, size_t dst_len) const;
+
     inline  String8&            operator=(const String8& other);
     inline  String8&            operator=(const char* other);
     
@@ -103,7 +209,7 @@
             void                toLower(size_t start, size_t numChars);
             void                toUpper();
             void                toUpper(size_t start, size_t numChars);
-            
+
     /*
      * These methods operate on the string as if it were a path name.
      */
@@ -346,7 +452,7 @@
     return mString;
 }
 
-}; // namespace android
+}  // namespace android
 
 // ---------------------------------------------------------------------------
 
diff --git a/libs/ui/Camera.cpp b/libs/ui/Camera.cpp
index 975594f..5015379 100644
--- a/libs/ui/Camera.cpp
+++ b/libs/ui/Camera.cpp
@@ -310,6 +310,19 @@
     }
 }
 
+// callback from camera service when timestamped frame is ready
+void Camera::dataCallbackTimestamp(nsecs_t timestamp, int32_t msgType, const sp<IMemory>& dataPtr)
+{
+    sp<CameraListener> listener;
+    {
+        Mutex::Autolock _l(mLock);
+        listener = mListener;
+    }
+    if (listener != NULL) {
+        listener->postDataTimestamp(timestamp, msgType, dataPtr);
+    }
+}
+
 void Camera::binderDied(const wp<IBinder>& who) {
     LOGW("ICamera died");
     notifyCallback(CAMERA_MSG_ERROR, CAMERA_ERROR_SERVER_DIED, 0);
diff --git a/libs/ui/ICameraClient.cpp b/libs/ui/ICameraClient.cpp
index c6cf75c..59a6cf2 100644
--- a/libs/ui/ICameraClient.cpp
+++ b/libs/ui/ICameraClient.cpp
@@ -27,6 +27,7 @@
 enum {
     NOTIFY_CALLBACK = IBinder::FIRST_CALL_TRANSACTION,
     DATA_CALLBACK,
+    DATA_CALLBACK_TIMESTAMP,
 };
 
 class BpCameraClient: public BpInterface<ICameraClient>
@@ -60,6 +61,17 @@
         remote()->transact(DATA_CALLBACK, data, &reply, IBinder::FLAG_ONEWAY);
     }
 
+    // generic data callback from camera service to app with image data
+    void dataCallbackTimestamp(nsecs_t timestamp, int32_t msgType, const sp<IMemory>& imageData)
+    {
+        LOGV("dataCallback");
+        Parcel data, reply;
+        data.writeInterfaceToken(ICameraClient::getInterfaceDescriptor());
+        data.writeInt64(timestamp);
+        data.writeInt32(msgType);
+        data.writeStrongBinder(imageData->asBinder());
+        remote()->transact(DATA_CALLBACK_TIMESTAMP, data, &reply, IBinder::FLAG_ONEWAY);
+    }
 };
 
 IMPLEMENT_META_INTERFACE(CameraClient, "android.hardware.ICameraClient");
@@ -86,13 +98,22 @@
             return NO_ERROR;
         } break;
         case DATA_CALLBACK: {
-            LOGV("RAW_CALLBACK");
+            LOGV("DATA_CALLBACK");
             CHECK_INTERFACE(ICameraClient, data, reply);
             int32_t msgType = data.readInt32();
             sp<IMemory> imageData = interface_cast<IMemory>(data.readStrongBinder());
             dataCallback(msgType, imageData);
             return NO_ERROR;
         } break;
+        case DATA_CALLBACK_TIMESTAMP: {
+            LOGV("DATA_CALLBACK_TIMESTAMP");
+            CHECK_INTERFACE(ICameraClient, data, reply);
+            nsecs_t timestamp = data.readInt64();
+            int32_t msgType = data.readInt32();
+            sp<IMemory> imageData = interface_cast<IMemory>(data.readStrongBinder());
+            dataCallbackTimestamp(timestamp, msgType, imageData);
+            return NO_ERROR;
+        } break;
         default:
             return BBinder::onTransact(code, data, reply, flags);
     }
diff --git a/libs/utils/String8.cpp b/libs/utils/String8.cpp
index c50d343..e908ec1 100644
--- a/libs/utils/String8.cpp
+++ b/libs/utils/String8.cpp
@@ -25,25 +25,39 @@
 
 #include <ctype.h>
 
-namespace android {
+/*
+ * Functions outside android is below the namespace android, since they use
+ * functions and constants in android namespace.
+ */
 
 // ---------------------------------------------------------------------------
 
-static const uint32_t kByteMask = 0x000000BF;
-static const uint32_t kByteMark = 0x00000080;
+namespace android {
+
+static const char32_t kByteMask = 0x000000BF;
+static const char32_t kByteMark = 0x00000080;
 
 // Surrogates aren't valid for UTF-32 characters, so define some
 // constants that will let us screen them out.
-static const uint32_t kUnicodeSurrogateHighStart  = 0x0000D800;
-static const uint32_t kUnicodeSurrogateHighEnd    = 0x0000DBFF;
-static const uint32_t kUnicodeSurrogateLowStart   = 0x0000DC00;
-static const uint32_t kUnicodeSurrogateLowEnd     = 0x0000DFFF;
-static const uint32_t kUnicodeSurrogateStart      = kUnicodeSurrogateHighStart;
-static const uint32_t kUnicodeSurrogateEnd        = kUnicodeSurrogateLowEnd;
+static const char32_t kUnicodeSurrogateHighStart  = 0x0000D800;
+static const char32_t kUnicodeSurrogateHighEnd    = 0x0000DBFF;
+static const char32_t kUnicodeSurrogateLowStart   = 0x0000DC00;
+static const char32_t kUnicodeSurrogateLowEnd     = 0x0000DFFF;
+static const char32_t kUnicodeSurrogateStart      = kUnicodeSurrogateHighStart;
+static const char32_t kUnicodeSurrogateEnd        = kUnicodeSurrogateLowEnd;
+static const char32_t kUnicodeMaxCodepoint        = 0x0010FFFF;
 
 // Mask used to set appropriate bits in first byte of UTF-8 sequence,
 // indexed by number of bytes in the sequence.
-static const uint32_t kFirstByteMark[] = {
+// 0xxxxxxx
+// -> (00-7f) 7bit. Bit mask for the first byte is 0x00000000
+// 110yyyyx 10xxxxxx
+// -> (c0-df)(80-bf) 11bit. Bit mask is 0x000000C0
+// 1110yyyy 10yxxxxx 10xxxxxx
+// -> (e0-ef)(80-bf)(80-bf) 16bit. Bit mask is 0x000000E0
+// 11110yyy 10yyxxxx 10xxxxxx 10xxxxxx
+// -> (f0-f7)(80-bf)(80-bf)(80-bf) 21bit. Bit mask is 0x000000F0
+static const char32_t kFirstByteMark[] = {
     0x00000000, 0x00000000, 0x000000C0, 0x000000E0, 0x000000F0
 };
 
@@ -52,7 +66,7 @@
 #define RES_PATH_SEPARATOR '/'
 
 // Return number of utf8 bytes required for the character.
-static size_t utf32_to_utf8_bytes(uint32_t srcChar)
+static size_t utf32_to_utf8_bytes(char32_t srcChar)
 {
     size_t bytesToWrite;
 
@@ -79,7 +93,7 @@
         }
     }
     // Max code point for Unicode is 0x0010FFFF.
-    else if (srcChar < 0x00110000)
+    else if (srcChar <= kUnicodeMaxCodepoint)
     {
         bytesToWrite = 4;
     }
@@ -94,7 +108,7 @@
 
 // Write out the source character to <dstP>.
 
-static void utf32_to_utf8(uint8_t* dstP, uint32_t srcChar, size_t bytes)
+static void utf32_to_utf8(uint8_t* dstP, char32_t srcChar, size_t bytes)
 {
     dstP += bytes;
     switch (bytes)
@@ -126,7 +140,7 @@
 	  // Bite me, Darwin!
 		gDarwinIsReallyAnnoying = gDarwinCantLoadAllObjects;
 #endif
-			
+
     SharedBuffer* buf = SharedBuffer::alloc(1);
     char* str = (char*)buf->data();
     *str = 0;
@@ -160,20 +174,20 @@
     return getEmptyString();
 }
 
-// Note: not dealing with expanding surrogate pairs.
-static char* allocFromUTF16(const char16_t* in, size_t len)
+template<typename T, typename L>
+static char* allocFromUTF16OrUTF32(const T* in, L len)
 {
     if (len == 0) return getEmptyString();
-    
+
     size_t bytes = 0;
-    const char16_t* end = in+len;
-    const char16_t* p = in;
-    
+    const T* end = in+len;
+    const T* p = in;
+
     while (p < end) {
         bytes += utf32_to_utf8_bytes(*p);
         p++;
     }
-    
+
     SharedBuffer* buf = SharedBuffer::alloc(bytes+1);
     LOG_ASSERT(buf, "Unable to allocate shared buffer");
     if (buf) {
@@ -181,19 +195,30 @@
         char* str = (char*)buf->data();
         char* d = str;
         while (p < end) {
-            uint32_t c = *p++;
+            const T c = *p++;
             size_t len = utf32_to_utf8_bytes(c);
             utf32_to_utf8((uint8_t*)d, c, len);
             d += len;
         }
         *d = 0;
-        
+
         return str;
     }
-    
+
     return getEmptyString();
 }
 
+// Note: not dealing with expanding surrogate pairs.
+static char* allocFromUTF16(const char16_t* in, size_t len)
+{
+    return allocFromUTF16OrUTF32<char16_t, size_t>(in, len);
+}
+
+static char* allocFromUTF32(const char32_t* in, size_t len)
+{
+    return allocFromUTF16OrUTF32<char32_t, size_t>(in, len);
+}
+
 // ---------------------------------------------------------------------------
 
 String8::String8()
@@ -238,6 +263,16 @@
 {
 }
 
+String8::String8(const char32_t* o)
+    : mString(allocFromUTF32(o, strlen32(o)))
+{
+}
+
+String8::String8(const char32_t* o, size_t len)
+    : mString(allocFromUTF32(o, len))
+{
+}
+
 String8::~String8()
 {
     SharedBuffer::bufferFromData(mString)->release();
@@ -280,6 +315,16 @@
     return NO_MEMORY;
 }
 
+status_t String8::setTo(const char32_t* other, size_t len)
+{
+    SharedBuffer::bufferFromData(mString)->release();
+    mString = allocFromUTF32(other, len);
+    if (mString) return NO_ERROR;
+
+    mString = getEmptyString();
+    return NO_MEMORY;
+}
+
 status_t String8::append(const String8& other)
 {
     const size_t otherLen = other.bytes();
@@ -418,6 +463,21 @@
     unlockBuffer(len);
 }
 
+size_t String8::getUtf32Length() const
+{
+    return utf32_length(mString, length());
+}
+
+int32_t String8::getUtf32At(size_t index, size_t *next_index) const
+{
+    return utf32_at(mString, length(), index, next_index);
+}
+
+size_t String8::getUtf32(char32_t* dst, size_t dst_len) const
+{
+    return utf8_to_utf32(mString, length(), dst, dst_len);
+}
+
 TextOutput& operator<<(TextOutput& to, const String8& val)
 {
     to << val.string();
@@ -427,7 +487,6 @@
 // ---------------------------------------------------------------------------
 // Path functions
 
-
 void String8::setPathName(const char* name)
 {
     setPathName(name, strlen(name));
@@ -600,5 +659,192 @@
     return *this;
 }
 
-
 }; // namespace android
+
+// ---------------------------------------------------------------------------
+
+size_t strlen32(const char32_t *s)
+{
+  const char32_t *ss = s;
+  while ( *ss )
+    ss++;
+  return ss-s;
+}
+
+size_t strnlen32(const char32_t *s, size_t maxlen)
+{
+  const char32_t *ss = s;
+  while ((maxlen > 0) && *ss) {
+    ss++;
+    maxlen--;
+  }
+  return ss-s;
+}
+
+size_t utf8_length(const char *src)
+{
+    const char *cur = src;
+    size_t ret = 0;
+    while (*cur != '\0') {
+        const char first_char = *cur++;
+        if ((first_char & 0x80) == 0) { // ASCII
+            ret += 1;
+            continue;
+        }
+        // (UTF-8's character must not be like 10xxxxxx,
+        //  but 110xxxxx, 1110xxxx, ... or 1111110x)
+        if ((first_char & 0x40) == 0) {
+            return 0;
+        }
+
+        int32_t mask, to_ignore_mask;
+        size_t num_to_read = 0;
+        char32_t utf32 = 0;
+        for (num_to_read = 1, mask = 0x40, to_ignore_mask = 0x80;
+             num_to_read < 5 && (first_char & mask);
+             num_to_read++, to_ignore_mask |= mask, mask >>= 1) {
+            if ((*cur & 0xC0) != 0x80) { // must be 10xxxxxx
+                return 0;
+            }
+            // 0x3F == 00111111
+            utf32 = (utf32 << 6) + (*cur++ & 0x3F);
+        }
+        // "first_char" must be (110xxxxx - 11110xxx)
+        if (num_to_read == 5) {
+            return 0;
+        }
+        to_ignore_mask |= mask;
+        utf32 |= ((~to_ignore_mask) & first_char) << (6 * (num_to_read - 1));
+        if (utf32 > android::kUnicodeMaxCodepoint) {
+            return 0;
+        }
+
+        ret += num_to_read;
+    }
+    return ret;
+}
+
+size_t utf32_length(const char *src, size_t src_len)
+{
+    if (src == NULL || src_len == 0) {
+        return 0;
+    }
+    size_t ret = 0;
+    const char* cur;
+    const char* end;
+    size_t num_to_skip;
+    for (cur = src, end = src + src_len, num_to_skip = 1;
+         cur < end;
+         cur += num_to_skip, ret++) {
+        const char first_char = *cur;
+        num_to_skip = 1;
+        if ((first_char & 0x80) == 0) {  // ASCII
+            continue;
+        }
+        int32_t mask;
+
+        for (mask = 0x40; (first_char & mask); num_to_skip++, mask >>= 1) {
+        }
+    }
+    return ret;
+}
+
+size_t utf8_length_from_utf32(const char32_t *src, size_t src_len)
+{
+    if (src == NULL || src_len == 0) {
+        return 0;
+    }
+    size_t ret = 0;
+    const char32_t *end = src + src_len;
+    while (src < end) {
+        ret += android::utf32_to_utf8_bytes(*src++);
+    }
+    return ret;
+}
+
+static int32_t utf32_at_internal(const char* cur, size_t *num_read)
+{
+    const char first_char = *cur;
+    if ((first_char & 0x80) == 0) { // ASCII
+        *num_read = 1;
+        return *cur;
+    }
+    cur++;
+    char32_t mask, to_ignore_mask;
+    size_t num_to_read = 0;
+    char32_t utf32 = first_char;
+    for (num_to_read = 1, mask = 0x40, to_ignore_mask = 0xFFFFFF80;
+         (first_char & mask);
+         num_to_read++, to_ignore_mask |= mask, mask >>= 1) {
+        // 0x3F == 00111111
+        utf32 = (utf32 << 6) + (*cur++ & 0x3F);
+    }
+    to_ignore_mask |= mask;
+    utf32 &= ~(to_ignore_mask << (6 * (num_to_read - 1)));
+
+    *num_read = num_to_read;
+    return static_cast<int32_t>(utf32);
+}
+
+int32_t utf32_at(const char *src, size_t src_len,
+                 size_t index, size_t *next_index)
+{
+    if (index >= src_len) {
+        return -1;
+    }
+    size_t dummy_index;
+    if (next_index == NULL) {
+        next_index = &dummy_index;
+    }
+    size_t num_read;
+    int32_t ret = utf32_at_internal(src + index, &num_read);
+    if (ret >= 0) {
+        *next_index = index + num_read;
+    }
+
+    return ret;
+}
+
+size_t utf8_to_utf32(const char* src, size_t src_len,
+                     char32_t* dst, size_t dst_len)
+{
+    if (src == NULL || src_len == 0 || dst == NULL || dst_len == 0) {
+        return 0;
+    }
+
+    const char* cur = src;
+    const char* end = src + src_len;
+    char32_t* cur_utf32 = dst;
+    const char32_t* end_utf32 = dst + dst_len;
+    while (cur_utf32 < end_utf32 && cur < end) {
+        size_t num_read;
+        *cur_utf32++ =
+                static_cast<char32_t>(utf32_at_internal(cur, &num_read));
+        cur += num_read;
+    }
+    if (cur_utf32 < end_utf32) {
+        *cur_utf32 = 0;
+    }
+    return static_cast<size_t>(cur_utf32 - dst);
+}
+
+size_t utf32_to_utf8(const char32_t* src, size_t src_len,
+                     char* dst, size_t dst_len)
+{
+    if (src == NULL || src_len == 0 || dst == NULL || dst_len == 0) {
+        return 0;
+    }
+    const char32_t *cur_utf32 = src;
+    const char32_t *end_utf32 = src + src_len;
+    char *cur = dst;
+    const char *end = dst + dst_len;
+    while (cur_utf32 < end_utf32 && cur < end) {
+        size_t len = android::utf32_to_utf8_bytes(*cur_utf32);
+        android::utf32_to_utf8((uint8_t *)cur, *cur_utf32++, len);
+        cur += len;
+    }
+    if (cur < end) {
+        *cur = '\0';
+    }
+    return cur - dst;
+}
diff --git a/media/java/android/media/ExifInterface.java b/media/java/android/media/ExifInterface.java
new file mode 100644
index 0000000..645f3f6
--- /dev/null
+++ b/media/java/android/media/ExifInterface.java
@@ -0,0 +1,398 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import android.util.Log;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Wrapper for native Exif library
+ * {@hide}
+ */
+public class ExifInterface {
+    private static final String TAG = "ExifInterface";
+    private String mFilename;
+
+    // Constants used for the Orientation Exif tag.
+    public static final int ORIENTATION_UNDEFINED = 0;
+    public static final int ORIENTATION_NORMAL = 1;
+
+    // Constants used for white balance
+    public static final int WHITEBALANCE_AUTO = 0;
+    public static final int WHITEBALANCE_MANUAL = 1;
+
+    // left right reversed mirror
+    public static final int ORIENTATION_FLIP_HORIZONTAL = 2;
+    public static final int ORIENTATION_ROTATE_180 = 3;
+
+    // upside down mirror
+    public static final int ORIENTATION_FLIP_VERTICAL = 4;
+
+    // flipped about top-left <--> bottom-right axis
+    public static final int ORIENTATION_TRANSPOSE = 5;
+
+    // rotate 90 cw to right it
+    public static final int ORIENTATION_ROTATE_90 = 6;
+
+    // flipped about top-right <--> bottom-left axis
+    public static final int ORIENTATION_TRANSVERSE = 7;
+
+    // rotate 270 to right it
+    public static final int ORIENTATION_ROTATE_270 = 8;
+
+    // The Exif tag names
+    public static final String TAG_ORIENTATION = "Orientation";
+
+    public static final String TAG_DATE_TIME_ORIGINAL = "DateTimeOriginal";
+    public static final String TAG_MAKE = "Make";
+    public static final String TAG_MODEL = "Model";
+    public static final String TAG_FLASH = "Flash";
+    public static final String TAG_IMAGE_WIDTH = "ImageWidth";
+    public static final String TAG_IMAGE_LENGTH = "ImageLength";
+
+    public static final String TAG_GPS_LATITUDE = "GPSLatitude";
+    public static final String TAG_GPS_LONGITUDE = "GPSLongitude";
+
+    public static final String TAG_GPS_LATITUDE_REF = "GPSLatitudeRef";
+    public static final String TAG_GPS_LONGITUDE_REF = "GPSLongitudeRef";
+    public static final String TAG_WHITE_BALANCE = "WhiteBalance";
+
+    private boolean mSavedAttributes = false;
+    private boolean mHasThumbnail = false;
+    private HashMap<String, String> mCachedAttributes = null;
+
+    static {
+        System.loadLibrary("exif");
+    }
+
+    private static ExifInterface sExifObj = null;
+    /**
+     * Since the underlying jhead native code is not thread-safe,
+     * ExifInterface should use singleton interface instead of public
+     * constructor.
+     */
+    private static synchronized ExifInterface instance() {
+        if (sExifObj == null) {
+            sExifObj = new ExifInterface();
+        }
+
+        return sExifObj;
+    }
+
+    /**
+     * The following 3 static methods are handy routines for atomic operation
+     * of underlying jhead library. It retrieves EXIF data and then release
+     * ExifInterface immediately.
+     */
+    public static synchronized HashMap<String, String> loadExifData(String filename) {
+        ExifInterface exif = instance();
+        HashMap<String, String> exifData = null;
+        if (exif != null) {
+            exif.setFilename(filename);
+            exifData = exif.getAttributes();
+        }
+        return exifData;
+    }
+
+    public static synchronized void saveExifData(String filename, HashMap<String, String> exifData) {
+        ExifInterface exif = instance();
+        if (exif != null) {
+            exif.setFilename(filename);
+            exif.saveAttributes(exifData);
+        }
+    }
+
+    public static synchronized byte[] getExifThumbnail(String filename) {
+        ExifInterface exif = instance();
+        if (exif != null) {
+            exif.setFilename(filename);
+            return exif.getThumbnail();
+        }
+        return null;
+    }
+
+    public void setFilename(String filename) {
+        mFilename = filename;
+    }
+
+    /**
+     * Given a HashMap of Exif tags and associated values, an Exif section in
+     * the JPG file is created and loaded with the tag data. saveAttributes()
+     * is expensive because it involves copying all the JPG data from one file
+     * to another and deleting the old file and renaming the other. It's best
+     * to collect all the attributes to write and make a single call rather
+     * than multiple calls for each attribute. You must call "commitChanges()"
+     * at some point to commit the changes.
+     */
+    public void saveAttributes(HashMap<String, String> attributes) {
+        // format of string passed to native C code:
+        // "attrCnt attr1=valueLen value1attr2=value2Len value2..."
+        // example:
+        // "4 attrPtr ImageLength=4 1024Model=6 FooImageWidth=4 1280Make=3 FOO"
+        StringBuilder sb = new StringBuilder();
+        int size = attributes.size();
+        if (attributes.containsKey("hasThumbnail")) {
+            --size;
+        }
+        sb.append(size + " ");
+        for (Map.Entry<String, String> iter : attributes.entrySet()) {
+            String key = iter.getKey();
+            if (key.equals("hasThumbnail")) {
+                // this is a fake attribute not saved as an exif tag
+                continue;
+            }
+            String val = iter.getValue();
+            sb.append(key + "=");
+            sb.append(val.length() + " ");
+            sb.append(val);
+        }
+        String s = sb.toString();
+        saveAttributesNative(mFilename, s);
+        commitChangesNative(mFilename);
+        mSavedAttributes = true;
+    }
+
+    /**
+     * Returns a HashMap loaded with the Exif attributes of the file. The key
+     * is the standard tag name and the value is the tag's value: e.g.
+     * Model -> Nikon. Numeric values are returned as strings.
+     */
+    public HashMap<String, String> getAttributes() {
+        if (mCachedAttributes != null) {
+            return mCachedAttributes;
+        }
+        // format of string passed from native C code:
+        // "attrCnt attr1=valueLen value1attr2=value2Len value2..."
+        // example:
+        // "4 attrPtr ImageLength=4 1024Model=6 FooImageWidth=4 1280Make=3 FOO"
+        mCachedAttributes = new HashMap<String, String>();
+
+        String attrStr = getAttributesNative(mFilename);
+
+        // get count
+        int ptr = attrStr.indexOf(' ');
+        int count = Integer.parseInt(attrStr.substring(0, ptr));
+        // skip past the space between item count and the rest of the attributes
+        ++ptr;
+
+        for (int i = 0; i < count; i++) {
+            // extract the attribute name
+            int equalPos = attrStr.indexOf('=', ptr);
+            String attrName = attrStr.substring(ptr, equalPos);
+            ptr = equalPos + 1;     // skip past =
+
+            // extract the attribute value length
+            int lenPos = attrStr.indexOf(' ', ptr);
+            int attrLen = Integer.parseInt(attrStr.substring(ptr, lenPos));
+            ptr = lenPos + 1;       // skip pas the space
+
+            // extract the attribute value
+            String attrValue = attrStr.substring(ptr, ptr + attrLen);
+            ptr += attrLen;
+
+            if (attrName.equals("hasThumbnail")) {
+                mHasThumbnail = attrValue.equalsIgnoreCase("true");
+            } else {
+                mCachedAttributes.put(attrName, attrValue);
+            }
+        }
+        return mCachedAttributes;
+    }
+
+    /**
+     * Given a numerical white balance value, return a
+     * human-readable string describing it.
+     */
+    public static String whiteBalanceToString(int whitebalance) {
+        switch (whitebalance) {
+            case WHITEBALANCE_AUTO:
+                return "Auto";
+            case WHITEBALANCE_MANUAL:
+                return "Manual";
+            default:
+                return "";
+        }
+    }
+
+    /**
+     * Given a numerical orientation, return a human-readable string describing
+     * the orientation.
+     */
+    public static String orientationToString(int orientation) {
+        // TODO: this function needs to be localized and use string resource ids
+        // rather than strings
+        String orientationString;
+        switch (orientation) {
+            case ORIENTATION_NORMAL:
+                orientationString = "Normal";
+                break;
+            case ORIENTATION_FLIP_HORIZONTAL:
+                orientationString = "Flipped horizontal";
+                break;
+            case ORIENTATION_ROTATE_180:
+                orientationString = "Rotated 180 degrees";
+                break;
+            case ORIENTATION_FLIP_VERTICAL:
+                orientationString = "Upside down mirror";
+                break;
+            case ORIENTATION_TRANSPOSE:
+                orientationString = "Transposed";
+                break;
+            case ORIENTATION_ROTATE_90:
+                orientationString = "Rotated 90 degrees";
+                break;
+            case ORIENTATION_TRANSVERSE:
+                orientationString = "Transversed";
+                break;
+            case ORIENTATION_ROTATE_270:
+                orientationString = "Rotated 270 degrees";
+                break;
+            default:
+                orientationString = "Undefined";
+                break;
+        }
+        return orientationString;
+    }
+
+    /**
+     * Copies the thumbnail data out of the filename and puts it in the Exif
+     * data associated with the file used to create this object. You must call
+     * "commitChanges()" at some point to commit the changes.
+     */
+    public boolean appendThumbnail(String thumbnailFileName) {
+        if (!mSavedAttributes) {
+            throw new RuntimeException("Must call saveAttributes "
+                    + "before calling appendThumbnail");
+        }
+        mHasThumbnail = appendThumbnailNative(mFilename, thumbnailFileName);
+        return mHasThumbnail;
+    }
+
+    public boolean hasThumbnail() {
+        if (!mSavedAttributes) {
+            getAttributes();
+        }
+        return mHasThumbnail;
+    }
+
+    public byte[] getThumbnail() {
+        return getThumbnailNative(mFilename);
+    }
+
+    public static float[] getLatLng(HashMap<String, String> exifData) {
+        if (exifData == null) {
+            return null;
+        }
+
+        String latValue = exifData.get(ExifInterface.TAG_GPS_LATITUDE);
+        String latRef = exifData.get(ExifInterface.TAG_GPS_LATITUDE_REF);
+        String lngValue = exifData.get(ExifInterface.TAG_GPS_LONGITUDE);
+        String lngRef = exifData.get(ExifInterface.TAG_GPS_LONGITUDE_REF);
+        float[] latlng = null;
+
+        if (latValue != null && latRef != null
+                && lngValue != null && lngRef != null) {
+            latlng = new float[2];
+            latlng[0] = ExifInterface.convertRationalLatLonToFloat(
+                    latValue, latRef);
+            latlng[1] = ExifInterface.convertRationalLatLonToFloat(
+                    lngValue, lngRef);
+        }
+
+        return latlng;
+    }
+
+    public static float convertRationalLatLonToFloat(
+            String rationalString, String ref) {
+        try {
+            String [] parts = rationalString.split(",");
+
+            String [] pair;
+            pair = parts[0].split("/");
+            int degrees = (int) (Float.parseFloat(pair[0].trim())
+                    / Float.parseFloat(pair[1].trim()));
+
+            pair = parts[1].split("/");
+            int minutes = (int) ((Float.parseFloat(pair[0].trim())
+                    / Float.parseFloat(pair[1].trim())));
+
+            pair = parts[2].split("/");
+            float seconds = Float.parseFloat(pair[0].trim())
+                    / Float.parseFloat(pair[1].trim());
+
+            float result = degrees + (minutes / 60F) + (seconds / (60F * 60F));
+            if ((ref.equals("S") || ref.equals("W"))) {
+                return -result;
+            }
+            return result;
+        } catch (RuntimeException ex) {
+            // if for whatever reason we can't parse the lat long then return
+            // null
+            return 0f;
+        }
+    }
+
+    public static String convertRationalLatLonToDecimalString(
+            String rationalString, String ref, boolean usePositiveNegative) {
+            float result = convertRationalLatLonToFloat(rationalString, ref);
+
+            String preliminaryResult = String.valueOf(result);
+            if (usePositiveNegative) {
+                String neg = (ref.equals("S") || ref.equals("E")) ? "-" : "";
+                return neg + preliminaryResult;
+            } else {
+                return preliminaryResult + String.valueOf((char) 186) + " "
+                        + ref;
+            }
+    }
+
+    public static String makeLatLongString(double d) {
+        d = Math.abs(d);
+
+        int degrees = (int) d;
+
+        double remainder = d - degrees;
+        int minutes = (int) (remainder * 60D);
+        // really seconds * 1000
+        int seconds = (int) (((remainder * 60D) - minutes) * 60D * 1000D);
+
+        String retVal = degrees + "/1," + minutes + "/1," + seconds + "/1000";
+        return retVal;
+    }
+
+    public static String makeLatStringRef(double lat) {
+        return lat >= 0D ? "N" : "S";
+    }
+
+    public static String makeLonStringRef(double lon) {
+        return lon >= 0D ? "W" : "E";
+    }
+
+    private native boolean appendThumbnailNative(String fileName,
+            String thumbnailFileName);
+
+    private native void saveAttributesNative(String fileName,
+            String compressedAttributes);
+
+    private native String getAttributesNative(String fileName);
+
+    private native void commitChangesNative(String fileName);
+
+    private native byte[] getThumbnailNative(String fileName);
+}
diff --git a/media/java/android/media/MediaScanner.java b/media/java/android/media/MediaScanner.java
index cccc0fc..6de7bc1 100644
--- a/media/java/android/media/MediaScanner.java
+++ b/media/java/android/media/MediaScanner.java
@@ -54,7 +54,7 @@
 
 /**
  * Internal service helper that no-one should use directly.
- * 
+ *
  * The way the scan currently works is:
  * - The Java MediaScannerService creates a MediaScanner (this class), and calls
  *   MediaScanner.scanDirectories on it.
@@ -96,7 +96,7 @@
  * {@hide}
  */
 public class MediaScanner
-{    
+{
     static {
         System.loadLibrary("media_jni");
     }
@@ -108,17 +108,17 @@
             Audio.Media.DATA, // 1
             Audio.Media.DATE_MODIFIED, // 2
     };
-    
+
     private static final int ID_AUDIO_COLUMN_INDEX = 0;
     private static final int PATH_AUDIO_COLUMN_INDEX = 1;
     private static final int DATE_MODIFIED_AUDIO_COLUMN_INDEX = 2;
- 
+
     private static final String[] VIDEO_PROJECTION = new String[] {
             Video.Media._ID, // 0
             Video.Media.DATA, // 1
             Video.Media.DATE_MODIFIED, // 2
     };
-    
+
     private static final int ID_VIDEO_COLUMN_INDEX = 0;
     private static final int PATH_VIDEO_COLUMN_INDEX = 1;
     private static final int DATE_MODIFIED_VIDEO_COLUMN_INDEX = 2;
@@ -128,11 +128,11 @@
             Images.Media.DATA, // 1
             Images.Media.DATE_MODIFIED, // 2
     };
-    
+
     private static final int ID_IMAGES_COLUMN_INDEX = 0;
     private static final int PATH_IMAGES_COLUMN_INDEX = 1;
     private static final int DATE_MODIFIED_IMAGES_COLUMN_INDEX = 2;
-    
+
     private static final String[] PLAYLISTS_PROJECTION = new String[] {
             Audio.Playlists._ID, // 0
             Audio.Playlists.DATA, // 1
@@ -157,7 +157,7 @@
     private static final String ALARMS_DIR = "/alarms/";
     private static final String MUSIC_DIR = "/music/";
     private static final String PODCAST_DIR = "/podcasts/";
-    
+
     private static final String[] ID3_GENRES = {
         // ID3v1 Genres
         "Blues",
@@ -317,11 +317,11 @@
      * to get the full system property.
      */
     private static final String DEFAULT_RINGTONE_PROPERTY_PREFIX = "ro.config.";
-    
+
     // set to true if file path comparisons should be case insensitive.
     // this should be set when scanning files on a case insensitive file system.
     private boolean mCaseInsensitivePaths;
-    
+
     private BitmapFactory.Options mBitmapOptions = new BitmapFactory.Options();
 
     private static class FileCacheEntry {
@@ -331,7 +331,7 @@
         long mLastModified;
         boolean mSeenInFileSystem;
         boolean mLastModifiedChanged;
-        
+
         FileCacheEntry(Uri tableUri, long rowId, String path, long lastModified) {
             mTableUri = tableUri;
             mRowId = rowId;
@@ -346,10 +346,10 @@
             return mPath;
         }
     }
-    
-    // hashes file path to FileCacheEntry.  
+
+    // hashes file path to FileCacheEntry.
     // path should be lower case if mCaseInsensitivePaths is true
-    private HashMap<String, FileCacheEntry> mFileCache; 
+    private HashMap<String, FileCacheEntry> mFileCache;
 
     private ArrayList<FileCacheEntry> mPlayLists;
     private HashMap<String, Uri> mGenreCache;
@@ -360,7 +360,7 @@
         mContext = c;
         mBitmapOptions.inSampleSize = 1;
         mBitmapOptions.inJustDecodeBounds = true;
-        
+
         setDefaultRingtoneFileNames();
     }
 
@@ -370,11 +370,11 @@
         mDefaultNotificationFilename = SystemProperties.get(DEFAULT_RINGTONE_PROPERTY_PREFIX
                 + Settings.System.NOTIFICATION_SOUND);
     }
-    
+
     private MyMediaScannerClient mClient = new MyMediaScannerClient();
-    
+
     private class MyMediaScannerClient implements MediaScannerClient {
-    
+
         private String mArtist;
         private String mAlbumArtist;    // use this if mArtist is missing
         private String mAlbum;
@@ -389,11 +389,11 @@
         private String mPath;
         private long mLastModified;
         private long mFileSize;
-    
+
         public FileCacheEntry beginFile(String path, String mimeType, long lastModified, long fileSize) {
-            
+
             // special case certain file names
-            // I use regionMatches() instead of substring() below 
+            // I use regionMatches() instead of substring() below
             // to avoid memory allocation
             int lastSlash = path.lastIndexOf('/');
             if (lastSlash >= 0 && lastSlash + 2 < path.length()) {
@@ -401,7 +401,7 @@
                 if (path.regionMatches(lastSlash + 1, "._", 0, 2)) {
                     return null;
                 }
-                
+
                 // ignore album art files created by Windows Media Player:
                 // Folder.jpg, AlbumArtSmall.jpg, AlbumArt_{...}_Large.jpg and AlbumArt_{...}_Small.jpg
                 if (path.regionMatches(true, path.length() - 4, ".jpg", 0, 4)) {
@@ -416,7 +416,7 @@
                     }
                 }
             }
-            
+
             mMimeType = null;
             // try mimeType first, if it is specified
             if (mimeType != null) {
@@ -435,7 +435,7 @@
                     mMimeType = mediaFileType.mimeType;
                 }
             }
-            
+
             String key = path;
             if (mCaseInsensitivePaths) {
                 key = path.toLowerCase();
@@ -446,20 +446,20 @@
                 mFileCache.put(key, entry);
             }
             entry.mSeenInFileSystem = true;
-            
+
             // add some slack to avoid a rounding error
             long delta = lastModified - entry.mLastModified;
             if (delta > 1 || delta < -1) {
                 entry.mLastModified = lastModified;
                 entry.mLastModifiedChanged = true;
             }
-                           
+
             if (mProcessPlaylists && MediaFile.isPlayListFileType(mFileType)) {
                 mPlayLists.add(entry);
                 // we don't process playlists in the main scan, so return null
                 return null;
             }
-            
+
             // clear all the metadata
             mArtist = null;
             mAlbumArtist = null;
@@ -472,10 +472,10 @@
             mDuration = 0;
             mPath = path;
             mLastModified = lastModified;
-            
+
             return entry;
         }
-        
+
         public void scanFile(String path, long lastModified, long fileSize) {
             doScanFile(path, null, lastModified, fileSize, false);
         }
@@ -513,7 +513,7 @@
                     } else if (MediaFile.isImageFileType(mFileType)) {
                         // we used to compute the width and height but it's not worth it
                     }
-                    
+
                     result = endFile(entry, ringtones, notifications, alarms, music, podcasts);
                 }
             } catch (RemoteException e) {
@@ -531,17 +531,17 @@
             char ch = s.charAt(start++);
             // return defaultValue if we have no integer at all
             if (ch < '0' || ch > '9') return defaultValue;
-            
+
             int result = ch - '0';
             while (start < length) {
                 ch = s.charAt(start++);
                 if (ch < '0' || ch > '9') return result;
                 result = result * 10 + (ch - '0');
             }
-            
+
             return result;
-        }                                
-                                
+        }
+
         public void handleStringTag(String name, String value) {
             if (name.equalsIgnoreCase("title") || name.startsWith("title;")) {
                 // Don't trim() here, to preserve the special \001 character
@@ -577,7 +577,7 @@
                 // track number might be of the form "2/12"
                 // we just read the number before the slash
                 int num = parseSubstring(value, 0, 0);
-                mTrack = (mTrack / 1000) * 1000 + num; 
+                mTrack = (mTrack / 1000) * 1000 + num;
             } else if (name.equalsIgnoreCase("discnumber") ||
                     name.equals("set") || name.startsWith("set;")) {
                 // set number might be of the form "1/3"
@@ -588,16 +588,16 @@
                 mDuration = parseSubstring(value, 0, 0);
             }
         }
-        
+
         public void setMimeType(String mimeType) {
             mMimeType = mimeType;
             mFileType = MediaFile.getFileTypeForMimeType(mimeType);
         }
-        
+
         /**
          * Formats the data into a values array suitable for use with the Media
          * Content Provider.
-         * 
+         *
          * @return a map of values
          */
         private ContentValues toValues() {
@@ -608,7 +608,7 @@
             map.put(MediaStore.MediaColumns.DATE_MODIFIED, mLastModified);
             map.put(MediaStore.MediaColumns.SIZE, mFileSize);
             map.put(MediaStore.MediaColumns.MIME_TYPE, mMimeType);
-            
+
             if (MediaFile.isVideoFileType(mFileType)) {
                 map.put(Video.Media.ARTIST, (mArtist != null && mArtist.length() > 0 ? mArtist : MediaFile.UNKNOWN_STRING));
                 map.put(Video.Media.ALBUM, (mAlbum != null && mAlbum.length() > 0 ? mAlbum : MediaFile.UNKNOWN_STRING));
@@ -629,9 +629,9 @@
             }
             return map;
         }
-    
+
         private Uri endFile(FileCacheEntry entry, boolean ringtones, boolean notifications,
-                boolean alarms, boolean music, boolean podcasts) 
+                boolean alarms, boolean music, boolean podcasts)
                 throws RemoteException {
             // update database
             Uri tableUri;
@@ -649,7 +649,7 @@
                 return null;
             }
             entry.mTableUri = tableUri;
-            
+
              // use album artist if artist is missing
             if (mArtist == null || mArtist.length() == 0) {
                 mArtist = mAlbumArtist;
@@ -680,10 +680,18 @@
                 values.put(Audio.Media.IS_ALARM, alarms);
                 values.put(Audio.Media.IS_MUSIC, music);
                 values.put(Audio.Media.IS_PODCAST, podcasts);
-            } else if (isImage) {
-                // nothing right now
+            } else if (mFileType == MediaFile.FILE_TYPE_JPEG) {
+                HashMap<String, String> exifData =
+                        ExifInterface.loadExifData(entry.mPath);
+                if (exifData != null) {
+                    float[] latlng = ExifInterface.getLatLng(exifData);
+                    if (latlng != null) {
+                        values.put(Images.Media.LATITUDE, latlng[0]);
+                        values.put(Images.Media.LONGITUDE, latlng[1]);
+                    }
+                }
             }
-            
+
             Uri result = null;
             long rowId = entry.mRowId;
             if (rowId == 0) {
@@ -730,15 +738,15 @@
                         }
                     }
                 }
-              
+
                 if (uri != null) {
-                    // add entry to audio_genre_map  
+                    // add entry to audio_genre_map
                     values.clear();
                     values.put(MediaStore.Audio.Genres.Members.AUDIO_ID, Long.valueOf(rowId));
                     mMediaProvider.insert(uri, values);
                 }
             }
-            
+
             if (notifications && !mDefaultNotificationSet) {
                 if (TextUtils.isEmpty(mDefaultNotificationFilename) ||
                         doesPathHaveFilename(entry.mPath, mDefaultNotificationFilename)) {
@@ -752,36 +760,36 @@
                     mDefaultRingtoneSet = true;
                 }
             }
-            
+
             return result;
         }
-        
+
         private boolean doesPathHaveFilename(String path, String filename) {
             int pathFilenameStart = path.lastIndexOf(File.separatorChar) + 1;
             int filenameLength = filename.length();
             return path.regionMatches(pathFilenameStart, filename, 0, filenameLength) &&
                     pathFilenameStart + filenameLength == path.length();
         }
-        
+
         private void setSettingIfNotSet(String settingName, Uri uri, long rowId) {
-            
+
             String existingSettingValue = Settings.System.getString(mContext.getContentResolver(),
                     settingName);
-            
+
             if (TextUtils.isEmpty(existingSettingValue)) {
                 // Set the setting to the given URI
                 Settings.System.putString(mContext.getContentResolver(), settingName,
                         ContentUris.withAppendedId(uri, rowId).toString());
             }
         }
-        
+
     }; // end of anonymous MediaScannerClient instance
-    
+
     private void prescan(String filePath) throws RemoteException {
         Cursor c = null;
         String where = null;
         String[] selectionArgs = null;
-         
+
         if (mFileCache == null) {
             mFileCache = new HashMap<String, FileCacheEntry>();
         } else {
@@ -792,7 +800,7 @@
         } else {
             mPlayLists.clear();
         }
-  
+
         // Build the list of files from the content provider
         try {
             // Read existing files from the audio table
@@ -801,14 +809,14 @@
                 selectionArgs = new String[] { filePath };
             }
             c = mMediaProvider.query(mAudioUri, AUDIO_PROJECTION, where, selectionArgs, null);
- 
+
             if (c != null) {
                 try {
                     while (c.moveToNext()) {
                         long rowId = c.getLong(ID_AUDIO_COLUMN_INDEX);
                         String path = c.getString(PATH_AUDIO_COLUMN_INDEX);
                         long lastModified = c.getLong(DATE_MODIFIED_AUDIO_COLUMN_INDEX);
-                        
+
                         String key = path;
                         if (mCaseInsensitivePaths) {
                             key = path.toLowerCase();
@@ -829,14 +837,14 @@
                 where = null;
             }
             c = mMediaProvider.query(mVideoUri, VIDEO_PROJECTION, where, selectionArgs, null);
- 
+
             if (c != null) {
                 try {
                     while (c.moveToNext()) {
                         long rowId = c.getLong(ID_VIDEO_COLUMN_INDEX);
                         String path = c.getString(PATH_VIDEO_COLUMN_INDEX);
                         long lastModified = c.getLong(DATE_MODIFIED_VIDEO_COLUMN_INDEX);
-                        
+
                         String key = path;
                         if (mCaseInsensitivePaths) {
                             key = path.toLowerCase();
@@ -858,7 +866,7 @@
             }
             mOriginalCount = 0;
             c = mMediaProvider.query(mImagesUri, IMAGES_PROJECTION, where, selectionArgs, null);
- 
+
             if (c != null) {
                 try {
                     mOriginalCount = c.getCount();
@@ -866,7 +874,7 @@
                         long rowId = c.getLong(ID_IMAGES_COLUMN_INDEX);
                         String path = c.getString(PATH_IMAGES_COLUMN_INDEX);
                        long lastModified = c.getLong(DATE_MODIFIED_IMAGES_COLUMN_INDEX);
-    
+
                         String key = path;
                         if (mCaseInsensitivePaths) {
                             key = path.toLowerCase();
@@ -879,7 +887,7 @@
                     c = null;
                 }
             }
-            
+
             if (mProcessPlaylists) {
                 // Read existing files from the playlists table
                 if (filePath != null) {
@@ -888,16 +896,16 @@
                     where = null;
                 }
                 c = mMediaProvider.query(mPlaylistsUri, PLAYLISTS_PROJECTION, where, selectionArgs, null);
-     
+
                 if (c != null) {
                     try {
                         while (c.moveToNext()) {
                             String path = c.getString(PATH_IMAGES_COLUMN_INDEX);
-            
+
                             if (path != null && path.length() > 0) {
                                 long rowId = c.getLong(ID_PLAYLISTS_COLUMN_INDEX);
                                 long lastModified = c.getLong(DATE_MODIFIED_PLAYLISTS_COLUMN_INDEX);
-    
+
                                 String key = path;
                                 if (mCaseInsensitivePaths) {
                                     key = path.toLowerCase();
@@ -919,7 +927,7 @@
             }
         }
     }
-    
+
     private boolean inScanDirectory(String path, String[] directories) {
         for (int i = 0; i < directories.length; i++) {
             if (path.startsWith(directories[i])) {
@@ -928,25 +936,25 @@
         }
         return false;
     }
-    
+
     private void pruneDeadThumbnailFiles() {
         HashSet<String> existingFiles = new HashSet<String>();
         String directory = "/sdcard/DCIM/.thumbnails";
         String [] files = (new File(directory)).list();
         if (files == null)
             files = new String[0];
-        
+
         for (int i = 0; i < files.length; i++) {
             String fullPathString = directory + "/" + files[i];
             existingFiles.add(fullPathString);
         }
-        
+
         try {
             Cursor c = mMediaProvider.query(
-                    mThumbsUri, 
-                    new String [] { "_data" }, 
-                    null, 
-                    null, 
+                    mThumbsUri,
+                    new String [] { "_data" },
+                    null,
+                    null,
                     null);
             Log.v(TAG, "pruneDeadThumbnailFiles... " + c);
             if (c != null && c.moveToFirst()) {
@@ -955,7 +963,7 @@
                     existingFiles.remove(fullPathString);
                 } while (c.moveToNext());
             }
-            
+
             for (String fileToDelete : existingFiles) {
                 if (Config.LOGV)
                     Log.v(TAG, "fileToDelete is " + fileToDelete);
@@ -964,7 +972,7 @@
                 } catch (SecurityException ex) {
                 }
             }
-            
+
             Log.v(TAG, "/pruneDeadThumbnailFiles... " + c);
             if (c != null) {
                 c.close();
@@ -980,10 +988,10 @@
         while (iterator.hasNext()) {
             FileCacheEntry entry = iterator.next();
             String path = entry.mPath;
-            
+
             // remove database entries for files that no longer exist.
             boolean fileMissing = false;
-            
+
             if (!entry.mSeenInFileSystem) {
                 if (inScanDirectory(path, directories)) {
                     // we didn't see this file in the scan directory.
@@ -997,7 +1005,7 @@
                     }
                 }
             }
-            
+
             if (fileMissing) {
                 // do not delete missing playlists, since they may have been modified by the user.
                 // the user can delete them in the media player instead.
@@ -1016,25 +1024,25 @@
                 }
             }
         }
-        
+
         // handle playlists last, after we know what media files are on the storage.
         if (mProcessPlaylists) {
             processPlayLists();
         }
-        
+
         if (mOriginalCount == 0 && mImagesUri.equals(Images.Media.getContentUri("external")))
             pruneDeadThumbnailFiles();
-        
+
         // allow GC to clean up
         mGenreCache = null;
         mPlayLists = null;
         mFileCache = null;
         mMediaProvider = null;
     }
-    
+
     private void initialize(String volumeName) {
         mMediaProvider = mContext.getContentResolver().acquireProvider("media");
-        
+
         mAudioUri = Audio.Media.getContentUri(volumeName);
         mVideoUri = Video.Media.getContentUri(volumeName);
         mImagesUri = Images.Media.getContentUri(volumeName);
@@ -1051,23 +1059,23 @@
             if ( Process.supportsProcesses()) {
                 mCaseInsensitivePaths = true;
             }
-        }          
+        }
     }
 
     public void scanDirectories(String[] directories, String volumeName) {
         try {
             long start = System.currentTimeMillis();
-            initialize(volumeName);    
+            initialize(volumeName);
             prescan(null);
             long prescan = System.currentTimeMillis();
-            
+
             for (int i = 0; i < directories.length; i++) {
                 processDirectory(directories[i], MediaFile.sFileExtensions, mClient);
             }
             long scan = System.currentTimeMillis();
             postscan(directories);
             long end = System.currentTimeMillis();
-            
+
             if (Config.LOGD) {
                 Log.d(TAG, " prescan time: " + (prescan - start) + "ms\n");
                 Log.d(TAG, "    scan time: " + (scan - prescan) + "ms\n");
@@ -1088,9 +1096,9 @@
     // this function is used to scan a single file
     public Uri scanSingleFile(String path, String volumeName, String mimeType) {
         try {
-            initialize(volumeName);        
+            initialize(volumeName);
             prescan(path);
-    
+
             File file = new File(path);
             // always scan the file, so we can return the content://media Uri for existing files
             return mClient.doScanFile(path, mimeType, file.lastModified(), file.length(), true);
@@ -1105,7 +1113,7 @@
         int result = 0;
         int end1 = path1.length();
         int end2 = path2.length();
-        
+
         while (end1 > 0 && end2 > 0) {
             int slash1 = path1.lastIndexOf('/', end1 - 1);
             int slash2 = path2.lastIndexOf('/', end2 - 1);
@@ -1123,13 +1131,13 @@
                 end2 = start2 - 1;
             } else break;
         }
-               
+
         return result;
     }
 
-    private boolean addPlayListEntry(String entry, String playListDirectory, 
+    private boolean addPlayListEntry(String entry, String playListDirectory,
             Uri uri, ContentValues values, int index) {
-        
+
         // watch for trailing whitespace
         int entryLength = entry.length();
         while (entryLength > 0 && Character.isWhitespace(entry.charAt(entryLength - 1))) entryLength--;
@@ -1146,36 +1154,36 @@
         // if we have a relative path, combine entry with playListDirectory
         if (!fullPath)
             entry = playListDirectory + entry;
-            
+
         //FIXME - should we look for "../" within the path?
-        
+
         // best matching MediaFile for the play list entry
         FileCacheEntry bestMatch = null;
-        
+
         // number of rightmost file/directory names for bestMatch
-        int bestMatchLength = 0;    
-                
+        int bestMatchLength = 0;
+
         Iterator<FileCacheEntry> iterator = mFileCache.values().iterator();
         while (iterator.hasNext()) {
             FileCacheEntry cacheEntry = iterator.next();
             String path = cacheEntry.mPath;
-        
+
             if (path.equalsIgnoreCase(entry)) {
                 bestMatch = cacheEntry;
                 break;    // don't bother continuing search
             }
-            
+
             int matchLength = matchPaths(path, entry);
             if (matchLength > bestMatchLength) {
                 bestMatch = cacheEntry;
                 bestMatchLength = matchLength;
             }
         }
-        
+
         if (bestMatch == null) {
             return false;
         }
-        
+
         try {
         // OK, now we need to add this to the database
             values.clear();
@@ -1189,7 +1197,7 @@
 
         return true;
     }
-    
+
     private void processM3uPlayList(String path, String playListDirectory, Uri uri, ContentValues values) {
         BufferedReader reader = null;
         try {
@@ -1266,7 +1274,7 @@
         public WplHandler(String playListDirectory, Uri uri) {
             this.playListDirectory = playListDirectory;
             this.uri = uri;
-            
+
             RootElement root = new RootElement("smil");
             Element body = root.getChild("body");
             Element seq = body.getChild("seq");
@@ -1316,12 +1324,12 @@
             }
         }
     }
-    
+
     private void processPlayLists() throws RemoteException {
         Iterator<FileCacheEntry> iterator = mPlayLists.iterator();
         while (iterator.hasNext()) {
             FileCacheEntry entry = iterator.next();
-            String path = entry.mPath;  
+            String path = entry.mPath;
 
             // only process playlist files if they are new or have been modified since the last scan
             if (entry.mLastModifiedChanged) {
@@ -1332,7 +1340,7 @@
                 long rowId = entry.mRowId;
                 if (rowId == 0) {
                     // Create a new playlist
-        
+
                     int lastDot = path.lastIndexOf('.');
                     String name = (lastDot < 0 ? path.substring(lastSlash + 1) : path.substring(lastSlash + 1, lastDot));
                     values.put(MediaStore.Audio.Playlists.NAME, name);
@@ -1343,7 +1351,7 @@
                     membersUri = Uri.withAppendedPath(uri, Playlists.Members.CONTENT_DIRECTORY);
                 } else {
                     uri = ContentUris.withAppendedId(mPlaylistsUri, rowId);
-                    
+
                     // update lastModified value of existing playlist
                     values.put(MediaStore.Audio.Playlists.DATE_MODIFIED, entry.mLastModified);
                     mMediaProvider.update(uri, values, null, null);
@@ -1352,7 +1360,7 @@
                     membersUri = Uri.withAppendedPath(uri, Playlists.Members.CONTENT_DIRECTORY);
                     mMediaProvider.delete(membersUri, null, null);
                 }
-               
+
                 String playListDirectory = path.substring(0, lastSlash + 1);
                 MediaFile.MediaFileType mediaFileType = MediaFile.getFileType(path);
                 int fileType = (mediaFileType == null ? 0 : mediaFileType.fileType);
@@ -1363,7 +1371,7 @@
                     processPlsPlayList(path, playListDirectory, membersUri, values);
                 else if (fileType == MediaFile.FILE_TYPE_WPL)
                     processWplPlayList(path, playListDirectory, membersUri);
-                    
+
                 Cursor cursor = mMediaProvider.query(membersUri, PLAYLIST_MEMBERS_PROJECTION, null,
                         null, null);
                 try {
@@ -1377,18 +1385,18 @@
             }
         }
     }
-    
+
     private native void processDirectory(String path, String extensions, MediaScannerClient client);
     private native void processFile(String path, String mimeType, MediaScannerClient client);
     public native void setLocale(String locale);
-    
+
     public native byte[] extractAlbumArt(FileDescriptor fd);
 
     private native final void native_setup();
     private native final void native_finalize();
     @Override
-    protected void finalize() { 
+    protected void finalize() {
         mContext.getContentResolver().releaseProvider(mMediaProvider);
-        native_finalize(); 
+        native_finalize();
     }
 }
diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java
index 3b82284..6e28515 100644
--- a/services/java/com/android/server/BackupManagerService.java
+++ b/services/java/com/android/server/BackupManagerService.java
@@ -810,17 +810,25 @@
 
                 // Now propagate the newly-backed-up data to the transport
                 if (success) {
-                    if (DEBUG) Log.v(TAG, "doBackup() success; calling transport");
-                    backupData =
-                        ParcelFileDescriptor.open(backupDataName, ParcelFileDescriptor.MODE_READ_ONLY);
-                    if (!transport.performBackup(packInfo, backupData)) {
-                        // STOPSHIP TODO: handle errors
-                        Log.e(TAG, "Backup failure in performBackup()");
+                    if (DEBUG) Log.v(TAG, "doBackup() success");
+                    if (backupDataName.length() > 0) {
+                        backupData =
+                            ParcelFileDescriptor.open(backupDataName,
+                                    ParcelFileDescriptor.MODE_READ_ONLY);
+                        if (!transport.performBackup(packInfo, backupData)) {
+                            // STOPSHIP TODO: handle errors
+                            Log.e(TAG, "Backup failure in performBackup()");
+                        }
+                    } else {
+                        if (DEBUG) {
+                            Log.i(TAG, "no backup data written; not calling transport");
+                        }
                     }
 
-                    // !!! TODO: After successful transport, delete the now-stale data
-                    // and juggle the files so that next time the new state is passed
-                    //backupDataName.delete();
+                    // After successful transport, delete the now-stale data
+                    // and juggle the files so that next time we supply the agent
+                    // with the new state file it just created.
+                    backupDataName.delete();
                     newStateName.renameTo(savedStateName);
                 }
             } catch (Exception e) {
diff --git a/services/java/com/android/server/NotificationManagerService.java b/services/java/com/android/server/NotificationManagerService.java
index 854138c..190d3e6 100644
--- a/services/java/com/android/server/NotificationManagerService.java
+++ b/services/java/com/android/server/NotificationManagerService.java
@@ -88,7 +88,8 @@
 
     private NotificationRecord mSoundNotification;
     private AsyncPlayer mSound;
-    private int mDisabledNotifications;
+    private boolean mSystemReady;
+    private int mDisabledNotifications = StatusBarManager.DISABLE_NOTIFICATION_ALERTS;
 
     private NotificationRecord mVibrateNotification;
     private Vibrator mVibrator = new Vibrator();
@@ -377,6 +378,11 @@
         mSettingsObserver.observe();
     }
 
+    void systemReady() {
+        // no beeping until we're basically done booting
+        mSystemReady = true;
+    }
+
     // Toasts
     // ============================================================================
     public void enqueueToast(String pkg, ITransientNotification callback, int duration)
@@ -637,7 +643,7 @@
                     }
                 }
 
-                sendAccessibilityEventTypeNotificationChangedDoCheck(notification, pkg);
+                sendAccessibilityEvent(notification, pkg);
 
             } else {
                 if (old != null && old.statusBarKey != null) {
@@ -654,7 +660,8 @@
             // If we're not supposed to beep, vibrate, etc. then don't.
             if (((mDisabledNotifications & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) == 0)
                     && (!(old != null
-                        && (notification.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0 ))) {
+                        && (notification.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0 ))
+                    && mSystemReady) {
                 // sound
                 final boolean useDefaultSound =
                     (notification.defaults & Notification.DEFAULT_SOUND) != 0; 
@@ -721,8 +728,7 @@
         idOut[0] = id;
     }
 
-    private void sendAccessibilityEventTypeNotificationChangedDoCheck(Notification notification,
-            CharSequence packageName) {
+    private void sendAccessibilityEvent(Notification notification, CharSequence packageName) {
         AccessibilityManager manager = AccessibilityManager.getInstance(mContext);
         if (!manager.isEnabled()) {
             return;
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 3e4d5f9..b038a64 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -190,6 +190,7 @@
         StatusBarService statusBar = null;
         InputMethodManagerService imm = null;
         AppWidgetService appWidget = null;
+        NotificationManagerService notification = null;
 
         if (factoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) {
             try {
@@ -240,8 +241,8 @@
 
             try {
                 Log.i(TAG, "Starting Notification Manager.");
-                ServiceManager.addService(Context.NOTIFICATION_SERVICE,
-                        new NotificationManagerService(context, statusBar, hardware));
+                notification = new NotificationManagerService(context, statusBar, hardware);
+                ServiceManager.addService(Context.NOTIFICATION_SERVICE, notification);
             } catch (Throwable e) {
                 Log.e(TAG, "Failure starting Notification Manager", e);
             }
@@ -348,6 +349,11 @@
 
         // It is now time to start up the app processes...
         boolean safeMode = wm.detectSafeMode();
+
+        if (notification != null) {
+            notification.systemReady();
+        }
+
         if (statusBar != null) {
             statusBar.systemReady();
         }
diff --git a/services/java/com/android/server/WindowManagerService.java b/services/java/com/android/server/WindowManagerService.java
index 2dd70ef..ad882a9 100644
--- a/services/java/com/android/server/WindowManagerService.java
+++ b/services/java/com/android/server/WindowManagerService.java
@@ -53,6 +53,7 @@
 import android.content.Context;
 import android.content.pm.ActivityInfo;
 import android.content.pm.PackageManager;
+import android.content.res.CompatibilityInfo;
 import android.content.res.Configuration;
 import android.graphics.Matrix;
 import android.graphics.PixelFormat;
@@ -417,7 +418,13 @@
 
     final Configuration mTempConfiguration = new Configuration();
     int screenLayout = Configuration.SCREENLAYOUT_UNDEFINED;
-    
+
+    // The frame use to limit the size of the app running in compatibility mode.
+    Rect mCompatibleScreenFrame = new Rect();
+    // The surface used to fill the outer rim of the app running in compatibility mode.
+    Surface mBackgroundFillerSurface = null;
+    boolean mBackgroundFillerShown = false;
+
     public static WindowManagerService main(Context context,
             PowerManagerService pm, boolean haveInputMethods) {
         WMThread thr = new WMThread(context, pm, haveInputMethods);
@@ -3738,12 +3745,14 @@
         }
         config.orientation = orientation;
         
+        DisplayMetrics dm = new DisplayMetrics();
+        mDisplay.getMetrics(dm);
+        CompatibilityInfo.updateCompatibleScreenFrame(dm, orientation, mCompatibleScreenFrame);
+
         if (screenLayout == Configuration.SCREENLAYOUT_UNDEFINED) {
             // Note we only do this once because at this point we don't
             // expect the screen to change in this way at runtime, and want
             // to avoid all of this computation for every config change.
-            DisplayMetrics dm = new DisplayMetrics();
-            mDisplay.getMetrics(dm);
             int longSize = dw;
             int shortSize = dh;
             if (longSize < shortSize) {
@@ -3753,7 +3762,7 @@
             }
             longSize = (int)(longSize/dm.density);
             shortSize = (int)(shortSize/dm.density);
-            
+
             // These semi-magic numbers define our compatibility modes for
             // applications with different screens.  Don't change unless you
             // make sure to test lots and lots of apps!
@@ -5845,8 +5854,19 @@
         public void computeFrameLw(Rect pf, Rect df, Rect cf, Rect vf) {
             mHaveFrame = true;
 
-            final int pw = pf.right-pf.left;
-            final int ph = pf.bottom-pf.top;
+            final Rect container = mContainingFrame;
+            container.set(pf);
+
+            final Rect display = mDisplayFrame;
+            display.set(df);
+
+            if ((mAttrs.flags & WindowManager.LayoutParams.FLAG_COMPATIBLE_WINDOW) != 0) {
+                container.intersect(mCompatibleScreenFrame);
+                display.intersect(mCompatibleScreenFrame);
+            }
+
+            final int pw = container.right - container.left;
+            final int ph = container.bottom - container.top;
 
             int w,h;
             if ((mAttrs.flags & mAttrs.FLAG_SCALED) != 0) {
@@ -5857,12 +5877,6 @@
                 h = mAttrs.height== mAttrs.FILL_PARENT ? ph : mRequestedHeight;
             }
 
-            final Rect container = mContainingFrame;
-            container.set(pf);
-
-            final Rect display = mDisplayFrame;
-            display.set(df);
-
             final Rect content = mContentFrame;
             content.set(cf);
 
@@ -5882,7 +5896,7 @@
 
             // Now make sure the window fits in the overall display.
             Gravity.applyDisplay(mAttrs.gravity, df, frame);
-
+            
             // Make sure the content and visible frames are inside of the
             // final window frame.
             if (content.left < frame.left) content.left = frame.left;
@@ -6573,16 +6587,29 @@
             return false;
         }
 
-        boolean isFullscreenOpaque(int screenWidth, int screenHeight) {
-            if (mAttrs.format != PixelFormat.OPAQUE || mSurface == null
-                    || mAnimation != null || mDrawPending || mCommitDrawPending) {
-                return false;
-            }
-            if (mFrame.left <= 0 && mFrame.top <= 0 &&
-                mFrame.right >= screenWidth && mFrame.bottom >= screenHeight) {
-                return true;
-            }
-            return false;
+        /**
+         * Return true if the window is opaque and fully drawn.
+         */
+        boolean isOpaqueDrawn() {
+            return mAttrs.format == PixelFormat.OPAQUE && mSurface != null
+                    && mAnimation == null && !mDrawPending && !mCommitDrawPending;
+        }
+
+        boolean needsBackgroundFiller(int screenWidth, int screenHeight) {
+            return
+                 // only if the application is requesting compatible window
+                 (mAttrs.flags & mAttrs.FLAG_COMPATIBLE_WINDOW) != 0 &&
+                 // and only if the application wanted to fill the screen
+                 mAttrs.width == mAttrs.FILL_PARENT &&
+                 mAttrs.height == mAttrs.FILL_PARENT &&
+                 // and only if the screen is bigger
+                 ((mFrame.right - mFrame.right) < screenWidth ||
+                         (mFrame.bottom - mFrame.top) < screenHeight);
+        }
+
+        boolean isFullscreen(int screenWidth, int screenHeight) {
+            return mFrame.left <= 0 && mFrame.top <= 0 &&
+                mFrame.right >= screenWidth && mFrame.bottom >= screenHeight;
         }
 
         void removeLocked() {
@@ -8102,6 +8129,7 @@
             boolean dimming = false;
             boolean covered = false;
             boolean syswin = false;
+            boolean backgroundFillerShown = false;
 
             for (i=N-1; i>=0; i--) {
                 WindowState w = (WindowState)mWindows.get(i);
@@ -8371,11 +8399,39 @@
                             syswin = true;
                         }
                     }
-                    if (w.isFullscreenOpaque(dw, dh)) {
+
+                    boolean opaqueDrawn = w.isOpaqueDrawn();
+                    if (opaqueDrawn && w.isFullscreen(dw, dh)) {
                         // This window completely covers everything behind it,
                         // so we want to leave all of them as unblurred (for
                         // performance reasons).
                         obscured = true;
+                    } else if (opaqueDrawn && w.needsBackgroundFiller(dw, dh)) {
+                        if (SHOW_TRANSACTIONS) Log.d(TAG, "showing background filler");
+                                                // This window is in compatibility mode, and needs background filler. 
+                        obscured = true;
+                        if (mBackgroundFillerSurface == null) {
+                            try {
+                                mBackgroundFillerSurface = new Surface(mFxSession, 0,
+                                        0, dw, dh,
+                                        PixelFormat.OPAQUE,
+                                        Surface.FX_SURFACE_NORMAL);
+                            } catch (Exception e) {
+                                Log.e(TAG, "Exception creating filler surface", e);
+                            }
+                        }
+                        try {
+                            mBackgroundFillerSurface.setPosition(0, 0);
+                            mBackgroundFillerSurface.setSize(dw, dh);
+                            // Using the same layer as Dim because they will never be shown at the
+                            // same time.
+                            mBackgroundFillerSurface.setLayer(w.mAnimLayer - 1);
+                            mBackgroundFillerSurface.show();
+                        } catch (RuntimeException e) {
+                            Log.e(TAG, "Exception showing filler surface");
+                        }
+                        backgroundFillerShown = true;
+                        mBackgroundFillerShown = true;
                     } else if (canBeSeen && !obscured &&
                             (attrFlags&FLAG_BLUR_BEHIND|FLAG_DIM_BEHIND) != 0) {
                         if (localLOGV) Log.v(TAG, "Win " + w
@@ -8472,6 +8528,16 @@
                     }
                 }
             }
+            
+            if (backgroundFillerShown == false && mBackgroundFillerShown) {
+                mBackgroundFillerShown = false;
+                if (SHOW_TRANSACTIONS) Log.d(TAG, "hiding background filler");
+                try {
+                    mBackgroundFillerSurface.hide();
+                } catch (RuntimeException e) {
+                    Log.e(TAG, "Exception hiding filler surface", e);
+                }
+            }
 
             if (!dimming && mDimShown) {
                 // Time to hide the dim surface...  start fading.
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index 2fe4dd4..aad542a 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -56,6 +56,7 @@
 import android.content.pm.IPackageManager;
 import android.content.pm.InstrumentationInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.PathPermission;
 import android.content.pm.ProviderInfo;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
@@ -7072,6 +7073,27 @@
                 == PackageManager.PERMISSION_GRANTED) {
             return null;
         }
+        
+        PathPermission[] pps = cpi.pathPermissions;
+        if (pps != null) {
+            int i = pps.length;
+            while (i > 0) {
+                i--;
+                PathPermission pp = pps[i];
+                if (checkComponentPermission(pp.getReadPermission(), callingPid, callingUid,
+                        cpi.exported ? -1 : cpi.applicationInfo.uid)
+                        == PackageManager.PERMISSION_GRANTED
+                        && mode == ParcelFileDescriptor.MODE_READ_ONLY || mode == -1) {
+                    return null;
+                }
+                if (checkComponentPermission(pp.getWritePermission(), callingPid, callingUid,
+                        cpi.exported ? -1 : cpi.applicationInfo.uid)
+                        == PackageManager.PERMISSION_GRANTED) {
+                    return null;
+                }
+            }
+        }
+        
         String msg = "Permission Denial: opening provider " + cpi.name
                 + " from " + (r != null ? r : "(null)") + " (pid=" + callingPid
                 + ", uid=" + callingUid + ") requires "