Merge "new accessibility section" into jb-dev
diff --git a/cmds/installd/commands.c b/cmds/installd/commands.c
index 96a6438..a509156 100644
--- a/cmds/installd/commands.c
+++ b/cmds/installd/commands.c
@@ -1056,7 +1056,12 @@
         rc = -errno;
         goto out;
     }
-
+    if (chmod(libdir, 0755) < 0) {
+        ALOGE("cannot chmod dir '%s': %s\n", libdir, strerror(errno));
+        unlink(libdir);
+        rc = -errno;
+        goto out;
+    }
     if (chown(libdir, AID_SYSTEM, AID_SYSTEM) < 0) {
         ALOGE("cannot chown dir '%s': %s\n", libdir, strerror(errno));
         unlink(libdir);
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index b902550..411f6d0 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -361,10 +361,10 @@
                     return PolicyManager.makeNewLayoutInflater(ctx.getOuterContext());
                 }});
 
-        registerService(LOCATION_SERVICE, new StaticServiceFetcher() {
-                public Object createStaticService() {
+        registerService(LOCATION_SERVICE, new ServiceFetcher() {
+                public Object createService(ContextImpl ctx) {
                     IBinder b = ServiceManager.getService(LOCATION_SERVICE);
-                    return new LocationManager(ILocationManager.Stub.asInterface(b));
+                    return new LocationManager(ctx, ILocationManager.Stub.asInterface(b));
                 }});
 
         registerService(NETWORK_POLICY_SERVICE, new ServiceFetcher() {
diff --git a/docs/html/about/versions/android-4.0.jd b/docs/html/about/versions/android-4.0.jd
index 06b63a0c..99ab9cb 100644
--- a/docs/html/about/versions/android-4.0.jd
+++ b/docs/html/about/versions/android-4.0.jd
@@ -1,4 +1,6 @@
-page.title=Android 4.1 API Overview
+page.title=Android 4.0 Platform
+sdk.platform.version=4.0
+sdk.platform.apiLevel=14
 @jd:body
 
 <div id="qv-wrapper">
diff --git a/docs/html/design/media/actionbar_drawer.png b/docs/html/design/media/actionbar_drawer.png
new file mode 100644
index 0000000..95e04f5
--- /dev/null
+++ b/docs/html/design/media/actionbar_drawer.png
Binary files differ
diff --git a/docs/html/design/media/dialogs_popups_example.png b/docs/html/design/media/dialogs_popups_example.png
index 2deb00d..c7536f3 100644
--- a/docs/html/design/media/dialogs_popups_example.png
+++ b/docs/html/design/media/dialogs_popups_example.png
Binary files differ
diff --git a/docs/html/design/media/tabs_youtube.png b/docs/html/design/media/tabs_youtube.png
index 69e9268..31c626c 100644
--- a/docs/html/design/media/tabs_youtube.png
+++ b/docs/html/design/media/tabs_youtube.png
Binary files differ
diff --git a/docs/html/design/patterns/actionbar.jd b/docs/html/design/patterns/actionbar.jd
index 21e8583..80aa77d 100644
--- a/docs/html/design/patterns/actionbar.jd
+++ b/docs/html/design/patterns/actionbar.jd
@@ -234,12 +234,7 @@
 
   </div>
   <div class="layout-content-col span-7">
-
-    <img src="{@docRoot}design/media/action_bar_pattern_default_tabs.png">
-    <div class="figure-caption">
-      Default fixed tabs shown in Holo Dark &amp; Light.
-    </div>
-
+    <img src="{@docRoot}design/media/actionbar_drawer.png">
   </div>
 </div>
 
diff --git a/location/java/android/location/ILocationManager.aidl b/location/java/android/location/ILocationManager.aidl
index 2255bf2..38a29d3 100644
--- a/location/java/android/location/ILocationManager.aidl
+++ b/location/java/android/location/ILocationManager.aidl
@@ -39,11 +39,11 @@
     boolean providerMeetsCriteria(String provider, in Criteria criteria);
 
     void requestLocationUpdates(String provider, in Criteria criteria, long minTime, float minDistance,
-        boolean singleShot, in ILocationListener listener);
+        boolean singleShot, in ILocationListener listener, String packageName);
     void requestLocationUpdatesPI(String provider, in Criteria criteria, long minTime, float minDistance,
-        boolean singleShot, in PendingIntent intent);
-    void removeUpdates(in ILocationListener listener);
-    void removeUpdatesPI(in PendingIntent intent);
+        boolean singleShot, in PendingIntent intent, String packageName);
+    void removeUpdates(in ILocationListener listener, String packageName);
+    void removeUpdatesPI(in PendingIntent intent, String packageName);
 
     boolean addGpsStatusListener(IGpsStatusListener listener);
     void removeGpsStatusListener(IGpsStatusListener listener);
@@ -54,13 +54,13 @@
     boolean sendExtraCommand(String provider, String command, inout Bundle extras);
 
     void addProximityAlert(double latitude, double longitude, float distance,
-        long expiration, in PendingIntent intent);
+        long expiration, in PendingIntent intent, String packageName);
     void removeProximityAlert(in PendingIntent intent);
 
     Bundle getProviderInfo(String provider);
     boolean isProviderEnabled(String provider);
 
-    Location getLastKnownLocation(String provider);
+    Location getLastKnownLocation(String provider, String packageName);
 
     // Used by location providers to tell the location manager when it has a new location.
     // Passive is true if the location is coming from the passive provider, in which case
diff --git a/location/java/android/location/LocationManager.java b/location/java/android/location/LocationManager.java
index 1299574..5c256a3 100644
--- a/location/java/android/location/LocationManager.java
+++ b/location/java/android/location/LocationManager.java
@@ -17,6 +17,7 @@
 package android.location;
 
 import android.app.PendingIntent;
+import android.content.Context;
 import android.content.Intent;
 import android.os.Bundle;
 import android.os.Looper;
@@ -160,6 +161,8 @@
      */
     public static final String EXTRA_GPS_ENABLED = "enabled";
 
+    private final Context mContext;
+
     // Map from LocationListeners to their associated ListenerTransport objects
     private HashMap<LocationListener,ListenerTransport> mListeners =
         new HashMap<LocationListener,ListenerTransport>();
@@ -260,8 +263,9 @@
      * right way to create an instance of this class is using the
      * factory Context.getSystemService.
      */
-    public LocationManager(ILocationManager service) {
+    public LocationManager(Context context, ILocationManager service) {
         mService = service;
+        mContext = context;
     }
 
     private LocationProvider createProvider(String name, Bundle info) {
@@ -657,7 +661,8 @@
                     transport = new ListenerTransport(listener, looper);
                 }
                 mListeners.put(listener, transport);
-                mService.requestLocationUpdates(provider, criteria, minTime, minDistance, singleShot, transport);
+                mService.requestLocationUpdates(provider, criteria, minTime, minDistance,
+                        singleShot, transport, mContext.getPackageName());
             }
         } catch (RemoteException ex) {
             Log.e(TAG, "requestLocationUpdates: DeadObjectException", ex);
@@ -837,7 +842,8 @@
         }
 
         try {
-            mService.requestLocationUpdatesPI(provider, criteria, minTime, minDistance, singleShot, intent);
+            mService.requestLocationUpdatesPI(provider, criteria, minTime, minDistance, singleShot,
+                    intent, mContext.getPackageName());
         } catch (RemoteException ex) {
             Log.e(TAG, "requestLocationUpdates: RemoteException", ex);
         }
@@ -1005,7 +1011,7 @@
         try {
             ListenerTransport transport = mListeners.remove(listener);
             if (transport != null) {
-                mService.removeUpdates(transport);
+                mService.removeUpdates(transport, mContext.getPackageName());
             }
         } catch (RemoteException ex) {
             Log.e(TAG, "removeUpdates: DeadObjectException", ex);
@@ -1028,7 +1034,7 @@
             Log.d(TAG, "removeUpdates: intent = " + intent);
         }
         try {
-            mService.removeUpdatesPI(intent);
+            mService.removeUpdatesPI(intent, mContext.getPackageName());
         } catch (RemoteException ex) {
             Log.e(TAG, "removeUpdates: RemoteException", ex);
         }
@@ -1087,7 +1093,7 @@
         }
         try {
             mService.addProximityAlert(latitude, longitude, radius,
-                                       expiration, intent);
+                                       expiration, intent, mContext.getPackageName());
         } catch (RemoteException ex) {
             Log.e(TAG, "addProximityAlert: RemoteException", ex);
         }
@@ -1153,7 +1159,7 @@
             throw new IllegalArgumentException("provider==null");
         }
         try {
-            return mService.getLastKnownLocation(provider);
+            return mService.getLastKnownLocation(provider, mContext.getPackageName());
         } catch (RemoteException ex) {
             Log.e(TAG, "getLastKnowLocation: RemoteException", ex);
             return null;
diff --git a/services/java/com/android/server/LocationManagerService.java b/services/java/com/android/server/LocationManagerService.java
index 2918dbc..8c1581c 100644
--- a/services/java/com/android/server/LocationManagerService.java
+++ b/services/java/com/android/server/LocationManagerService.java
@@ -32,6 +32,7 @@
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.Signature;
 import android.content.res.Resources;
+import android.database.ContentObserver;
 import android.database.Cursor;
 import android.location.Address;
 import android.location.Criteria;
@@ -79,6 +80,8 @@
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Observable;
@@ -109,6 +112,9 @@
     private static final String INSTALL_LOCATION_PROVIDER =
         android.Manifest.permission.INSTALL_LOCATION_PROVIDER;
 
+    private static final String BLACKLIST_CONFIG_NAME = "locationPackagePrefixBlacklist";
+    private static final String WHITELIST_CONFIG_NAME = "locationPackagePrefixWhitelist";
+
     // Location Providers may sometimes deliver location updates
     // slightly faster that requested - provide grace period so
     // we don't unnecessarily filter events that are otherwise on
@@ -193,6 +199,10 @@
 
     private int mNetworkState = LocationProvider.TEMPORARILY_UNAVAILABLE;
 
+    // for prefix blacklist
+    private String[] mWhitelist = new String[0];
+    private String[] mBlacklist = new String[0];
+
     // for Settings change notification
     private ContentQueryMap mSettings;
 
@@ -205,20 +215,23 @@
         final PendingIntent mPendingIntent;
         final Object mKey;
         final HashMap<String,UpdateRecord> mUpdateRecords = new HashMap<String,UpdateRecord>();
+        final String mPackageName;
 
         int mPendingBroadcasts;
         String mRequiredPermissions;
 
-        Receiver(ILocationListener listener) {
+        Receiver(ILocationListener listener, String packageName) {
             mListener = listener;
             mPendingIntent = null;
             mKey = listener.asBinder();
+            mPackageName = packageName;
         }
 
-        Receiver(PendingIntent intent) {
+        Receiver(PendingIntent intent, String packageName) {
             mPendingIntent = intent;
             mListener = null;
             mKey = intent;
+            mPackageName = packageName;
         }
 
         @Override
@@ -601,6 +614,7 @@
 
         // Load providers
         loadProviders();
+        loadBlacklist();
 
         // Register for Network (Wifi or Mobile) updates
         IntentFilter intentFilter = new IntentFilter();
@@ -1110,11 +1124,11 @@
         }
     }
 
-    private Receiver getReceiver(ILocationListener listener) {
+    private Receiver getReceiver(ILocationListener listener, String packageName) {
         IBinder binder = listener.asBinder();
         Receiver receiver = mReceivers.get(binder);
         if (receiver == null) {
-            receiver = new Receiver(listener);
+            receiver = new Receiver(listener, packageName);
             mReceivers.put(binder, receiver);
 
             try {
@@ -1129,10 +1143,10 @@
         return receiver;
     }
 
-    private Receiver getReceiver(PendingIntent intent) {
+    private Receiver getReceiver(PendingIntent intent, String packageName) {
         Receiver receiver = mReceivers.get(intent);
         if (receiver == null) {
-            receiver = new Receiver(intent);
+            receiver = new Receiver(intent, packageName);
             mReceivers.put(intent, receiver);
         }
         return receiver;
@@ -1157,7 +1171,9 @@
     }
 
     public void requestLocationUpdates(String provider, Criteria criteria,
-        long minTime, float minDistance, boolean singleShot, ILocationListener listener) {
+        long minTime, float minDistance, boolean singleShot, ILocationListener listener,
+        String packageName) {
+        checkPackageName(Binder.getCallingUid(), packageName);
         if (criteria != null) {
             // FIXME - should we consider using multiple providers simultaneously
             // rather than only the best one?
@@ -1170,7 +1186,7 @@
         try {
             synchronized (mLock) {
                 requestLocationUpdatesLocked(provider, minTime, minDistance, singleShot,
-                        getReceiver(listener));
+                        getReceiver(listener, packageName));
             }
         } catch (SecurityException se) {
             throw se;
@@ -1194,7 +1210,9 @@
     }
 
     public void requestLocationUpdatesPI(String provider, Criteria criteria,
-            long minTime, float minDistance, boolean singleShot, PendingIntent intent) {
+            long minTime, float minDistance, boolean singleShot, PendingIntent intent,
+            String packageName) {
+        checkPackageName(Binder.getCallingUid(), packageName);
         validatePendingIntent(intent);
         if (criteria != null) {
             // FIXME - should we consider using multiple providers simultaneously
@@ -1208,7 +1226,7 @@
         try {
             synchronized (mLock) {
                 requestLocationUpdatesLocked(provider, minTime, minDistance, singleShot,
-                        getReceiver(intent));
+                        getReceiver(intent, packageName));
             }
         } catch (SecurityException se) {
             throw se;
@@ -1270,10 +1288,10 @@
         }
     }
 
-    public void removeUpdates(ILocationListener listener) {
+    public void removeUpdates(ILocationListener listener, String packageName) {
         try {
             synchronized (mLock) {
-                removeUpdatesLocked(getReceiver(listener));
+                removeUpdatesLocked(getReceiver(listener, packageName));
             }
         } catch (SecurityException se) {
             throw se;
@@ -1284,10 +1302,10 @@
         }
     }
 
-    public void removeUpdatesPI(PendingIntent intent) {
+    public void removeUpdatesPI(PendingIntent intent, String packageName) {
         try {
             synchronized (mLock) {
-                removeUpdatesLocked(getReceiver(intent));
+                removeUpdatesLocked(getReceiver(intent, packageName));
             }
         } catch (SecurityException se) {
             throw se;
@@ -1446,15 +1464,17 @@
         final long mExpiration;
         final PendingIntent mIntent;
         final Location mLocation;
+        final String mPackageName;
 
         public ProximityAlert(int uid, double latitude, double longitude,
-            float radius, long expiration, PendingIntent intent) {
+            float radius, long expiration, PendingIntent intent, String packageName) {
             mUid = uid;
             mLatitude = latitude;
             mLongitude = longitude;
             mRadius = radius;
             mExpiration = expiration;
             mIntent = intent;
+            mPackageName = packageName;
 
             mLocation = new Location("");
             mLocation.setLatitude(latitude);
@@ -1522,6 +1542,10 @@
                 PendingIntent intent = alert.getIntent();
                 long expiration = alert.getExpiration();
 
+                if (inBlacklist(alert.mPackageName)) {
+                    continue;
+                }
+
                 if ((expiration == -1) || (now <= expiration)) {
                     boolean entered = mProximitiesEntered.contains(alert);
                     boolean inProximity =
@@ -1632,11 +1656,12 @@
     }
 
     public void addProximityAlert(double latitude, double longitude,
-        float radius, long expiration, PendingIntent intent) {
+        float radius, long expiration, PendingIntent intent, String packageName) {
         validatePendingIntent(intent);
         try {
             synchronized (mLock) {
-                addProximityAlertLocked(latitude, longitude, radius, expiration, intent);
+                addProximityAlertLocked(latitude, longitude, radius, expiration, intent,
+                        packageName);
             }
         } catch (SecurityException se) {
             throw se;
@@ -1648,7 +1673,7 @@
     }
 
     private void addProximityAlertLocked(double latitude, double longitude,
-        float radius, long expiration, PendingIntent intent) {
+        float radius, long expiration, PendingIntent intent, String packageName) {
         if (LOCAL_LOGV) {
             Slog.v(TAG, "addProximityAlert: latitude = " + latitude +
                     ", longitude = " + longitude +
@@ -1656,6 +1681,8 @@
                     ", intent = " + intent);
         }
 
+        checkPackageName(Binder.getCallingUid(), packageName);
+
         // Require ability to access all providers for now
         if (!isAllowedProviderSafe(LocationManager.GPS_PROVIDER) ||
             !isAllowedProviderSafe(LocationManager.NETWORK_PROVIDER)) {
@@ -1666,12 +1693,12 @@
             expiration += System.currentTimeMillis();
         }
         ProximityAlert alert = new ProximityAlert(Binder.getCallingUid(),
-                latitude, longitude, radius, expiration, intent);
+                latitude, longitude, radius, expiration, intent, packageName);
         mProximityAlerts.put(intent, alert);
 
         if (mProximityReceiver == null) {
             mProximityListener = new ProximityListener();
-            mProximityReceiver = new Receiver(mProximityListener);
+            mProximityReceiver = new Receiver(mProximityListener, packageName);
 
             for (int i = mProviders.size() - 1; i >= 0; i--) {
                 LocationProviderInterface provider = mProviders.get(i);
@@ -1787,13 +1814,13 @@
         return isAllowedBySettingsLocked(provider);
     }
 
-    public Location getLastKnownLocation(String provider) {
+    public Location getLastKnownLocation(String provider, String packageName) {
         if (LOCAL_LOGV) {
             Slog.v(TAG, "getLastKnownLocation: " + provider);
         }
         try {
             synchronized (mLock) {
-                return _getLastKnownLocationLocked(provider);
+                return _getLastKnownLocationLocked(provider, packageName);
             }
         } catch (SecurityException se) {
             throw se;
@@ -1803,8 +1830,9 @@
         }
     }
 
-    private Location _getLastKnownLocationLocked(String provider) {
+    private Location _getLastKnownLocationLocked(String provider, String packageName) {
         checkPermissionsSafe(provider, null);
+        checkPackageName(Binder.getCallingUid(), packageName);
 
         LocationProviderInterface p = mProvidersByName.get(provider);
         if (p == null) {
@@ -1815,6 +1843,10 @@
             return null;
         }
 
+        if (inBlacklist(packageName)) {
+            return null;
+        }
+
         return mLastKnownLocation.get(provider);
     }
 
@@ -1877,6 +1909,10 @@
             Receiver receiver = r.mReceiver;
             boolean receiverDead = false;
 
+            if (inBlacklist(receiver.mPackageName)) {
+                continue;
+            }
+
             Location lastLoc = r.mLastFixBroadcast;
             if ((lastLoc == null) || shouldBroadcastSafe(location, lastLoc, r)) {
                 if (lastLoc == null) {
@@ -2315,6 +2351,113 @@
         }
     }
 
+    public class BlacklistObserver extends ContentObserver {
+        public BlacklistObserver(Handler handler) {
+            super(handler);
+        }
+        @Override
+        public void onChange(boolean selfChange) {
+            reloadBlacklist();
+        }
+    }
+
+    private void loadBlacklist() {
+        // Register for changes
+        BlacklistObserver observer = new BlacklistObserver(mLocationHandler);
+        mContext.getContentResolver().registerContentObserver(Settings.Secure.getUriFor(
+                BLACKLIST_CONFIG_NAME), false, observer);
+        mContext.getContentResolver().registerContentObserver(Settings.Secure.getUriFor(
+                WHITELIST_CONFIG_NAME), false, observer);
+        reloadBlacklist();
+    }
+
+    private void reloadBlacklist() {
+        String blacklist[] = getStringArray(BLACKLIST_CONFIG_NAME);
+        String whitelist[] = getStringArray(WHITELIST_CONFIG_NAME);
+        synchronized (mLock) {
+            mWhitelist = whitelist;
+            Slog.i(TAG, "whitelist: " + arrayToString(mWhitelist));
+            mBlacklist = blacklist;
+            Slog.i(TAG, "blacklist: " + arrayToString(mBlacklist));
+        }
+    }
+
+    private static String arrayToString(String[] array) {
+        StringBuilder s = new StringBuilder();
+        s.append('[');
+        boolean first = true;
+        for (String a : array) {
+            if (!first) s.append(',');
+            first = false;
+            s.append(a);
+        }
+        s.append(']');
+        return s.toString();
+    }
+
+    private String[] getStringArray(String key) {
+        String flatString = Settings.Secure.getString(mContext.getContentResolver(), key);
+        if (flatString == null) {
+            return new String[0];
+        }
+        String[] splitStrings = flatString.split(",");
+        ArrayList<String> result = new ArrayList<String>();
+        for (String pkg : splitStrings) {
+            pkg = pkg.trim();
+            if (pkg.isEmpty()) {
+                continue;
+            }
+            result.add(pkg);
+        }
+        return result.toArray(new String[result.size()]);
+    }
+
+    /**
+     * Return true if in blacklist, and not in whitelist.
+     */
+    private boolean inBlacklist(String packageName) {
+        synchronized (mLock) {
+            for (String black : mBlacklist) {
+                if (packageName.startsWith(black)) {
+                    if (inWhitelist(packageName)) {
+                        continue;
+                    } else {
+                        if (LOCAL_LOGV) Log.d(TAG, "dropping location (blacklisted): "
+                                + packageName + " matches " + black);
+                        return true;
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Return true if any of packages are in whitelist
+     */
+    private boolean inWhitelist(String pkg) {
+        synchronized (mLock) {
+            for (String white : mWhitelist) {
+                if (pkg.startsWith(white)) return true;
+            }
+        }
+        return false;
+    }
+
+    private void checkPackageName(int uid, String packageName) {
+        if (packageName == null) {
+            throw new SecurityException("packageName cannot be null");
+        }
+        String[] packages = mPackageManager.getPackagesForUid(uid);
+        if (packages == null) {
+            throw new SecurityException("invalid UID " + uid);
+        }
+        for (String pkg : packages) {
+            if (packageName.equals(pkg)) return;
+        }
+        throw new SecurityException("invalid package name");
+    }
+
     private void log(String log) {
         if (Log.isLoggable(TAG, Log.VERBOSE)) {
             Slog.d(TAG, log);
@@ -2346,6 +2489,8 @@
                     j.getValue().dump(pw, "        ");
                 }
             }
+            pw.println("  Package blacklist:" + arrayToString(mBlacklist));
+            pw.println("  Package whitelist:" + arrayToString(mWhitelist));
             pw.println("  Records by Provider:");
             for (Map.Entry<String, ArrayList<UpdateRecord>> i
                     : mRecordsByProvider.entrySet()) {