summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--api/current.txt31
-rw-r--r--core/java/android/accounts/AccountManager.java5
-rw-r--r--core/java/android/app/Application.java8
-rw-r--r--core/java/android/app/INotificationManager.aidl2
-rw-r--r--core/java/android/content/Context.java10
-rw-r--r--core/java/android/content/Intent.java15
-rw-r--r--core/java/android/content/RestrictionEntry.aidl20
-rw-r--r--core/java/android/content/RestrictionEntry.java275
-rw-r--r--core/java/android/content/pm/PackageInfo.java7
-rw-r--r--core/java/android/content/pm/PackageParser.java9
-rw-r--r--core/java/android/os/IUserManager.aidl4
-rw-r--r--core/java/android/os/UserManager.java41
-rw-r--r--core/java/android/provider/Settings.java16
-rw-r--r--core/java/android/webkit/AccessibilityInjectorFallback.java1
-rw-r--r--core/java/android/widget/ImageView.java2
-rw-r--r--core/java/android/widget/TextView.java3
-rw-r--r--core/res/AndroidManifest.xml4
-rw-r--r--core/res/res/layout/keyguard_emergency_carrier_area.xml5
-rw-r--r--core/res/res/values/attrs_manifest.xml7
-rw-r--r--core/res/res/values/config.xml2
-rw-r--r--core/res/res/values/public.xml1
-rw-r--r--core/res/res/values/strings.xml14
-rw-r--r--core/res/res/values/symbols.xml4
-rw-r--r--libs/hwui/Caches.cpp1
-rw-r--r--libs/hwui/DisplayList.cpp6
-rw-r--r--libs/hwui/FontRenderer.cpp12
-rw-r--r--libs/hwui/FontRenderer.h15
-rw-r--r--libs/hwui/OpenGLRenderer.cpp135
-rw-r--r--libs/hwui/OpenGLRenderer.h4
-rw-r--r--libs/hwui/PathCache.cpp7
-rw-r--r--libs/hwui/PathCache.h16
-rw-r--r--libs/hwui/Snapshot.cpp11
-rw-r--r--libs/hwui/Snapshot.h2
-rw-r--r--libs/hwui/thread/TaskManager.cpp6
-rw-r--r--libs/hwui/thread/TaskManager.h6
-rw-r--r--libs/hwui/utils/Pair.h58
-rw-r--r--policy/src/com/android/internal/policy/impl/keyguard/EmergencyCarrierArea.java62
-rw-r--r--services/java/com/android/server/NotificationManagerService.java129
-rw-r--r--services/java/com/android/server/Watchdog.java9
-rw-r--r--services/java/com/android/server/accounts/AccountManagerService.java94
-rw-r--r--services/java/com/android/server/am/ServiceRecord.java41
-rw-r--r--services/java/com/android/server/pm/UserManagerService.java154
-rw-r--r--services/java/com/android/server/wifi/WifiService.java12
-rw-r--r--wifi/java/android/net/wifi/WifiManager.java9
44 files changed, 1135 insertions, 140 deletions
diff --git a/api/current.txt b/api/current.txt
index 12dc6b4..f4b4d53 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -846,6 +846,7 @@ package android {
field public static final int reqNavigation = 16843306; // 0x101022a
field public static final int reqTouchScreen = 16843303; // 0x1010227
field public static final int required = 16843406; // 0x101028e
+ field public static final int requiredForAllUsers = 16843728; // 0x10103d0
field public static final int requiresFadingEdge = 16843685; // 0x10103a5
field public static final int requiresSmallestWidthDp = 16843620; // 0x1010364
field public static final int resizeMode = 16843619; // 0x1010363
@@ -5405,6 +5406,7 @@ package android.content {
method public abstract java.lang.String[] fileList();
method public abstract android.content.Context getApplicationContext();
method public abstract android.content.pm.ApplicationInfo getApplicationInfo();
+ method public java.util.List<android.content.RestrictionEntry> getApplicationRestrictions();
method public abstract android.content.res.AssetManager getAssets();
method public abstract java.io.File getCacheDir();
method public abstract java.lang.ClassLoader getClassLoader();
@@ -5848,6 +5850,7 @@ package android.content {
field public static final java.lang.String ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE = "android.intent.action.EXTERNAL_APPLICATIONS_UNAVAILABLE";
field public static final java.lang.String ACTION_FACTORY_TEST = "android.intent.action.FACTORY_TEST";
field public static final java.lang.String ACTION_GET_CONTENT = "android.intent.action.GET_CONTENT";
+ field public static final java.lang.String ACTION_GET_RESTRICTION_ENTRIES = "android.intent.action.GET_RESTRICTION_ENTRIES";
field public static final java.lang.String ACTION_GTALK_SERVICE_CONNECTED = "android.intent.action.GTALK_CONNECTED";
field public static final java.lang.String ACTION_GTALK_SERVICE_DISCONNECTED = "android.intent.action.GTALK_DISCONNECTED";
field public static final java.lang.String ACTION_HEADSET_PLUG = "android.intent.action.HEADSET_PLUG";
@@ -5989,6 +5992,7 @@ package android.content {
field public static final java.lang.String EXTRA_REFERRER = "android.intent.extra.REFERRER";
field public static final java.lang.String EXTRA_REMOTE_INTENT_TOKEN = "android.intent.extra.remote_intent_token";
field public static final java.lang.String EXTRA_REPLACING = "android.intent.extra.REPLACING";
+ field public static final java.lang.String EXTRA_RESTRICTIONS = "android.intent.extra.restrictions";
field public static final java.lang.String EXTRA_RETURN_RESULT = "android.intent.extra.RETURN_RESULT";
field public static final java.lang.String EXTRA_SHORTCUT_ICON = "android.intent.extra.shortcut.ICON";
field public static final java.lang.String EXTRA_SHORTCUT_ICON_RESOURCE = "android.intent.extra.shortcut.ICON_RESOURCE";
@@ -6231,6 +6235,33 @@ package android.content {
ctor public ReceiverCallNotAllowedException(java.lang.String);
}
+ public class RestrictionEntry implements android.os.Parcelable {
+ ctor public RestrictionEntry(java.lang.String, java.lang.String);
+ ctor public RestrictionEntry(java.lang.String, boolean);
+ ctor public RestrictionEntry(java.lang.String, java.lang.String[]);
+ ctor public RestrictionEntry(android.os.Parcel);
+ method public int describeContents();
+ method public boolean getBooleanValue();
+ method public java.lang.String[] getMultipleValues();
+ method public java.lang.String getStringValue();
+ method public void setMultipleValues(java.lang.String[]);
+ method public void setValue(java.lang.String);
+ method public void setValue(boolean);
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator CREATOR;
+ field public static final int TYPE_BOOLEAN = 1; // 0x1
+ field public static final int TYPE_CHOICE = 2; // 0x2
+ field public static final int TYPE_CHOICE_LEVEL = 3; // 0x3
+ field public static final int TYPE_MULTI_SELECT = 4; // 0x4
+ field public static final int TYPE_NULL = 0; // 0x0
+ field public java.lang.String[] choices;
+ field public java.lang.String description;
+ field public java.lang.String key;
+ field public java.lang.String title;
+ field public int type;
+ field public java.lang.String[] values;
+ }
+
public class SearchRecentSuggestionsProvider extends android.content.ContentProvider {
ctor public SearchRecentSuggestionsProvider();
method public int delete(android.net.Uri, java.lang.String, java.lang.String[]);
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index f8b7a0c..313260f 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -152,6 +152,9 @@ public class AccountManager {
public static final int ERROR_CODE_BAD_ARGUMENTS = 7;
public static final int ERROR_CODE_BAD_REQUEST = 8;
+ /** @hide */
+ public static final int ERROR_CODE_USER_RESTRICTED = 100;
+
/**
* Bundle key used for the {@link String} account name in results
* from methods which return information about a particular account.
@@ -1526,7 +1529,7 @@ public class AccountManager {
}
public void onError(int code, String message) {
- if (code == ERROR_CODE_CANCELED) {
+ if (code == ERROR_CODE_CANCELED || code == ERROR_CODE_USER_RESTRICTED) {
// the authenticator indicated that this request was canceled, do so now
cancel(true /* mayInterruptIfRunning */);
return;
diff --git a/core/java/android/app/Application.java b/core/java/android/app/Application.java
index 132388e..7b07438 100644
--- a/core/java/android/app/Application.java
+++ b/core/java/android/app/Application.java
@@ -17,14 +17,17 @@
package android.app;
import java.util.ArrayList;
+import java.util.List;
import android.content.ComponentCallbacks;
import android.content.ComponentCallbacks2;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
+import android.content.RestrictionEntry;
import android.content.res.Configuration;
import android.os.Bundle;
+import android.os.UserManager;
/**
* Base class for those who need to maintain global application state. You can
@@ -131,6 +134,11 @@ public class Application extends ContextWrapper implements ComponentCallbacks2 {
}
}
+ public List<RestrictionEntry> getApplicationRestrictions() {
+ return ((UserManager) getSystemService(USER_SERVICE))
+ .getApplicationRestrictions(getPackageName(), android.os.Process.myUserHandle());
+ }
+
public void registerComponentCallbacks(ComponentCallbacks callback) {
synchronized (mComponentCallbacks) {
mComponentCallbacks.add(callback);
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 14bcc0d..3d9b2ae 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -41,7 +41,7 @@ interface INotificationManager
StatusBarNotification[] getActiveNotifications(String callingPkg);
StatusBarNotification[] getHistoricalNotifications(String callingPkg, int count);
- void registerListener(in INotificationListener listener, int userid);
+ void registerListener(in INotificationListener listener, String pkg, int userid);
void unregisterListener(in INotificationListener listener, int userid);
}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 8a9eed2..7dd76cd 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -45,6 +45,7 @@ import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.util.List;
/**
* Interface to global information about an application environment. This is
@@ -286,6 +287,15 @@ public abstract class Context {
public abstract Context getApplicationContext();
/**
+ * Returns the list of restrictions for the application, or null if there are no
+ * restrictions.
+ * @return
+ */
+ public List<RestrictionEntry> getApplicationRestrictions() {
+ return getApplicationContext().getApplicationRestrictions();
+ }
+
+ /**
* Add a new {@link ComponentCallbacks} to the base application of the
* Context, which will be called at the same times as the ComponentCallbacks
* methods of activities and other components are called. Note that you
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 53c47d2..e1461e3 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -2415,6 +2415,15 @@ public class Intent implements Parcelable, Cloneable {
"android.intent.action.PRE_BOOT_COMPLETED";
/**
+ * Broadcast to a specific application to query any supported restrictions to impose
+ * on restricted users. The response should contain an extra {@link #EXTRA_RESTRICTIONS}
+ * which is of type <code>ArrayList&lt;RestrictionEntry&gt;</code>.
+ * @see RestrictionEntry
+ */
+ public static final String ACTION_GET_RESTRICTION_ENTRIES =
+ "android.intent.action.GET_RESTRICTION_ENTRIES";
+
+ /**
* Sent the first time a user is starting, to allow system apps to
* perform one time initialization. (This will not be seen by third
* party applications because a newly initialized user does not have any
@@ -3146,6 +3155,12 @@ public class Intent implements Parcelable, Cloneable {
public static final String EXTRA_USER_HANDLE =
"android.intent.extra.user_handle";
+ /**
+ * Extra used in the response from a BroadcastReceiver that handles
+ * {@link #ACTION_GET_RESTRICTION_ENTRIES}.
+ */
+ public static final String EXTRA_RESTRICTIONS = "android.intent.extra.restrictions";
+
// ---------------------------------------------------------------------
// ---------------------------------------------------------------------
// Intent flags (see mFlags variable).
diff --git a/core/java/android/content/RestrictionEntry.aidl b/core/java/android/content/RestrictionEntry.aidl
new file mode 100644
index 0000000..b93eee3
--- /dev/null
+++ b/core/java/android/content/RestrictionEntry.aidl
@@ -0,0 +1,20 @@
+/*
+**
+** Copyright 2013, 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;
+
+parcelable RestrictionEntry;
diff --git a/core/java/android/content/RestrictionEntry.java b/core/java/android/content/RestrictionEntry.java
new file mode 100644
index 0000000..97c4cb8
--- /dev/null
+++ b/core/java/android/content/RestrictionEntry.java
@@ -0,0 +1,275 @@
+/*
+ * Copyright (C) 2013 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;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Inherited;
+
+/**
+ * Applications can expose restrictions for a restricted user on a
+ * multiuser device. The administrator can configure these restrictions that will then be
+ * applied to the restricted user. Each RestrictionsEntry is one configurable restriction.
+ * <p/>
+ * Any application that chooses to expose such restrictions does so by implementing a
+ * receiver that handles the {@link Intent#ACTION_GET_RESTRICTION_ENTRIES} action.
+ * The receiver then returns a result bundle that contains an entry called "restrictions", whose
+ * value is an ArrayList<RestrictionsEntry>.
+ */
+public class RestrictionEntry implements Parcelable {
+
+ /**
+ * A type of restriction. Use this one for information that needs to be transferred across
+ * but shouldn't be presented to the user in the UI.
+ */
+ public static final int TYPE_NULL = 0;
+ /**
+ * A type of restriction. Use this for storing true/false values, typically presented as
+ * a checkbox in the UI.
+ */
+ public static final int TYPE_BOOLEAN = 1;
+ /**
+ * A type of restriction. Use this for storing a string value, typically presented as
+ * a single-select list. The {@link #values} and {@link #choices} need to have the list of
+ * possible values and the corresponding localized strings, respectively, to present in the UI.
+ */
+ public static final int TYPE_CHOICE = 2;
+ /**
+ * A type of restriction. Use this for storing a string value, typically presented as
+ * a single-select list. The {@link #values} and {@link #choices} need to have the list of
+ * possible values and the corresponding localized strings, respectively, to present in the UI.
+ * The presentation could imply that values in lower array indices are included when a
+ * particular value is chosen.
+ */
+ public static final int TYPE_CHOICE_LEVEL = 3;
+ /**
+ * A type of restriction. Use this for presenting a multi-select list where more than one
+ * entry can be selected, such as for choosing specific titles to white-list.
+ * The {@link #values} and {@link #choices} need to have the list of
+ * possible values and the corresponding localized strings, respectively, to present in the UI.
+ * Use {@link #getMultipleValues()} and {@link #setMultipleValues(String[])} to manipulate
+ * the selections.
+ */
+ public static final int TYPE_MULTI_SELECT = 4;
+
+ /** The type of restriction. */
+ public int type;
+
+ /** The unique key that identifies the restriction. */
+ public String key;
+
+ /** The user-visible title of the restriction. */
+ public String title;
+
+ /** The user-visible secondary description of the restriction. */
+ public String description;
+
+ /** The user-visible set of choices used for single-select and multi-select lists. */
+ public String [] choices;
+
+ /** The values corresponding to the user-visible choices. The value(s) of this entry will
+ * one or more of these, returned by {@link #getMultipleValues()} and
+ * {@link #getStringValue()}.
+ */
+ public String [] values;
+
+ /* The chosen value, whose content depends on the type of the restriction. */
+ private String currentValue;
+ /* List of selected choices in the multi-select case. */
+ private String[] currentValues;
+
+ /**
+ * Constructor for {@link #TYPE_CHOICE} and {@link #TYPE_CHOICE_LEVEL} types.
+ * @param key the unique key for this restriction
+ * @param value the current value
+ */
+ public RestrictionEntry(String key, String value) {
+ this.key = key;
+ this.currentValue = value;
+ }
+
+ /**
+ * Constructor for {@link #TYPE_BOOLEAN} type.
+ * @param key the unique key for this restriction
+ * @param value the current value
+ */
+ public RestrictionEntry(String key, boolean value) {
+ this.key = key;
+ setValue(value);
+ }
+
+ /**
+ * Constructor for {@link #TYPE_MULTI_SELECT} type.
+ * @param key the unique key for this restriction
+ * @param multipleValues the list of values that are currently selected
+ */
+ public RestrictionEntry(String key, String[] multipleValues) {
+ this.key = key;
+ this.currentValues = multipleValues;
+ }
+
+ /**
+ * Returns the current value. Null for {@link #TYPE_MULTI_SELECT} type.
+ * @return the current value
+ */
+ public String getStringValue() {
+ return currentValue;
+ }
+
+ /**
+ * Returns the list of current selections. Null if the type is not {@link #TYPE_MULTI_SELECT}.
+ * @return the list of current selections.
+ */
+ public String[] getMultipleValues() {
+ return currentValues;
+ }
+
+ /**
+ * Returns the current boolean value for entries of type {@link #TYPE_BOOLEAN}.
+ * @return the current value
+ */
+ public boolean getBooleanValue() {
+ return Boolean.parseBoolean(currentValue);
+ }
+
+ /**
+ * Set the current string value.
+ * @param s the current value
+ */
+ public void setValue(String s) {
+ currentValue = s;
+ }
+
+ /**
+ * Sets the current boolean value.
+ * @param b the current value
+ */
+ public void setValue(boolean b) {
+ currentValue = Boolean.toString(b);
+ }
+
+ /**
+ * Sets the current list of selected values.
+ * @param values the current list of selected values
+ */
+ public void setMultipleValues(String[] values) {
+ currentValues = values;
+ }
+
+ private boolean equalArrays(String[] one, String[] other) {
+ if (one.length != other.length) return false;
+ for (int i = 0; i < one.length; i++) {
+ if (!one[i].equals(other[i])) return false;
+ }
+ return true;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) return true;
+ if (!(o instanceof RestrictionEntry)) return false;
+ final RestrictionEntry other = (RestrictionEntry) o;
+ // Make sure that either currentValue matches or currentValues matches.
+ return type == other.type && key.equals(other.key)
+ &&
+ ((currentValues == null && other.currentValues == null
+ && currentValue != null && currentValue.equals(other.currentValue))
+ ||
+ (currentValue == null && other.currentValue == null
+ && currentValues != null && equalArrays(currentValues, other.currentValues)));
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 17;
+ result = 31 * result + key.hashCode();
+ if (currentValue != null) {
+ result = 31 * result + currentValue.hashCode();
+ } else if (currentValues != null) {
+ for (String value : currentValues) {
+ if (value != null) {
+ result = 31 * result + value.hashCode();
+ }
+ }
+ }
+ return result;
+ }
+
+ private String[] readArray(Parcel in) {
+ int count = in.readInt();
+ String[] values = new String[count];
+ for (int i = 0; i < count; i++) {
+ values[i] = in.readString();
+ }
+ return values;
+ }
+
+ public RestrictionEntry(Parcel in) {
+ type = in.readInt();
+ key = in.readString();
+ title = in.readString();
+ description = in.readString();
+ choices = readArray(in);
+ values = readArray(in);
+ currentValue = in.readString();
+ currentValues = readArray(in);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ private void writeArray(Parcel dest, String[] values) {
+ if (values == null) {
+ dest.writeInt(0);
+ } else {
+ dest.writeInt(values.length);
+ for (int i = 0; i < values.length; i++) {
+ dest.writeString(values[i]);
+ }
+ }
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(type);
+ dest.writeString(key);
+ dest.writeString(title);
+ dest.writeString(description);
+ writeArray(dest, choices);
+ writeArray(dest, values);
+ dest.writeString(currentValue);
+ writeArray(dest, currentValues);
+ }
+
+ public static final Creator<RestrictionEntry> CREATOR = new Creator<RestrictionEntry>() {
+ public RestrictionEntry createFromParcel(Parcel source) {
+ return new RestrictionEntry(source);
+ }
+
+ public RestrictionEntry[] newArray(int size) {
+ return new RestrictionEntry[size];
+ }
+ };
+
+ @Override
+ public String toString() {
+ return "RestrictionsEntry {type=" + type + ", key=" + key + ", value=" + currentValue + "}";
+ }
+}
diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java
index 85f7aa5..77ca7f6 100644
--- a/core/java/android/content/pm/PackageInfo.java
+++ b/core/java/android/content/pm/PackageInfo.java
@@ -217,7 +217,10 @@ public class PackageInfo implements Parcelable {
* @hide
*/
public int installLocation = INSTALL_LOCATION_INTERNAL_ONLY;
-
+
+ /** @hide */
+ public boolean requiredForAllUsers;
+
public PackageInfo() {
}
@@ -258,6 +261,7 @@ public class PackageInfo implements Parcelable {
dest.writeTypedArray(configPreferences, parcelableFlags);
dest.writeTypedArray(reqFeatures, parcelableFlags);
dest.writeInt(installLocation);
+ dest.writeInt(requiredForAllUsers ? 1 : 0);
}
public static final Parcelable.Creator<PackageInfo> CREATOR
@@ -296,5 +300,6 @@ public class PackageInfo implements Parcelable {
configPreferences = source.createTypedArray(ConfigurationInfo.CREATOR);
reqFeatures = source.createTypedArray(FeatureInfo.CREATOR);
installLocation = source.readInt();
+ requiredForAllUsers = source.readInt() != 0;
}
}
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 5eac903..149b8e5 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -289,6 +289,7 @@ public class PackageParser {
pi.sharedUserLabel = p.mSharedUserLabel;
pi.applicationInfo = generateApplicationInfo(p, flags, state, userId);
pi.installLocation = p.installLocation;
+ pi.requiredForAllUsers = p.mRequiredForAllUsers;
pi.firstInstallTime = firstInstallTime;
pi.lastUpdateTime = lastUpdateTime;
if ((flags&PackageManager.GET_GIDS) != 0) {
@@ -1760,6 +1761,11 @@ public class PackageParser {
false)) {
ai.flags |= ApplicationInfo.FLAG_PERSISTENT;
}
+ if (sa.getBoolean(
+ com.android.internal.R.styleable.AndroidManifestApplication_requiredForAllUsers,
+ false)) {
+ owner.mRequiredForAllUsers = true;
+ }
}
if (sa.getBoolean(
@@ -3271,6 +3277,9 @@ public class PackageParser {
public int installLocation;
+ /* An app that's required for all users and cannot be uninstalled for a user */
+ public boolean mRequiredForAllUsers;
+
/**
* Digest suitable for comparing whether this package's manifest is the
* same as another.
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index 34c9740..4c2d7a6 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -20,6 +20,7 @@ package android.os;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
import android.content.pm.UserInfo;
+import android.content.RestrictionEntry;
import android.graphics.Bitmap;
/**
@@ -40,4 +41,7 @@ interface IUserManager {
int getUserHandle(int userSerialNumber);
Bundle getUserRestrictions(int userHandle);
void setUserRestrictions(in Bundle restrictions, int userHandle);
+ void setApplicationRestrictions(in String packageName, in List<RestrictionEntry> entries,
+ int userHandle);
+ List<RestrictionEntry> getApplicationRestrictions(in String packageName, int userHandle);
}
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 51e3e7c..7c05528 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -17,6 +17,7 @@ package android.os;
import android.app.ActivityManagerNative;
import android.content.Context;
+import android.content.RestrictionEntry;
import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.graphics.Bitmap;
@@ -126,7 +127,19 @@ public class UserManager {
public boolean isUserAGoat() {
return false;
}
-
+
+ /**
+ * @hide
+ */
+ public boolean isUserRestricted() {
+ try {
+ return mService.getUserInfo(getUserHandle()).isRestricted();
+ } catch (RemoteException re) {
+ Log.w(TAG, "Could not check if user restricted ", re);
+ return false;
+ }
+ }
+
/**
* Return whether the given user is actively running. This means that
* the user is in the "started" state, not "stopped" -- it is currently
@@ -450,10 +463,34 @@ public class UserManager {
}
/**
- * Returns whether the current user is allow to toggle location sharing settings.
+ * Returns whether the current user is allowed to toggle location sharing settings.
* @hide
*/
public boolean isLocationSharingToggleAllowed() {
return getUserRestrictions().getBoolean(ALLOW_CONFIG_LOCATION_ACCESS);
}
+
+ /**
+ * @hide
+ */
+ public List<RestrictionEntry> getApplicationRestrictions(String packageName, UserHandle user) {
+ try {
+ return mService.getApplicationRestrictions(packageName, user.getIdentifier());
+ } catch (RemoteException re) {
+ Log.w(TAG, "Could not get application restrictions for user " + user.getIdentifier());
+ }
+ return null;
+ }
+
+ /**
+ * @hide
+ */
+ public void setApplicationRestrictions(String packageName, List<RestrictionEntry> entries,
+ UserHandle user) {
+ try {
+ mService.setApplicationRestrictions(packageName, entries, user.getIdentifier());
+ } catch (RemoteException re) {
+ Log.w(TAG, "Could not set application restrictions for user " + user.getIdentifier());
+ }
+ }
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index d251ca2..19283be 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -4039,6 +4039,14 @@ public final class Settings {
public static final String SCREENSAVER_DEFAULT_COMPONENT = "screensaver_default_component";
/**
+ * Name of a package that the current user has explicitly allowed to see all of that
+ * user's notifications.
+ *
+ * @hide
+ */
+ public static final String ENABLED_NOTIFICATION_LISTENERS = "enabled_notification_listeners";
+
+ /**
* This are the settings to be backed up.
*
* NOTE: Settings are backed up and restored in the order they appear
@@ -4791,6 +4799,14 @@ public final class Settings {
"wifi_scan_always_enabled";
/**
+ * Setting to indicate whether the user should be notified that scans are still
+ * available when Wi-Fi is turned off
+ * @hide
+ */
+ public static final String WIFI_NOTIFY_SCAN_ALWAYS_AVAILABLE =
+ "wifi_notify_scan_always_enabled";
+
+ /**
* Used to save the Wifi_ON state prior to tethering.
* This state will be checked to restore Wifi after
* the user turns off tethering.
diff --git a/core/java/android/webkit/AccessibilityInjectorFallback.java b/core/java/android/webkit/AccessibilityInjectorFallback.java
index 6417527..40cc4e9 100644
--- a/core/java/android/webkit/AccessibilityInjectorFallback.java
+++ b/core/java/android/webkit/AccessibilityInjectorFallback.java
@@ -438,7 +438,6 @@ class AccessibilityInjectorFallback {
event.setFromIndex(0);
event.setToIndex(selection.length());
sendAccessibilityEvent(event);
- event.recycle();
}
}
diff --git a/core/java/android/widget/ImageView.java b/core/java/android/widget/ImageView.java
index 1bbf4eb..cde6ceb 100644
--- a/core/java/android/widget/ImageView.java
+++ b/core/java/android/widget/ImageView.java
@@ -204,7 +204,7 @@ public class ImageView extends View {
@Override
public boolean hasOverlappingRendering() {
- return (getBackground() != null);
+ return (getBackground() != null && getBackground().getCurrent() != null);
}
@Override
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 5fbb642..52b7a81 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -4794,7 +4794,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
@Override
public boolean hasOverlappingRendering() {
- return (getBackground() != null || mText instanceof Spannable || hasSelection());
+ return ((getBackground() != null && getBackground().getCurrent() != null)
+ || mText instanceof Spannable || hasSelection());
}
/**
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 5a1c0f8..be8f5f4 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1073,10 +1073,10 @@
<!-- Group of permissions that are related to the screenlock. -->
<permission-group android:name="android.permission-group.SCREENLOCK"
- android:label="@string/permgrouplab_storage"
+ android:label="@string/permgrouplab_screenlock"
android:icon="@drawable/perm_group_screenlock"
android:permissionGroupFlags="personalInfo"
- android:description="@string/permgroupdesc_storage"
+ android:description="@string/permgroupdesc_screenlock"
android:priority="230" />
<!-- Allows applications to disable the keyguard -->
diff --git a/core/res/res/layout/keyguard_emergency_carrier_area.xml b/core/res/res/layout/keyguard_emergency_carrier_area.xml
index 52adc04..b8a7654 100644
--- a/core/res/res/layout/keyguard_emergency_carrier_area.xml
+++ b/core/res/res/layout/keyguard_emergency_carrier_area.xml
@@ -18,7 +18,7 @@
-->
<!-- This contains emergency call button and carrier as shared by pin/pattern/password screens -->
-<LinearLayout
+<com.android.internal.policy.impl.keyguard.EmergencyCarrierArea
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -29,6 +29,7 @@
android:clickable="true">
<com.android.internal.policy.impl.keyguard.CarrierText
+ android:id="@+id/carrier_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
@@ -72,4 +73,4 @@
android:visibility="gone"/>
</LinearLayout>
-</LinearLayout>
+</com.android.internal.policy.impl.keyguard.EmergencyCarrierArea>
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index f1d8c03..6f59817 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -254,7 +254,11 @@
not normally be used by applications; it requires that the system keep
your application running at all times. -->
<attr name="persistent" format="boolean" />
-
+
+ <!-- Flag to specify if this application needs to be present for all users. Only pre-installed
+ applications can request this feature. Default value is false. -->
+ <attr name="requiredForAllUsers" format="boolean" />
+
<!-- Flag indicating whether the application can be debugged, even when
running on a device that is running in user mode. -->
<attr name="debuggable" format="boolean" />
@@ -844,6 +848,7 @@
for normal behavior. -->
<attr name="hasCode" format="boolean" />
<attr name="persistent" />
+ <attr name="requiredForAllUsers" />
<!-- Specify whether the components in this application are enabled or not (that is, can be
instantiated by the system).
If "false", it overrides any component specific values (a value of "true" will not
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 6a8407f..7a6a1e9 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1041,4 +1041,6 @@
<string name="config_chooseTypeAndAccountActivity"
>android/android.accounts.ChooseTypeAndAccountActivity</string>
+ <!-- Apps that are authorized to access shared accounts, overridden by product overlays -->
+ <string name="config_appsAuthorizedForSharedAccounts"></string>
</resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 42e5cf1..ef0ae03 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2042,6 +2042,7 @@
<eat-comment />
<public type="attr" name="windowOverscan" />
+ <public type="attr" name="requiredForAllUsers" />
<public type="style" name="Theme.NoTitleBar.Overscan" />
<public type="style" name="Theme.Light.NoTitleBar.Overscan" />
<public type="style" name="Theme.Black.NoTitleBar.Overscan" />
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 43c518a..a2d4570 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -474,6 +474,11 @@
<string name="permgroupdesc_camera">Direct access to camera for image or video capture.</string>
<!-- Title of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permgrouplab_screenlock">Lock screen</string>
+ <!-- Description of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permgroupdesc_screenlock">Ability to affect behavior of the lock screen on your device.</string>
+
+ <!-- Title of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permgrouplab_appInfo">Your applications information</string>
<!-- Description of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permgroupdesc_appInfo">Ability to affect behavior of other applications on your device.</string>
@@ -2984,6 +2989,15 @@
<!-- If the device is getting low on internal storage, a notification is shown to the user. This is the message of that notification. -->
<string name="low_internal_storage_view_text">Some system functions may not work</string>
+ <!-- [CHAR LIMIT=NONE] Stub notification title for an app running a service that has provided
+ a bad bad notification for itself. -->
+ <string name="app_running_notification_title"><xliff:g id="app_name">%1$s</xliff:g>
+ running</string>
+ <!-- [CHAR LIMIT=NONE] Stub notification text for an app running a service that has provided
+ a bad bad notification for itself. -->
+ <string name="app_running_notification_text"><xliff:g id="app_name">%1$s</xliff:g>
+ is currently running</string>
+
<!-- Preference framework strings. -->
<string name="ok">OK</string>
<!-- Preference framework strings. -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 614ac07..80e77dd 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -330,6 +330,8 @@
<java-symbol type="string" name="addToDictionary" />
<java-symbol type="string" name="action_bar_home_description" />
<java-symbol type="string" name="action_bar_up_description" />
+ <java-symbol type="string" name="app_running_notification_title" />
+ <java-symbol type="string" name="app_running_notification_text" />
<java-symbol type="string" name="delete" />
<java-symbol type="string" name="deleteText" />
<java-symbol type="string" name="ellipsis_two_dots" />
@@ -865,6 +867,7 @@
<java-symbol type="string" name="owner_name" />
<java-symbol type="string" name="config_chooseAccountActivity" />
<java-symbol type="string" name="config_chooseTypeAndAccountActivity" />
+ <java-symbol type="string" name="config_appsAuthorizedForSharedAccounts" />
<java-symbol type="plurals" name="abbrev_in_num_days" />
@@ -1323,6 +1326,7 @@
<java-symbol type="id" name="keyguard_bouncer_frame" />
<java-symbol type="id" name="app_widget_container" />
<java-symbol type="id" name="view_flipper" />
+ <java-symbol type="id" name="carrier_text" />
<java-symbol type="id" name="emergency_call_button" />
<java-symbol type="id" name="keyguard_host_view" />
<java-symbol type="id" name="delete_button" />
diff --git a/libs/hwui/Caches.cpp b/libs/hwui/Caches.cpp
index dc3a4e2..57d1a4f 100644
--- a/libs/hwui/Caches.cpp
+++ b/libs/hwui/Caches.cpp
@@ -310,6 +310,7 @@ void Caches::flush(FlushMode mode) {
fontRenderer->flush();
textureCache.flush();
pathCache.clear();
+ tasks.stop();
// fall through
case kFlushMode_Layers:
layerCache.clear();
diff --git a/libs/hwui/DisplayList.cpp b/libs/hwui/DisplayList.cpp
index 55ddd17..d985ad0 100644
--- a/libs/hwui/DisplayList.cpp
+++ b/libs/hwui/DisplayList.cpp
@@ -351,9 +351,9 @@ void DisplayList::outputViewProperties(const int level) {
level * 2, "", mTransformMatrix, MATRIX_ARGS(mTransformMatrix));
}
}
- if (mAlpha < 1 && !mCaching) {
- if (!mHasOverlappingRendering) {
- ALOGD("%*sSetAlpha %.2f", level * 2, "", mAlpha);
+ if (mAlpha < 1) {
+ if (mCaching || !mHasOverlappingRendering) {
+ ALOGD("%*sScaleAlpha %.2f", level * 2, "", mAlpha);
} else {
int flags = SkCanvas::kHasAlphaLayer_SaveFlag;
if (mClipChildren) {
diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp
index 009dcb9..26c7e5d 100644
--- a/libs/hwui/FontRenderer.cpp
+++ b/libs/hwui/FontRenderer.cpp
@@ -21,6 +21,7 @@
#include <cutils/properties.h>
+#include <utils/Functor.h>
#include <utils/Log.h>
#include <RenderScript.h>
@@ -416,6 +417,8 @@ void FontRenderer::issueDrawCommand() {
CacheTexture* texture = mCacheTextures[i];
if (texture->canDraw()) {
if (first) {
+ if (mFunctor) (*mFunctor)(0, NULL);
+
checkTextureUpdate();
caches.bindIndicesBuffer(mIndexBufferID);
@@ -561,11 +564,12 @@ FontRenderer::DropShadow FontRenderer::renderDropShadow(SkPaint* paint, const ch
return image;
}
-void FontRenderer::initRender(const Rect* clip, Rect* bounds) {
+void FontRenderer::initRender(const Rect* clip, Rect* bounds, Functor* functor) {
checkInit();
mDrawn = false;
mBounds = bounds;
+ mFunctor = functor;
mClip = clip;
}
@@ -583,13 +587,13 @@ void FontRenderer::precache(SkPaint* paint, const char* text, int numGlyphs, con
bool FontRenderer::renderPosText(SkPaint* paint, const Rect* clip, const char *text,
uint32_t startIndex, uint32_t len, int numGlyphs, int x, int y,
- const float* positions, Rect* bounds) {
+ const float* positions, Rect* bounds, Functor* functor) {
if (!mCurrentFont) {
ALOGE("No font set");
return false;
}
- initRender(clip, bounds);
+ initRender(clip, bounds, functor);
mCurrentFont->render(paint, text, startIndex, len, numGlyphs, x, y, positions);
finishRender();
@@ -604,7 +608,7 @@ bool FontRenderer::renderTextOnPath(SkPaint* paint, const Rect* clip, const char
return false;
}
- initRender(clip, bounds);
+ initRender(clip, bounds, NULL);
mCurrentFont->render(paint, text, startIndex, len, numGlyphs, path, hOffset, vOffset);
finishRender();
diff --git a/libs/hwui/FontRenderer.h b/libs/hwui/FontRenderer.h
index 080cc71..1da3b6c 100644
--- a/libs/hwui/FontRenderer.h
+++ b/libs/hwui/FontRenderer.h
@@ -38,6 +38,8 @@ namespace RSC {
class ScriptIntrinsicBlur;
}
+class Functor;
+
namespace android {
namespace uirenderer {
@@ -62,7 +64,8 @@ public:
// bounds is an out parameter
bool renderPosText(SkPaint* paint, const Rect* clip, const char *text, uint32_t startIndex,
- uint32_t len, int numGlyphs, int x, int y, const float* positions, Rect* bounds);
+ uint32_t len, int numGlyphs, int x, int y, const float* positions, Rect* bounds,
+ Functor* functor);
// bounds is an out parameter
bool renderTextOnPath(SkPaint* paint, const Rect* clip, const char *text, uint32_t startIndex,
uint32_t len, int numGlyphs, SkPath* path, float hOffset, float vOffset, Rect* bounds);
@@ -88,13 +91,8 @@ public:
DropShadow renderDropShadow(SkPaint* paint, const char *text, uint32_t startIndex,
uint32_t len, int numGlyphs, uint32_t radius, const float* positions);
- GLuint getTexture(bool linearFiltering = false) {
- checkInit();
-
- mCurrentCacheTexture->setLinearFiltering(linearFiltering);
+ void setTextureFiltering(bool linearFiltering) {
mLinearFiltering = linearFiltering;
-
- return mCurrentCacheTexture->getTextureId();
}
uint32_t getCacheSize() const {
@@ -125,7 +123,7 @@ private:
void initVertexArrayBuffers();
void checkInit();
- void initRender(const Rect* clip, Rect* bounds);
+ void initRender(const Rect* clip, Rect* bounds, Functor* functor);
void finishRender();
void issueDrawCommand();
@@ -167,6 +165,7 @@ private:
uint32_t mMaxNumberOfQuads;
uint32_t mIndexBufferID;
+ Functor* mFunctor;
const Rect* mClip;
Rect* mBounds;
bool mDrawn;
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index 0a299d3..2cf7183 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -193,6 +193,7 @@ status_t OpenGLRenderer::prepareDirty(float left, float top,
mSaveCount = 1;
mSnapshot->setClip(left, top, right, bottom);
+ mTilingClip.set(left, top, right, bottom);
mDirtyClip = true;
updateLayers();
@@ -206,8 +207,7 @@ status_t OpenGLRenderer::prepareDirty(float left, float top,
// invoked during the frame
mSuppressTiling = mCaches.hasRegisteredFunctors();
- mTilingSnapshot = mSnapshot;
- startTiling(mTilingSnapshot, true);
+ startTiling(mSnapshot, true);
debugOverdraw(true, true);
@@ -252,9 +252,9 @@ void OpenGLRenderer::syncState() {
void OpenGLRenderer::startTiling(const sp<Snapshot>& s, bool opaque) {
if (!mSuppressTiling) {
- Rect* clip = mTilingSnapshot->clipRect;
+ Rect* clip = &mTilingClip;
if (s->flags & Snapshot::kFlagFboTarget) {
- clip = &s->layer->clipRect;
+ clip = &(s->layer->clipRect);
}
startTiling(*clip, s->height, opaque);
@@ -480,10 +480,10 @@ void OpenGLRenderer::debugOverdraw(bool enable, bool clear) {
void OpenGLRenderer::renderOverdraw() {
if (mCaches.debugOverdraw && getTargetFbo() == 0) {
- const Rect* clip = mTilingSnapshot->clipRect;
+ const Rect* clip = &mTilingClip;
mCaches.enableScissor();
- mCaches.setScissor(clip->left, mTilingSnapshot->height - clip->bottom,
+ mCaches.setScissor(clip->left, mFirstSnapshot->height - clip->bottom,
clip->right - clip->left, clip->bottom - clip->top);
mCaches.stencil.enableDebugTest(2);
@@ -932,7 +932,7 @@ void OpenGLRenderer::composeLayer(sp<Snapshot> current, sp<Snapshot> previous) {
}
void OpenGLRenderer::drawTextureLayer(Layer* layer, const Rect& rect) {
- float alpha = layer->getAlpha() / 255.0f;
+ float alpha = layer->getAlpha() / 255.0f * mSnapshot->alpha;
setupDraw();
if (layer->getRenderTarget() == GL_TEXTURE_2D) {
@@ -996,9 +996,10 @@ void OpenGLRenderer::composeLayerRect(Layer* layer, const Rect& rect, bool swap)
layer->setFilter(GL_LINEAR, true);
}
+ float alpha = layer->getAlpha() / 255.0f * mSnapshot->alpha;
+ bool blend = layer->isBlend() || alpha < 1.0f;
drawTextureMesh(x, y, x + rect.getWidth(), y + rect.getHeight(),
- layer->getTexture(), layer->getAlpha() / 255.0f,
- layer->getMode(), layer->isBlend(),
+ layer->getTexture(), alpha, layer->getMode(), blend,
&mMeshVertices[0].position[0], &mMeshVertices[0].texture[0],
GL_TRIANGLE_STRIP, gMeshCount, swap, swap || simpleTransform);
@@ -1033,7 +1034,7 @@ void OpenGLRenderer::composeLayerRegion(Layer* layer, const Rect& rect) {
rects = safeRegion.getArray(&count);
}
- const float alpha = layer->getAlpha() / 255.0f;
+ const float alpha = layer->getAlpha() / 255.0f * mSnapshot->alpha;
const float texX = 1.0f / float(layer->getWidth());
const float texY = 1.0f / float(layer->getHeight());
const float height = rect.getHeight();
@@ -1751,7 +1752,7 @@ void OpenGLRenderer::setupDrawSimpleMesh() {
}
void OpenGLRenderer::setupDrawTexture(GLuint texture) {
- bindTexture(texture);
+ if (texture) bindTexture(texture);
mTextureUnit++;
mCaches.enableTexCoordsVertexArray();
}
@@ -2410,7 +2411,8 @@ status_t OpenGLRenderer::drawShape(float left, float top, const PathTexture* tex
status_t OpenGLRenderer::drawRoundRect(float left, float top, float right, float bottom,
float rx, float ry, SkPaint* p) {
- if (mSnapshot->isIgnored() || quickRejectPreStroke(left, top, right, bottom, p)) {
+ if (mSnapshot->isIgnored() || quickRejectPreStroke(left, top, right, bottom, p) ||
+ (p->getAlpha() == 0 && getXfermode(p->getXfermode()) != SkXfermode::kClear_Mode)) {
return DrawGlInfo::kStatusDone;
}
@@ -2435,7 +2437,8 @@ status_t OpenGLRenderer::drawRoundRect(float left, float top, float right, float
status_t OpenGLRenderer::drawCircle(float x, float y, float radius, SkPaint* p) {
if (mSnapshot->isIgnored() || quickRejectPreStroke(x - radius, y - radius,
- x + radius, y + radius, p)) {
+ x + radius, y + radius, p) ||
+ (p->getAlpha() == 0 && getXfermode(p->getXfermode()) != SkXfermode::kClear_Mode)) {
return DrawGlInfo::kStatusDone;
}
if (p->getPathEffect() != 0) {
@@ -2455,7 +2458,8 @@ status_t OpenGLRenderer::drawCircle(float x, float y, float radius, SkPaint* p)
status_t OpenGLRenderer::drawOval(float left, float top, float right, float bottom,
SkPaint* p) {
- if (mSnapshot->isIgnored() || quickRejectPreStroke(left, top, right, bottom, p)) {
+ if (mSnapshot->isIgnored() || quickRejectPreStroke(left, top, right, bottom, p) ||
+ (p->getAlpha() == 0 && getXfermode(p->getXfermode()) != SkXfermode::kClear_Mode)) {
return DrawGlInfo::kStatusDone;
}
@@ -2476,7 +2480,8 @@ status_t OpenGLRenderer::drawOval(float left, float top, float right, float bott
status_t OpenGLRenderer::drawArc(float left, float top, float right, float bottom,
float startAngle, float sweepAngle, bool useCenter, SkPaint* p) {
- if (mSnapshot->isIgnored() || quickRejectPreStroke(left, top, right, bottom, p)) {
+ if (mSnapshot->isIgnored() || quickRejectPreStroke(left, top, right, bottom, p) ||
+ (p->getAlpha() == 0 && getXfermode(p->getXfermode()) != SkXfermode::kClear_Mode)) {
return DrawGlInfo::kStatusDone;
}
@@ -2512,7 +2517,8 @@ status_t OpenGLRenderer::drawArc(float left, float top, float right, float botto
#define SkPaintDefaults_MiterLimit SkIntToScalar(4)
status_t OpenGLRenderer::drawRect(float left, float top, float right, float bottom, SkPaint* p) {
- if (mSnapshot->isIgnored() || quickRejectPreStroke(left, top, right, bottom, p)) {
+ if (mSnapshot->isIgnored() || quickRejectPreStroke(left, top, right, bottom, p) ||
+ (p->getAlpha() == 0 && getXfermode(p->getXfermode()) != SkXfermode::kClear_Mode)) {
return DrawGlInfo::kStatusDone;
}
@@ -2588,6 +2594,48 @@ bool OpenGLRenderer::canSkipText(const SkPaint* paint) const {
return alpha == 0.0f && getXfermode(paint->getXfermode()) == SkXfermode::kSrcOver_Mode;
}
+class TextSetupFunctor: public Functor {
+public:
+ TextSetupFunctor(OpenGLRenderer& renderer, float x, float y, bool pureTranslate,
+ int alpha, SkXfermode::Mode mode, SkPaint* paint): Functor(),
+ renderer(renderer), x(x), y(y), pureTranslate(pureTranslate),
+ alpha(alpha), mode(mode), paint(paint) {
+ }
+ ~TextSetupFunctor() { }
+
+ status_t operator ()(int what, void* data) {
+ renderer.setupDraw();
+ renderer.setupDrawTextGamma(paint);
+ renderer.setupDrawDirtyRegionsDisabled();
+ renderer.setupDrawWithTexture(true);
+ renderer.setupDrawAlpha8Color(paint->getColor(), alpha);
+ renderer.setupDrawColorFilter();
+ renderer.setupDrawShader();
+ renderer.setupDrawBlending(true, mode);
+ renderer.setupDrawProgram();
+ renderer.setupDrawModelView(x, y, x, y, pureTranslate, true);
+ // Calling setupDrawTexture with the name 0 will enable the
+ // uv attributes and increase the texture unit count
+ // texture binding will be performed by the font renderer as
+ // needed
+ renderer.setupDrawTexture(0);
+ renderer.setupDrawPureColorUniforms();
+ renderer.setupDrawColorFilterUniforms();
+ renderer.setupDrawShaderUniforms(pureTranslate);
+ renderer.setupDrawTextGammaUniforms();
+
+ return NO_ERROR;
+ }
+
+ OpenGLRenderer& renderer;
+ float x;
+ float y;
+ bool pureTranslate;
+ int alpha;
+ SkXfermode::Mode mode;
+ SkPaint* paint;
+};
+
status_t OpenGLRenderer::drawPosText(const char* text, int bytesCount, int count,
const float* positions, SkPaint* paint) {
if (text == NULL || count == 0 || mSnapshot->isIgnored() || canSkipText(paint)) {
@@ -2624,31 +2672,16 @@ status_t OpenGLRenderer::drawPosText(const char* text, int bytesCount, int count
if (pureTranslate && !linearFilter) {
linearFilter = fabs(y - (int) y) > 0.0f || fabs(x - (int) x) > 0.0f;
}
-
- mCaches.activeTexture(0);
- setupDraw();
- setupDrawTextGamma(paint);
- setupDrawDirtyRegionsDisabled();
- setupDrawWithTexture(true);
- setupDrawAlpha8Color(paint->getColor(), alpha);
- setupDrawColorFilter();
- setupDrawShader();
- setupDrawBlending(true, mode);
- setupDrawProgram();
- setupDrawModelView(x, y, x, y, pureTranslate, true);
- setupDrawTexture(fontRenderer.getTexture(linearFilter));
- setupDrawPureColorUniforms();
- setupDrawColorFilterUniforms();
- setupDrawShaderUniforms(pureTranslate);
- setupDrawTextGammaUniforms();
+ fontRenderer.setTextureFiltering(linearFilter);
const Rect* clip = pureTranslate ? mSnapshot->clipRect : &mSnapshot->getLocalClip();
Rect bounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f);
const bool hasActiveLayer = hasLayer();
+ TextSetupFunctor functor(*this, x, y, pureTranslate, alpha, mode, paint);
if (fontRenderer.renderPosText(paint, clip, text, 0, bytesCount, count, x, y,
- positions, hasActiveLayer ? &bounds : NULL)) {
+ positions, hasActiveLayer ? &bounds : NULL, &functor)) {
if (hasActiveLayer) {
if (!pureTranslate) {
currentTransform().mapRect(bounds);
@@ -2741,40 +2774,22 @@ status_t OpenGLRenderer::drawText(const char* text, int bytesCount, int count,
// Pick the appropriate texture filtering
bool linearFilter = !pureTranslate || fabs(y - (int) y) > 0.0f || fabs(x - (int) x) > 0.0f;
-
- // The font renderer will always use texture unit 0
- mCaches.activeTexture(0);
- setupDraw();
- setupDrawTextGamma(paint);
- setupDrawDirtyRegionsDisabled();
- setupDrawWithTexture(true);
- setupDrawAlpha8Color(paint->getColor(), alpha);
- setupDrawColorFilter();
- setupDrawShader();
- setupDrawBlending(true, mode);
- setupDrawProgram();
- setupDrawModelView(x, y, x, y, pureTranslate, true);
- // See comment above; the font renderer must use texture unit 0
- // assert(mTextureUnit == 0)
- setupDrawTexture(fontRenderer.getTexture(linearFilter));
- setupDrawPureColorUniforms();
- setupDrawColorFilterUniforms();
- setupDrawShaderUniforms(pureTranslate);
- setupDrawTextGammaUniforms();
+ fontRenderer.setTextureFiltering(linearFilter);
// TODO: Implement better clipping for scaled/rotated text
const Rect* clip = !pureTranslate ? NULL : mSnapshot->clipRect;
Rect bounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f);
bool status;
+ TextSetupFunctor functor(*this, x, y, pureTranslate, alpha, mode, paint);
if (CC_UNLIKELY(paint->getTextAlign() != SkPaint::kLeft_Align)) {
SkPaint paintCopy(*paint);
paintCopy.setTextAlign(SkPaint::kLeft_Align);
status = fontRenderer.renderPosText(&paintCopy, clip, text, 0, bytesCount, count, x, y,
- positions, hasActiveLayer ? &bounds : NULL);
+ positions, hasActiveLayer ? &bounds : NULL, &functor);
} else {
status = fontRenderer.renderPosText(paint, clip, text, 0, bytesCount, count, x, y,
- positions, hasActiveLayer ? &bounds : NULL);
+ positions, hasActiveLayer ? &bounds : NULL, &functor);
}
if (status && hasActiveLayer) {
@@ -2797,12 +2812,12 @@ status_t OpenGLRenderer::drawTextOnPath(const char* text, int bytesCount, int co
FontRenderer& fontRenderer = mCaches.fontRenderer->getFontRenderer(paint);
fontRenderer.setFont(paint, mat4::identity());
+ fontRenderer.setTextureFiltering(true);
int alpha;
SkXfermode::Mode mode;
getAlphaAndMode(paint, &alpha, &mode);
- mCaches.activeTexture(0);
setupDraw();
setupDrawTextGamma(paint);
setupDrawDirtyRegionsDisabled();
@@ -2813,7 +2828,11 @@ status_t OpenGLRenderer::drawTextOnPath(const char* text, int bytesCount, int co
setupDrawBlending(true, mode);
setupDrawProgram();
setupDrawModelView(0.0f, 0.0f, 0.0f, 0.0f, false, true);
- setupDrawTexture(fontRenderer.getTexture(true));
+ // Calling setupDrawTexture with the name 0 will enable the
+ // uv attributes and increase the texture unit count
+ // texture binding will be performed by the font renderer as
+ // needed
+ setupDrawTexture(0);
setupDrawPureColorUniforms();
setupDrawColorFilterUniforms();
setupDrawShaderUniforms(false);
diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h
index 01fcfc6..7bb9395 100644
--- a/libs/hwui/OpenGLRenderer.h
+++ b/libs/hwui/OpenGLRenderer.h
@@ -85,6 +85,7 @@ struct DeferredDisplayState {
///////////////////////////////////////////////////////////////////////////////
class DisplayList;
+class TextSetupFunctor;
class VertexBuffer;
/**
@@ -946,7 +947,7 @@ private:
// Current state
sp<Snapshot> mSnapshot;
// State used to define the clipping region
- sp<Snapshot> mTilingSnapshot;
+ Rect mTilingClip;
// Used to draw textured quads
TextureVertex mMeshVertices[4];
@@ -995,6 +996,7 @@ private:
String8 mName;
friend class DisplayListRenderer;
+ friend class TextSetupFunctor;
}; // class OpenGLRenderer
diff --git a/libs/hwui/PathCache.cpp b/libs/hwui/PathCache.cpp
index 490c22a..07c4207 100644
--- a/libs/hwui/PathCache.cpp
+++ b/libs/hwui/PathCache.cpp
@@ -347,14 +347,15 @@ void PathCache::PathProcessor::onProcess(const sp<Task<SkBitmap*> >& task) {
// Paths
///////////////////////////////////////////////////////////////////////////////
-void PathCache::remove(SkPath* path) {
+void PathCache::remove(const path_pair_t& pair) {
Vector<PathDescription> pathsToRemove;
LruCache<PathDescription, PathTexture*>::Iterator i(mCache);
while (i.next()) {
const PathDescription& key = i.key();
if (key.type == kShapePath &&
- (key.shape.path.mPath == path || key.shape.path.mPath == path->getSourcePath())) {
+ (key.shape.path.mPath == pair.getFirst() ||
+ key.shape.path.mPath == pair.getSecond())) {
pathsToRemove.push(key);
}
}
@@ -366,7 +367,7 @@ void PathCache::remove(SkPath* path) {
void PathCache::removeDeferred(SkPath* path) {
Mutex::Autolock l(mLock);
- mGarbage.push(path);
+ mGarbage.push(path_pair_t(path, const_cast<SkPath*>(path->getSourcePath())));
}
void PathCache::clearGarbage() {
diff --git a/libs/hwui/PathCache.h b/libs/hwui/PathCache.h
index e6d92df..1467231 100644
--- a/libs/hwui/PathCache.h
+++ b/libs/hwui/PathCache.h
@@ -26,6 +26,7 @@
#include "Debug.h"
#include "Properties.h"
#include "Texture.h"
+#include "utils/Pair.h"
class SkBitmap;
class SkCanvas;
@@ -215,10 +216,6 @@ public:
PathTexture* get(SkPath* path, SkPaint* paint);
/**
- * Removes an entry.
- */
- void remove(SkPath* path);
- /**
* Removes the specified path. This is meant to be called from threads
* that are not the EGL context thread.
*/
@@ -251,6 +248,8 @@ public:
float& left, float& top, float& offset, uint32_t& width, uint32_t& height);
private:
+ typedef Pair<SkPath*, SkPath*> path_pair_t;
+
PathTexture* addTexture(const PathDescription& entry,
const SkPath *path, const SkPaint* paint);
PathTexture* addTexture(const PathDescription& entry, SkBitmap* bitmap);
@@ -261,6 +260,12 @@ private:
}
/**
+ * Removes an entry.
+ * The pair must define first=path, second=sourcePath
+ */
+ void remove(const path_pair_t& pair);
+
+ /**
* Ensures there is enough space in the cache for a texture of the specified
* dimensions.
*/
@@ -318,7 +323,8 @@ private:
bool mDebugEnabled;
sp<PathProcessor> mProcessor;
- Vector<SkPath*> mGarbage;
+
+ Vector<path_pair_t> mGarbage;
mutable Mutex mLock;
}; // class PathCache
diff --git a/libs/hwui/Snapshot.cpp b/libs/hwui/Snapshot.cpp
index 923913e..d26ee38 100644
--- a/libs/hwui/Snapshot.cpp
+++ b/libs/hwui/Snapshot.cpp
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+#define LOG_TAG "OpenGLRenderer"
+
#include "Snapshot.h"
#include <SkCanvas.h>
@@ -199,5 +201,14 @@ bool Snapshot::isIgnored() const {
return invisible || empty;
}
+void Snapshot::dump() const {
+ ALOGD("Snapshot %p, flags %x, prev %p, height %d, ignored %d, hasComplexClip %d",
+ this, flags, previous.get(), height, isIgnored(), clipRegion && !clipRegion->isEmpty());
+ ALOGD(" ClipRect (at %p) %.1f %.1f %.1f %.1f",
+ clipRect, clipRect->left, clipRect->top, clipRect->right, clipRect->bottom);
+ ALOGD(" Transform (at %p):", transform);
+ transform->dump();
+}
+
}; // namespace uirenderer
}; // namespace android
diff --git a/libs/hwui/Snapshot.h b/libs/hwui/Snapshot.h
index ffd4729..cc6d0cd 100644
--- a/libs/hwui/Snapshot.h
+++ b/libs/hwui/Snapshot.h
@@ -228,6 +228,8 @@ public:
*/
float alpha;
+ void dump() const;
+
private:
void ensureClipRegion();
void copyClipRectFromRegion();
diff --git a/libs/hwui/thread/TaskManager.cpp b/libs/hwui/thread/TaskManager.cpp
index ce6c8c0..c8bfd9c 100644
--- a/libs/hwui/thread/TaskManager.cpp
+++ b/libs/hwui/thread/TaskManager.cpp
@@ -48,6 +48,12 @@ bool TaskManager::canRunTasks() const {
return mThreads.size() > 0;
}
+void TaskManager::stop() {
+ for (size_t i = 0; i < mThreads.size(); i++) {
+ mThreads[i]->exit();
+ }
+}
+
bool TaskManager::addTaskBase(const sp<TaskBase>& task, const sp<TaskProcessorBase>& processor) {
if (mThreads.size() > 0) {
TaskWrapper wrapper(task, processor);
diff --git a/libs/hwui/thread/TaskManager.h b/libs/hwui/thread/TaskManager.h
index bc86062..477314b 100644
--- a/libs/hwui/thread/TaskManager.h
+++ b/libs/hwui/thread/TaskManager.h
@@ -47,6 +47,12 @@ public:
*/
bool canRunTasks() const;
+ /**
+ * Stops all allocated threads. Adding tasks will start
+ * the threads again as necessary.
+ */
+ void stop();
+
private:
template <typename T>
friend class TaskProcessor;
diff --git a/libs/hwui/utils/Pair.h b/libs/hwui/utils/Pair.h
new file mode 100644
index 0000000..172606a
--- /dev/null
+++ b/libs/hwui/utils/Pair.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_HWUI_PAIR_H
+#define ANDROID_HWUI_PAIR_H
+
+namespace android {
+namespace uirenderer {
+
+template <typename F, typename S>
+struct Pair {
+ F first;
+ S second;
+
+ Pair() { }
+ Pair(const Pair& o) : first(o.first), second(o.second) { }
+ Pair(const F& f, const S& s) : first(f), second(s) { }
+
+ inline const F& getFirst() const {
+ return first;
+ }
+
+ inline const S& getSecond() const {
+ return second;
+ }
+};
+
+}; // namespace uirenderer
+
+template <typename F, typename S>
+struct trait_trivial_ctor< uirenderer::Pair<F, S> >
+{ enum { value = aggregate_traits<F, S>::has_trivial_ctor }; };
+template <typename F, typename S>
+struct trait_trivial_dtor< uirenderer::Pair<F, S> >
+{ enum { value = aggregate_traits<F, S>::has_trivial_dtor }; };
+template <typename F, typename S>
+struct trait_trivial_copy< uirenderer::Pair<F, S> >
+{ enum { value = aggregate_traits<F, S>::has_trivial_copy }; };
+template <typename F, typename S>
+struct trait_trivial_move< uirenderer::Pair<F, S> >
+{ enum { value = aggregate_traits<F, S>::has_trivial_move }; };
+
+}; // namespace android
+
+#endif // ANDROID_HWUI_PAIR_H
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/EmergencyCarrierArea.java b/policy/src/com/android/internal/policy/impl/keyguard/EmergencyCarrierArea.java
new file mode 100644
index 0000000..cfe1ef4
--- /dev/null
+++ b/policy/src/com/android/internal/policy/impl/keyguard/EmergencyCarrierArea.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.policy.impl.keyguard;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.LinearLayout;
+
+import com.android.internal.R;
+
+public class EmergencyCarrierArea extends LinearLayout {
+
+ private CarrierText mCarrierText;
+ private EmergencyButton mEmergencyButton;
+
+ public EmergencyCarrierArea(Context context) {
+ super(context);
+ }
+
+ public EmergencyCarrierArea(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mCarrierText = (CarrierText) findViewById(R.id.carrier_text);
+ mEmergencyButton = (EmergencyButton) findViewById(R.id.emergency_call_button);
+
+ // The emergency button overlaps the carrier text, only noticeable when highlighted.
+ // So temporarily hide the carrier text while the emergency button is pressed.
+ mEmergencyButton.setOnTouchListener(new OnTouchListener(){
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ switch(event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ mCarrierText.animate().alpha(0);
+ break;
+ case MotionEvent.ACTION_UP:
+ mCarrierText.animate().alpha(1);
+ break;
+ }
+ return false;
+ }});
+ }
+}
diff --git a/services/java/com/android/server/NotificationManagerService.java b/services/java/com/android/server/NotificationManagerService.java
index 9e036d1..5cf1c28 100644
--- a/services/java/com/android/server/NotificationManagerService.java
+++ b/services/java/com/android/server/NotificationManagerService.java
@@ -50,12 +50,11 @@ import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
-import android.os.Parcel;
-import android.os.Parcelable;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
+import android.os.UserManager;
import android.os.Vibrator;
import android.provider.Settings;
import android.telephony.TelephonyManager;
@@ -124,6 +123,7 @@ public class NotificationManagerService extends INotificationManager.Stub
final Context mContext;
final IActivityManager mAm;
+ final UserManager mUserManager;
final IBinder mForegroundToken = new Binder();
private WorkerHandler mHandler;
@@ -164,6 +164,7 @@ public class NotificationManagerService extends INotificationManager.Stub
private final AppOpsManager mAppOps;
private ArrayList<NotificationListenerInfo> mListeners = new ArrayList<NotificationListenerInfo>();
+ private ArrayList<String> mEnabledListenersForCurrentUser = new ArrayList<String>();
// Notification control database. For now just contains disabled packages.
private AtomicFile mPolicyFile;
@@ -180,20 +181,27 @@ public class NotificationManagerService extends INotificationManager.Stub
private class NotificationListenerInfo implements DeathRecipient {
INotificationListener listener;
+ String pkg;
int userid;
- public NotificationListenerInfo(INotificationListener listener, int userid) {
+ boolean isSystem;
+
+ public NotificationListenerInfo(INotificationListener listener, String pkg, int userid,
+ boolean isSystem) {
this.listener = listener;
+ this.pkg = pkg;
this.userid = userid;
+ this.isSystem = isSystem;
}
- boolean userMatches(StatusBarNotification sbn) {
+ boolean enabledAndUserMatches(StatusBarNotification sbn) {
+ final int nid = sbn.getUserId();
+ if (!(isSystem || isEnabledForUser(nid))) return false;
if (this.userid == UserHandle.USER_ALL) return true;
- int nid = sbn.getUserId();
return (nid == UserHandle.USER_ALL || nid == this.userid);
}
public void notifyPostedIfUserMatch(StatusBarNotification sbn) {
- if (!userMatches(sbn)) return;
+ if (!enabledAndUserMatches(sbn)) return;
try {
listener.onNotificationPosted(sbn);
} catch (RemoteException ex) {
@@ -202,7 +210,7 @@ public class NotificationManagerService extends INotificationManager.Stub
}
public void notifyRemovedIfUserMatch(StatusBarNotification sbn) {
- if (!userMatches(sbn)) return;
+ if (!enabledAndUserMatches(sbn)) return;
try {
listener.onNotificationRemoved(sbn);
} catch (RemoteException ex) {
@@ -214,6 +222,14 @@ public class NotificationManagerService extends INotificationManager.Stub
public void binderDied() {
unregisterListener(this.listener, this.userid);
}
+
+ /** convenience method for looking in mEnabledListenersForCurrentUser */
+ public boolean isEnabledForUser(int userid) {
+ for (int i=0; i<mEnabledListenersForCurrentUser.size(); i++) {
+ if (this.pkg.equals(mEnabledListenersForCurrentUser.get(i))) return true;
+ }
+ return false;
+ }
}
private static class Archive {
@@ -413,12 +429,14 @@ public class NotificationManagerService extends INotificationManager.Stub
}
public StatusBarNotification[] getActiveNotifications(String callingPkg) {
+ // enforce() will ensure the calling uid has the correct permission
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_NOTIFICATIONS,
"NotificationManagerService.getActiveNotifications");
StatusBarNotification[] tmp = null;
int uid = Binder.getCallingUid();
+ // noteOp will check to make sure the callingPkg matches the uid
if (mAppOps.noteOpNoThrow(AppOpsManager.OP_ACCESS_NOTIFICATIONS, uid, callingPkg)
== AppOpsManager.MODE_ALLOWED) {
synchronized (mNotificationList) {
@@ -433,12 +451,14 @@ public class NotificationManagerService extends INotificationManager.Stub
}
public StatusBarNotification[] getHistoricalNotifications(String callingPkg, int count) {
+ // enforce() will ensure the calling uid has the correct permission
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_NOTIFICATIONS,
"NotificationManagerService.getHistoricalNotifications");
StatusBarNotification[] tmp = null;
int uid = Binder.getCallingUid();
+ // noteOp will check to make sure the callingPkg matches the uid
if (mAppOps.noteOpNoThrow(AppOpsManager.OP_ACCESS_NOTIFICATIONS, uid, callingPkg)
== AppOpsManager.MODE_ALLOWED) {
synchronized (mArchive) {
@@ -448,12 +468,27 @@ public class NotificationManagerService extends INotificationManager.Stub
return tmp;
}
+ boolean packageCanTapNotificationsForUser(final int uid, final String pkg) {
+ // Make sure the package and uid match, and that the package is allowed access
+ return (AppOpsManager.MODE_ALLOWED
+ == mAppOps.checkOpNoThrow(AppOpsManager.OP_ACCESS_NOTIFICATIONS, uid, pkg));
+ }
+
@Override
- public void registerListener(final INotificationListener listener, final int userid) {
- checkCallerIsSystem();
+ public void registerListener(final INotificationListener listener,
+ final String pkg, final int userid) {
+ // ensure system or allowed pkg
+ int uid = Binder.getCallingUid();
+ boolean isSystem = (UserHandle.getAppId(uid) == Process.SYSTEM_UID || uid == 0);
+ if (!(isSystem || packageCanTapNotificationsForUser(uid, pkg))) {
+ throw new SecurityException("Package " + pkg
+ + " may not listen for notifications");
+ }
+
synchronized (mNotificationList) {
try {
- NotificationListenerInfo info = new NotificationListenerInfo(listener, userid);
+ NotificationListenerInfo info
+ = new NotificationListenerInfo(listener, pkg, userid, isSystem);
listener.asBinder().linkToDeath(info, 0);
mListeners.add(info);
} catch (RemoteException e) {
@@ -464,7 +499,9 @@ public class NotificationManagerService extends INotificationManager.Stub
@Override
public void unregisterListener(INotificationListener listener, int userid) {
- checkCallerIsSystem();
+ // no need to check permissions; if your listener binder is in the list,
+ // that's proof that you had permission to add it in the first place
+
synchronized (mNotificationList) {
final int N = mListeners.size();
for (int i=N-1; i>=0; i--) {
@@ -740,37 +777,67 @@ public class NotificationManagerService extends INotificationManager.Stub
} else if (action.equals(Intent.ACTION_USER_PRESENT)) {
// turn off LED when user passes through lock screen
mNotificationLight.turnOff();
+ } else if (action.equals(Intent.ACTION_USER_SWITCHED)) {
+ // reload per-user settings
+ mSettingsObserver.update(null);
}
}
};
class SettingsObserver extends ContentObserver {
+ private final Uri NOTIFICATION_LIGHT_PULSE_URI
+ = Settings.System.getUriFor(Settings.System.NOTIFICATION_LIGHT_PULSE);
+
+ private final Uri ENABLED_NOTIFICATION_LISTENERS_URI
+ = Settings.System.getUriFor(Settings.Secure.ENABLED_NOTIFICATION_LISTENERS);
+
SettingsObserver(Handler handler) {
super(handler);
}
void observe() {
ContentResolver resolver = mContext.getContentResolver();
- resolver.registerContentObserver(Settings.System.getUriFor(
- Settings.System.NOTIFICATION_LIGHT_PULSE), false, this);
- update();
+ resolver.registerContentObserver(NOTIFICATION_LIGHT_PULSE_URI,
+ false, this);
+ resolver.registerContentObserver(ENABLED_NOTIFICATION_LISTENERS_URI,
+ false, this);
+ update(null);
}
- @Override public void onChange(boolean selfChange) {
- update();
+ @Override public void onChange(boolean selfChange, Uri uri) {
+ update(uri);
}
- public void update() {
+ public void update(Uri uri) {
ContentResolver resolver = mContext.getContentResolver();
- boolean pulseEnabled = Settings.System.getInt(resolver,
- Settings.System.NOTIFICATION_LIGHT_PULSE, 0) != 0;
- if (mNotificationPulseEnabled != pulseEnabled) {
- mNotificationPulseEnabled = pulseEnabled;
- updateNotificationPulse();
+ if (uri == null || NOTIFICATION_LIGHT_PULSE_URI.equals(uri)) {
+ boolean pulseEnabled = Settings.System.getInt(resolver,
+ Settings.System.NOTIFICATION_LIGHT_PULSE, 0) != 0;
+ if (mNotificationPulseEnabled != pulseEnabled) {
+ mNotificationPulseEnabled = pulseEnabled;
+ updateNotificationPulse();
+ }
+ }
+ if (uri == null || ENABLED_NOTIFICATION_LISTENERS_URI.equals(uri)) {
+ String pkglist = Settings.Secure.getString(
+ mContext.getContentResolver(),
+ Settings.Secure.ENABLED_NOTIFICATION_LISTENERS);
+ mEnabledListenersForCurrentUser.clear();
+ if (pkglist != null) {
+ String[] pkgs = pkglist.split(";");
+ for (int i=0; i<pkgs.length; i++) {
+ final String pkg = pkgs[i];
+ if (pkg != null && ! "".equals(pkg)) {
+ mEnabledListenersForCurrentUser.add(pkgs[i]);
+ }
+ }
+ }
}
}
}
+ private SettingsObserver mSettingsObserver;
+
static long[] getLongArray(Resources r, int resid, int maxlen, long[] def) {
int[] ar = r.getIntArray(resid);
if (ar == null) {
@@ -791,6 +858,7 @@ public class NotificationManagerService extends INotificationManager.Stub
mContext = context;
mVibrator = (Vibrator)context.getSystemService(Context.VIBRATOR_SERVICE);
mAm = ActivityManagerNative.getDefault();
+ mUserManager = (UserManager)context.getSystemService(Context.USER_SERVICE);
mToastQueue = new ArrayList<ToastRecord>();
mHandler = new WorkerHandler();
@@ -838,6 +906,7 @@ public class NotificationManagerService extends INotificationManager.Stub
filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
filter.addAction(Intent.ACTION_USER_PRESENT);
filter.addAction(Intent.ACTION_USER_STOPPED);
+ filter.addAction(Intent.ACTION_USER_SWITCHED);
mContext.registerReceiver(mIntentReceiver, filter);
IntentFilter pkgFilter = new IntentFilter();
pkgFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
@@ -849,8 +918,8 @@ public class NotificationManagerService extends INotificationManager.Stub
IntentFilter sdFilter = new IntentFilter(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
mContext.registerReceiver(mIntentReceiver, sdFilter);
- SettingsObserver observer = new SettingsObserver(mHandler);
- observer.observe();
+ mSettingsObserver = new SettingsObserver(mHandler);
+ mSettingsObserver.observe();
}
/**
@@ -1706,6 +1775,18 @@ public class NotificationManagerService extends INotificationManager.Stub
pw.println("Current Notification Manager state:");
+ pw.print(" Enabled listeners: [");
+ for (String pkg : mEnabledListenersForCurrentUser) {
+ pw.print(" " + pkg);
+ }
+ pw.println(" ]");
+
+ pw.println(" Live listeners:");
+ for (NotificationListenerInfo info : mListeners) {
+ pw.println(" " + info.pkg + " (user " + info.userid + "): " + info.listener
+ + (info.isSystem?" SYSTEM":""));
+ }
+
int N;
synchronized (mToastQueue) {
diff --git a/services/java/com/android/server/Watchdog.java b/services/java/com/android/server/Watchdog.java
index b2a8ad8..1663106 100644
--- a/services/java/com/android/server/Watchdog.java
+++ b/services/java/com/android/server/Watchdog.java
@@ -29,6 +29,7 @@ import android.content.IntentFilter;
import android.os.BatteryManager;
import android.os.Debug;
import android.os.Handler;
+import android.os.Looper;
import android.os.Message;
import android.os.Process;
import android.os.ServiceManager;
@@ -114,6 +115,10 @@ public class Watchdog extends Thread {
* Used for scheduling monitor callbacks and checking memory usage.
*/
final class HeartbeatHandler extends Handler {
+ HeartbeatHandler(Looper looper) {
+ super(looper);
+ }
+
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
@@ -183,7 +188,9 @@ public class Watchdog extends Thread {
private Watchdog() {
super("watchdog");
- mHandler = new HeartbeatHandler();
+ // Explicitly bind the HeartbeatHandler to run on the ServerThread, so
+ // that it can't get accidentally bound to another thread.
+ mHandler = new HeartbeatHandler(Looper.getMainLooper());
}
public void init(Context context, BatteryService battery,
diff --git a/services/java/com/android/server/accounts/AccountManagerService.java b/services/java/com/android/server/accounts/AccountManagerService.java
index 49295f5..09daf56 100644
--- a/services/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/java/com/android/server/accounts/AccountManagerService.java
@@ -455,7 +455,6 @@ public class AccountManagerService
@Override
public void onServiceChanged(AuthenticatorDescription desc, int userId, boolean removed) {
- Slog.d(TAG, "onServiceChanged() for userId " + userId);
validateAccountsInternal(getUserAccounts(userId), false /* invalidateAuthenticatorCache */);
}
@@ -588,16 +587,12 @@ public class AccountManagerService
if (result != null) {
if (result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT, false)) {
// Create a Session for the target user and pass in the bundle
- Slog.i(TAG, "getAccountCredentialsForCloning returned success, "
- + "sending result to target user");
completeCloningAccount(result, account, toAccounts);
} else {
- Slog.e(TAG, "getAccountCredentialsForCloning returned failure");
clonePassword(fromAccounts, toAccounts, account);
}
return;
} else {
- Slog.e(TAG, "getAccountCredentialsForCloning returned null");
clonePassword(fromAccounts, toAccounts, account);
super.onResult(result);
}
@@ -645,15 +640,12 @@ public class AccountManagerService
if (result != null) {
if (result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT, false)) {
// TODO: Anything?
- Slog.i(TAG, "addAccount returned success");
} else {
// TODO: Show error notification
// TODO: Should we remove the shadow account to avoid retries?
- Slog.e(TAG, "addAccountFromCredentials returned failure");
}
return;
} else {
- Slog.e(TAG, "addAccountFromCredentials returned null");
super.onResult(result);
}
}
@@ -1433,6 +1425,17 @@ public class AccountManagerService
if (accountType == null) throw new IllegalArgumentException("accountType is null");
checkManageAccountsPermission();
+ // Is user allowed to modify accounts?
+ if (!getUserManager().getUserRestrictions(Binder.getCallingUserHandle())
+ .getBoolean(UserManager.ALLOW_MODIFY_ACCOUNTS)) {
+ try {
+ response.onError(AccountManager.ERROR_CODE_USER_RESTRICTED,
+ "User is not allowed to add an account!");
+ } catch (RemoteException re) {
+ }
+ return;
+ }
+
UserAccounts accounts = getUserAccountsForCaller();
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
@@ -1573,17 +1576,19 @@ public class AccountManagerService
private volatile Account[] mAccountsOfType = null;
private volatile ArrayList<Account> mAccountsWithFeatures = null;
private volatile int mCurrentAccount = 0;
+ private int mCallingUid;
public GetAccountsByTypeAndFeatureSession(UserAccounts accounts,
- IAccountManagerResponse response, String type, String[] features) {
+ IAccountManagerResponse response, String type, String[] features, int callingUid) {
super(accounts, response, type, false /* expectActivityLaunch */,
true /* stripAuthTokenFromResult */);
+ mCallingUid = callingUid;
mFeatures = features;
}
public void run() throws RemoteException {
synchronized (mAccounts.cacheLock) {
- mAccountsOfType = getAccountsFromCacheLocked(mAccounts, mAccountType);
+ mAccountsOfType = getAccountsFromCacheLocked(mAccounts, mAccountType, mCallingUid);
}
// check whether each account matches the requested features
mAccountsWithFeatures = new ArrayList<Account>(mAccountsOfType.length);
@@ -1668,10 +1673,11 @@ public class AccountManagerService
public Account[] getAccounts(int userId) {
checkReadAccountsPermission();
UserAccounts accounts = getUserAccounts(userId);
+ int callingUid = Binder.getCallingUid();
long identityToken = clearCallingIdentity();
try {
synchronized (accounts.cacheLock) {
- return getAccountsFromCacheLocked(accounts, null);
+ return getAccountsFromCacheLocked(accounts, null, callingUid);
}
} finally {
restoreCallingIdentity(identityToken);
@@ -1711,7 +1717,8 @@ public class AccountManagerService
UserAccounts userAccounts = getUserAccounts(userId);
if (userAccounts == null) continue;
synchronized (userAccounts.cacheLock) {
- Account[] accounts = getAccountsFromCacheLocked(userAccounts, null);
+ Account[] accounts = getAccountsFromCacheLocked(userAccounts, null,
+ Binder.getCallingUid());
for (int a = 0; a < accounts.length; a++) {
runningAccounts.add(new AccountAndUser(accounts[a], userId));
}
@@ -1725,9 +1732,10 @@ public class AccountManagerService
@Override
public Account[] getAccountsAsUser(String type, int userId) {
+ final int callingUid = Binder.getCallingUid();
// Only allow the system process to read accounts of other users
if (userId != UserHandle.getCallingUserId()
- && Binder.getCallingUid() != android.os.Process.myUid()) {
+ && callingUid != android.os.Process.myUid()) {
throw new SecurityException("User " + UserHandle.getCallingUserId()
+ " trying to get account for " + userId);
}
@@ -1742,7 +1750,7 @@ public class AccountManagerService
long identityToken = clearCallingIdentity();
try {
synchronized (accounts.cacheLock) {
- return getAccountsFromCacheLocked(accounts, type);
+ return getAccountsFromCacheLocked(accounts, type, callingUid);
}
} finally {
restoreCallingIdentity(identityToken);
@@ -1826,19 +1834,21 @@ public class AccountManagerService
if (type == null) throw new IllegalArgumentException("accountType is null");
checkReadAccountsPermission();
UserAccounts userAccounts = getUserAccountsForCaller();
+ int callingUid = Binder.getCallingUid();
long identityToken = clearCallingIdentity();
try {
if (features == null || features.length == 0) {
Account[] accounts;
synchronized (userAccounts.cacheLock) {
- accounts = getAccountsFromCacheLocked(userAccounts, type);
+ accounts = getAccountsFromCacheLocked(userAccounts, type, callingUid);
}
Bundle result = new Bundle();
result.putParcelableArray(AccountManager.KEY_ACCOUNTS, accounts);
onResult(response, result);
return;
}
- new GetAccountsByTypeAndFeatureSession(userAccounts, response, type, features).bind();
+ new GetAccountsByTypeAndFeatureSession(userAccounts, response, type, features,
+ callingUid).bind();
} finally {
restoreCallingIdentity(identityToken);
}
@@ -2352,7 +2362,8 @@ public class AccountManagerService
}
}
} else {
- Account[] accounts = getAccountsFromCacheLocked(userAccounts, null /* type */);
+ Account[] accounts = getAccountsFromCacheLocked(userAccounts, null /* type */,
+ android.os.Process.myUid());
fout.println("Accounts: " + accounts.length);
for (Account account : accounts) {
fout.println(" " + account);
@@ -2691,13 +2702,56 @@ public class AccountManagerService
accounts.accountCache.put(account.type, newAccountsForType);
}
- protected Account[] getAccountsFromCacheLocked(UserAccounts userAccounts, String accountType) {
+ private Account[] filterSharedAccounts(UserAccounts userAccounts, Account[] unfiltered,
+ int callingUid) {
+ if (getUserManager() == null || userAccounts == null || userAccounts.userId < 0
+ || callingUid == android.os.Process.myUid()) {
+ return unfiltered;
+ }
+ if (mUserManager.getUserInfo(userAccounts.userId).isRestricted()) {
+ String[] packages = mPackageManager.getPackagesForUid(callingUid);
+ // If any of the packages includes a white listed package, return the full set,
+ // otherwise return non-shared accounts only.
+ // This might be a temporary way to specify a whitelist
+ String whiteList = mContext.getResources().getString(
+ com.android.internal.R.string.config_appsAuthorizedForSharedAccounts);
+ for (String packageName : packages) {
+ if (whiteList.contains(";" + packageName + ";")) {
+ return unfiltered;
+ }
+ }
+ ArrayList<Account> allowed = new ArrayList<Account>();
+ Account[] sharedAccounts = getSharedAccountsAsUser(userAccounts.userId);
+ if (sharedAccounts == null || sharedAccounts.length == 0) return unfiltered;
+ for (Account account : unfiltered) {
+ boolean found = false;
+ for (Account shared : sharedAccounts) {
+ if (shared.equals(account)) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ allowed.add(account);
+ }
+ }
+ Account[] filtered = new Account[allowed.size()];
+ allowed.toArray(filtered);
+ return filtered;
+ } else {
+ return unfiltered;
+ }
+ }
+
+ protected Account[] getAccountsFromCacheLocked(UserAccounts userAccounts, String accountType,
+ int callingUid) {
if (accountType != null) {
final Account[] accounts = userAccounts.accountCache.get(accountType);
if (accounts == null) {
return EMPTY_ACCOUNT_ARRAY;
} else {
- return Arrays.copyOf(accounts, accounts.length);
+ return filterSharedAccounts(userAccounts, Arrays.copyOf(accounts, accounts.length),
+ callingUid);
}
} else {
int totalLength = 0;
@@ -2714,7 +2768,7 @@ public class AccountManagerService
accountsOfType.length);
totalLength += accountsOfType.length;
}
- return accounts;
+ return filterSharedAccounts(userAccounts, accounts, callingUid);
}
}
diff --git a/services/java/com/android/server/am/ServiceRecord.java b/services/java/com/android/server/am/ServiceRecord.java
index 1ac6bdf..8ff1c7d 100644
--- a/services/java/com/android/server/am/ServiceRecord.java
+++ b/services/java/com/android/server/am/ServiceRecord.java
@@ -16,6 +16,9 @@
package com.android.server.am;
+import android.app.PendingIntent;
+import android.net.Uri;
+import android.provider.Settings;
import com.android.internal.os.BatteryStatsImpl;
import com.android.server.NotificationManagerService;
@@ -369,6 +372,44 @@ class ServiceRecord extends Binder {
}
try {
if (foregroundNoti.icon == 0) {
+ // It is not correct for the caller to supply a notification
+ // icon, but this used to be able to slip through, so for
+ // those dirty apps give it the app's icon.
+ foregroundNoti.icon = appInfo.icon;
+ if (foregroundNoti.contentView == null) {
+ // In this case the app may not have specified a
+ // content view... so we'll give them something to show.
+ CharSequence appName = appInfo.loadLabel(
+ ams.mContext.getPackageManager());
+ if (appName == null) {
+ appName = appInfo.packageName;
+ }
+ Context ctx = null;
+ try {
+ ctx = ams.mContext.createPackageContext(
+ appInfo.packageName, 0);
+ Intent runningIntent = new Intent(
+ Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
+ runningIntent.setData(Uri.fromParts("package",
+ appInfo.packageName, null));
+ PendingIntent pi = PendingIntent.getActivity(ams.mContext, 0,
+ runningIntent, PendingIntent.FLAG_UPDATE_CURRENT);
+ foregroundNoti.setLatestEventInfo(ctx,
+ ams.mContext.getString(
+ com.android.internal.R.string
+ .app_running_notification_title,
+ appName),
+ ams.mContext.getString(
+ com.android.internal.R.string
+ .app_running_notification_text,
+ appName),
+ pi);
+ } catch (PackageManager.NameNotFoundException e) {
+ foregroundNoti.icon = 0;
+ }
+ }
+ }
+ if (foregroundNoti.icon == 0) {
// Notifications whose icon is 0 are defined to not show
// a notification, silently ignoring it. We don't want to
// just ignore it, we want to prevent the service from
diff --git a/services/java/com/android/server/pm/UserManagerService.java b/services/java/com/android/server/pm/UserManagerService.java
index 1414cbd..636b0e5 100644
--- a/services/java/com/android/server/pm/UserManagerService.java
+++ b/services/java/com/android/server/pm/UserManagerService.java
@@ -25,7 +25,10 @@ import android.app.IStopUserCallback;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
+import android.content.RestrictionEntry;
+import android.content.SharedPreferences;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.UserInfo;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
@@ -83,11 +86,17 @@ public class UserManagerService extends IUserManager.Stub {
private static final String TAG_USERS = "users";
private static final String TAG_USER = "user";
private static final String TAG_RESTRICTIONS = "restrictions";
+ private static final String TAG_ENTRY = "entry";
+ private static final String TAG_VALUE = "value";
+ private static final String ATTR_KEY = "key";
+ private static final String ATTR_MULTIPLE = "m";
private static final String USER_INFO_DIR = "system" + File.separator + "users";
private static final String USER_LIST_FILENAME = "userlist.xml";
private static final String USER_PHOTO_FILENAME = "photo.png";
+ private static final String RESTRICTIONS_FILE_PREFIX = "res_";
+
private static final int MIN_USER_ID = 10;
private static final int USER_VERSION = 2;
@@ -947,6 +956,151 @@ public class UserManagerService extends IUserManager.Stub {
}
@Override
+ public List<RestrictionEntry> getApplicationRestrictions(String packageName, int userId) {
+ if (UserHandle.getCallingUserId() != userId
+ || Binder.getCallingUid() != getUidForPackage(packageName)) {
+ checkManageUsersPermission("Only system can get restrictions for other users/apps");
+ }
+ synchronized (mPackagesLock) {
+ // Read the restrictions from XML
+ return readApplicationRestrictionsLocked(packageName, userId);
+ }
+ }
+
+ @Override
+ public void setApplicationRestrictions(String packageName, List<RestrictionEntry> entries,
+ int userId) {
+ if (UserHandle.getCallingUserId() != userId
+ || Binder.getCallingUid() != getUidForPackage(packageName)) {
+ checkManageUsersPermission("Only system can set restrictions for other users/apps");
+ }
+ synchronized (mPackagesLock) {
+ // Write the restrictions to XML
+ writeApplicationRestrictionsLocked(packageName, entries, userId);
+ }
+ }
+
+ private int getUidForPackage(String packageName) {
+ try {
+ return mContext.getPackageManager().getApplicationInfo(packageName,
+ PackageManager.GET_UNINSTALLED_PACKAGES).uid;
+ } catch (NameNotFoundException nnfe) {
+ return -1;
+ }
+ }
+
+ private List<RestrictionEntry> readApplicationRestrictionsLocked(String packageName,
+ int userId) {
+ final ArrayList<RestrictionEntry> entries = new ArrayList<RestrictionEntry>();
+ final ArrayList<String> values = new ArrayList<String>();
+
+ FileInputStream fis = null;
+ try {
+ AtomicFile restrictionsFile =
+ new AtomicFile(new File(Environment.getUserSystemDirectory(userId),
+ RESTRICTIONS_FILE_PREFIX + packageName + ".xml"));
+ fis = restrictionsFile.openRead();
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(fis, null);
+ int type;
+ while ((type = parser.next()) != XmlPullParser.START_TAG
+ && type != XmlPullParser.END_DOCUMENT) {
+ ;
+ }
+
+ if (type != XmlPullParser.START_TAG) {
+ Slog.e(LOG_TAG, "Unable to read restrictions file "
+ + restrictionsFile.getBaseFile());
+ return entries;
+ }
+
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
+ if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_ENTRY)) {
+ String key = parser.getAttributeValue(null, ATTR_KEY);
+ String multiple = parser.getAttributeValue(null, ATTR_MULTIPLE);
+ if (multiple != null) {
+ int count = Integer.parseInt(multiple);
+ while (count > 0 && (type = parser.next()) != XmlPullParser.END_DOCUMENT) {
+ if (type == XmlPullParser.START_TAG
+ && parser.getName().equals(TAG_VALUE)) {
+ values.add(parser.nextText().trim());
+ count--;
+ }
+ }
+ String [] valueStrings = new String[values.size()];
+ values.toArray(valueStrings);
+ Slog.d(LOG_TAG, "Got RestrictionEntry " + key + "," + valueStrings);
+ RestrictionEntry entry = new RestrictionEntry(key, valueStrings);
+ entries.add(entry);
+ } else {
+ String value = parser.nextText().trim();
+ Slog.d(LOG_TAG, "Got RestrictionEntry " + key + "," + value);
+ RestrictionEntry entry = new RestrictionEntry(key, value);
+ entries.add(entry);
+ }
+ }
+ }
+
+ } catch (IOException ioe) {
+ } catch (XmlPullParserException pe) {
+ } finally {
+ if (fis != null) {
+ try {
+ fis.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+ return entries;
+ }
+
+ private void writeApplicationRestrictionsLocked(String packageName,
+ List<RestrictionEntry> entries, int userId) {
+ FileOutputStream fos = null;
+ AtomicFile restrictionsFile = new AtomicFile(
+ new File(Environment.getUserSystemDirectory(userId),
+ RESTRICTIONS_FILE_PREFIX + packageName + ".xml"));
+ try {
+ fos = restrictionsFile.startWrite();
+ final BufferedOutputStream bos = new BufferedOutputStream(fos);
+
+ // XmlSerializer serializer = XmlUtils.serializerInstance();
+ final XmlSerializer serializer = new FastXmlSerializer();
+ serializer.setOutput(bos, "utf-8");
+ serializer.startDocument(null, true);
+ serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
+
+ serializer.startTag(null, TAG_RESTRICTIONS);
+
+ for (RestrictionEntry entry : entries) {
+ serializer.startTag(null, TAG_ENTRY);
+ serializer.attribute(null, ATTR_KEY, entry.key);
+ if (entry.getStringValue() != null || entry.getMultipleValues() == null) {
+ String value = entry.getStringValue();
+ serializer.text(value != null ? value : "");
+ } else {
+ String[] values = entry.getMultipleValues();
+ serializer.attribute(null, ATTR_MULTIPLE, Integer.toString(values.length));
+ for (String value : values) {
+ serializer.startTag(null, TAG_VALUE);
+ serializer.text(value != null ? value : "");
+ serializer.endTag(null, TAG_VALUE);
+ }
+ }
+ serializer.endTag(null, TAG_ENTRY);
+ }
+
+ serializer.endTag(null, TAG_RESTRICTIONS);
+
+ serializer.endDocument();
+ restrictionsFile.finishWrite(fos);
+ } catch (Exception e) {
+ restrictionsFile.failWrite(fos);
+ Slog.e(LOG_TAG, "Error writing application restrictions list");
+ }
+ }
+
+ @Override
public int getUserSerialNumber(int userHandle) {
synchronized (mPackagesLock) {
if (!exists(userHandle)) return -1;
diff --git a/services/java/com/android/server/wifi/WifiService.java b/services/java/com/android/server/wifi/WifiService.java
index c46f217..9dde58f 100644
--- a/services/java/com/android/server/wifi/WifiService.java
+++ b/services/java/com/android/server/wifi/WifiService.java
@@ -396,6 +396,18 @@ public final class WifiService extends IWifiManager.Stub {
long ident = Binder.clearCallingIdentity();
try {
+
+ /* Turning off Wi-Fi when scans are still available */
+ if (!enable && isScanningAlwaysAvailable()) {
+ /* This can be changed by user in the app to not be notified again */
+ boolean notifyUser = (Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.WIFI_NOTIFY_SCAN_ALWAYS_AVAILABLE, 1) == 1);
+ if (notifyUser) {
+ Intent intent = new Intent(WifiManager.ACTION_NOTIFY_SCAN_ALWAYS_AVAILABLE);
+ mContext.startActivityAsUser(intent, null, UserHandle.CURRENT);
+ }
+ }
+
if (! mSettingsStore.handleWifiToggled(enable)) {
// Nothing to do if wifi cannot be toggled
return true;
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index f654310..0c0a144 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -408,6 +408,15 @@ public class WifiManager {
"android.net.wifi.action.REQUEST_SCAN_ALWAYS_AVAILABLE";
/**
+ * Activity Action: Show a system activity that notifies the user that
+ * scanning is still available when Wi-Fi is turned off
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_NOTIFY_SCAN_ALWAYS_AVAILABLE =
+ "android.net.wifi.action.NOTIFY_SCAN_ALWAYS_AVAILABLE";
+
+ /**
* Activity Action: Pick a Wi-Fi network to connect to.
* <p>Input: Nothing.
* <p>Output: Nothing.