summaryrefslogtreecommitdiffstats
path: root/services
diff options
context:
space:
mode:
Diffstat (limited to 'services')
-rw-r--r--services/Android.mk1
-rw-r--r--services/backup/java/com/android/server/backup/BackupManagerService.java38
-rw-r--r--services/core/java/com/android/server/InputMethodManagerService.java98
-rw-r--r--services/core/java/com/android/server/MidiService.java39
-rw-r--r--services/core/java/com/android/server/MountService.java26
-rw-r--r--services/core/java/com/android/server/SystemConfig.java18
-rw-r--r--services/core/java/com/android/server/UiModeManagerService.java26
-rwxr-xr-xservices/core/java/com/android/server/am/ActiveServices.java111
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java55
-rw-r--r--services/core/java/com/android/server/am/ActivityStack.java8
-rw-r--r--services/core/java/com/android/server/am/ActivityStackSupervisor.java6
-rw-r--r--services/core/java/com/android/server/am/BatteryStatsService.java26
-rw-r--r--services/core/java/com/android/server/am/BroadcastQueue.java2
-rw-r--r--services/core/java/com/android/server/am/PendingIntentRecord.java28
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java7
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java10
-rw-r--r--services/core/java/com/android/server/job/JobSchedulerService.java63
-rw-r--r--services/core/java/com/android/server/job/controllers/StateController.java6
-rw-r--r--services/core/java/com/android/server/media/MediaSessionService.java24
-rw-r--r--services/core/java/com/android/server/net/NetworkPolicyManagerService.java45
-rw-r--r--services/core/java/com/android/server/notification/NotificationIntrusivenessExtractor.java9
-rw-r--r--services/core/java/com/android/server/notification/NotificationManagerService.java21
-rw-r--r--services/core/java/com/android/server/notification/NotificationRecord.java2
-rw-r--r--services/core/java/com/android/server/notification/RankingConfig.java4
-rw-r--r--services/core/java/com/android/server/notification/RankingHelper.java230
-rw-r--r--services/core/java/com/android/server/pm/BasePermission.java40
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java535
-rw-r--r--services/core/java/com/android/server/pm/PackageSetting.java6
-rw-r--r--services/core/java/com/android/server/pm/PackageSettingBase.java10
-rw-r--r--services/core/java/com/android/server/pm/PermissionsState.java526
-rw-r--r--services/core/java/com/android/server/pm/SettingBase.java (renamed from services/core/java/com/android/server/pm/GrantedPermissions.java)21
-rw-r--r--services/core/java/com/android/server/pm/Settings.java701
-rw-r--r--services/core/java/com/android/server/pm/SharedUserSetting.java2
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java6
-rw-r--r--services/core/java/com/android/server/power/DeviceIdleController.java461
-rw-r--r--services/core/java/com/android/server/power/PowerManagerService.java26
-rw-r--r--services/core/java/com/android/server/wm/DimLayer.java39
-rw-r--r--services/core/java/com/android/server/wm/TaskStack.java16
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java111
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java71
-rw-r--r--services/java/com/android/server/SystemServer.java79
-rw-r--r--services/net/Android.mk10
-rw-r--r--services/net/java/android/net/dhcp/DhcpAckPacket.java99
-rw-r--r--services/net/java/android/net/dhcp/DhcpClient.java807
-rw-r--r--services/net/java/android/net/dhcp/DhcpDeclinePacket.java58
-rw-r--r--services/net/java/android/net/dhcp/DhcpDiscoverPacket.java59
-rw-r--r--services/net/java/android/net/dhcp/DhcpInformPacket.java65
-rw-r--r--services/net/java/android/net/dhcp/DhcpNakPacket.java64
-rw-r--r--services/net/java/android/net/dhcp/DhcpOfferPacket.java90
-rw-r--r--services/net/java/android/net/dhcp/DhcpPacket.java1065
-rw-r--r--services/net/java/android/net/dhcp/DhcpRequestPacket.java77
-rw-r--r--services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java36
52 files changed, 5251 insertions, 732 deletions
diff --git a/services/Android.mk b/services/Android.mk
index da85528..8777085 100644
--- a/services/Android.mk
+++ b/services/Android.mk
@@ -24,6 +24,7 @@ services := \
appwidget \
backup \
devicepolicy \
+ net \
print \
restrictions \
usage \
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 8fcdd39..31f9e22 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -22,6 +22,7 @@ import android.app.AppGlobals;
import android.app.IActivityManager;
import android.app.IApplicationThread;
import android.app.IBackupAgent;
+import android.app.PackageInstallObserver;
import android.app.PendingIntent;
import android.app.backup.BackupAgent;
import android.app.backup.BackupDataInput;
@@ -45,7 +46,6 @@ import android.content.ServiceConnection;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageDataObserver;
import android.content.pm.IPackageDeleteObserver;
-import android.content.pm.IPackageInstallObserver;
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
@@ -200,7 +200,6 @@ public class BackupManagerService {
private static final String RUN_BACKUP_ACTION = "android.app.backup.intent.RUN";
private static final String RUN_INITIALIZE_ACTION = "android.app.backup.intent.INIT";
- private static final String RUN_CLEAR_ACTION = "android.app.backup.intent.CLEAR";
private static final int MSG_RUN_BACKUP = 1;
private static final int MSG_RUN_ADB_BACKUP = 2;
private static final int MSG_RUN_RESTORE = 3;
@@ -1198,11 +1197,13 @@ public class BackupManagerService {
temp = new RandomAccessFile(tempProcessedFile, "rws");
in = new RandomAccessFile(mEverStored, "r");
+ // Loop until we hit EOF
while (true) {
- PackageInfo info;
String pkg = in.readUTF();
try {
- info = mPackageManager.getPackageInfo(pkg, 0);
+ // is this package still present?
+ mPackageManager.getPackageInfo(pkg, 0);
+ // if we get here then yes it is; remember it
mEverStoredApps.add(pkg);
temp.writeUTF(pkg);
if (MORE_DEBUG) Slog.v(TAG, " + " + pkg);
@@ -4100,7 +4101,6 @@ public class BackupManagerService {
SinglePackageBackupRunner(ParcelFileDescriptor output, PackageInfo target,
AtomicBoolean latch) throws IOException {
- int oldfd = output.getFd();
mOutput = ParcelFileDescriptor.dup(output.getFileDescriptor());
mTarget = target;
mLatch = latch;
@@ -4812,7 +4812,7 @@ public class BackupManagerService {
}
}
- class RestoreInstallObserver extends IPackageInstallObserver.Stub {
+ class RestoreInstallObserver extends PackageInstallObserver {
final AtomicBoolean mDone = new AtomicBoolean();
String mPackageName;
int mResult;
@@ -4838,8 +4838,8 @@ public class BackupManagerService {
}
@Override
- public void packageInstalled(String packageName, int returnCode)
- throws RemoteException {
+ public void onPackageInstalled(String packageName, int returnCode,
+ String msg, Bundle extras) {
synchronized (mDone) {
mResult = returnCode;
mPackageName = packageName;
@@ -5095,7 +5095,9 @@ public class BackupManagerService {
offset = extractLine(buffer, offset, str);
version = Integer.parseInt(str[0]); // app version
offset = extractLine(buffer, offset, str);
- int platformVersion = Integer.parseInt(str[0]);
+ // This is the platform version, which we don't use, but we parse it
+ // as a safety against corruption in the manifest.
+ Integer.parseInt(str[0]);
offset = extractLine(buffer, offset, str);
info.installerPackageName = (str[0].length() > 0) ? str[0] : null;
offset = extractLine(buffer, offset, str);
@@ -6156,7 +6158,7 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
}
}
- class RestoreInstallObserver extends IPackageInstallObserver.Stub {
+ class RestoreInstallObserver extends PackageInstallObserver {
final AtomicBoolean mDone = new AtomicBoolean();
String mPackageName;
int mResult;
@@ -6182,8 +6184,8 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
}
@Override
- public void packageInstalled(String packageName, int returnCode)
- throws RemoteException {
+ public void onPackageInstalled(String packageName, int returnCode,
+ String msg, Bundle extras) {
synchronized (mDone) {
mResult = returnCode;
mPackageName = packageName;
@@ -6432,7 +6434,9 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
offset = extractLine(buffer, offset, str);
version = Integer.parseInt(str[0]); // app version
offset = extractLine(buffer, offset, str);
- int platformVersion = Integer.parseInt(str[0]);
+ // This is the platform version, which we don't use, but we parse it
+ // as a safety against corruption in the manifest.
+ Integer.parseInt(str[0]);
offset = extractLine(buffer, offset, str);
info.installerPackageName = (str[0].length() > 0) ? str[0] : null;
offset = extractLine(buffer, offset, str);
@@ -8357,7 +8361,9 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
}
// make sure the screen is lit for the user interaction
- mPowerManager.userActivity(SystemClock.uptimeMillis(), false);
+ mPowerManager.userActivity(SystemClock.uptimeMillis(),
+ PowerManager.USER_ACTIVITY_EVENT_OTHER,
+ 0);
// start the confirmation countdown
startConfirmationTimeout(token, params);
@@ -8440,7 +8446,9 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
}
// make sure the screen is lit for the user interaction
- mPowerManager.userActivity(SystemClock.uptimeMillis(), false);
+ mPowerManager.userActivity(SystemClock.uptimeMillis(),
+ PowerManager.USER_ACTIVITY_EVENT_OTHER,
+ 0);
// start the confirmation countdown
startConfirmationTimeout(token, params);
diff --git a/services/core/java/com/android/server/InputMethodManagerService.java b/services/core/java/com/android/server/InputMethodManagerService.java
index fd35b5e..4677f65 100644
--- a/services/core/java/com/android/server/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/InputMethodManagerService.java
@@ -86,7 +86,10 @@ import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
import android.text.TextUtils;
+import android.text.TextUtils.SimpleStringSplitter;
import android.text.style.SuggestionSpan;
+import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.AtomicFile;
import android.util.EventLog;
import android.util.LruCache;
@@ -125,6 +128,7 @@ import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Locale;
@@ -134,8 +138,12 @@ import java.util.Locale;
public class InputMethodManagerService extends IInputMethodManager.Stub
implements ServiceConnection, Handler.Callback {
static final boolean DEBUG = false;
+ static final boolean DEBUG_RESTORE = DEBUG || false;
static final String TAG = "InputMethodManagerService";
+ private static final char INPUT_METHOD_SEPARATOR = ':';
+ private static final char INPUT_METHOD_SUBTYPE_SEPARATOR = ';';
+
static final int MSG_SHOW_IM_PICKER = 1;
static final int MSG_SHOW_IM_SUBTYPE_PICKER = 2;
static final int MSG_SHOW_IM_SUBTYPE_ENABLER = 3;
@@ -466,12 +474,101 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
|| Intent.ACTION_USER_REMOVED.equals(action)) {
updateCurrentProfileIds();
return;
+ } else if (Intent.ACTION_SETTING_RESTORED.equals(action)) {
+ final String name = intent.getStringExtra(Intent.EXTRA_SETTING_NAME);
+ if (Settings.Secure.ENABLED_INPUT_METHODS.equals(name)) {
+ final String prevValue = intent.getStringExtra(
+ Intent.EXTRA_SETTING_PREVIOUS_VALUE);
+ final String newValue = intent.getStringExtra(
+ Intent.EXTRA_SETTING_NEW_VALUE);
+ restoreEnabledInputMethods(mContext, prevValue, newValue);
+ }
} else {
Slog.w(TAG, "Unexpected intent " + intent);
}
}
}
+ // Apply the results of a restore operation to the set of enabled IMEs. Note that this
+ // does not attempt to validate on the fly with any installed device policy, so must only
+ // be run in the context of initial device setup.
+ //
+ // TODO: Move this method to InputMethodUtils with adding unit tests.
+ static void restoreEnabledInputMethods(Context context, String prevValue, String newValue) {
+ if (DEBUG_RESTORE) {
+ Slog.i(TAG, "Restoring enabled input methods:");
+ Slog.i(TAG, "prev=" + prevValue);
+ Slog.i(TAG, " new=" + newValue);
+ }
+ // 'new' is the just-restored state, 'prev' is what was in settings prior to the restore
+ ArrayMap<String, ArraySet<String>> prevMap = parseInputMethodsAndSubtypesString(prevValue);
+ ArrayMap<String, ArraySet<String>> newMap = parseInputMethodsAndSubtypesString(newValue);
+
+ // Merge the restored ime+subtype enabled states into the live state
+ for (ArrayMap.Entry<String, ArraySet<String>> entry : newMap.entrySet()) {
+ final String imeId = entry.getKey();
+ ArraySet<String> prevSubtypes = prevMap.get(imeId);
+ if (prevSubtypes == null) {
+ prevSubtypes = new ArraySet<String>(2);
+ prevMap.put(imeId, prevSubtypes);
+ }
+ prevSubtypes.addAll(entry.getValue());
+ }
+
+ final String mergedImesAndSubtypesString = buildInputMethodsAndSubtypesString(prevMap);
+ if (DEBUG_RESTORE) {
+ Slog.i(TAG, "Merged IME string:");
+ Slog.i(TAG, " " + mergedImesAndSubtypesString);
+ }
+ Settings.Secure.putString(context.getContentResolver(),
+ Settings.Secure.ENABLED_INPUT_METHODS, mergedImesAndSubtypesString);
+ }
+
+ // TODO: Move this method to InputMethodUtils with adding unit tests.
+ static String buildInputMethodsAndSubtypesString(ArrayMap<String, ArraySet<String>> map) {
+ // we want to use the canonical InputMethodSettings implementation,
+ // so we convert data structures first.
+ List<Pair<String, ArrayList<String>>> imeMap =
+ new ArrayList<Pair<String, ArrayList<String>>>(4);
+ for (ArrayMap.Entry<String, ArraySet<String>> entry : map.entrySet()) {
+ final String imeName = entry.getKey();
+ final ArraySet<String> subtypeSet = entry.getValue();
+ final ArrayList<String> subtypes = new ArrayList<String>(2);
+ if (subtypeSet != null) {
+ subtypes.addAll(subtypeSet);
+ }
+ imeMap.add(new Pair<String, ArrayList<String>>(imeName, subtypes));
+ }
+ return InputMethodSettings.buildInputMethodsSettingString(imeMap);
+ }
+
+ // TODO: Move this method to InputMethodUtils with adding unit tests.
+ static ArrayMap<String, ArraySet<String>> parseInputMethodsAndSubtypesString(
+ final String inputMethodsAndSubtypesString) {
+ final ArrayMap<String, ArraySet<String>> imeMap =
+ new ArrayMap<String, ArraySet<String>>();
+ if (TextUtils.isEmpty(inputMethodsAndSubtypesString)) {
+ return imeMap;
+ }
+
+ final SimpleStringSplitter typeSplitter =
+ new SimpleStringSplitter(INPUT_METHOD_SEPARATOR);
+ final SimpleStringSplitter subtypeSplitter =
+ new SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATOR);
+ List<Pair<String, ArrayList<String>>> allImeSettings =
+ InputMethodSettings.buildInputMethodsAndSubtypeList(inputMethodsAndSubtypesString,
+ typeSplitter,
+ subtypeSplitter);
+ for (Pair<String, ArrayList<String>> ime : allImeSettings) {
+ ArraySet<String> subtypes = new ArraySet<String>();
+ if (ime.second != null) {
+ subtypes.addAll(ime.second);
+ }
+ imeMap.put(ime.first, subtypes);
+ }
+ return imeMap;
+ }
+
class MyPackageMonitor extends PackageMonitor {
private boolean isChangingPackagesOfCurrentUser() {
final int userId = getChangingUserId();
@@ -675,6 +772,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
broadcastFilter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
broadcastFilter.addAction(Intent.ACTION_USER_ADDED);
broadcastFilter.addAction(Intent.ACTION_USER_REMOVED);
+ broadcastFilter.addAction(Intent.ACTION_SETTING_RESTORED);
mContext.registerReceiver(new ImmsBroadcastReceiver(), broadcastFilter);
mNotificationShown = false;
diff --git a/services/core/java/com/android/server/MidiService.java b/services/core/java/com/android/server/MidiService.java
index 3418930..e753664 100644
--- a/services/core/java/com/android/server/MidiService.java
+++ b/services/core/java/com/android/server/MidiService.java
@@ -47,6 +47,7 @@ import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.Iterator;
import java.util.List;
public class MidiService extends IMidiManager.Stub {
@@ -269,7 +270,9 @@ public class MidiService extends IMidiManager.Stub {
public void binderDied() {
synchronized (mDevicesByInfo) {
- removeDeviceLocked(this);
+ if (mDevicesByInfo.remove(mDeviceInfo) != null) {
+ removeDeviceLocked(this);
+ }
}
}
@@ -368,6 +371,7 @@ public class MidiService extends IMidiManager.Stub {
synchronized (mDevicesByInfo) {
Device device = mDevicesByServer.get(server.asBinder());
if (device != null) {
+ mDevicesByInfo.remove(device.getDeviceInfo());
removeDeviceLocked(device);
}
}
@@ -454,16 +458,14 @@ public class MidiService extends IMidiManager.Stub {
// synchronize on mDevicesByInfo
private void removeDeviceLocked(Device device) {
- if (mDevicesByInfo.remove(device.getDeviceInfo()) != null) {
- IMidiDeviceServer server = device.getDeviceServer();
- if (server != null) {
- mDevicesByServer.remove(server);
- }
+ IMidiDeviceServer server = device.getDeviceServer();
+ if (server != null) {
+ mDevicesByServer.remove(server);
+ }
- synchronized (mClients) {
- for (Client c : mClients.values()) {
- c.deviceRemoved(device);
- }
+ synchronized (mClients) {
+ for (Client c : mClients.values()) {
+ c.deviceRemoved(device);
}
}
}
@@ -616,8 +618,11 @@ public class MidiService extends IMidiManager.Stub {
private void removePackageDeviceServers(String packageName) {
synchronized (mDevicesByInfo) {
- for (Device device : mDevicesByInfo.values()) {
+ Iterator<Device> iterator = mDevicesByInfo.values().iterator();
+ while (iterator.hasNext()) {
+ Device device = iterator.next();
if (packageName.equals(device.getPackageName())) {
+ iterator.remove();
removeDeviceLocked(device);
}
}
@@ -634,15 +639,19 @@ public class MidiService extends IMidiManager.Stub {
pw.println("Devices:");
pw.increaseIndent();
- for (Device device : mDevicesByInfo.values()) {
- pw.println(device.toString());
+ synchronized (mDevicesByInfo) {
+ for (Device device : mDevicesByInfo.values()) {
+ pw.println(device.toString());
+ }
}
pw.decreaseIndent();
pw.println("Clients:");
pw.increaseIndent();
- for (Client client : mClients.values()) {
- pw.println(client.toString());
+ synchronized (mClients) {
+ for (Client client : mClients.values()) {
+ pw.println(client.toString());
+ }
}
pw.decreaseIndent();
}
diff --git a/services/core/java/com/android/server/MountService.java b/services/core/java/com/android/server/MountService.java
index 1e3b46b..b8d9ec5 100644
--- a/services/core/java/com/android/server/MountService.java
+++ b/services/core/java/com/android/server/MountService.java
@@ -123,6 +123,27 @@ class MountService extends IMountService.Stub
// TODO: listen for user creation/deletion
+ public static class Lifecycle extends SystemService {
+ private MountService mMountService;
+
+ public Lifecycle(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void onStart() {
+ mMountService = new MountService(getContext());
+ publishBinderService("mount", mMountService);
+ }
+
+ @Override
+ public void onBootPhase(int phase) {
+ if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
+ mMountService.systemReady();
+ }
+ }
+ }
+
private static final boolean LOCAL_LOGD = false;
private static final boolean DEBUG_UNMOUNT = false;
private static final boolean DEBUG_EVENTS = false;
@@ -574,7 +595,8 @@ class MountService extends IMountService.Stub
private final Handler mHandler;
- void waitForAsecScan() {
+ @Override
+ public void waitForAsecScan() {
waitForLatch(mAsecsScanned);
}
@@ -1538,7 +1560,7 @@ class MountService extends IMountService.Stub
}
}
- public void systemReady() {
+ private void systemReady() {
mSystemReady = true;
mHandler.obtainMessage(H_SYSTEM_READY).sendToTarget();
}
diff --git a/services/core/java/com/android/server/SystemConfig.java b/services/core/java/com/android/server/SystemConfig.java
index 6ad128c..4c9d7d3 100644
--- a/services/core/java/com/android/server/SystemConfig.java
+++ b/services/core/java/com/android/server/SystemConfig.java
@@ -71,9 +71,11 @@ public class SystemConfig {
public static final class PermissionEntry {
public final String name;
public int[] gids;
+ public boolean perUser;
- PermissionEntry(String _name) {
- name = _name;
+ PermissionEntry(String name, boolean perUser) {
+ this.name = name;
+ this.perUser = perUser;
}
}
@@ -363,14 +365,14 @@ public class SystemConfig {
void readPermission(XmlPullParser parser, String name)
throws IOException, XmlPullParserException {
+ if (mPermissions.containsKey(name)) {
+ throw new IllegalStateException("Duplicate permission definition for " + name);
+ }
- name = name.intern();
+ final boolean perUser = XmlUtils.readBooleanAttribute(parser, "perUser", false);
+ final PermissionEntry perm = new PermissionEntry(name, perUser);
+ mPermissions.put(name, perm);
- PermissionEntry perm = mPermissions.get(name);
- if (perm == null) {
- perm = new PermissionEntry(name);
- mPermissions.put(name, perm);
- }
int outerDepth = parser.getDepth();
int type;
while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index 87dc420..64f3070 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -157,7 +157,6 @@ final class UiModeManagerService extends SystemService {
@Override
public void onStart() {
final Context context = getContext();
- mTwilightManager = getLocalService(TwilightManager.class);
final PowerManager powerManager =
(PowerManager) context.getSystemService(Context.POWER_SERVICE);
@@ -188,7 +187,11 @@ final class UiModeManagerService extends SystemService {
mNightMode = Settings.Secure.getInt(context.getContentResolver(),
Settings.Secure.UI_NIGHT_MODE, defaultNightMode);
- mTwilightManager.registerListener(mTwilightListener, mHandler);
+ // Update the initial, static configurations.
+ synchronized (this) {
+ updateConfigurationLocked();
+ sendConfigurationLocked();
+ }
publishBinderService(Context.UI_MODE_SERVICE, mService);
}
@@ -297,8 +300,11 @@ final class UiModeManagerService extends SystemService {
pw.print(" mSetUiMode=0x"); pw.println(Integer.toHexString(mSetUiMode));
pw.print(" mHoldingConfiguration="); pw.print(mHoldingConfiguration);
pw.print(" mSystemReady="); pw.println(mSystemReady);
- pw.print(" mTwilightService.getCurrentState()=");
- pw.println(mTwilightManager.getCurrentState());
+ if (mTwilightManager != null) {
+ // We may not have a TwilightManager.
+ pw.print(" mTwilightService.getCurrentState()=");
+ pw.println(mTwilightManager.getCurrentState());
+ }
}
}
@@ -306,6 +312,10 @@ final class UiModeManagerService extends SystemService {
public void onBootPhase(int phase) {
if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
synchronized (mLock) {
+ mTwilightManager = getLocalService(TwilightManager.class);
+ if (mTwilightManager != null) {
+ mTwilightManager.registerListener(mTwilightListener, mHandler);
+ }
mSystemReady = true;
mCarModeEnabled = mDockState == Intent.EXTRA_DOCK_STATE_CAR;
updateComputedNightModeLocked();
@@ -623,9 +633,11 @@ final class UiModeManagerService extends SystemService {
}
private void updateComputedNightModeLocked() {
- TwilightState state = mTwilightManager.getCurrentState();
- if (state != null) {
- mComputedNightMode = state.isNight();
+ if (mTwilightManager != null) {
+ TwilightState state = mTwilightManager.getCurrentState();
+ if (state != null) {
+ mComputedNightMode = state.isNight();
+ }
}
}
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index fc95b00..3859904 100755
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -33,6 +33,7 @@ import android.os.DeadObjectException;
import android.os.Handler;
import android.os.Looper;
import android.os.SystemProperties;
+import android.os.TransactionTooLargeException;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -249,7 +250,11 @@ public final class ActiveServices {
}
}
r.delayed = false;
- startServiceInnerLocked(this, r.pendingStarts.get(0).intent, r, false, true);
+ try {
+ startServiceInnerLocked(this, r.pendingStarts.get(0).intent, r, false, true);
+ } catch (TransactionTooLargeException e) {
+ // Ignore, nobody upstack cares.
+ }
}
if (mStartingBackground.size() > 0) {
ServiceRecord next = mStartingBackground.get(0);
@@ -301,9 +306,9 @@ public final class ActiveServices {
return getServiceMap(callingUser).mServicesByName;
}
- ComponentName startServiceLocked(IApplicationThread caller,
- Intent service, String resolvedType,
- int callingPid, int callingUid, int userId) {
+ ComponentName startServiceLocked(IApplicationThread caller, Intent service,
+ String resolvedType, int callingPid, int callingUid, int userId)
+ throws TransactionTooLargeException {
if (DEBUG_DELAYED_STARTS) Slog.v(TAG_SERVICE, "startService: " + service
+ " type=" + resolvedType + " args=" + service.getExtras());
@@ -418,8 +423,8 @@ public final class ActiveServices {
return startServiceInnerLocked(smap, service, r, callerFg, addToStarting);
}
- ComponentName startServiceInnerLocked(ServiceMap smap, Intent service,
- ServiceRecord r, boolean callerFg, boolean addToStarting) {
+ ComponentName startServiceInnerLocked(ServiceMap smap, Intent service, ServiceRecord r,
+ boolean callerFg, boolean addToStarting) throws TransactionTooLargeException {
ProcessStats.ServiceState stracker = r.getTracker();
if (stracker != null) {
stracker.setStarted(true, mAm.mProcessStats.getMemFactorLocked(), r.lastActivity);
@@ -692,9 +697,9 @@ public final class ActiveServices {
return false;
}
- int bindServiceLocked(IApplicationThread caller, IBinder token,
- Intent service, String resolvedType,
- IServiceConnection connection, int flags, int userId) {
+ int bindServiceLocked(IApplicationThread caller, IBinder token, Intent service,
+ String resolvedType, IServiceConnection connection, int flags, int userId)
+ throws TransactionTooLargeException {
if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "bindService: " + service
+ " type=" + resolvedType + " conn=" + connection.asBinder()
+ " flags=0x" + Integer.toHexString(flags));
@@ -971,7 +976,11 @@ public final class ActiveServices {
break;
}
}
- requestServiceBindingLocked(r, b, inFg, true);
+ try {
+ requestServiceBindingLocked(r, b, inFg, true);
+ } catch (TransactionTooLargeException e) {
+ // Don't pass this back to ActivityThread, it's unrelated.
+ }
} else {
// Note to tell the service the next time there is
// a new client.
@@ -1145,8 +1154,8 @@ public final class ActiveServices {
r.executingStart = now;
}
- private final boolean requestServiceBindingLocked(ServiceRecord r,
- IntentBindRecord i, boolean execInFg, boolean rebind) {
+ private final boolean requestServiceBindingLocked(ServiceRecord r, IntentBindRecord i,
+ boolean execInFg, boolean rebind) throws TransactionTooLargeException {
if (r.app == null || r.app.thread == null) {
// If service is not currently running, can't yet bind.
return false;
@@ -1162,8 +1171,17 @@ public final class ActiveServices {
}
i.hasBound = true;
i.doRebind = false;
+ } catch (TransactionTooLargeException e) {
+ // Keep the executeNesting count accurate.
+ if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Crashed while binding " + r, e);
+ final boolean inDestroying = mDestroyingServices.contains(r);
+ serviceDoneExecutingLocked(r, inDestroying, inDestroying);
+ throw e;
} catch (RemoteException e) {
if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Crashed while binding " + r);
+ // Keep the executeNesting count accurate.
+ final boolean inDestroying = mDestroyingServices.contains(r);
+ serviceDoneExecutingLocked(r, inDestroying, inDestroying);
return false;
}
}
@@ -1288,7 +1306,11 @@ public final class ActiveServices {
if (!mRestartingServices.contains(r)) {
return;
}
- bringUpServiceLocked(r, r.intent.getIntent().getFlags(), r.createdFromFg, true);
+ try {
+ bringUpServiceLocked(r, r.intent.getIntent().getFlags(), r.createdFromFg, true);
+ } catch (TransactionTooLargeException e) {
+ // Ignore, it's been logged and nothing upstack cares.
+ }
}
private final boolean unscheduleServiceRestartLocked(ServiceRecord r, int callingUid,
@@ -1329,8 +1351,8 @@ public final class ActiveServices {
}
}
- private final String bringUpServiceLocked(ServiceRecord r,
- int intentFlags, boolean execInFg, boolean whileRestarting) {
+ private final String bringUpServiceLocked(ServiceRecord r, int intentFlags, boolean execInFg,
+ boolean whileRestarting) throws TransactionTooLargeException {
//Slog.i(TAG, "Bring up service:");
//r.dump(" ");
@@ -1395,6 +1417,8 @@ public final class ActiveServices {
app.addPackage(r.appInfo.packageName, r.appInfo.versionCode, mAm.mProcessStats);
realStartServiceLocked(r, app, execInFg);
return null;
+ } catch (TransactionTooLargeException e) {
+ throw e;
} catch (RemoteException e) {
Slog.w(TAG, "Exception when starting service " + r.shortName, e);
}
@@ -1447,7 +1471,8 @@ public final class ActiveServices {
return null;
}
- private final void requestServiceBindingsLocked(ServiceRecord r, boolean execInFg) {
+ private final void requestServiceBindingsLocked(ServiceRecord r, boolean execInFg)
+ throws TransactionTooLargeException {
for (int i=r.bindings.size()-1; i>=0; i--) {
IntentBindRecord ibr = r.bindings.valueAt(i);
if (!requestServiceBindingLocked(r, ibr, execInFg, false)) {
@@ -1467,7 +1492,7 @@ public final class ActiveServices {
r.app = app;
r.restartTime = r.lastActivity = SystemClock.uptimeMillis();
- app.services.add(r);
+ final boolean newService = app.services.add(r);
bumpServiceExecutingLocked(r, execInFg, "create");
mAm.updateLruProcessLocked(app, false, null);
mAm.updateOomAdjLocked();
@@ -1496,10 +1521,20 @@ public final class ActiveServices {
mAm.appDiedLocked(app);
} finally {
if (!created) {
- app.services.remove(r);
- r.app = null;
- scheduleServiceRestartLocked(r, false);
- return;
+ // Keep the executeNesting count accurate.
+ final boolean inDestroying = mDestroyingServices.contains(r);
+ serviceDoneExecutingLocked(r, inDestroying, inDestroying);
+
+ // Cleanup.
+ if (newService) {
+ app.services.remove(r);
+ r.app = null;
+ }
+
+ // Retry.
+ if (!inDestroying) {
+ scheduleServiceRestartLocked(r, false);
+ }
}
}
@@ -1535,15 +1570,17 @@ public final class ActiveServices {
}
private final void sendServiceArgsLocked(ServiceRecord r, boolean execInFg,
- boolean oomAdjusted) {
+ boolean oomAdjusted) throws TransactionTooLargeException {
final int N = r.pendingStarts.size();
if (N == 0) {
return;
}
while (r.pendingStarts.size() > 0) {
+ Exception caughtException = null;
+ ServiceRecord.StartItem si;
try {
- ServiceRecord.StartItem si = r.pendingStarts.remove(0);
+ si = r.pendingStarts.remove(0);
if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Sending arguments to: "
+ r + " " + r.intent + " args=" + si.intent);
if (si.intent == null && N > 1) {
@@ -1573,13 +1610,26 @@ public final class ActiveServices {
flags |= Service.START_FLAG_REDELIVERY;
}
r.app.thread.scheduleServiceArgs(r, si.taskRemoved, si.id, flags, si.intent);
+ } catch (TransactionTooLargeException e) {
+ if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Transaction too large: intent="
+ + si.intent);
+ caughtException = e;
} catch (RemoteException e) {
- // Remote process gone... we'll let the normal cleanup take
- // care of this.
- if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Crashed while scheduling start: " + r);
- break;
+ // Remote process gone... we'll let the normal cleanup take care of this.
+ if (DEBUG_SERVICE) Slog.v(TAG_SERVICE, "Crashed while sending args: " + r);
+ caughtException = e;
} catch (Exception e) {
Slog.w(TAG, "Unexpected exception", e);
+ caughtException = e;
+ }
+
+ if (caughtException != null) {
+ // Keep nesting count correct
+ final boolean inDestroying = mDestroyingServices.contains(r);
+ serviceDoneExecutingLocked(r, inDestroying, inDestroying);
+ if (caughtException instanceof TransactionTooLargeException) {
+ throw (TransactionTooLargeException)caughtException;
+ }
break;
}
}
@@ -1887,6 +1937,7 @@ public final class ActiveServices {
} else if (r.executeNesting != 1) {
Slog.wtfStack(TAG, "Service done with onDestroy, but executeNesting="
+ r.executeNesting + ": " + r);
+ // Fake it to keep from ANR due to orphaned entry.
r.executeNesting = 1;
}
}
@@ -2102,7 +2153,11 @@ public final class ActiveServices {
if (sr.app != null && sr.app.thread != null) {
// We always run in the foreground, since this is called as
// part of the "remove task" UI operation.
- sendServiceArgsLocked(sr, true, false);
+ try {
+ sendServiceArgsLocked(sr, true, false);
+ } catch (TransactionTooLargeException e) {
+ // Ignore, keep going.
+ }
}
}
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 6f7dfa2..78bd15d 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -52,6 +52,7 @@ import android.graphics.Rect;
import android.os.BatteryStats;
import android.os.PersistableBundle;
import android.os.PowerManager;
+import android.os.TransactionTooLargeException;
import android.os.WorkSource;
import android.os.storage.IMountService;
import android.os.storage.StorageManager;
@@ -75,6 +76,7 @@ import com.android.internal.os.IResultReceiver;
import com.android.internal.os.ProcessCpuTracker;
import com.android.internal.os.TransferPipe;
import com.android.internal.os.Zygote;
+import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FastPrintWriter;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.MemInfoReader;
@@ -1230,7 +1232,7 @@ public final class ActivityManagerService extends ActivityManagerNative
TAG, "Death received in " + this
+ " for thread " + mAppThread.asBinder());
synchronized(ActivityManagerService.this) {
- appDiedLocked(mApp, mPid, mAppThread);
+ appDiedLocked(mApp, mPid, mAppThread, true);
}
}
}
@@ -3044,12 +3046,12 @@ public final class ActivityManagerService extends ActivityManagerNative
int[] permGids = null;
try {
checkTime(startTime, "startProcess: getting gids from package manager");
- final PackageManager pm = mContext.getPackageManager();
- permGids = pm.getPackageGids(app.info.packageName);
+ permGids = AppGlobals.getPackageManager().getPackageGids(app.info.packageName,
+ app.userId);
if (Environment.isExternalStorageEmulated()) {
checkTime(startTime, "startProcess: checking external storage perm");
- if (pm.checkPermission(
+ if (mContext.getPackageManager().checkPermission(
android.Manifest.permission.ACCESS_ALL_EXTERNAL_STORAGE,
app.info.packageName) == PERMISSION_GRANTED) {
mountExternal = Zygote.MOUNT_EXTERNAL_MULTIUSER_ALL;
@@ -3057,7 +3059,7 @@ public final class ActivityManagerService extends ActivityManagerNative
mountExternal = Zygote.MOUNT_EXTERNAL_MULTIUSER;
}
}
- } catch (PackageManager.NameNotFoundException e) {
+ } catch (RemoteException e) {
Slog.w(TAG, "Unable to retrieve gids", e);
}
@@ -3065,7 +3067,7 @@ public final class ActivityManagerService extends ActivityManagerNative
* Add shared application and profile GIDs so applications can share some
* resources like shared libraries and access user-wide resources
*/
- if (permGids == null) {
+ if (ArrayUtils.isEmpty(permGids)) {
gids = new int[2];
} else {
gids = new int[permGids.length + 2];
@@ -3590,10 +3592,10 @@ public final class ActivityManagerService extends ActivityManagerNative
}
@Override
- public int startActivityIntentSender(IApplicationThread caller,
- IntentSender intent, Intent fillInIntent, String resolvedType,
- IBinder resultTo, String resultWho, int requestCode,
- int flagsMask, int flagsValues, Bundle options) {
+ public int startActivityIntentSender(IApplicationThread caller, IntentSender intent,
+ Intent fillInIntent, String resolvedType, IBinder resultTo, String resultWho,
+ int requestCode, int flagsMask, int flagsValues, Bundle options)
+ throws TransactionTooLargeException {
enforceNotIsolatedCaller("startActivityIntentSender");
// Refuse possible leaked file descriptors
if (fillInIntent != null && fillInIntent.hasFileDescriptors()) {
@@ -4302,10 +4304,11 @@ public final class ActivityManagerService extends ActivityManagerNative
}
final void appDiedLocked(ProcessRecord app) {
- appDiedLocked(app, app.pid, app.thread);
+ appDiedLocked(app, app.pid, app.thread, false);
}
- final void appDiedLocked(ProcessRecord app, int pid, IApplicationThread thread) {
+ final void appDiedLocked(ProcessRecord app, int pid, IApplicationThread thread,
+ boolean fromBinderDied) {
// First check if this ProcessRecord is actually active for the pid.
synchronized (mPidsSelfLocked) {
ProcessRecord curProc = mPidsSelfLocked.get(pid);
@@ -4321,7 +4324,9 @@ public final class ActivityManagerService extends ActivityManagerNative
}
if (!app.killed) {
- Process.killProcessQuiet(pid);
+ if (!fromBinderDied) {
+ Process.killProcessQuiet(pid);
+ }
Process.killProcessGroup(app.info.uid, pid);
app.killed = true;
}
@@ -6529,7 +6534,7 @@ public final class ActivityManagerService extends ActivityManagerNative
if (permission == null) {
return PackageManager.PERMISSION_DENIED;
}
- return checkComponentPermission(permission, pid, UserHandle.getAppId(uid), -1, true);
+ return checkComponentPermission(permission, pid, uid, -1, true);
}
@Override
@@ -6549,7 +6554,7 @@ public final class ActivityManagerService extends ActivityManagerNative
pid = tlsIdentity.pid;
}
- return checkComponentPermission(permission, pid, UserHandle.getAppId(uid), -1, true);
+ return checkComponentPermission(permission, pid, uid, -1, true);
}
/**
@@ -14893,7 +14898,7 @@ public final class ActivityManagerService extends ActivityManagerNative
@Override
public ComponentName startService(IApplicationThread caller, Intent service,
- String resolvedType, int userId) {
+ String resolvedType, int userId) throws TransactionTooLargeException {
enforceNotIsolatedCaller("startService");
// Refuse possible leaked file descriptors
if (service != null && service.hasFileDescriptors() == true) {
@@ -14913,8 +14918,8 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
- ComponentName startServiceInPackage(int uid,
- Intent service, String resolvedType, int userId) {
+ ComponentName startServiceInPackage(int uid, Intent service, String resolvedType, int userId)
+ throws TransactionTooLargeException {
synchronized(this) {
if (DEBUG_SERVICE)
Slog.v(TAG, "startServiceInPackage: " + service + " type=" + resolvedType);
@@ -15117,9 +15122,9 @@ public final class ActivityManagerService extends ActivityManagerNative
== PackageManager.PERMISSION_GRANTED;
}
- public int bindService(IApplicationThread caller, IBinder token,
- Intent service, String resolvedType,
- IServiceConnection connection, int flags, int userId) {
+ public int bindService(IApplicationThread caller, IBinder token, Intent service,
+ String resolvedType, IServiceConnection connection, int flags, int userId)
+ throws TransactionTooLargeException {
enforceNotIsolatedCaller("bindService");
// Refuse possible leaked file descriptors
@@ -15807,6 +15812,7 @@ public final class ActivityManagerService extends ActivityManagerNative
if (userId == UserHandle.USER_OWNER) {
mTaskPersister.removeFromPackageCache(ssp);
}
+ mBatteryStatsService.notePackageUninstalled(ssp);
}
} else {
removeTasksByRemovedPackageComponentsLocked(ssp, userId);
@@ -15833,6 +15839,13 @@ public final class ActivityManagerService extends ActivityManagerNative
if (userId == UserHandle.USER_OWNER) {
mTaskPersister.addOtherDeviceTasksToRecentsLocked(ssp);
}
+ try {
+ ApplicationInfo ai = AppGlobals.getPackageManager().
+ getApplicationInfo(ssp, 0, 0);
+ mBatteryStatsService.notePackageInstalled(ssp,
+ ai != null ? ai.versionCode : 0);
+ } catch (RemoteException e) {
+ }
}
break;
case Intent.ACTION_TIMEZONE_CHANGED:
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index b102a07..f5fef63 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -1112,7 +1112,7 @@ final class ActivityStack {
}
}
- private void setVisibile(ActivityRecord r, boolean visible) {
+ private void setVisible(ActivityRecord r, boolean visible) {
r.visible = visible;
mWindowManager.setAppVisibility(r.appToken, visible);
final ArrayList<ActivityContainer> containers = r.mChildContainers;
@@ -1297,7 +1297,7 @@ final class ActivityStack {
if (!r.visible || r.mLaunchTaskBehind) {
if (DEBUG_VISBILITY) Slog.v(
TAG, "Starting and making visible: " + r);
- setVisibile(r, true);
+ setVisible(r, true);
}
if (r != starting) {
mStackSupervisor.startSpecificActivityLocked(r, false, false);
@@ -1329,7 +1329,7 @@ final class ActivityStack {
r.updateOptionsLocked(r.returningOptions);
mUndrawnActivitiesBelowTopTranslucent.add(r);
}
- setVisibile(r, true);
+ setVisible(r, true);
r.sleeping = false;
r.app.pendingUiClean = true;
r.app.thread.scheduleWindowVisibility(r.appToken, true);
@@ -1364,7 +1364,7 @@ final class ActivityStack {
if (r.visible) {
if (DEBUG_VISBILITY) Slog.v(TAG, "Making invisible: " + r);
try {
- setVisibile(r, false);
+ setVisible(r, false);
switch (r.state) {
case STOPPING:
case STOPPED:
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 8782950..cb96680 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -83,6 +83,7 @@ import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
+import android.os.TransactionTooLargeException;
import android.os.UserHandle;
import android.os.WorkSource;
import android.provider.Settings;
@@ -128,7 +129,7 @@ public final class ActivityStackSupervisor implements DisplayListener {
static final boolean DEBUG_RELEASE = DEBUG || false;
static final boolean DEBUG_SAVED_STATE = DEBUG || false;
static final boolean DEBUG_SCREENSHOTS = DEBUG || false;
- static final boolean DEBUG_STATES = DEBUG || false;
+ static final boolean DEBUG_STATES = DEBUG || true;
static final boolean DEBUG_VISIBLE_BEHIND = DEBUG || false;
public static final int HOME_STACK_ID = 0;
@@ -3961,7 +3962,8 @@ public final class ActivityStackSupervisor implements DisplayListener {
}
@Override
- public final int startActivityIntentSender(IIntentSender intentSender) {
+ public final int startActivityIntentSender(IIntentSender intentSender)
+ throws TransactionTooLargeException {
mService.enforceNotIsolatedCaller("ActivityContainer.startActivityIntentSender");
if (!(intentSender instanceof PendingIntentRecord)) {
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 6eacfa9..197b51d 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -86,7 +86,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub
public void initPowerManagement() {
mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
mPowerManagerInternal.registerLowPowerModeObserver(this);
- mStats.noteLowPowerMode(mPowerManagerInternal.getLowPowerModeEnabled());
+ mStats.notePowerSaveMode(mPowerManagerInternal.getLowPowerModeEnabled());
(new WakeupReasonThread()).start();
}
@@ -109,7 +109,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub
@Override
public void onLowPowerModeChanged(boolean enabled) {
synchronized (mStats) {
- mStats.noteLowPowerMode(enabled);
+ mStats.notePowerSaveMode(enabled);
}
}
@@ -686,6 +686,28 @@ public final class BatteryStatsService extends IBatteryStats.Stub
}
}
+ @Override
+ public void noteDeviceIdleMode(boolean enabled, boolean fromActive, boolean fromMotion) {
+ enforceCallingPermission();
+ synchronized (mStats) {
+ mStats.noteDeviceIdleModeLocked(enabled, fromActive, fromMotion);
+ }
+ }
+
+ public void notePackageInstalled(String pkgName, int versionCode) {
+ enforceCallingPermission();
+ synchronized (mStats) {
+ mStats.notePackageInstalledLocked(pkgName, versionCode);
+ }
+ }
+
+ public void notePackageUninstalled(String pkgName) {
+ enforceCallingPermission();
+ synchronized (mStats) {
+ mStats.notePackageUninstalledLocked(pkgName);
+ }
+ }
+
public boolean isOnBattery() {
return mStats.isOnBattery();
}
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 8fe1238..34c1c53 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -765,7 +765,7 @@ public final class BroadcastQueue {
try {
perm = AppGlobals.getPackageManager().
checkPermission(r.requiredPermission,
- info.activityInfo.applicationInfo.packageName);
+ info.activityInfo.applicationInfo.packageName, r.userId);
} catch (RemoteException e) {
perm = PackageManager.PERMISSION_DENIED;
}
diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java
index 7f2cae4..9c0db87 100644
--- a/services/core/java/com/android/server/am/PendingIntentRecord.java
+++ b/services/core/java/com/android/server/am/PendingIntentRecord.java
@@ -29,6 +29,7 @@ import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
+import android.os.TransactionTooLargeException;
import android.os.UserHandle;
import android.util.Slog;
@@ -166,7 +167,7 @@ final class PendingIntentRecord extends IIntentSender.Stub {
public int hashCode() {
return hashCode;
}
-
+
public String toString() {
return "Key{" + typeName() + " pkg=" + packageName
+ " intent="
@@ -174,7 +175,7 @@ final class PendingIntentRecord extends IIntentSender.Stub {
? requestIntent.toShortString(false, true, false, false) : "<null>")
+ " flags=0x" + Integer.toHexString(flags) + " u=" + userId + "}";
}
-
+
String typeName() {
switch (type) {
case ActivityManager.INTENT_SENDER_ACTIVITY:
@@ -189,7 +190,7 @@ final class PendingIntentRecord extends IIntentSender.Stub {
return Integer.toString(type);
}
}
-
+
PendingIntentRecord(ActivityManagerService _owner, Key _k, int _u) {
owner = _owner;
key = _k;
@@ -197,16 +198,16 @@ final class PendingIntentRecord extends IIntentSender.Stub {
ref = new WeakReference<PendingIntentRecord>(this);
}
- public int send(int code, Intent intent, String resolvedType,
- IIntentReceiver finishedReceiver, String requiredPermission) {
+ public int send(int code, Intent intent, String resolvedType, IIntentReceiver finishedReceiver,
+ String requiredPermission) throws TransactionTooLargeException {
return sendInner(code, intent, resolvedType, finishedReceiver,
requiredPermission, null, null, 0, 0, 0, null, null);
}
-
- int sendInner(int code, Intent intent, String resolvedType,
- IIntentReceiver finishedReceiver, String requiredPermission,
- IBinder resultTo, String resultWho, int requestCode,
- int flagsMask, int flagsValues, Bundle options, IActivityContainer container) {
+
+ int sendInner(int code, Intent intent, String resolvedType, IIntentReceiver finishedReceiver,
+ String requiredPermission, IBinder resultTo, String resultWho, int requestCode,
+ int flagsMask, int flagsValues, Bundle options, IActivityContainer container)
+ throws TransactionTooLargeException {
synchronized(owner) {
final ActivityContainer activityContainer = (ActivityContainer)container;
if (activityContainer != null && activityContainer.mParentActivity != null &&
@@ -234,9 +235,9 @@ final class PendingIntentRecord extends IIntentSender.Stub {
flagsMask &= ~Intent.IMMUTABLE_FLAGS;
flagsValues &= flagsMask;
finalIntent.setFlags((finalIntent.getFlags()&~flagsMask) | flagsValues);
-
+
final long origId = Binder.clearCallingIdentity();
-
+
boolean sendFinish = finishedReceiver != null;
int userId = key.userId;
if (userId == UserHandle.USER_CURRENT) {
@@ -297,8 +298,7 @@ final class PendingIntentRecord extends IIntentSender.Stub {
break;
case ActivityManager.INTENT_SENDER_SERVICE:
try {
- owner.startServiceInPackage(uid,
- finalIntent, resolvedType, userId);
+ owner.startServiceInPackage(uid, finalIntent, resolvedType, userId);
} catch (RuntimeException e) {
Slog.w(TAG, "Unable to send startService intent", e);
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 83ecf91..65b2ae2 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -4111,11 +4111,12 @@ public class AudioService extends IAudioService.Stub {
Iterator i = set.iterator();
while (i.hasNext()) {
Map.Entry device = (Map.Entry)i.next();
+ DeviceListSpec spec = (DeviceListSpec)device.getValue();
AudioSystem.setDeviceConnectionState(
- ((Integer)device.getKey()).intValue(),
+ spec.mDeviceType,
AudioSystem.DEVICE_STATE_AVAILABLE,
- (String)device.getValue(),
- "unknown-device");
+ spec.mDeviceAddress,
+ spec.mDeviceName);
}
}
// Restore call state
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
index 1e43670..70fa441 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevicePlayback.java
@@ -254,6 +254,16 @@ final class HdmiCecLocalDevicePlayback extends HdmiCecLocalDevice {
@Override
@ServiceThreadOnly
+ protected void sendStandby(int deviceId) {
+ assertRunOnServiceThread();
+
+ // Playback device can send <Standby> to TV only. Ignore the parameter.
+ int targetAddress = Constants.ADDR_TV;
+ mService.sendCecCommand(HdmiCecMessageBuilder.buildStandby(mAddress, targetAddress));
+ }
+
+ @Override
+ @ServiceThreadOnly
protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) {
super.disableDevice(initiatedByCec, callback);
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index 83d6986..d79b5fd 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -22,6 +22,7 @@ import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
+import android.app.ActivityManager;
import android.app.AppGlobals;
import android.app.job.JobInfo;
import android.app.job.JobScheduler;
@@ -40,6 +41,7 @@ import android.os.Binder;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
+import android.os.PowerManager;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
@@ -72,7 +74,8 @@ public class JobSchedulerService extends com.android.server.SystemService
implements StateChangedListener, JobCompletedListener {
static final boolean DEBUG = false;
/** The number of concurrent jobs we run at one time. */
- private static final int MAX_JOB_CONTEXTS_COUNT = 3;
+ private static final int MAX_JOB_CONTEXTS_COUNT
+ = ActivityManager.isLowRamDeviceStatic() ? 1 : 3;
static final String TAG = "JobSchedulerService";
/** Master list of jobs. */
final JobStore mJobs;
@@ -107,21 +110,22 @@ public class JobSchedulerService extends com.android.server.SystemService
* Track Services that have currently active or pending jobs. The index is provided by
* {@link JobStatus#getServiceToken()}
*/
- final List<JobServiceContext> mActiveServices = new ArrayList<JobServiceContext>();
+ final List<JobServiceContext> mActiveServices = new ArrayList<>();
/** List of controllers that will notify this service of updates to jobs. */
List<StateController> mControllers;
/**
* Queue of pending jobs. The JobServiceContext class will receive jobs from this list
* when ready to execute them.
*/
- final ArrayList<JobStatus> mPendingJobs = new ArrayList<JobStatus>();
+ final ArrayList<JobStatus> mPendingJobs = new ArrayList<>();
- final ArrayList<Integer> mStartedUsers = new ArrayList();
+ final ArrayList<Integer> mStartedUsers = new ArrayList<>();
final JobHandler mHandler;
final JobSchedulerStub mJobSchedulerStub;
IBatteryStats mBatteryStats;
+ PowerManager mPowerManager;
/**
* Set to true once we are allowed to run third party apps.
@@ -129,6 +133,11 @@ public class JobSchedulerService extends com.android.server.SystemService
boolean mReadyToRock;
/**
+ * True when in device idle mode, so we don't want to schedule any jobs.
+ */
+ boolean mDeviceIdleMode;
+
+ /**
* Cleans up outstanding jobs when a package is removed. Even if it's being replaced later we
* still clean up. On reinstall the package will have a new uid.
*/
@@ -152,6 +161,8 @@ public class JobSchedulerService extends com.android.server.SystemService
Slog.d(TAG, "Removing jobs for user: " + userId);
}
cancelJobsForUser(userId);
+ } else if (PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED.equals(intent.getAction())) {
+ updateIdleMode(mPowerManager != null ? mPowerManager.isDeviceIdleMode() : false);
}
}
};
@@ -197,7 +208,7 @@ public class JobSchedulerService extends com.android.server.SystemService
return outList;
}
- private void cancelJobsForUser(int userHandle) {
+ void cancelJobsForUser(int userHandle) {
List<JobStatus> jobsForUser;
synchronized (mJobs) {
jobsForUser = mJobs.getJobsByUser(userHandle);
@@ -255,6 +266,40 @@ public class JobSchedulerService extends com.android.server.SystemService
}
}
+ void updateIdleMode(boolean enabled) {
+ boolean changed = false;
+ boolean rocking;
+ synchronized (mJobs) {
+ if (mDeviceIdleMode != enabled) {
+ changed = true;
+ }
+ rocking = mReadyToRock;
+ }
+ if (changed) {
+ if (rocking) {
+ for (int i=0; i<mControllers.size(); i++) {
+ mControllers.get(i).deviceIdleModeChanged(enabled);
+ }
+ }
+ synchronized (mJobs) {
+ mDeviceIdleMode = enabled;
+ if (enabled) {
+ // When becoming idle, make sure no jobs are actively running.
+ for (int i=0; i<mActiveServices.size(); i++) {
+ JobServiceContext jsc = mActiveServices.get(i);
+ final JobStatus executing = jsc.getRunningJob();
+ if (executing != null) {
+ jsc.cancelExecutingJob();
+ }
+ }
+ } else {
+ // When coming out of idle, allow thing to start back up.
+ mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
+ }
+ }
+ }
+ }
+
/**
* Initializes the system service.
* <p>
@@ -292,8 +337,10 @@ public class JobSchedulerService extends com.android.server.SystemService
getContext().registerReceiverAsUser(
mBroadcastReceiver, UserHandle.ALL, filter, null, null);
final IntentFilter userFilter = new IntentFilter(Intent.ACTION_USER_REMOVED);
+ userFilter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
getContext().registerReceiverAsUser(
mBroadcastReceiver, UserHandle.ALL, userFilter, null, null);
+ mPowerManager = (PowerManager)getContext().getSystemService(Context.POWER_SERVICE);
} else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
synchronized (mJobs) {
// Let's go!
@@ -311,6 +358,7 @@ public class JobSchedulerService extends com.android.server.SystemService
for (int i=0; i<jobs.size(); i++) {
JobStatus job = jobs.valueAt(i);
for (int controller=0; controller<mControllers.size(); controller++) {
+ mControllers.get(controller).deviceIdleModeChanged(mDeviceIdleMode);
mControllers.get(controller).maybeStartTrackingJob(job);
}
}
@@ -665,6 +713,10 @@ public class JobSchedulerService extends com.android.server.SystemService
*/
private void maybeRunPendingJobsH() {
synchronized (mJobs) {
+ if (mDeviceIdleMode) {
+ // If device is idle, we will not schedule jobs to run.
+ return;
+ }
Iterator<JobStatus> it = mPendingJobs.iterator();
if (DEBUG) {
Slog.d(TAG, "pending queue: " + mPendingJobs.size() + " jobs.");
@@ -876,6 +928,7 @@ public class JobSchedulerService extends com.android.server.SystemService
}
pw.println();
pw.print("mReadyToRock="); pw.println(mReadyToRock);
+ pw.print("mDeviceIdleMode="); pw.println(mDeviceIdleMode);
}
pw.println();
}
diff --git a/services/core/java/com/android/server/job/controllers/StateController.java b/services/core/java/com/android/server/job/controllers/StateController.java
index 7d76fc0..efd1928 100644
--- a/services/core/java/com/android/server/job/controllers/StateController.java
+++ b/services/core/java/com/android/server/job/controllers/StateController.java
@@ -31,12 +31,17 @@ public abstract class StateController {
protected static final boolean DEBUG = false;
protected Context mContext;
protected StateChangedListener mStateChangedListener;
+ protected boolean mDeviceIdleMode;
public StateController(StateChangedListener stateChangedListener, Context context) {
mStateChangedListener = stateChangedListener;
mContext = context;
}
+ public void deviceIdleModeChanged(boolean enabled) {
+ mDeviceIdleMode = enabled;
+ }
+
/**
* Implement the logic here to decide whether a job should be tracked by this controller.
* This logic is put here so the JobManger can be completely agnostic of Controller logic.
@@ -50,5 +55,4 @@ public abstract class StateController {
public abstract void maybeStopTrackingJob(JobStatus jobStatus);
public abstract void dumpControllerState(PrintWriter pw);
-
}
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index a530dfa..65949bf 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -55,6 +55,7 @@ import android.provider.Settings;
import android.speech.RecognizerIntent;
import android.text.TextUtils;
import android.util.Log;
+import android.util.Slog;
import android.util.SparseArray;
import android.view.KeyEvent;
@@ -743,15 +744,23 @@ public class MediaSessionService extends SystemService implements Monitor {
Log.w(TAG, "Attempted to dispatch null or non-media key event.");
return;
}
+
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
final long token = Binder.clearCallingIdentity();
- if (DEBUG) {
- Log.d(TAG, "dispatchMediaKeyEvent, pid=" + pid + ", uid=" + uid + ", event="
- + keyEvent);
- }
-
try {
+ if (DEBUG) {
+ Log.d(TAG, "dispatchMediaKeyEvent, pid=" + pid + ", uid=" + uid + ", event="
+ + keyEvent);
+ }
+ if (!isUserSetupComplete()) {
+ // Global media key handling can have the side-effect of starting new
+ // activities which is undesirable while setup is in progress.
+ Slog.i(TAG, "Not dispatching media key event because user "
+ + "setup is in progress.");
+ return;
+ }
+
synchronized (mLock) {
// If we don't have a media button receiver to fall back on
// include non-playing sessions for dispatching
@@ -1025,6 +1034,11 @@ public class MediaSessionService extends SystemService implements Monitor {
return keyCode == KeyEvent.KEYCODE_HEADSETHOOK;
}
+ private boolean isUserSetupComplete() {
+ return Settings.Secure.getIntForUser(getContext().getContentResolver(),
+ Settings.Secure.USER_SETUP_COMPLETE, 0, UserHandle.USER_CURRENT) != 0;
+ }
+
// we only handle public stream types, which are 0-5
private boolean isValidLocalStreamType(int streamType) {
return streamType >= AudioManager.STREAM_VOICE_CALL
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 8044333..5de7d42 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -71,7 +71,9 @@ import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_UP
import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
import static org.xmlpull.v1.XmlPullParser.START_TAG;
+import android.Manifest;
import android.app.ActivityManager;
+import android.app.AppGlobals;
import android.app.IActivityManager;
import android.app.INotificationManager;
import android.app.IProcessObserver;
@@ -83,6 +85,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.content.res.Resources;
@@ -243,9 +246,11 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
final Object mRulesLock = new Object();
+ volatile boolean mSystemReady;
volatile boolean mScreenOn;
volatile boolean mRestrictBackground;
volatile boolean mRestrictPower;
+ volatile boolean mDeviceIdleMode;
private final boolean mSuppressDefaultPolicy;
@@ -367,11 +372,12 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
}
});
mRestrictPower = mPowerManagerInternal.getLowPowerModeEnabled();
+ mSystemReady = true;
// read policy from disk
readPolicyLocked();
- if (mRestrictBackground || mRestrictPower) {
+ if (mRestrictBackground || mRestrictPower || mDeviceIdleMode) {
updateRulesForGlobalChangeLocked(true);
updateNotificationsLocked();
}
@@ -1031,7 +1037,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
// will not have a bandwidth limit. Also only do this if restrict
// background data use is *not* enabled, since that takes precendence
// use over those networks can have a cost associated with it).
- final boolean powerSave = mRestrictPower && !mRestrictBackground;
+ final boolean powerSave = (mRestrictPower || mDeviceIdleMode) && !mRestrictBackground;
// First, generate identities of all connected networks so we can
// quickly compare them against all defined policies below.
@@ -1696,6 +1702,20 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
}
}
+ @Override
+ public void setDeviceIdleMode(boolean enabled) {
+ mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
+
+ synchronized (mRulesLock) {
+ if (mDeviceIdleMode != enabled) {
+ mDeviceIdleMode = enabled;
+ if (mSystemReady) {
+ updateRulesForGlobalChangeLocked(true);
+ }
+ }
+ }
+ }
+
private NetworkPolicy findPolicyForNetworkLocked(NetworkIdentity ident) {
for (int i = mNetworkPolicy.size()-1; i >= 0; i--) {
NetworkPolicy policy = mNetworkPolicy.valueAt(i);
@@ -1801,8 +1821,10 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
return;
}
+ fout.print("System ready: "); fout.println(mSystemReady);
fout.print("Restrict background: "); fout.println(mRestrictBackground);
fout.print("Restrict power: "); fout.println(mRestrictPower);
+ fout.print("Device idle: "); fout.println(mDeviceIdleMode);
fout.print("Current foreground state: "); fout.println(mCurForegroundState);
fout.println("Network policies:");
fout.increaseIndent();
@@ -1952,8 +1974,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
}
/**
- * Update rules that might be changed by {@link #mRestrictBackground}
- * or {@link #mRestrictPower} value.
+ * Update rules that might be changed by {@link #mRestrictBackground},
+ * {@link #mRestrictPower}, or {@link #mDeviceIdleMode} value.
*/
void updateRulesForGlobalChangeLocked(boolean restrictedNetworksChanged) {
final PackageManager pm = mContext.getPackageManager();
@@ -1962,7 +1984,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
// If we are in restrict power mode, we allow all important apps
// to have data access. Otherwise, we restrict data access to only
// the top apps.
- mCurForegroundState = (!mRestrictBackground && mRestrictPower)
+ mCurForegroundState = (!mRestrictBackground && (mRestrictPower || mDeviceIdleMode))
? ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
: ActivityManager.PROCESS_STATE_TOP;
@@ -2002,6 +2024,17 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
void updateRulesForUidLocked(int uid) {
if (!isUidValidForRules(uid)) return;
+ // quick check: if this uid doesn't have INTERNET permission, it doesn't have
+ // network access anyway, so it is a waste to mess with it here.
+ final IPackageManager ipm = AppGlobals.getPackageManager();
+ try {
+ if (ipm.checkUidPermission(Manifest.permission.INTERNET, uid)
+ != PackageManager.PERMISSION_GRANTED) {
+ return;
+ }
+ } catch (RemoteException e) {
+ }
+
final int uidPolicy = mUidPolicy.get(uid, POLICY_NONE);
final boolean uidForeground = isUidForegroundLocked(uid);
@@ -2015,7 +2048,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
// uid in background, and global background disabled
uidRules = RULE_REJECT_METERED;
}
- } else if (mRestrictPower) {
+ } else if (mRestrictPower || mDeviceIdleMode) {
final boolean whitelisted = mPowerSaveWhitelistAppIds.get(UserHandle.getAppId(uid));
if (!whitelisted && !uidForeground
&& (uidPolicy & POLICY_ALLOW_BACKGROUND_BATTERY_SAVE) == 0) {
diff --git a/services/core/java/com/android/server/notification/NotificationIntrusivenessExtractor.java b/services/core/java/com/android/server/notification/NotificationIntrusivenessExtractor.java
index 1335706..22cdd58 100644
--- a/services/core/java/com/android/server/notification/NotificationIntrusivenessExtractor.java
+++ b/services/core/java/com/android/server/notification/NotificationIntrusivenessExtractor.java
@@ -18,6 +18,7 @@ package com.android.server.notification;
import android.app.Notification;
import android.content.Context;
+import android.util.Log;
import android.util.Slog;
/**
@@ -25,8 +26,8 @@ import android.util.Slog;
* notifications and marks them to get a temporary ranking bump.
*/
public class NotificationIntrusivenessExtractor implements NotificationSignalExtractor {
- private static final String TAG = "NotificationNoiseExtractor";
- private static final boolean DBG = false;
+ private static final String TAG = "IntrusivenessExtractor";
+ private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
/** Length of time (in milliseconds) that an intrusive or noisy notification will stay at
the top of the ranking order, before it falls back to its natural position. */
@@ -48,7 +49,7 @@ public class NotificationIntrusivenessExtractor implements NotificationSignalExt
(notification.defaults & Notification.DEFAULT_SOUND) != 0 ||
notification.sound != null ||
notification.fullScreenIntent != null) {
- record.setRecentlyIntusive(true);
+ record.setRecentlyIntrusive(true);
}
return new RankingReconsideration(record.getKey(), HANG_TIME_MS) {
@@ -59,7 +60,7 @@ public class NotificationIntrusivenessExtractor implements NotificationSignalExt
@Override
public void applyChangesLocked(NotificationRecord record) {
- record.setRecentlyIntusive(false);
+ record.setRecentlyIntrusive(false);
}
};
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 0c71d5f..c330046 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -1216,6 +1216,19 @@ public class NotificationManagerService extends SystemService {
}
@Override
+ public void setPackagePeekable(String pkg, int uid, boolean peekable) {
+ checkCallerIsSystem();
+
+ mRankingHelper.setPackagePeekable(pkg, uid, peekable);
+ }
+
+ @Override
+ public boolean getPackagePeekable(String pkg, int uid) {
+ checkCallerIsSystem();
+ return mRankingHelper.getPackagePeekable(pkg, uid);
+ }
+
+ @Override
public void setPackageVisibilityOverride(String pkg, int uid, int visibility) {
checkCallerIsSystem();
mRankingHelper.setPackageVisibilityOverride(pkg, uid, visibility);
@@ -1845,6 +1858,14 @@ public class NotificationManagerService extends SystemService {
notification.priority = Notification.PRIORITY_HIGH;
}
}
+ // force no heads up per package config
+ if (!mRankingHelper.getPackagePeekable(pkg, callingUid)) {
+ if (notification.extras == null) {
+ notification.extras = new Bundle();
+ }
+ notification.extras.putInt(Notification.EXTRA_AS_HEADS_UP,
+ Notification.HEADS_UP_NEVER);
+ }
// 1. initial score: buckets of 10, around the app [-20..20]
final int score = notification.priority * NOTIFICATION_PRIORITY_MULTIPLIER;
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index ea6f2db..39fd9ab 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -209,7 +209,7 @@ public final class NotificationRecord {
return mContactAffinity;
}
- public void setRecentlyIntusive(boolean recentlyIntrusive) {
+ public void setRecentlyIntrusive(boolean recentlyIntrusive) {
mRecentlyIntrusive = recentlyIntrusive;
}
diff --git a/services/core/java/com/android/server/notification/RankingConfig.java b/services/core/java/com/android/server/notification/RankingConfig.java
index aea137b..803db10 100644
--- a/services/core/java/com/android/server/notification/RankingConfig.java
+++ b/services/core/java/com/android/server/notification/RankingConfig.java
@@ -20,6 +20,10 @@ public interface RankingConfig {
void setPackagePriority(String packageName, int uid, int priority);
+ boolean getPackagePeekable(String packageName, int uid);
+
+ void setPackagePeekable(String packageName, int uid, boolean peekable);
+
int getPackageVisibilityOverride(String packageName, int uid);
void setPackageVisibilityOverride(String packageName, int uid, int visibility);
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index 518e223..88055ba 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -23,9 +23,8 @@ import android.os.UserHandle;
import android.service.notification.NotificationListenerService;
import android.text.TextUtils;
import android.util.ArrayMap;
-import android.util.ArraySet;
import android.util.Slog;
-import android.util.SparseIntArray;
+
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
@@ -34,12 +33,10 @@ import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.Set;
import java.util.concurrent.TimeUnit;
public class RankingHelper implements RankingConfig {
private static final String TAG = "RankingHelper";
- private static final boolean DEBUG = false;
private static final int XML_VERSION = 1;
@@ -50,16 +47,20 @@ public class RankingHelper implements RankingConfig {
private static final String ATT_NAME = "name";
private static final String ATT_UID = "uid";
private static final String ATT_PRIORITY = "priority";
+ private static final String ATT_PEEKABLE = "peekable";
private static final String ATT_VISIBILITY = "visibility";
+ private static final int DEFAULT_PRIORITY = Notification.PRIORITY_DEFAULT;
+ private static final boolean DEFAULT_PEEKABLE = true;
+ private static final int DEFAULT_VISIBILITY =
+ NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE;
+
private final NotificationSignalExtractor[] mSignalExtractors;
private final NotificationComparator mPreliminaryComparator = new NotificationComparator();
private final GlobalSortKeyComparator mFinalComparator = new GlobalSortKeyComparator();
- // Package name to uid, to priority. Would be better as Table<String, Int, Int>
- private final ArrayMap<String, SparseIntArray> mPackagePriorities;
- private final ArrayMap<String, SparseIntArray> mPackageVisibilities;
- private final ArrayMap<String, NotificationRecord> mProxyByGroupTmp;
+ private final ArrayMap<String, Record> mRecords = new ArrayMap<>(); // pkg|uid => Record
+ private final ArrayMap<String, NotificationRecord> mProxyByGroupTmp = new ArrayMap<>();
private final Context mContext;
private final Handler mRankingHandler;
@@ -67,8 +68,6 @@ public class RankingHelper implements RankingConfig {
public RankingHelper(Context context, Handler rankingHandler, String[] extractorNames) {
mContext = context;
mRankingHandler = rankingHandler;
- mPackagePriorities = new ArrayMap<String, SparseIntArray>();
- mPackageVisibilities = new ArrayMap<String, SparseIntArray>();
final int N = extractorNames.length;
mSignalExtractors = new NotificationSignalExtractor[N];
@@ -88,9 +87,9 @@ public class RankingHelper implements RankingConfig {
Slog.w(TAG, "Problem accessing extractor " + extractorNames[i] + ".", e);
}
}
- mProxyByGroupTmp = new ArrayMap<String, NotificationRecord>();
}
+ @SuppressWarnings("unchecked")
public <T extends NotificationSignalExtractor> T findExtractor(Class<T> extractorClass) {
final int N = mSignalExtractors.length;
for (int i = 0; i < N; i++) {
@@ -125,8 +124,7 @@ public class RankingHelper implements RankingConfig {
if (type != XmlPullParser.START_TAG) return;
String tag = parser.getName();
if (!TAG_RANKING.equals(tag)) return;
- mPackagePriorities.clear();
- final int version = safeInt(parser, ATT_VERSION, XML_VERSION);
+ mRecords.clear();
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
tag = parser.getName();
if (type == XmlPullParser.END_TAG && TAG_RANKING.equals(tag)) {
@@ -135,27 +133,20 @@ public class RankingHelper implements RankingConfig {
if (type == XmlPullParser.START_TAG) {
if (TAG_PACKAGE.equals(tag)) {
int uid = safeInt(parser, ATT_UID, UserHandle.USER_ALL);
- int priority = safeInt(parser, ATT_PRIORITY, Notification.PRIORITY_DEFAULT);
- int vis = safeInt(parser, ATT_VISIBILITY,
- NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE);
+ int priority = safeInt(parser, ATT_PRIORITY, DEFAULT_PRIORITY);
+ boolean peekable = safeBool(parser, ATT_PEEKABLE, DEFAULT_PEEKABLE);
+ int vis = safeInt(parser, ATT_VISIBILITY, DEFAULT_VISIBILITY);
String name = parser.getAttributeValue(null, ATT_NAME);
if (!TextUtils.isEmpty(name)) {
- if (priority != Notification.PRIORITY_DEFAULT) {
- SparseIntArray priorityByUid = mPackagePriorities.get(name);
- if (priorityByUid == null) {
- priorityByUid = new SparseIntArray();
- mPackagePriorities.put(name, priorityByUid);
- }
- priorityByUid.put(uid, priority);
+ if (priority != DEFAULT_PRIORITY) {
+ getOrCreateRecord(name, uid).priority = priority;
+ }
+ if (peekable != DEFAULT_PEEKABLE) {
+ getOrCreateRecord(name, uid).peekable = peekable;
}
- if (vis != NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE) {
- SparseIntArray visibilityByUid = mPackageVisibilities.get(name);
- if (visibilityByUid == null) {
- visibilityByUid = new SparseIntArray();
- mPackageVisibilities.put(name, visibilityByUid);
- }
- visibilityByUid.put(uid, vis);
+ if (vis != DEFAULT_VISIBILITY) {
+ getOrCreateRecord(name, uid).visibility = vis;
}
}
}
@@ -164,49 +155,53 @@ public class RankingHelper implements RankingConfig {
throw new IllegalStateException("Failed to reach END_DOCUMENT");
}
+ private static String recordKey(String pkg, int uid) {
+ return pkg + "|" + uid;
+ }
+
+ private Record getOrCreateRecord(String pkg, int uid) {
+ final String key = recordKey(pkg, uid);
+ Record r = mRecords.get(key);
+ if (r == null) {
+ r = new Record();
+ r.pkg = pkg;
+ r.uid = uid;
+ mRecords.put(key, r);
+ }
+ return r;
+ }
+
+ private void removeDefaultRecords() {
+ final int N = mRecords.size();
+ for (int i = N - 1; i >= 0; i--) {
+ final Record r = mRecords.valueAt(i);
+ if (r.priority == DEFAULT_PRIORITY && r.peekable == DEFAULT_PEEKABLE
+ && r.visibility == DEFAULT_VISIBILITY) {
+ mRecords.remove(i);
+ }
+ }
+ }
+
public void writeXml(XmlSerializer out) throws IOException {
out.startTag(null, TAG_RANKING);
out.attribute(null, ATT_VERSION, Integer.toString(XML_VERSION));
- final Set<String> packageNames = new ArraySet<>(mPackagePriorities.size()
- + mPackageVisibilities.size());
- packageNames.addAll(mPackagePriorities.keySet());
- packageNames.addAll(mPackageVisibilities.keySet());
- final Set<Integer> packageUids = new ArraySet<>();
- for (String packageName : packageNames) {
- packageUids.clear();
- SparseIntArray priorityByUid = mPackagePriorities.get(packageName);
- SparseIntArray visibilityByUid = mPackageVisibilities.get(packageName);
- if (priorityByUid != null) {
- final int M = priorityByUid.size();
- for (int j = 0; j < M; j++) {
- packageUids.add(priorityByUid.keyAt(j));
- }
+ final int N = mRecords.size();
+ for (int i = 0; i < N; i++) {
+ final Record r = mRecords.valueAt(i);
+ out.startTag(null, TAG_PACKAGE);
+ out.attribute(null, ATT_NAME, r.pkg);
+ if (r.priority != DEFAULT_PRIORITY) {
+ out.attribute(null, ATT_PRIORITY, Integer.toString(r.priority));
}
- if (visibilityByUid != null) {
- final int M = visibilityByUid.size();
- for (int j = 0; j < M; j++) {
- packageUids.add(visibilityByUid.keyAt(j));
- }
+ if (r.peekable != DEFAULT_PEEKABLE) {
+ out.attribute(null, ATT_PEEKABLE, Boolean.toString(r.peekable));
}
- for (Integer uid : packageUids) {
- out.startTag(null, TAG_PACKAGE);
- out.attribute(null, ATT_NAME, packageName);
- if (priorityByUid != null) {
- final int priority = priorityByUid.get(uid);
- if (priority != Notification.PRIORITY_DEFAULT) {
- out.attribute(null, ATT_PRIORITY, Integer.toString(priority));
- }
- }
- if (visibilityByUid != null) {
- final int visibility = visibilityByUid.get(uid);
- if (visibility != NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE) {
- out.attribute(null, ATT_VISIBILITY, Integer.toString(visibility));
- }
- }
- out.attribute(null, ATT_UID, Integer.toString(uid));
- out.endTag(null, TAG_PACKAGE);
+ if (r.visibility != DEFAULT_VISIBILITY) {
+ out.attribute(null, ATT_VISIBILITY, Integer.toString(r.visibility));
}
+ out.attribute(null, ATT_UID, Integer.toString(r.uid));
+ out.endTag(null, TAG_PACKAGE);
}
out.endTag(null, TAG_RANKING);
}
@@ -295,14 +290,20 @@ public class RankingHelper implements RankingConfig {
}
}
+ private static boolean safeBool(XmlPullParser parser, String att, boolean defValue) {
+ final String val = parser.getAttributeValue(null, att);
+ return tryParseBool(val, defValue);
+ }
+
+ private static boolean tryParseBool(String value, boolean defValue) {
+ if (TextUtils.isEmpty(value)) return defValue;
+ return Boolean.valueOf(value);
+ }
+
@Override
public int getPackagePriority(String packageName, int uid) {
- int priority = Notification.PRIORITY_DEFAULT;
- SparseIntArray priorityByUid = mPackagePriorities.get(packageName);
- if (priorityByUid != null) {
- priority = priorityByUid.get(uid, Notification.PRIORITY_DEFAULT);
- }
- return priority;
+ final Record r = mRecords.get(recordKey(packageName, uid));
+ return r != null ? r.priority : DEFAULT_PRIORITY;
}
@Override
@@ -310,24 +311,31 @@ public class RankingHelper implements RankingConfig {
if (priority == getPackagePriority(packageName, uid)) {
return;
}
- SparseIntArray priorityByUid = mPackagePriorities.get(packageName);
- if (priorityByUid == null) {
- priorityByUid = new SparseIntArray();
- mPackagePriorities.put(packageName, priorityByUid);
+ getOrCreateRecord(packageName, uid).priority = priority;
+ removeDefaultRecords();
+ updateConfig();
+ }
+
+ @Override
+ public boolean getPackagePeekable(String packageName, int uid) {
+ final Record r = mRecords.get(recordKey(packageName, uid));
+ return r != null ? r.peekable : DEFAULT_PEEKABLE;
+ }
+
+ @Override
+ public void setPackagePeekable(String packageName, int uid, boolean peekable) {
+ if (peekable == getPackagePeekable(packageName, uid)) {
+ return;
}
- priorityByUid.put(uid, priority);
+ getOrCreateRecord(packageName, uid).peekable = peekable;
+ removeDefaultRecords();
updateConfig();
}
@Override
public int getPackageVisibilityOverride(String packageName, int uid) {
- int visibility = NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE;
- SparseIntArray visibilityByUid = mPackageVisibilities.get(packageName);
- if (visibilityByUid != null) {
- visibility = visibilityByUid.get(uid,
- NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE);
- }
- return visibility;
+ final Record r = mRecords.get(recordKey(packageName, uid));
+ return r != null ? r.visibility : DEFAULT_VISIBILITY;
}
@Override
@@ -335,12 +343,8 @@ public class RankingHelper implements RankingConfig {
if (visibility == getPackageVisibilityOverride(packageName, uid)) {
return;
}
- SparseIntArray visibilityByUid = mPackageVisibilities.get(packageName);
- if (visibilityByUid == null) {
- visibilityByUid = new SparseIntArray();
- mPackageVisibilities.put(packageName, visibilityByUid);
- }
- visibilityByUid.put(uid, visibility);
+ getOrCreateRecord(packageName, uid).visibility = visibility;
+ removeDefaultRecords();
updateConfig();
}
@@ -356,28 +360,42 @@ public class RankingHelper implements RankingConfig {
pw.println(mSignalExtractors[i]);
}
}
- final int N = mPackagePriorities.size();
if (filter == null) {
pw.print(prefix);
- pw.println("package priorities:");
+ pw.println("per-package config:");
}
+ final int N = mRecords.size();
for (int i = 0; i < N; i++) {
- String name = mPackagePriorities.keyAt(i);
- if (filter == null || filter.matches(name)) {
- SparseIntArray priorityByUid = mPackagePriorities.get(name);
- final int M = priorityByUid.size();
- for (int j = 0; j < M; j++) {
- int uid = priorityByUid.keyAt(j);
- int priority = priorityByUid.get(uid);
- pw.print(prefix);
- pw.print(" ");
- pw.print(name);
- pw.print(" (");
- pw.print(uid);
- pw.print(") has priority: ");
- pw.println(priority);
+ final Record r = mRecords.valueAt(i);
+ if (filter == null || filter.matches(r.pkg)) {
+ pw.print(prefix);
+ pw.print(" ");
+ pw.print(r.pkg);
+ pw.print(" (");
+ pw.print(r.uid);
+ pw.print(')');
+ if (r.priority != DEFAULT_PRIORITY) {
+ pw.print(" priority=");
+ pw.print(Notification.priorityToString(r.priority));
+ }
+ if (r.peekable != DEFAULT_PEEKABLE) {
+ pw.print(" peekable=");
+ pw.print(r.peekable);
+ }
+ if (r.visibility != DEFAULT_VISIBILITY) {
+ pw.print(" visibility=");
+ pw.print(Notification.visibilityToString(r.visibility));
}
+ pw.println();
}
}
}
+
+ private static class Record {
+ String pkg;
+ int uid;
+ int priority = DEFAULT_PRIORITY;
+ boolean peekable = DEFAULT_PEEKABLE;
+ int visibility = DEFAULT_VISIBILITY;
+ }
}
diff --git a/services/core/java/com/android/server/pm/BasePermission.java b/services/core/java/com/android/server/pm/BasePermission.java
index 4f27408..ec290ef 100644
--- a/services/core/java/com/android/server/pm/BasePermission.java
+++ b/services/core/java/com/android/server/pm/BasePermission.java
@@ -18,6 +18,9 @@ package com.android.server.pm;
import android.content.pm.PackageParser;
import android.content.pm.PermissionInfo;
+import android.os.UserHandle;
+
+import com.android.internal.util.ArrayUtils;
final class BasePermission {
final static int TYPE_NORMAL = 0;
@@ -40,9 +43,17 @@ final class BasePermission {
PermissionInfo pendingInfo;
+ /** UID that owns the definition of this permission */
int uid;
- int[] gids;
+ /** Additional GIDs given to apps granted this permission */
+ private int[] gids;
+
+ /**
+ * Flag indicating that {@link #gids} should be adjusted based on the
+ * {@link UserHandle} the granted app is running as.
+ */
+ private boolean perUser;
BasePermission(String _name, String _sourcePackage, int _type) {
name = _name;
@@ -52,8 +63,35 @@ final class BasePermission {
protectionLevel = PermissionInfo.PROTECTION_SIGNATURE;
}
+ @Override
public String toString() {
return "BasePermission{" + Integer.toHexString(System.identityHashCode(this)) + " " + name
+ "}";
}
+
+ public void setGids(int[] gids, boolean perUser) {
+ this.gids = gids;
+ this.perUser = perUser;
+ }
+
+ public boolean hasGids() {
+ return ArrayUtils.isEmpty(gids);
+ }
+
+ public int[] computeGids(int userId) {
+ if (perUser) {
+ final int[] userGids = new int[gids.length];
+ for (int i = 0; i < gids.length; i++) {
+ userGids[i] = UserHandle.getUid(userId, gids[i]);
+ }
+ return userGids;
+ } else {
+ return gids;
+ }
+ }
+
+ public boolean isRuntime() {
+ return (protectionLevel & PermissionInfo.PROTECTION_MASK_BASE)
+ == PermissionInfo.PROTECTION_DANGEROUS;
+ }
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 52411bf..2629e48 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -54,7 +54,6 @@ import static com.android.internal.app.IntentForwarderActivity.FORWARD_INTENT_TO
import static com.android.internal.content.NativeLibraryHelper.LIB64_DIR_NAME;
import static com.android.internal.content.NativeLibraryHelper.LIB_DIR_NAME;
import static com.android.internal.util.ArrayUtils.appendInt;
-import static com.android.internal.util.ArrayUtils.removeInt;
import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
import static com.android.server.pm.InstructionSets.getDexCodeInstructionSet;
import static com.android.server.pm.InstructionSets.getDexCodeInstructionSets;
@@ -249,6 +248,9 @@ public class PackageManagerService extends IPackageManager.Stub {
private static final boolean DEBUG_DEXOPT = false;
private static final boolean DEBUG_ABI_SELECTION = false;
+ private static final boolean RUNTIME_PERMISSIONS_ENABLED =
+ SystemProperties.getInt("ro.runtime.premissions.enabled", 0) == 1;
+
private static final int RADIO_UID = Process.PHONE_UID;
private static final int LOG_UID = Process.LOG_UID;
private static final int NFC_UID = Process.NFC_UID;
@@ -321,10 +323,28 @@ public class PackageManagerService extends IPackageManager.Stub {
DEFAULT_CONTAINER_PACKAGE,
"com.android.defcontainer.DefaultContainerService");
+ private static final String KILL_APP_REASON_GIDS_CHANGED =
+ "permission grant or revoke changed gids";
+
+ private static final String KILL_APP_REASON_PERMISSIONS_REVOKED =
+ "permissions revoked";
+
private static final String PACKAGE_MIME_TYPE = "application/vnd.android.package-archive";
private static final String VENDOR_OVERLAY_DIR = "/vendor/overlay";
+ /** Permission grant: not grant the permission. */
+ private static final int GRANT_DENIED = 1;
+
+ /** Permission grant: grant the permission as an install permission. */
+ private static final int GRANT_INSTALL = 2;
+
+ /** Permission grant: grant the permission as a runtime permission. */
+ private static final int GRANT_RUNTIME = 3;
+
+ /** Permission grant: grant as runtime a permission that was granted as an install time one. */
+ private static final int GRANT_UPGRADE = 4;
+
final ServiceThread mHandlerThread;
final PackageHandler mHandler;
@@ -994,6 +1014,15 @@ public class PackageManagerService extends IPackageManager.Stub {
res.removedInfo.sendBroadcast(false, true, false);
Bundle extras = new Bundle(1);
extras.putInt(Intent.EXTRA_UID, res.uid);
+
+ // Now that we successfully installed the package, grant runtime
+ // permissions if requested before broadcasting the install.
+ if ((args.installFlags
+ & PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS) != 0) {
+ grantRequestedRuntimePermissions(res.pkg,
+ args.user.getIdentifier());
+ }
+
// Determine the set of users who are adding this
// package for the first time vs. those who are seeing
// an update.
@@ -1214,6 +1243,32 @@ public class PackageManagerService extends IPackageManager.Stub {
}
}
+ private void grantRequestedRuntimePermissions(PackageParser.Package pkg, int userId) {
+ if (userId >= UserHandle.USER_OWNER) {
+ grantRequestedRuntimePermissionsForUser(pkg, userId);
+ } else if (userId == UserHandle.USER_ALL) {
+ for (int someUserId : UserManagerService.getInstance().getUserIds()) {
+ grantRequestedRuntimePermissionsForUser(pkg, someUserId);
+ }
+ }
+ }
+
+ private void grantRequestedRuntimePermissionsForUser(PackageParser.Package pkg, int userId) {
+ SettingBase sb = (SettingBase) pkg.mExtras;
+ if (sb == null) {
+ return;
+ }
+
+ PermissionsState permissionsState = sb.getPermissionsState();
+
+ for (String permission : pkg.requestedPermissions) {
+ BasePermission bp = mSettings.mPermissions.get(permission);
+ if (bp != null && bp.isRuntime()) {
+ permissionsState.grantRuntimePermission(bp, userId);
+ }
+ }
+ }
+
Bundle extrasForInstallResult(PackageInstalledInfo res) {
Bundle extras = null;
switch (res.returnCode) {
@@ -1243,7 +1298,7 @@ public class PackageManagerService extends IPackageManager.Stub {
}
}
- public static final PackageManagerService main(Context context, Installer installer,
+ public static PackageManagerService main(Context context, Installer installer,
boolean factoryTest, boolean onlyCore) {
PackageManagerService m = new PackageManagerService(context, installer,
factoryTest, onlyCore);
@@ -1293,7 +1348,7 @@ public class PackageManagerService extends IPackageManager.Stub {
mOnlyCore = onlyCore;
mLazyDexOpt = "eng".equals(SystemProperties.get("ro.build.type"));
mMetrics = new DisplayMetrics();
- mSettings = new Settings(context);
+ mSettings = new Settings(mContext, mPackages);
mSettings.addSharedUserLPw("android.uid.system", Process.SYSTEM_UID,
ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
mSettings.addSharedUserLPw("android.uid.phone", RADIO_UID,
@@ -1374,7 +1429,7 @@ public class PackageManagerService extends IPackageManager.Stub {
mSettings.mPermissions.put(perm.name, bp);
}
if (perm.gids != null) {
- bp.gids = appendInts(bp.gids, perm.gids);
+ bp.setGids(perm.gids, perm.perUser);
}
}
@@ -1832,14 +1887,8 @@ public class PackageManagerService extends IPackageManager.Stub {
final String packageName = info.activityInfo.packageName;
- final PackageSetting ps = mSettings.mPackages.get(packageName);
- if (ps == null) {
- continue;
- }
-
- final GrantedPermissions gp = ps.sharedUser != null ? ps.sharedUser : ps;
- if (!gp.grantedPermissions
- .contains(android.Manifest.permission.PACKAGE_VERIFICATION_AGENT)) {
+ if (checkPermission(android.Manifest.permission.PACKAGE_VERIFICATION_AGENT,
+ packageName, UserHandle.USER_OWNER) != PackageManager.PERMISSION_GRANTED) {
continue;
}
@@ -1895,26 +1944,21 @@ public class PackageManagerService extends IPackageManager.Stub {
return cur;
}
- static int[] removeInts(int[] cur, int[] rem) {
- if (rem == null) return cur;
- if (cur == null) return cur;
- final int N = rem.length;
- for (int i=0; i<N; i++) {
- cur = removeInt(cur, rem[i]);
- }
- return cur;
- }
-
PackageInfo generatePackageInfo(PackageParser.Package p, int flags, int userId) {
if (!sUserManager.exists(userId)) return null;
final PackageSetting ps = (PackageSetting) p.mExtras;
if (ps == null) {
return null;
}
- final GrantedPermissions gp = ps.sharedUser != null ? ps.sharedUser : ps;
+
+ PermissionsState permissionsState = ps.getPermissionsState();
+
+ final int[] gids = permissionsState.computeGids(userId);
+ Set<String> permissions = permissionsState.getPermissions(userId);
+
final PackageUserState state = ps.readUserState(userId);
- return PackageParser.generatePackageInfo(p, gp.gids, flags,
- ps.firstInstallTime, ps.lastUpdateTime, gp.grantedPermissions,
+ return PackageParser.generatePackageInfo(p, gids, flags,
+ ps.firstInstallTime, ps.lastUpdateTime, permissions,
state, userId);
}
@@ -1986,6 +2030,7 @@ public class PackageManagerService extends IPackageManager.Stub {
public int getPackageUid(String packageName, int userId) {
if (!sUserManager.exists(userId)) return -1;
enforceCrossUserPermission(Binder.getCallingUid(), userId, false, false, "get package uid");
+
// reader
synchronized (mPackages) {
PackageParser.Package p = mPackages.get(packageName);
@@ -2002,22 +2047,30 @@ public class PackageManagerService extends IPackageManager.Stub {
}
@Override
- public int[] getPackageGids(String packageName) {
+ public int[] getPackageGids(String packageName, int userId) throws RemoteException {
+ if (!sUserManager.exists(userId)) {
+ return null;
+ }
+
+ enforceCrossUserPermission(Binder.getCallingUid(), userId, false, false,
+ "getPackageGids");
+
// reader
synchronized (mPackages) {
PackageParser.Package p = mPackages.get(packageName);
- if (DEBUG_PACKAGE_INFO)
+ if (DEBUG_PACKAGE_INFO) {
Log.v(TAG, "getPackageGids" + packageName + ": " + p);
+ }
if (p != null) {
- final PackageSetting ps = (PackageSetting)p.mExtras;
- return ps.getGids();
+ PackageSetting ps = (PackageSetting) p.mExtras;
+ return ps.getPermissionsState().computeGids(userId);
}
}
- // stupid thing to indicate an error.
- return new int[0];
+
+ return null;
}
- static final PermissionInfo generatePermissionInfo(
+ static PermissionInfo generatePermissionInfo(
BasePermission bp, int flags) {
if (bp.perm != null) {
return PackageParser.generatePermissionInfo(bp.perm, flags);
@@ -2381,30 +2434,37 @@ public class PackageManagerService extends IPackageManager.Stub {
}
@Override
- public int checkPermission(String permName, String pkgName) {
+ public int checkPermission(String permName, String pkgName, int userId) {
+ if (!sUserManager.exists(userId)) {
+ return PackageManager.PERMISSION_DENIED;
+ }
+
synchronized (mPackages) {
- PackageParser.Package p = mPackages.get(pkgName);
+ final PackageParser.Package p = mPackages.get(pkgName);
if (p != null && p.mExtras != null) {
- PackageSetting ps = (PackageSetting)p.mExtras;
- if (ps.sharedUser != null) {
- if (ps.sharedUser.grantedPermissions.contains(permName)) {
- return PackageManager.PERMISSION_GRANTED;
- }
- } else if (ps.grantedPermissions.contains(permName)) {
+ final PackageSetting ps = (PackageSetting) p.mExtras;
+ if (ps.getPermissionsState().hasPermission(permName, userId)) {
return PackageManager.PERMISSION_GRANTED;
}
}
}
+
return PackageManager.PERMISSION_DENIED;
}
@Override
public int checkUidPermission(String permName, int uid) {
+ final int userId = UserHandle.getUserId(uid);
+
+ if (!sUserManager.exists(userId)) {
+ return PackageManager.PERMISSION_DENIED;
+ }
+
synchronized (mPackages) {
Object obj = mSettings.getUserIdLPr(UserHandle.getAppId(uid));
if (obj != null) {
- GrantedPermissions gp = (GrantedPermissions)obj;
- if (gp.grantedPermissions.contains(permName)) {
+ final SettingBase ps = (SettingBase) obj;
+ if (ps.getPermissionsState().hasPermission(permName, userId)) {
return PackageManager.PERMISSION_GRANTED;
}
} else {
@@ -2414,6 +2474,7 @@ public class PackageManagerService extends IPackageManager.Stub {
}
}
}
+
return PackageManager.PERMISSION_DENIED;
}
@@ -2620,120 +2681,114 @@ public class PackageManagerService extends IPackageManager.Stub {
}
}
- private static void checkGrantRevokePermissions(PackageParser.Package pkg, BasePermission bp) {
+ private static void enforceDeclaredAsUsedAndRuntimePermission(PackageParser.Package pkg,
+ BasePermission bp) {
int index = pkg.requestedPermissions.indexOf(bp.name);
if (index == -1) {
throw new SecurityException("Package " + pkg.packageName
+ " has not requested permission " + bp.name);
}
- boolean isNormal =
- ((bp.protectionLevel&PermissionInfo.PROTECTION_MASK_BASE)
- == PermissionInfo.PROTECTION_NORMAL);
- boolean isDangerous =
- ((bp.protectionLevel&PermissionInfo.PROTECTION_MASK_BASE)
- == PermissionInfo.PROTECTION_DANGEROUS);
- boolean isDevelopment =
- ((bp.protectionLevel&PermissionInfo.PROTECTION_FLAG_DEVELOPMENT) != 0);
-
- if (!isNormal && !isDangerous && !isDevelopment) {
+ if (!bp.isRuntime()) {
throw new SecurityException("Permission " + bp.name
+ " is not a changeable permission type");
}
-
- if (isNormal || isDangerous) {
- if (pkg.requestedPermissionsRequired.get(index)) {
- throw new SecurityException("Can't change " + bp.name
- + ". It is required by the application");
- }
- }
}
@Override
- public void grantPermission(String packageName, String permissionName) {
+ public boolean grantPermission(String packageName, String name, int userId) {
+ if (!sUserManager.exists(userId)) {
+ return false;
+ }
+
mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.GRANT_REVOKE_PERMISSIONS, null);
+ android.Manifest.permission.GRANT_REVOKE_PERMISSIONS,
+ "grantPermission");
+
+ enforceCrossUserPermission(Binder.getCallingUid(), userId, true, false,
+ "grantPermission");
+
synchronized (mPackages) {
final PackageParser.Package pkg = mPackages.get(packageName);
if (pkg == null) {
throw new IllegalArgumentException("Unknown package: " + packageName);
}
- final BasePermission bp = mSettings.mPermissions.get(permissionName);
+
+ final BasePermission bp = mSettings.mPermissions.get(name);
if (bp == null) {
- throw new IllegalArgumentException("Unknown permission: " + permissionName);
+ throw new IllegalArgumentException("Unknown permission: " + name);
}
- checkGrantRevokePermissions(pkg, bp);
+ enforceDeclaredAsUsedAndRuntimePermission(pkg, bp);
- final PackageSetting ps = (PackageSetting) pkg.mExtras;
- if (ps == null) {
- return;
+ final SettingBase sb = (SettingBase) pkg.mExtras;
+ if (sb == null) {
+ throw new IllegalArgumentException("Unknown package: " + packageName);
}
- final GrantedPermissions gp = (ps.sharedUser != null) ? ps.sharedUser : ps;
- if (gp.grantedPermissions.add(permissionName)) {
- if (ps.haveGids) {
- gp.gids = appendInts(gp.gids, bp.gids);
+
+ final PermissionsState permissionsState = sb.getPermissionsState();
+
+ final int result = permissionsState.grantRuntimePermission(bp, userId);
+ switch (result) {
+ case PermissionsState.PERMISSION_OPERATION_FAILURE: {
+ return false;
}
- mSettings.writeLPr();
+
+ case PermissionsState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED: {
+ killSettingPackagesForUser(sb, userId, KILL_APP_REASON_GIDS_CHANGED);
+ } break;
}
+
+ // Not critical if that is lost - app has to request again.
+ mSettings.writeRuntimePermissionsForUserLPr(userId, false);
+
+ return true;
}
}
@Override
- public void revokePermission(String packageName, String permissionName) {
- int changedAppId = -1;
+ public boolean revokePermission(String packageName, String name, int userId) {
+ if (!sUserManager.exists(userId)) {
+ return false;
+ }
+
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.GRANT_REVOKE_PERMISSIONS,
+ "revokePermission");
+
+ enforceCrossUserPermission(Binder.getCallingUid(), userId, true, false,
+ "revokePermission");
synchronized (mPackages) {
final PackageParser.Package pkg = mPackages.get(packageName);
if (pkg == null) {
throw new IllegalArgumentException("Unknown package: " + packageName);
}
- if (pkg.applicationInfo.uid != Binder.getCallingUid()) {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.GRANT_REVOKE_PERMISSIONS, null);
- }
- final BasePermission bp = mSettings.mPermissions.get(permissionName);
+
+ final BasePermission bp = mSettings.mPermissions.get(name);
if (bp == null) {
- throw new IllegalArgumentException("Unknown permission: " + permissionName);
+ throw new IllegalArgumentException("Unknown permission: " + name);
}
- checkGrantRevokePermissions(pkg, bp);
+ enforceDeclaredAsUsedAndRuntimePermission(pkg, bp);
- final PackageSetting ps = (PackageSetting) pkg.mExtras;
- if (ps == null) {
- return;
- }
- final GrantedPermissions gp = (ps.sharedUser != null) ? ps.sharedUser : ps;
- if (gp.grantedPermissions.remove(permissionName)) {
- gp.grantedPermissions.remove(permissionName);
- if (ps.haveGids) {
- gp.gids = removeInts(gp.gids, bp.gids);
- }
- mSettings.writeLPr();
- changedAppId = ps.appId;
+ final SettingBase sb = (SettingBase) pkg.mExtras;
+ if (sb == null) {
+ throw new IllegalArgumentException("Unknown package: " + packageName);
}
- }
- if (changedAppId >= 0) {
- // We changed the perm on someone, kill its processes.
- IActivityManager am = ActivityManagerNative.getDefault();
- if (am != null) {
- final int callingUserId = UserHandle.getCallingUserId();
- final long ident = Binder.clearCallingIdentity();
- try {
- //XXX we should only revoke for the calling user's app permissions,
- // but for now we impact all users.
- //am.killUid(UserHandle.getUid(callingUserId, changedAppId),
- // "revoke " + permissionName);
- int[] users = sUserManager.getUserIds();
- for (int user : users) {
- am.killUid(UserHandle.getUid(user, changedAppId),
- "revoke " + permissionName);
- }
- } catch (RemoteException e) {
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
+ final PermissionsState permissionsState = sb.getPermissionsState();
+
+ if (permissionsState.revokeRuntimePermission(bp, userId) ==
+ PermissionsState.PERMISSION_OPERATION_FAILURE) {
+ return false;
}
+
+ killSettingPackagesForUser(sb, userId, KILL_APP_REASON_PERMISSIONS_REVOKED);
+
+ // Critical, after this call all should never have the permission.
+ mSettings.writeRuntimePermissionsForUserLPr(userId, true);
+
+ return true;
}
}
@@ -2794,6 +2849,46 @@ public class PackageManagerService extends IPackageManager.Stub {
}
}
+ private void killSettingPackagesForUser(SettingBase sb, int userId, String reason) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ if (sb instanceof SharedUserSetting) {
+ SharedUserSetting sus = (SharedUserSetting) sb;
+ final int packageCount = sus.packages.size();
+ for (int i = 0; i < packageCount; i++) {
+ PackageSetting susPs = sus.packages.valueAt(i);
+ if (userId == UserHandle.USER_ALL) {
+ killApplication(susPs.pkg.packageName, susPs.appId, reason);
+ } else {
+ final int uid = UserHandle.getUid(userId, susPs.appId);
+ killUid(uid, reason);
+ }
+ }
+ } else if (sb instanceof PackageSetting) {
+ PackageSetting ps = (PackageSetting) sb;
+ if (userId == UserHandle.USER_ALL) {
+ killApplication(ps.pkg.packageName, ps.appId, reason);
+ } else {
+ final int uid = UserHandle.getUid(userId, ps.appId);
+ killUid(uid, reason);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ private static void killUid(int uid, String reason) {
+ IActivityManager am = ActivityManagerNative.getDefault();
+ if (am != null) {
+ try {
+ am.killUid(uid, reason);
+ } catch (RemoteException e) {
+ /* ignore - same process */
+ }
+ }
+ }
+
/**
* Compares two sets of signatures. Returns:
* <br />
@@ -3875,9 +3970,10 @@ public class PackageManagerService extends IPackageManager.Stub {
private void addPackageHoldingPermissions(ArrayList<PackageInfo> list, PackageSetting ps,
String[] permissions, boolean[] tmp, int flags, int userId) {
int numMatch = 0;
- final GrantedPermissions gp = ps.sharedUser != null ? ps.sharedUser : ps;
+ final PermissionsState permissionsState = ps.getPermissionsState();
for (int i=0; i<permissions.length; i++) {
- if (gp.grantedPermissions.contains(permissions[i])) {
+ final String permission = permissions[i];
+ if (permissionsState.hasPermission(permission, userId)) {
tmp[i] = true;
numMatch++;
} else {
@@ -6853,36 +6949,42 @@ public class PackageManagerService extends IPackageManager.Stub {
private void grantPermissionsLPw(PackageParser.Package pkg, boolean replace,
String packageOfInterest) {
+ // IMPORTANT: There are two types of permissions: install and runtime.
+ // Install time permissions are granted when the app is installed to
+ // all device users and users added in the future. Runtime permissions
+ // are granted at runtime explicitly to specific users. Normal and signature
+ // protected permissions are install time permissions. Dangerous permissions
+ // are install permissions if the app's target SDK is Lollipop MR1 or older,
+ // otherwise they are runtime permissions. This function does not manage
+ // runtime permissions except for the case an app targeting Lollipop MR1
+ // being upgraded to target a newer SDK, in which case dangerous permissions
+ // are transformed from install time to runtime ones.
+
final PackageSetting ps = (PackageSetting) pkg.mExtras;
if (ps == null) {
return;
}
- final GrantedPermissions gp = ps.sharedUser != null ? ps.sharedUser : ps;
- ArraySet<String> origPermissions = gp.grantedPermissions;
+
+ PermissionsState permissionsState = ps.getPermissionsState();
+ PermissionsState origPermissions = permissionsState;
+
boolean changedPermission = false;
if (replace) {
ps.permissionsFixed = false;
- if (gp == ps) {
- origPermissions = new ArraySet<String>(gp.grantedPermissions);
- gp.grantedPermissions.clear();
- gp.gids = mGlobalGids;
- }
+ origPermissions = new PermissionsState(permissionsState);
+ permissionsState.reset();
}
- if (gp.gids == null) {
- gp.gids = mGlobalGids;
- }
+ permissionsState.setGlobalGids(mGlobalGids);
final int N = pkg.requestedPermissions.size();
for (int i=0; i<N; i++) {
final String name = pkg.requestedPermissions.get(i);
- final boolean required = pkg.requestedPermissionsRequired.get(i);
final BasePermission bp = mSettings.mPermissions.get(name);
+
if (DEBUG_INSTALL) {
- if (gp != ps) {
- Log.i(TAG, "Package " + pkg.packageName + " checking " + name + ": " + bp);
- }
+ Log.i(TAG, "Package " + pkg.packageName + " checking " + name + ": " + bp);
}
if (bp == null || bp.packageSetting == null) {
@@ -6894,10 +6996,11 @@ public class PackageManagerService extends IPackageManager.Stub {
}
final String perm = bp.name;
- boolean allowed;
boolean allowedSig = false;
- if ((bp.protectionLevel&PermissionInfo.PROTECTION_FLAG_APPOP) != 0) {
- // Keep track of app op permissions.
+ int grant = GRANT_DENIED;
+
+ // Keep track of app op permissions.
+ if ((bp.protectionLevel & PermissionInfo.PROTECTION_FLAG_APPOP) != 0) {
ArraySet<String> pkgs = mAppOpPermissionPackages.get(bp.name);
if (pkgs == null) {
pkgs = new ArraySet<>();
@@ -6905,65 +7008,106 @@ public class PackageManagerService extends IPackageManager.Stub {
}
pkgs.add(pkg.packageName);
}
+
final int level = bp.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE;
- if (level == PermissionInfo.PROTECTION_NORMAL
- || level == PermissionInfo.PROTECTION_DANGEROUS) {
- // We grant a normal or dangerous permission if any of the following
- // are true:
- // 1) The permission is required
- // 2) The permission is optional, but was granted in the past
- // 3) The permission is optional, but was requested by an
- // app in /system (not /data)
- //
- // Otherwise, reject the permission.
- allowed = (required || origPermissions.contains(perm)
- || (isSystemApp(ps) && !isUpdatedSystemApp(ps)));
- } else if (bp.packageSetting == null) {
- // This permission is invalid; skip it.
- allowed = false;
- } else if (level == PermissionInfo.PROTECTION_SIGNATURE) {
- allowed = grantSignaturePermission(perm, pkg, bp, origPermissions);
- if (allowed) {
- allowedSig = true;
- }
- } else {
- allowed = false;
+ switch (level) {
+ case PermissionInfo.PROTECTION_NORMAL: {
+ // For all apps normal permissions are install time ones.
+ grant = GRANT_INSTALL;
+ } break;
+
+ case PermissionInfo.PROTECTION_DANGEROUS: {
+ if (!RUNTIME_PERMISSIONS_ENABLED
+ || pkg.applicationInfo.targetSdkVersion
+ <= Build.VERSION_CODES.LOLLIPOP_MR1) {
+ // For legacy apps dangerous permissions are install time ones.
+ grant = GRANT_INSTALL;
+ } else if ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+ // For modern system apps dangerous permissions are install time ones.
+ grant = GRANT_INSTALL;
+ } else {
+ if (origPermissions.hasInstallPermission(bp.name)) {
+ // For legacy apps that became modern, install becomes runtime.
+ grant = GRANT_UPGRADE;
+ } else if (replace) {
+ // For upgraded modern apps keep runtime permissions unchanged.
+ grant = GRANT_RUNTIME;
+ }
+ }
+ } break;
+
+ case PermissionInfo.PROTECTION_SIGNATURE: {
+ // For all apps signature permissions are install time ones.
+ allowedSig = grantSignaturePermission(perm, pkg, bp, origPermissions);
+ if (allowedSig) {
+ grant = GRANT_INSTALL;
+ }
+ } break;
}
+
if (DEBUG_INSTALL) {
- if (gp != ps) {
- Log.i(TAG, "Package " + pkg.packageName + " granting " + perm);
- }
+ Log.i(TAG, "Package " + pkg.packageName + " granting " + perm);
}
- if (allowed) {
+
+ if (grant != GRANT_DENIED) {
if (!isSystemApp(ps) && ps.permissionsFixed) {
// If this is an existing, non-system package, then
// we can't add any new permissions to it.
- if (!allowedSig && !gp.grantedPermissions.contains(perm)) {
+ if (!allowedSig && !origPermissions.hasInstallPermission(perm)) {
// Except... if this is a permission that was added
// to the platform (note: need to only do this when
// updating the platform).
- allowed = isNewPlatformPermissionForPackage(perm, pkg);
+ if (!isNewPlatformPermissionForPackage(perm, pkg)) {
+ grant = GRANT_DENIED;
+ }
}
}
- if (allowed) {
- if (!gp.grantedPermissions.contains(perm)) {
- changedPermission = true;
- gp.grantedPermissions.add(perm);
- gp.gids = appendInts(gp.gids, bp.gids);
- } else if (!ps.haveGids) {
- gp.gids = appendInts(gp.gids, bp.gids);
- }
- } else {
- if (packageOfInterest == null || packageOfInterest.equals(pkg.packageName)) {
- Slog.w(TAG, "Not granting permission " + perm
- + " to package " + pkg.packageName
- + " because it was previously installed without");
- }
+
+ switch (grant) {
+ case GRANT_INSTALL: {
+ // Grant an install permission.
+ if (permissionsState.grantInstallPermission(bp) !=
+ PermissionsState.PERMISSION_OPERATION_FAILURE) {
+ changedPermission = true;
+ }
+ } break;
+
+ case GRANT_RUNTIME: {
+ // Grant previously granted runtime permissions.
+ for (int userId : UserManagerService.getInstance().getUserIds()) {
+ if (origPermissions.hasRuntimePermission(bp.name, userId)) {
+ if (permissionsState.grantRuntimePermission(bp, userId) !=
+ PermissionsState.PERMISSION_OPERATION_FAILURE) {
+ changedPermission = true;
+ }
+ }
+ }
+ } break;
+
+ case GRANT_UPGRADE: {
+ // Grant runtime permissions for a previously held install permission.
+ permissionsState.revokeInstallPermission(bp);
+ for (int userId : UserManagerService.getInstance().getUserIds()) {
+ if (permissionsState.grantRuntimePermission(bp, userId) !=
+ PermissionsState.PERMISSION_OPERATION_FAILURE) {
+ changedPermission = true;
+ }
+ }
+ } break;
+
+ default: {
+ if (packageOfInterest == null
+ || packageOfInterest.equals(pkg.packageName)) {
+ Slog.w(TAG, "Not granting permission " + perm
+ + " to package " + pkg.packageName
+ + " because it was previously installed without");
+ }
+ } break;
}
} else {
- if (gp.grantedPermissions.remove(perm)) {
+ if (permissionsState.revokeInstallPermission(bp) !=
+ PermissionsState.PERMISSION_OPERATION_FAILURE) {
changedPermission = true;
- gp.gids = removeInts(gp.gids, bp.gids);
Slog.i(TAG, "Un-granting permission " + perm
+ " from package " + pkg.packageName
+ " (protectionLevel=" + bp.protectionLevel
@@ -6990,7 +7134,6 @@ public class PackageManagerService extends IPackageManager.Stub {
// changed.
ps.permissionsFixed = true;
}
- ps.haveGids = true;
}
private boolean isNewPlatformPermissionForPackage(String perm, PackageParser.Package pkg) {
@@ -7011,7 +7154,7 @@ public class PackageManagerService extends IPackageManager.Stub {
}
private boolean grantSignaturePermission(String perm, PackageParser.Package pkg,
- BasePermission bp, ArraySet<String> origPermissions) {
+ BasePermission bp, PermissionsState origPermissions) {
boolean allowed;
allowed = (compareSignatures(
bp.packageSetting.signatures.mSignatures, pkg.mSignatures)
@@ -7026,10 +7169,7 @@ public class PackageManagerService extends IPackageManager.Stub {
if (isUpdatedSystemApp(pkg)) {
final PackageSetting sysPs = mSettings
.getDisabledSystemPkgLPr(pkg.packageName);
- final GrantedPermissions origGp = sysPs.sharedUser != null
- ? sysPs.sharedUser : sysPs;
-
- if (origGp.grantedPermissions.contains(perm)) {
+ if (sysPs.getPermissionsState().hasInstallPermission(perm)) {
// If the original was granted this permission, we take
// that grant decision as read and propagate it to the
// update.
@@ -7063,7 +7203,7 @@ public class PackageManagerService extends IPackageManager.Stub {
& PermissionInfo.PROTECTION_FLAG_DEVELOPMENT) != 0) {
// For development permissions, a development permission
// is granted only if it was already granted.
- allowed = origPermissions.contains(perm);
+ allowed = origPermissions.hasInstallPermission(perm);
}
return allowed;
}
@@ -10821,11 +10961,26 @@ public class PackageManagerService extends IPackageManager.Stub {
mSettings.mKeySetManagerService.removeAppKeySetDataLPw(packageName);
outInfo.removedAppId = mSettings.removePackageLPw(packageName);
}
- if (deletedPs != null) {
- updatePermissionsLPw(deletedPs.name, null, 0);
- if (deletedPs.sharedUser != null) {
- // remove permissions associated with package
- mSettings.updateSharedUserPermsLPw(deletedPs, mGlobalGids);
+ updatePermissionsLPw(deletedPs.name, null, 0);
+ if (deletedPs.sharedUser != null) {
+ // Remove permissions associated with package. Since runtime
+ // permissions are per user we have to kill the removed package
+ // or packages running under the shared user of the removed
+ // package if revoking the permissions requested only by the removed
+ // package is successful and this causes a change in gids.
+ for (int userId : UserManagerService.getInstance().getUserIds()) {
+ final int userIdToKill = mSettings.updateSharedUserPermsLPw(deletedPs,
+ userId);
+ if (userIdToKill == userId) {
+ // If gids changed for this user, kill all affected packages.
+ killSettingPackagesForUser(deletedPs, userIdToKill,
+ KILL_APP_REASON_GIDS_CHANGED);
+ } else if (userIdToKill == UserHandle.USER_ALL) {
+ // If gids changed for all users, kill them all - done.
+ killSettingPackagesForUser(deletedPs, userIdToKill,
+ KILL_APP_REASON_GIDS_CHANGED);
+ break;
+ }
}
}
clearPackagePreferredActivitiesLPw(deletedPs.name, UserHandle.USER_ALL);
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 06d842a..889164c 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -57,8 +57,10 @@ final class PackageSetting extends PackageSettingBase {
+ " " + name + "/" + appId + "}";
}
- public int[] getGids() {
- return sharedUser != null ? sharedUser.gids : gids;
+ public PermissionsState getPermissionsState() {
+ return (sharedUser != null)
+ ? sharedUser.getPermissionsState()
+ : super.getPermissionsState();
}
public boolean isPrivileged() {
diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java
index 4b8ca42..9e8b3df 100644
--- a/services/core/java/com/android/server/pm/PackageSettingBase.java
+++ b/services/core/java/com/android/server/pm/PackageSettingBase.java
@@ -29,7 +29,7 @@ import java.io.File;
/**
* Settings base class for pending and resolved classes.
*/
-class PackageSettingBase extends GrantedPermissions {
+abstract class PackageSettingBase extends SettingBase {
/**
* Indicates the state of installation. Used by PackageManager to figure out
* incomplete installations. Say a package is being installed (the state is
@@ -93,7 +93,6 @@ class PackageSettingBase extends GrantedPermissions {
PackageSignatures signatures = new PackageSignatures();
boolean permissionsFixed;
- boolean haveGids;
PackageKeySetData keySetData = new PackageKeySetData();
@@ -147,7 +146,6 @@ class PackageSettingBase extends GrantedPermissions {
signatures = new PackageSignatures(base.signatures);
permissionsFixed = base.permissionsFixed;
- haveGids = base.haveGids;
userState.clear();
for (int i=0; i<base.userState.size(); i++) {
userState.put(base.userState.keyAt(i),
@@ -160,7 +158,6 @@ class PackageSettingBase extends GrantedPermissions {
installerPackageName = base.installerPackageName;
keySetData = new PackageKeySetData(base.keySetData);
-
}
void init(File codePath, File resourcePath, String legacyNativeLibraryPathString,
@@ -201,9 +198,7 @@ class PackageSettingBase extends GrantedPermissions {
* Make a shallow copy of this package settings.
*/
public void copyFrom(PackageSettingBase base) {
- grantedPermissions = base.grantedPermissions;
- gids = base.gids;
-
+ getPermissionsState().copyFrom(base.getPermissionsState());
primaryCpuAbiString = base.primaryCpuAbiString;
secondaryCpuAbiString = base.secondaryCpuAbiString;
cpuAbiOverrideString = base.cpuAbiOverrideString;
@@ -212,7 +207,6 @@ class PackageSettingBase extends GrantedPermissions {
lastUpdateTime = base.lastUpdateTime;
signatures = base.signatures;
permissionsFixed = base.permissionsFixed;
- haveGids = base.haveGids;
userState.clear();
for (int i=0; i<base.userState.size(); i++) {
userState.put(base.userState.keyAt(i), base.userState.valueAt(i));
diff --git a/services/core/java/com/android/server/pm/PermissionsState.java b/services/core/java/com/android/server/pm/PermissionsState.java
new file mode 100644
index 0000000..3e0e342
--- /dev/null
+++ b/services/core/java/com/android/server/pm/PermissionsState.java
@@ -0,0 +1,526 @@
+/*
+ * Copyright (C) 2015 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.server.pm;
+
+import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+
+import com.android.internal.util.ArrayUtils;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ * This class encapsulates the permissions for a package or a shared user.
+ * <p>
+ * There are two types of permissions: install (granted at installation)
+ * and runtime (granted at runtime). Install permissions are granted to
+ * all device users while runtime permissions are granted explicitly to
+ * specific users.
+ * </p>
+ * <p>
+ * The permissions are kept on a per device user basis. For example, an
+ * application may have some runtime permissions granted under the device
+ * owner but not granted under the secondary user.
+ * <p>
+ * This class is also responsible for keeping track of the Linux gids per
+ * user for a package or a shared user. The gids are computed as a set of
+ * the gids for all granted permissions' gids on a per user basis.
+ * </p>
+ */
+public final class PermissionsState {
+
+ /** The permission operation succeeded and no gids changed. */
+ public static final int PERMISSION_OPERATION_SUCCESS = 1;
+
+ /** The permission operation succeeded and gids changed. */
+ public static final int PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED = 2;
+
+ /** The permission operation failed. */
+ public static final int PERMISSION_OPERATION_FAILURE = 3;
+
+ private static final int[] USERS_ALL = {UserHandle.USER_ALL};
+
+ private static final int[] USERS_NONE = {};
+
+ private static final int[] NO_GIDS = {};
+
+ private ArrayMap<String, PermissionData> mPermissions;
+
+ private int[] mGlobalGids = NO_GIDS;
+
+ public PermissionsState() {
+ /* do nothing */
+ }
+
+ public PermissionsState(PermissionsState prototype) {
+ copyFrom(prototype);
+ }
+
+ /**
+ * Sets the global gids, applicable to all users.
+ *
+ * @param globalGids The global gids.
+ */
+ public void setGlobalGids(int[] globalGids) {
+ if (!ArrayUtils.isEmpty(globalGids)) {
+ mGlobalGids = Arrays.copyOf(globalGids, globalGids.length);
+ }
+ }
+
+ /**
+ * Initialized this instance from another one.
+ *
+ * @param other The other instance.
+ */
+ public void copyFrom(PermissionsState other) {
+ if (mPermissions != null) {
+ if (other.mPermissions == null) {
+ mPermissions = null;
+ } else {
+ mPermissions.clear();
+ }
+ }
+ if (other.mPermissions != null) {
+ if (mPermissions == null) {
+ mPermissions = new ArrayMap<>();
+ }
+ final int permissionCount = other.mPermissions.size();
+ for (int i = 0; i < permissionCount; i++) {
+ String name = other.mPermissions.keyAt(i);
+ PermissionData permissionData = other.mPermissions.valueAt(i);
+ mPermissions.put(name, new PermissionData(permissionData));
+ }
+ }
+
+ mGlobalGids = NO_GIDS;
+ if (other.mGlobalGids != NO_GIDS) {
+ mGlobalGids = Arrays.copyOf(other.mGlobalGids,
+ other.mGlobalGids.length);
+ }
+ }
+
+ /**
+ * Grant an install permission.
+ *
+ * @param permission The permission to grant.
+ * @return The operation result which is either {@link #PERMISSION_OPERATION_SUCCESS},
+ * or {@link #PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED}, or {@link
+ * #PERMISSION_OPERATION_FAILURE}.
+ */
+ public int grantInstallPermission(BasePermission permission) {
+ return grantPermission(permission, UserHandle.USER_ALL);
+ }
+
+ /**
+ * Revoke an install permission.
+ *
+ * @param permission The permission to revoke.
+ * @return The operation result which is either {@link #PERMISSION_OPERATION_SUCCESS},
+ * or {@link #PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED}, or {@link
+ * #PERMISSION_OPERATION_FAILURE}.
+ */
+ public int revokeInstallPermission(BasePermission permission) {
+ return revokePermission(permission, UserHandle.USER_ALL);
+ }
+
+ /**
+ * Grant a runtime permission.
+ *
+ * @param permission The permission to grant.
+ * @return The operation result which is either {@link #PERMISSION_OPERATION_SUCCESS},
+ * or {@link #PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED}, or {@link
+ * #PERMISSION_OPERATION_FAILURE}.
+ */
+ public int grantRuntimePermission(BasePermission permission, int userId) {
+ return grantPermission(permission, userId);
+ }
+
+ /**
+ * Revoke a runtime permission for a given device user.
+ *
+ * @param permission The permission to revoke.
+ * @param userId The device user id.
+ * @return The operation result which is either {@link #PERMISSION_OPERATION_SUCCESS},
+ * or {@link #PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED}, or {@link
+ * #PERMISSION_OPERATION_FAILURE}.
+ */
+ public int revokeRuntimePermission(BasePermission permission, int userId) {
+ return revokePermission(permission, userId);
+ }
+
+ /**
+ * Gets whether this state has a given permission, regardless if
+ * it is install time or runtime one.
+ *
+ * @param name The permission name.
+ * @return Whether this state has the permission.
+ */
+ public boolean hasPermission(String name) {
+ return mPermissions != null && mPermissions.get(name) != null;
+ }
+
+ /**
+ * Gets whether this state has a given runtime permission for a
+ * given device user id.
+ *
+ * @param name The permission name.
+ * @param userId The device user id.
+ * @return Whether this state has the permission.
+ */
+ public boolean hasRuntimePermission(String name, int userId) {
+ return !hasInstallPermission(name) && hasPermission(name, userId);
+ }
+
+ /**
+ * Gets whether this state has a given install permission.
+ *
+ * @param name The permission name.
+ * @return Whether this state has the permission.
+ */
+ public boolean hasInstallPermission(String name) {
+ return hasPermission(name, UserHandle.USER_ALL);
+ }
+
+ /**
+ * Revokes a permission for all users regardless if it is an install or
+ * a runtime permission.
+ *
+ * @param permission The permission to revoke.
+ * @return The operation result which is either {@link #PERMISSION_OPERATION_SUCCESS},
+ * or {@link #PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED}, or {@link
+ * #PERMISSION_OPERATION_FAILURE}.
+ */
+ public int revokePermission(BasePermission permission) {
+ if (!hasPermission(permission.name)) {
+ return PERMISSION_OPERATION_FAILURE;
+ }
+
+ int result = PERMISSION_OPERATION_SUCCESS;
+
+ PermissionData permissionData = mPermissions.get(permission.name);
+ if (permissionData.hasGids()) {
+ for (int userId : permissionData.getUserIds()) {
+ if (revokePermission(permission, userId)
+ == PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED) {
+ result = PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED;
+ break;
+ }
+ }
+ }
+
+ mPermissions.remove(permission.name);
+
+ return result;
+ }
+
+ /**
+ * Gets whether the state has a given permission for the specified
+ * user, regardless if this is an install or a runtime permission.
+ *
+ * @param name The permission name.
+ * @param userId The device user id.
+ * @return Whether the user has the permission.
+ */
+ public boolean hasPermission(String name, int userId) {
+ enforceValidUserId(userId);
+
+ if (mPermissions == null) {
+ return false;
+ }
+
+ PermissionData permissionData = mPermissions.get(name);
+ return permissionData != null && permissionData.hasUserId(userId);
+ }
+
+ /**
+ * Gets all permissions regardless if they are install or runtime.
+ *
+ * @return The permissions or an empty set.
+ */
+ public Set<String> getPermissions() {
+ if (mPermissions != null) {
+ return mPermissions.keySet();
+ }
+
+ return Collections.emptySet();
+ }
+
+ /**
+ * Gets all permissions for a given device user id regardless if they
+ * are install time or runtime permissions.
+ *
+ * @param userId The device user id.
+ * @return The permissions or an empty set.
+ */
+ public Set<String> getPermissions(int userId) {
+ enforceValidUserId(userId);
+
+ if (mPermissions == null) {
+ return Collections.emptySet();
+ }
+
+ Set<String> permissions = new ArraySet<>();
+
+ final int permissionCount = mPermissions.size();
+ for (int i = 0; i < permissionCount; i++) {
+ String permission = mPermissions.keyAt(i);
+ if (userId == UserHandle.USER_ALL) {
+ if (hasInstallPermission(permission)) {
+ permissions.add(permission);
+ }
+ } else {
+ if (hasRuntimePermission(permission, userId)) {
+ permissions.add(permission);
+ }
+ }
+ }
+
+ return permissions;
+ }
+
+ /**
+ * Gets all runtime permissions.
+ *
+ * @return The permissions or an empty set.
+ */
+ public Set<String> getRuntimePermissions(int userId) {
+ return getPermissions(userId);
+ }
+
+ /**
+ * Gets all install permissions.
+ *
+ * @return The permissions or an empty set.
+ */
+ public Set<String> getInstallPermissions() {
+ return getPermissions(UserHandle.USER_ALL);
+ }
+
+ /**
+ * Compute the Linux gids for a given device user from the permissions
+ * granted to this user. Note that these are computed to avoid additional
+ * state as they are rarely accessed.
+ *
+ * @param userId The device user id.
+ * @return The gids for the device user.
+ */
+ public int[] computeGids(int userId) {
+ enforceValidUserId(userId);
+
+ int[] gids = mGlobalGids;
+
+ if (mPermissions != null) {
+ final int permissionCount = mPermissions.size();
+ for (int i = 0; i < permissionCount; i++) {
+ String permission = mPermissions.keyAt(i);
+ if (!hasPermission(permission, userId)) {
+ continue;
+ }
+ PermissionData permissionData = mPermissions.valueAt(i);
+ final int[] permGids = permissionData.computeGids(userId);
+ if (permGids != NO_GIDS) {
+ gids = appendInts(gids, permGids);
+ }
+ }
+ }
+
+ return gids;
+ }
+
+ /**
+ * Compute the Linux gids for all device users from the permissions
+ * granted to these users.
+ *
+ * @return The gids for all device users.
+ */
+ public int[] computeGids() {
+ int[] gids = mGlobalGids;
+
+ for (int userId : UserManagerService.getInstance().getUserIds()) {
+ final int[] userGids = computeGids(userId);
+ gids = appendInts(gids, userGids);
+ }
+
+ return gids;
+ }
+
+ /**
+ * Resets the internal state of this object.
+ */
+ public void reset() {
+ mGlobalGids = NO_GIDS;
+ mPermissions = null;
+ }
+
+ private int grantPermission(BasePermission permission, int userId) {
+ if (hasPermission(permission.name, userId)) {
+ return PERMISSION_OPERATION_FAILURE;
+ }
+
+ final boolean hasGids = permission.hasGids();
+ final int[] oldGids = hasGids ? computeGids(userId) : NO_GIDS;
+
+ if (mPermissions == null) {
+ mPermissions = new ArrayMap<>();
+ }
+
+ PermissionData permissionData = mPermissions.get(permission.name);
+ if (permissionData == null) {
+ permissionData = new PermissionData(permission);
+ mPermissions.put(permission.name, permissionData);
+ }
+
+ if (!permissionData.addUserId(userId)) {
+ return PERMISSION_OPERATION_FAILURE;
+ }
+
+ if (hasGids) {
+ final int[] newGids = computeGids(userId);
+ if (oldGids.length != newGids.length) {
+ return PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED;
+ }
+ }
+
+ return PERMISSION_OPERATION_SUCCESS;
+ }
+
+ private int revokePermission(BasePermission permission, int userId) {
+ if (!hasPermission(permission.name, userId)) {
+ return PERMISSION_OPERATION_FAILURE;
+ }
+
+ final boolean hasGids = permission.hasGids();
+ final int[] oldGids = hasGids ? computeGids(userId) : NO_GIDS;
+
+ PermissionData permissionData = mPermissions.get(permission.name);
+
+ if (!permissionData.removeUserId(userId)) {
+ return PERMISSION_OPERATION_FAILURE;
+ }
+
+ if (permissionData.getUserIds() == USERS_NONE) {
+ mPermissions.remove(permission.name);
+ }
+
+ if (mPermissions.isEmpty()) {
+ mPermissions = null;
+ }
+
+ if (hasGids) {
+ final int[] newGids = computeGids(userId);
+ if (oldGids.length != newGids.length) {
+ return PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED;
+ }
+ }
+
+ return PERMISSION_OPERATION_SUCCESS;
+ }
+
+ private static int[] appendInts(int[] current, int[] added) {
+ if (current != null && added != null) {
+ for (int guid : added) {
+ current = ArrayUtils.appendInt(current, guid);
+ }
+ }
+ return current;
+ }
+
+ private static void enforceValidUserId(int userId) {
+ if (userId != UserHandle.USER_ALL && userId < 0) {
+ throw new IllegalArgumentException("Invalid userId:" + userId);
+ }
+ }
+
+ private static final class PermissionData {
+ private final BasePermission mPerm;
+ private int[] mUserIds = USERS_NONE;
+
+ public PermissionData(BasePermission perm) {
+ mPerm = perm;
+ }
+
+ public PermissionData(PermissionData other) {
+ this(other.mPerm);
+
+ if (other.mUserIds == USERS_ALL || other.mUserIds == USERS_NONE) {
+ mUserIds = other.mUserIds;
+ } else {
+ mUserIds = Arrays.copyOf(other.mUserIds, other.mUserIds.length);
+ }
+ }
+
+ public boolean hasGids() {
+ return mPerm.hasGids();
+ }
+
+ public int[] computeGids(int userId) {
+ return mPerm.computeGids(userId);
+ }
+
+ public int[] getUserIds() {
+ return mUserIds;
+ }
+
+ public boolean hasUserId(int userId) {
+ if (mUserIds == USERS_ALL) {
+ return true;
+ }
+
+ if (userId != UserHandle.USER_ALL) {
+ return ArrayUtils.contains(mUserIds, userId);
+ }
+
+ return false;
+ }
+
+ public boolean addUserId(int userId) {
+ if (hasUserId(userId)) {
+ return false;
+ }
+
+ if (userId == UserHandle.USER_ALL) {
+ mUserIds = USERS_ALL;
+ return true;
+ }
+
+ mUserIds = ArrayUtils.appendInt(mUserIds, userId);
+
+ return true;
+ }
+
+ public boolean removeUserId(int userId) {
+ if (!hasUserId(userId)) {
+ return false;
+ }
+
+ if (mUserIds == USERS_ALL) {
+ mUserIds = UserManagerService.getInstance().getUserIds();
+ }
+
+ mUserIds = ArrayUtils.removeInt(mUserIds, userId);
+
+ if (mUserIds.length == 0) {
+ mUserIds = USERS_NONE;
+ }
+
+ return true;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/pm/GrantedPermissions.java b/services/core/java/com/android/server/pm/SettingBase.java
index e87546c..d350c09 100644
--- a/services/core/java/com/android/server/pm/GrantedPermissions.java
+++ b/services/core/java/com/android/server/pm/SettingBase.java
@@ -19,27 +19,26 @@ package com.android.server.pm;
import android.content.pm.ApplicationInfo;
import android.util.ArraySet;
-class GrantedPermissions {
+abstract class SettingBase {
int pkgFlags;
int pkgPrivateFlags;
- ArraySet<String> grantedPermissions = new ArraySet<String>();
+ private final PermissionsState mPermissionsState;
- int[] gids;
-
- GrantedPermissions(int pkgFlags, int pkgPrivateFlags) {
+ SettingBase(int pkgFlags, int pkgPrivateFlags) {
setFlags(pkgFlags);
setPrivateFlags(pkgPrivateFlags);
+ mPermissionsState = new PermissionsState();
}
- @SuppressWarnings("unchecked")
- GrantedPermissions(GrantedPermissions base) {
+ SettingBase(SettingBase base) {
pkgFlags = base.pkgFlags;
- grantedPermissions = new ArraySet<>(base.grantedPermissions);
+ pkgPrivateFlags = base.pkgPrivateFlags;
+ mPermissionsState = new PermissionsState(base.mPermissionsState);
+ }
- if (base.gids != null) {
- gids = base.gids.clone();
- }
+ public PermissionsState getPermissionsState() {
+ return mPermissionsState;
}
void setFlags(int pkgFlags) {
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index b820d7e..82aa74a 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -25,6 +25,7 @@ import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
import static android.os.Process.SYSTEM_UID;
import static android.os.Process.PACKAGE_INFO_GID;
+import android.content.Context;
import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.pm.ResolveInfo;
@@ -33,17 +34,27 @@ import android.os.Binder;
import android.os.Build;
import android.os.Environment;
import android.os.FileUtils;
+import android.os.Handler;
+import android.os.Message;
import android.os.PatternMatcher;
import android.os.Process;
+import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
+import android.util.AtomicFile;
import android.util.LogPrinter;
+import android.util.SparseBooleanArray;
+import android.util.SparseLongArray;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.BackgroundThread;
+import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.JournaledFile;
import com.android.internal.util.XmlUtils;
import com.android.server.pm.PackageManagerService.DumpState;
+import java.io.FileNotFoundException;
import java.util.Collection;
import org.xmlpull.v1.XmlPullParser;
@@ -51,7 +62,6 @@ import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
import android.content.ComponentName;
-import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.ComponentInfo;
@@ -134,6 +144,8 @@ final class Settings {
private static final boolean DEBUG_STOPPED = false;
private static final boolean DEBUG_MU = false;
+ private static final String RUNTIME_PERMISSIONS_FILE_NAME = "runtime-permissions.xml";
+
private static final String TAG_READ_EXTERNAL_STORAGE = "read-external-storage";
private static final String ATTR_ENFORCEMENT = "enforcement";
@@ -142,6 +154,9 @@ final class Settings {
private static final String TAG_ENABLED_COMPONENTS = "enabled-components";
private static final String TAG_PACKAGE_RESTRICTIONS = "package-restrictions";
private static final String TAG_PACKAGE = "pkg";
+ private static final String TAG_SHARED_USER = "shared-user";
+ private static final String TAG_RUNTIME_PERMISSIONS = "runtime-permissions";
+ private static final String TAG_PERMISSIONS = "perms";
private static final String TAG_PERSISTENT_PREFERRED_ACTIVITIES =
"persistent-preferred-activities";
static final String TAG_CROSS_PROFILE_INTENT_FILTERS =
@@ -161,6 +176,11 @@ final class Settings {
private static final String ATTR_INSTALLED = "inst";
private static final String ATTR_BLOCK_UNINSTALL = "blockUninstall";
+ private final Object mLock;
+ private final Context mContext;
+
+ private final RuntimePermissionPersistence mRuntimePermissionsPersistence;
+
private final File mSettingsFilename;
private final File mBackupSettingsFilename;
private final File mPackageListFilename;
@@ -257,11 +277,16 @@ final class Settings {
public final KeySetManagerService mKeySetManagerService = new KeySetManagerService(mPackages);
- Settings(Context context) {
- this(context, Environment.getDataDirectory());
+ Settings(Context context, Object lock) {
+ this(context, Environment.getDataDirectory(), lock);
}
- Settings(Context context, File dataDir) {
+ Settings(Context context, File dataDir, Object lock) {
+ mContext = context;
+ mLock = lock;
+
+ mRuntimePermissionsPersistence = new RuntimePermissionPersistence(mLock);
+
mSystemDir = new File(dataDir, "system");
mSystemDir.mkdirs();
FileUtils.setPermissions(mSystemDir.toString(),
@@ -460,7 +485,7 @@ final class Settings {
bp.pendingInfo.packageName = newPkg;
}
bp.uid = 0;
- bp.gids = null;
+ bp.setGids(null, false);
}
}
}
@@ -468,9 +493,9 @@ final class Settings {
private PackageSetting getPackageLPw(String name, PackageSetting origPackage,
String realName, SharedUserSetting sharedUser, File codePath, File resourcePath,
- String legacyNativeLibraryPathString, String primaryCpuAbiString, String secondaryCpuAbiString,
- int vc, int pkgFlags, int pkgPrivateFlags, UserHandle installUser, boolean add,
- boolean allowInstall) {
+ String legacyNativeLibraryPathString, String primaryCpuAbiString,
+ String secondaryCpuAbiString, int vc, int pkgFlags, int pkgPrivateFlags,
+ UserHandle installUser, boolean add, boolean allowInstall) {
PackageSetting p = mPackages.get(name);
UserManagerService userManager = UserManagerService.getInstance();
if (p != null) {
@@ -589,7 +614,7 @@ final class Settings {
}
p.appId = dis.appId;
// Clone permissions
- p.grantedPermissions = new ArraySet<String>(dis.grantedPermissions);
+ p.getPermissionsState().copyFrom(dis.getPermissionsState());
// Clone component info
List<UserInfo> users = getAllUsers();
if (users != null) {
@@ -732,45 +757,60 @@ final class Settings {
* not in use by other permissions of packages in the
* shared user setting.
*/
- void updateSharedUserPermsLPw(PackageSetting deletedPs, int[] globalGids) {
+ int updateSharedUserPermsLPw(PackageSetting deletedPs, int userId) {
if ((deletedPs == null) || (deletedPs.pkg == null)) {
Slog.i(PackageManagerService.TAG,
"Trying to update info for null package. Just ignoring");
- return;
+ return UserHandle.USER_NULL;
}
+
// No sharedUserId
if (deletedPs.sharedUser == null) {
- return;
+ return UserHandle.USER_NULL;
}
+
SharedUserSetting sus = deletedPs.sharedUser;
+
// Update permissions
for (String eachPerm : deletedPs.pkg.requestedPermissions) {
- boolean used = false;
- if (!sus.grantedPermissions.contains(eachPerm)) {
+ BasePermission bp = mPermissions.get(eachPerm);
+ if (bp == null) {
continue;
}
- for (PackageSetting pkg:sus.packages) {
- if (pkg.pkg != null &&
- !pkg.pkg.packageName.equals(deletedPs.pkg.packageName) &&
- pkg.pkg.requestedPermissions.contains(eachPerm)) {
+
+ // If no user has the permission, nothing to remove.
+ if (!sus.getPermissionsState().hasPermission(bp.name, userId)) {
+ continue;
+ }
+
+ boolean used = false;
+
+ // Check if another package in the shared user needs the permission.
+ for (PackageSetting pkg : sus.packages) {
+ if (pkg.pkg != null
+ && !pkg.pkg.packageName.equals(deletedPs.pkg.packageName)
+ && pkg.pkg.requestedPermissions.contains(eachPerm)) {
used = true;
break;
}
}
+
if (!used) {
- // can safely delete this permission from list
- sus.grantedPermissions.remove(eachPerm);
- }
- }
- // Update gids
- int newGids[] = globalGids;
- for (String eachPerm : sus.grantedPermissions) {
- BasePermission bp = mPermissions.get(eachPerm);
- if (bp != null) {
- newGids = PackageManagerService.appendInts(newGids, bp.gids);
+ // Try to revoke as an install permission which is for all users.
+ if (sus.getPermissionsState().revokeInstallPermission(bp) ==
+ PermissionsState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED) {
+ return UserHandle.USER_ALL;
+ }
+
+ // Try to revoke as an install permission which is per user.
+ if (sus.getPermissionsState().revokeRuntimePermission(bp, userId) ==
+ PermissionsState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED) {
+ return userId;
+ }
}
}
- sus.gids = newGids;
+
+ return UserHandle.USER_NULL;
}
int removePackageLPw(String name) {
@@ -895,7 +935,17 @@ final class Settings {
}
private File getUserPackagesStateFile(int userId) {
- return new File(Environment.getUserSystemDirectory(userId), "package-restrictions.xml");
+ // TODO: Implement a cleaner solution when adding tests.
+ // This instead of Environment.getUserSystemDirectory(userId) to support testing.
+ File userDir = new File(new File(mSystemDir, "users"), Integer.toString(userId));
+ return new File(userDir, "package-restrictions.xml");
+ }
+
+ private File getUserRuntimePermissionsFile(int userId) {
+ // TODO: Implement a cleaner solution when adding tests.
+ // This instead of Environment.getUserSystemDirectory(userId) to support testing.
+ File userDir = new File(new File(mSystemDir, "users"), Integer.toString(userId));
+ return new File(userDir, RUNTIME_PERMISSIONS_FILE_NAME);
}
private File getUserPackagesStateBackupFile(int userId) {
@@ -912,15 +962,9 @@ final class Settings {
}
}
- void readAllUsersPackageRestrictionsLPr() {
- List<UserInfo> users = getAllUsers();
- if (users == null) {
- readPackageRestrictionsLPr(0);
- return;
- }
-
- for (UserInfo user : users) {
- readPackageRestrictionsLPr(user.id);
+ void writeAllRuntimePermissionsLPr() {
+ for (int userId : UserManagerService.getInstance().getUserIds()) {
+ mRuntimePermissionsPersistence.writePermissionsForUserAsyncLPr(userId);
}
}
@@ -1360,6 +1404,7 @@ final class Settings {
}
serializer.endTag(null, TAG_DISABLED_COMPONENTS);
}
+
serializer.endTag(null, TAG_PACKAGE);
}
}
@@ -1403,6 +1448,58 @@ final class Settings {
}
}
+ void readInstallPermissionsLPr(XmlPullParser parser,
+ PermissionsState permissionsState) throws IOException, XmlPullParserException {
+ int outerDepth = parser.getDepth();
+ int type;
+ while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG
+ || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG
+ || type == XmlPullParser.TEXT) {
+ continue;
+ }
+ String tagName = parser.getName();
+ if (tagName.equals(TAG_ITEM)) {
+ String name = parser.getAttributeValue(null, ATTR_NAME);
+
+ BasePermission bp = mPermissions.get(name);
+ if (bp == null) {
+ Slog.w(PackageManagerService.TAG, "Unknown permission: " + name);
+ XmlUtils.skipCurrentTag(parser);
+ continue;
+ }
+
+ if (permissionsState.grantInstallPermission(bp) ==
+ PermissionsState.PERMISSION_OPERATION_FAILURE) {
+ Slog.w(PackageManagerService.TAG, "Permission already added: " + name);
+ XmlUtils.skipCurrentTag(parser);
+ }
+ } else {
+ Slog.w(PackageManagerService.TAG, "Unknown element under <permissions>: "
+ + parser.getName());
+ XmlUtils.skipCurrentTag(parser);
+ }
+ }
+ }
+
+ void writePermissionsLPr(XmlSerializer serializer, Set<String> permissions)
+ throws IOException {
+ if (permissions.isEmpty()) {
+ return;
+ }
+
+ serializer.startTag(null, TAG_PERMISSIONS);
+
+ for (String permission : permissions) {
+ serializer.startTag(null, TAG_ITEM);
+ serializer.attribute(null, ATTR_NAME, permission);
+ serializer.endTag(null, TAG_ITEM);
+ }
+
+ serializer.endTag(null, TAG_PERMISSIONS);
+ }
+
// Note: assumed "stopped" field is already cleared in all packages.
// Legacy reader, used to read in the old file format after an upgrade. Not used after that.
void readStoppedLPw() {
@@ -1594,13 +1691,7 @@ final class Settings {
serializer.attribute(null, "userId",
Integer.toString(usr.userId));
usr.signatures.writeXml(serializer, "sigs", mPastSignatures);
- serializer.startTag(null, "perms");
- for (String name : usr.grantedPermissions) {
- serializer.startTag(null, TAG_ITEM);
- serializer.attribute(null, ATTR_NAME, name);
- serializer.endTag(null, TAG_ITEM);
- }
- serializer.endTag(null, "perms");
+ writePermissionsLPr(serializer, usr.getPermissionsState().getInstallPermissions());
serializer.endTag(null, "shared-user");
}
@@ -1614,7 +1705,7 @@ final class Settings {
serializer.endTag(null, "cleaning-package");
}
}
-
+
if (mRenamedPackages.size() > 0) {
for (Map.Entry<String, String> e : mRenamedPackages.entrySet()) {
serializer.startTag(null, "renamed-package");
@@ -1623,7 +1714,7 @@ final class Settings {
serializer.endTag(null, "renamed-package");
}
}
-
+
mKeySetManagerService.writeKeySetManagerServiceLPr(serializer);
serializer.endTag(null, "packages");
@@ -1662,7 +1753,7 @@ final class Settings {
final ApplicationInfo ai = pkg.pkg.applicationInfo;
final String dataPath = ai.dataDir;
final boolean isDebug = (ai.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
- final int[] gids = pkg.getGids();
+ final int[] gids = pkg.getPermissionsState().computeGids();
// Avoid any application that has a space in its path.
if (dataPath.indexOf(" ") >= 0)
@@ -1718,6 +1809,8 @@ final class Settings {
}
writeAllUsersPackageRestrictionsLPr();
+
+ writeAllRuntimePermissionsLPr();
return;
} catch(XmlPullParserException e) {
@@ -1770,26 +1863,12 @@ final class Settings {
} else {
serializer.attribute(null, "sharedUserId", Integer.toString(pkg.appId));
}
- serializer.startTag(null, "perms");
+
+ // If this is a shared user, the permissions will be written there.
if (pkg.sharedUser == null) {
- // If this is a shared user, the permissions will
- // be written there. We still need to write an
- // empty permissions list so permissionsFixed will
- // be set.
- for (final String name : pkg.grantedPermissions) {
- BasePermission bp = mPermissions.get(name);
- if (bp != null) {
- // We only need to write signature or system permissions but
- // this wont
- // match the semantics of grantedPermissions. So write all
- // permissions.
- serializer.startTag(null, TAG_ITEM);
- serializer.attribute(null, ATTR_NAME, name);
- serializer.endTag(null, TAG_ITEM);
- }
- }
+ writePermissionsLPr(serializer, pkg.getPermissionsState().getInstallPermissions());
}
- serializer.endTag(null, "perms");
+
serializer.endTag(null, "updated-package");
}
@@ -1840,19 +1919,7 @@ final class Settings {
}
pkg.signatures.writeXml(serializer, "sigs", mPastSignatures);
if ((pkg.pkgFlags & ApplicationInfo.FLAG_SYSTEM) == 0) {
- serializer.startTag(null, "perms");
- if (pkg.sharedUser == null) {
- // If this is a shared user, the permissions will
- // be written there. We still need to write an
- // empty permissions list so permissionsFixed will
- // be set.
- for (final String name : pkg.grantedPermissions) {
- serializer.startTag(null, TAG_ITEM);
- serializer.attribute(null, ATTR_NAME, name);
- serializer.endTag(null, TAG_ITEM);
- }
- }
- serializer.endTag(null, "perms");
+ writePermissionsLPr(serializer, pkg.getPermissionsState().getInstallPermissions());
}
writeSigningKeySetsLPr(serializer, pkg.keySetData);
@@ -2161,9 +2228,11 @@ final class Settings {
} else {
if (users == null) {
readPackageRestrictionsLPr(0);
+ mRuntimePermissionsPersistence.readStateForUserSyncLPr(UserHandle.USER_OWNER);
} else {
for (UserInfo user : users) {
readPackageRestrictionsLPr(user.id);
+ mRuntimePermissionsPersistence.readStateForUserSyncLPr(user.id);
}
}
}
@@ -2537,7 +2606,7 @@ final class Settings {
final String ptype = parser.getAttributeValue(null, "type");
if (name != null && sourcePackage != null) {
final boolean dynamic = "dynamic".equals(ptype);
- final BasePermission bp = new BasePermission(name, sourcePackage,
+ final BasePermission bp = new BasePermission(name.intern(), sourcePackage,
dynamic ? BasePermission.TYPE_DYNAMIC : BasePermission.TYPE_NORMAL);
bp.protectionLevel = readInt(parser, null, "protection",
PermissionInfo.PROTECTION_NORMAL);
@@ -2643,6 +2712,7 @@ final class Settings {
String sharedIdStr = parser.getAttributeValue(null, "sharedUserId");
ps.appId = sharedIdStr != null ? Integer.parseInt(sharedIdStr) : 0;
}
+
int outerDepth = parser.getDepth();
int type;
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
@@ -2651,9 +2721,8 @@ final class Settings {
continue;
}
- String tagName = parser.getName();
- if (tagName.equals("perms")) {
- readGrantedPermissionsLPw(parser, ps.grantedPermissions);
+ if (parser.getName().equals(TAG_PERMISSIONS)) {
+ readInstallPermissionsLPr(parser, ps.getPermissionsState());
} else {
PackageManagerService.reportSettingsProblem(Log.WARN,
"Unknown element under <updated-package>: " + parser.getName());
@@ -2711,7 +2780,7 @@ final class Settings {
if (primaryCpuAbiString == null && legacyCpuAbiString != null) {
primaryCpuAbiString = legacyCpuAbiString;
}
-;
+
version = parser.getAttributeValue(null, "version");
if (version != null) {
try {
@@ -2902,7 +2971,6 @@ final class Settings {
packageSetting.installStatus = PackageSettingBase.PKG_INSTALL_COMPLETE;
}
}
-
int outerDepth = parser.getDepth();
int type;
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
@@ -2919,8 +2987,9 @@ final class Settings {
readEnabledComponentsLPw(packageSetting, parser, 0);
} else if (tagName.equals("sigs")) {
packageSetting.signatures.readXml(parser, mPastSignatures);
- } else if (tagName.equals("perms")) {
- readGrantedPermissionsLPw(parser, packageSetting.grantedPermissions);
+ } else if (tagName.equals(TAG_PERMISSIONS)) {
+ readInstallPermissionsLPr(parser,
+ packageSetting.getPermissionsState());
packageSetting.permissionsFixed = true;
} else if (tagName.equals("proper-signing-keyset")) {
long id = Long.parseLong(parser.getAttributeValue(null, "identifier"));
@@ -3039,7 +3108,6 @@ final class Settings {
"Error in package manager settings: package " + name + " has bad userId "
+ idStr + " at " + parser.getPositionDescription());
}
- ;
if (su != null) {
int outerDepth = parser.getDepth();
@@ -3054,47 +3122,18 @@ final class Settings {
if (tagName.equals("sigs")) {
su.signatures.readXml(parser, mPastSignatures);
} else if (tagName.equals("perms")) {
- readGrantedPermissionsLPw(parser, su.grantedPermissions);
+ readInstallPermissionsLPr(parser, su.getPermissionsState());
} else {
PackageManagerService.reportSettingsProblem(Log.WARN,
"Unknown element under <shared-user>: " + parser.getName());
XmlUtils.skipCurrentTag(parser);
}
}
-
} else {
XmlUtils.skipCurrentTag(parser);
}
}
- private void readGrantedPermissionsLPw(XmlPullParser parser, ArraySet<String> outPerms)
- throws IOException, XmlPullParserException {
- int outerDepth = parser.getDepth();
- int type;
- while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
- && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
- if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
- continue;
- }
-
- String tagName = parser.getName();
- if (tagName.equals(TAG_ITEM)) {
- String name = parser.getAttributeValue(null, ATTR_NAME);
- if (name != null) {
- outPerms.add(name.intern());
- } else {
- PackageManagerService.reportSettingsProblem(Log.WARN,
- "Error in package manager settings: <perms> has" + " no name at "
- + parser.getPositionDescription());
- }
- } else {
- PackageManagerService.reportSettingsProblem(Log.WARN,
- "Unknown element under <perms>: " + parser.getName());
- }
- XmlUtils.skipCurrentTag(parser);
- }
- }
-
void createNewUserLILPw(PackageManagerService service, Installer installer,
int userHandle, File path) {
path.mkdir();
@@ -3126,6 +3165,8 @@ final class Settings {
file = getUserPackagesStateBackupFile(userId);
file.delete();
removeCrossProfileIntentFiltersLPw(userId);
+
+ mRuntimePermissionsPersistence.onUserRemoved(userId);
}
void removeCrossProfileIntentFiltersLPw(int userId) {
@@ -3317,7 +3358,7 @@ final class Settings {
return null;
}
- static final void printFlags(PrintWriter pw, int val, Object[] spec) {
+ static void printFlags(PrintWriter pw, int val, Object[] spec) {
pw.print("[ ");
for (int i=0; i<spec.length; i+=2) {
int mask = (Integer)spec[i];
@@ -3414,8 +3455,8 @@ final class Settings {
pw.println(ps.name);
}
- pw.print(prefix); pw.print(" userId="); pw.print(ps.appId);
- pw.print(" gids="); pw.println(PackageManagerService.arrayToString(ps.gids));
+ pw.print(prefix); pw.print(" userId="); pw.println(ps.appId);
+
if (ps.sharedUser != null) {
pw.print(prefix); pw.print(" sharedUser="); pw.println(ps.sharedUser);
}
@@ -3525,10 +3566,15 @@ final class Settings {
}
pw.print(prefix); pw.print(" signatures="); pw.println(ps.signatures);
pw.print(prefix); pw.print(" permissionsFixed="); pw.print(ps.permissionsFixed);
- pw.print(" haveGids="); pw.print(ps.haveGids);
pw.print(" installStatus="); pw.println(ps.installStatus);
pw.print(prefix); pw.print(" pkgFlags="); printFlags(pw, ps.pkgFlags, FLAG_DUMP_SPEC);
pw.println();
+
+ if (ps.sharedUser == null) {
+ PermissionsState permissionsState = ps.getPermissionsState();
+ dumpInstallPermissionsLPr(pw, prefix + " ", permissionsState);
+ }
+
for (UserInfo user : users) {
pw.print(prefix); pw.print(" User "); pw.print(user.id); pw.print(": ");
pw.print(" installed=");
@@ -3546,6 +3592,14 @@ final class Settings {
pw.print(prefix); pw.print(" lastDisabledCaller: ");
pw.println(lastDisabledAppCaller);
}
+
+ if (ps.sharedUser == null) {
+ PermissionsState permissionsState = ps.getPermissionsState();
+ dumpGidsLPr(pw, prefix + " ", permissionsState.computeGids(user.id));
+ dumpRuntimePermissionsLPr(pw, prefix + " ", permissionsState
+ .getRuntimePermissions(user.id));
+ }
+
ArraySet<String> cmp = ps.getDisabledComponents(user.id);
if (cmp != null && cmp.size() > 0) {
pw.print(prefix); pw.println(" disabledComponents:");
@@ -3561,12 +3615,6 @@ final class Settings {
}
}
}
- if (ps.grantedPermissions.size() > 0) {
- pw.print(prefix); pw.println(" grantedPermissions:");
- for (String s : ps.grantedPermissions) {
- pw.print(prefix); pw.print(" "); pw.println(s);
- }
- }
}
void dumpPackagesLPr(PrintWriter pw, String packageName, DumpState dumpState, boolean checkin) {
@@ -3652,7 +3700,8 @@ final class Settings {
pw.println("):");
pw.print(" sourcePackage="); pw.println(p.sourcePackage);
pw.print(" uid="); pw.print(p.uid);
- pw.print(" gids="); pw.print(PackageManagerService.arrayToString(p.gids));
+ pw.print(" gids="); pw.print(Arrays.toString(
+ p.computeGids(UserHandle.USER_OWNER)));
pw.print(" type="); pw.print(p.type);
pw.print(" prot=");
pw.println(PermissionInfo.protectionToString(p.protectionLevel));
@@ -3688,14 +3737,21 @@ final class Settings {
pw.print("] (");
pw.print(Integer.toHexString(System.identityHashCode(su)));
pw.println("):");
- pw.print(" userId=");
- pw.print(su.userId);
- pw.print(" gids=");
- pw.println(PackageManagerService.arrayToString(su.gids));
- pw.println(" grantedPermissions:");
- for (String s : su.grantedPermissions) {
- pw.print(" ");
- pw.println(s);
+
+ String prefix = " ";
+ pw.print(prefix); pw.print("userId="); pw.println(su.userId);
+
+ PermissionsState permissionsState = su.getPermissionsState();
+ dumpInstallPermissionsLPr(pw, prefix, permissionsState);
+
+ for (int userId : UserManagerService.getInstance().getUserIds()) {
+ final int[] gids = permissionsState.computeGids(userId);
+ Set<String> permissions = permissionsState.getRuntimePermissions(userId);
+ if (!ArrayUtils.isEmpty(gids) || !permissions.isEmpty()) {
+ pw.print(prefix); pw.print("User "); pw.print(userId); pw.println(": ");
+ dumpGidsLPr(pw, prefix + " ", gids);
+ dumpRuntimePermissionsLPr(pw, prefix + " ", permissions);
+ }
}
} else {
pw.print("suid,"); pw.print(su.userId); pw.print(","); pw.println(su.name);
@@ -3730,4 +3786,373 @@ final class Settings {
pw.print("]");
}
}
+
+ void dumpGidsLPr(PrintWriter pw, String prefix, int[] gids) {
+ if (!ArrayUtils.isEmpty(gids)) {
+ pw.print(prefix); pw.print("gids="); pw.println(
+ PackageManagerService.arrayToString(gids));
+ }
+ }
+
+ void dumpRuntimePermissionsLPr(PrintWriter pw, String prefix, Set<String> permissions) {
+ if (!permissions.isEmpty()) {
+ pw.print(prefix); pw.println("runtime permissions:");
+ for (String permission : permissions) {
+ pw.print(prefix); pw.print(" "); pw.println(permission);
+ }
+ }
+ }
+
+ void dumpInstallPermissionsLPr(PrintWriter pw, String prefix,
+ PermissionsState permissionsState) {
+ Set<String> permissions = permissionsState.getInstallPermissions();
+ if (!permissions.isEmpty()) {
+ pw.print(prefix); pw.println("install permissions:");
+ for (String permission : permissions) {
+ pw.print(prefix); pw.print(" "); pw.println(permission);
+ }
+ }
+ }
+
+ public void writeRuntimePermissionsForUserLPr(int userId, boolean sync) {
+ if (sync) {
+ mRuntimePermissionsPersistence.writePermissionsForUserSyncLPr(userId);
+ } else {
+ mRuntimePermissionsPersistence.writePermissionsForUserAsyncLPr(userId);
+ }
+ }
+
+ private final class RuntimePermissionPersistence {
+ private static final long WRITE_PERMISSIONS_DELAY_MILLIS = 200;
+
+ private static final long MAX_WRITE_PERMISSIONS_DELAY_MILLIS = 2000;
+
+ private final Handler mHandler = new MyHandler();
+
+ private final Object mLock;
+
+ @GuardedBy("mLock")
+ private SparseBooleanArray mWriteScheduled = new SparseBooleanArray();
+
+ @GuardedBy("mLock")
+ private SparseLongArray mLastNotWrittenMutationTimesMillis = new SparseLongArray();
+
+ public RuntimePermissionPersistence(Object lock) {
+ mLock = lock;
+ }
+
+ public void writePermissionsForUserSyncLPr(int userId) {
+ mHandler.removeMessages(userId);
+ writePermissionsSync(userId);
+ }
+
+ public void writePermissionsForUserAsyncLPr(int userId) {
+ final long currentTimeMillis = SystemClock.uptimeMillis();
+
+ if (mWriteScheduled.get(userId)) {
+ mHandler.removeMessages(userId);
+
+ // If enough time passed, write without holding off anymore.
+ final long lastNotWrittenMutationTimeMillis = mLastNotWrittenMutationTimesMillis
+ .get(userId);
+ final long timeSinceLastNotWrittenMutationMillis = currentTimeMillis
+ - lastNotWrittenMutationTimeMillis;
+ if (timeSinceLastNotWrittenMutationMillis >= MAX_WRITE_PERMISSIONS_DELAY_MILLIS) {
+ mHandler.obtainMessage(userId).sendToTarget();
+ return;
+ }
+
+ // Hold off a bit more as settings are frequently changing.
+ final long maxDelayMillis = Math.max(lastNotWrittenMutationTimeMillis
+ + MAX_WRITE_PERMISSIONS_DELAY_MILLIS - currentTimeMillis, 0);
+ final long writeDelayMillis = Math.min(WRITE_PERMISSIONS_DELAY_MILLIS,
+ maxDelayMillis);
+
+ Message message = mHandler.obtainMessage(userId);
+ mHandler.sendMessageDelayed(message, writeDelayMillis);
+ } else {
+ mLastNotWrittenMutationTimesMillis.put(userId, currentTimeMillis);
+ Message message = mHandler.obtainMessage(userId);
+ mHandler.sendMessageDelayed(message, WRITE_PERMISSIONS_DELAY_MILLIS);
+ mWriteScheduled.put(userId, true);
+ }
+ }
+
+ private void writePermissionsSync(int userId) {
+ AtomicFile destination = new AtomicFile(getUserRuntimePermissionsFile(userId));
+
+ ArrayMap<String, Set<String>> permissionsForPackage = new ArrayMap<>();
+ ArrayMap<String, Set<String>> permissionsForSharedUser = new ArrayMap<>();
+
+ synchronized (mLock) {
+ mWriteScheduled.delete(userId);
+
+ final int packageCount = mPackages.size();
+ for (int i = 0; i < packageCount; i++) {
+ String packageName = mPackages.keyAt(i);
+ PackageSetting packageSetting = mPackages.valueAt(i);
+ if (packageSetting.sharedUser == null) {
+ PermissionsState permissionsState = packageSetting.getPermissionsState();
+ Set<String> permissions = permissionsState.getRuntimePermissions(userId);
+ if (!permissions.isEmpty()) {
+ permissionsForPackage.put(packageName, permissions);
+ }
+ }
+ }
+
+ final int sharedUserCount = mSharedUsers.size();
+ for (int i = 0; i < sharedUserCount; i++) {
+ String sharedUserName = mSharedUsers.keyAt(i);
+ SharedUserSetting sharedUser = mSharedUsers.valueAt(i);
+ PermissionsState permissionsState = sharedUser.getPermissionsState();
+ Set<String> permissions = permissionsState.getRuntimePermissions(userId);
+ if (!permissions.isEmpty()) {
+ permissionsForSharedUser.put(sharedUserName, permissions);
+ }
+ }
+ }
+
+ FileOutputStream out = null;
+ try {
+ out = destination.startWrite();
+
+ XmlSerializer serializer = Xml.newSerializer();
+ serializer.setOutput(out, "utf-8");
+ serializer.setFeature(
+ "http://xmlpull.org/v1/doc/features.html#indent-output", true);
+ serializer.startDocument(null, true);
+ serializer.startTag(null, TAG_RUNTIME_PERMISSIONS);
+
+ final int packageCount = permissionsForPackage.size();
+ for (int i = 0; i < packageCount; i++) {
+ String packageName = permissionsForPackage.keyAt(i);
+ Set<String> permissions = permissionsForPackage.valueAt(i);
+ serializer.startTag(null, TAG_PACKAGE);
+ serializer.attribute(null, ATTR_NAME, packageName);
+ writePermissions(serializer, permissions);
+ serializer.endTag(null, TAG_PACKAGE);
+ }
+
+ final int sharedUserCount = permissionsForSharedUser.size();
+ for (int i = 0; i < sharedUserCount; i++) {
+ String packageName = permissionsForSharedUser.keyAt(i);
+ Set<String> permissions = permissionsForSharedUser.valueAt(i);
+ serializer.startTag(null, TAG_SHARED_USER);
+ serializer.attribute(null, ATTR_NAME, packageName);
+ writePermissions(serializer, permissions);
+ serializer.endTag(null, TAG_SHARED_USER);
+ }
+
+ serializer.endTag(null, TAG_RUNTIME_PERMISSIONS);
+ serializer.endDocument();
+ destination.finishWrite(out);
+ } catch (IOException e) {
+ Slog.wtf(PackageManagerService.TAG,
+ "Failed to write settings, restoring backup", e);
+ destination.failWrite(out);
+ } finally {
+ IoUtils.closeQuietly(out);
+ }
+ }
+
+ private void onUserRemoved(int userId) {
+ // Make sure we do not
+ mHandler.removeMessages(userId);
+
+ for (SettingBase sb : mPackages.values()) {
+ revokeRuntimePermissions(sb, userId);
+ }
+
+ for (SettingBase sb : mSharedUsers.values()) {
+ revokeRuntimePermissions(sb, userId);
+ }
+ }
+
+ private void revokeRuntimePermissions(SettingBase sb, int userId) {
+ PermissionsState permissionsState = sb.getPermissionsState();
+ for (String permission : permissionsState.getRuntimePermissions(userId)) {
+ BasePermission bp = mPermissions.get(permission);
+ if (bp != null) {
+ permissionsState.revokeRuntimePermission(bp, userId);
+ }
+ }
+ }
+
+ public void readStateForUserSyncLPr(int userId) {
+ File permissionsFile = getUserRuntimePermissionsFile(userId);
+ if (!permissionsFile.exists()) {
+ return;
+ }
+
+ FileInputStream in;
+ try {
+ in = new FileInputStream(permissionsFile);
+ } catch (FileNotFoundException fnfe) {
+ Slog.i(PackageManagerService.TAG, "No permissions state");
+ return;
+ }
+
+ try {
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(in, null);
+ parseRuntimePermissionsLPr(parser, userId);
+ } catch (XmlPullParserException | IOException ise) {
+ throw new IllegalStateException("Failed parsing permissions file: "
+ + permissionsFile , ise);
+ } finally {
+ IoUtils.closeQuietly(in);
+ }
+ }
+
+ private void parseRuntimePermissionsLPr(XmlPullParser parser, int userId)
+ throws IOException, XmlPullParserException {
+ parser.next();
+ skipEmptyTextTags(parser);
+ if (!accept(parser, XmlPullParser.START_TAG, TAG_RUNTIME_PERMISSIONS)) {
+ return;
+ }
+
+ parser.next();
+
+ while (parsePackageLPr(parser, userId)
+ || parseSharedUserLPr(parser, userId)) {
+ parser.next();
+ }
+
+ skipEmptyTextTags(parser);
+ expect(parser, XmlPullParser.END_TAG, TAG_RUNTIME_PERMISSIONS);
+ }
+
+ private boolean parsePackageLPr(XmlPullParser parser, int userId)
+ throws IOException, XmlPullParserException {
+ skipEmptyTextTags(parser);
+ if (!accept(parser, XmlPullParser.START_TAG, TAG_PACKAGE)) {
+ return false;
+ }
+
+ String name = parser.getAttributeValue(null, ATTR_NAME);
+
+ parser.next();
+
+ PackageSetting ps = mPackages.get(name);
+ if (ps != null) {
+ while (parsePermissionLPr(parser, ps.getPermissionsState(), userId)) {
+ parser.next();
+ }
+ }
+
+ skipEmptyTextTags(parser);
+ expect(parser, XmlPullParser.END_TAG, TAG_PACKAGE);
+
+ return true;
+ }
+
+ private boolean parseSharedUserLPr(XmlPullParser parser, int userId)
+ throws IOException, XmlPullParserException {
+ skipEmptyTextTags(parser);
+ if (!accept(parser, XmlPullParser.START_TAG, TAG_SHARED_USER)) {
+ return false;
+ }
+
+ String name = parser.getAttributeValue(null, ATTR_NAME);
+
+ parser.next();
+
+ SharedUserSetting sus = mSharedUsers.get(name);
+ if (sus != null) {
+ while (parsePermissionLPr(parser, sus.getPermissionsState(), userId)) {
+ parser.next();
+ }
+ }
+
+ skipEmptyTextTags(parser);
+ expect(parser, XmlPullParser.END_TAG, TAG_SHARED_USER);
+
+ return true;
+ }
+
+ private boolean parsePermissionLPr(XmlPullParser parser, PermissionsState permissionsState,
+ int userId) throws IOException, XmlPullParserException {
+ skipEmptyTextTags(parser);
+ if (!accept(parser, XmlPullParser.START_TAG, TAG_ITEM)) {
+ return false;
+ }
+
+ String name = parser.getAttributeValue(null, ATTR_NAME);
+
+ parser.next();
+
+ BasePermission bp = mPermissions.get(name);
+ if (bp != null) {
+ if (permissionsState.grantRuntimePermission(bp, userId) ==
+ PermissionsState.PERMISSION_OPERATION_FAILURE) {
+ Slog.w(PackageManagerService.TAG, "Duplicate permission:" + name);
+ }
+ } else {
+ Slog.w(PackageManagerService.TAG, "Unknown permission:" + name);
+ }
+
+ skipEmptyTextTags(parser);
+ expect(parser, XmlPullParser.END_TAG, TAG_ITEM);
+
+ return true;
+ }
+
+ private void expect(XmlPullParser parser, int type, String tag)
+ throws IOException, XmlPullParserException {
+ if (!accept(parser, type, tag)) {
+ throw new XmlPullParserException("Expected event: " + type
+ + " and tag: " + tag + " but got event: " + parser.getEventType()
+ + " and tag:" + parser.getName());
+ }
+ }
+
+ private void skipEmptyTextTags(XmlPullParser parser)
+ throws IOException, XmlPullParserException {
+ while (accept(parser, XmlPullParser.TEXT, null)
+ && parser.isWhitespace()) {
+ parser.next();
+ }
+ }
+
+ private boolean accept(XmlPullParser parser, int type, String tag)
+ throws IOException, XmlPullParserException {
+ if (parser.getEventType() != type) {
+ return false;
+ }
+ if (tag != null) {
+ if (!tag.equals(parser.getName())) {
+ return false;
+ }
+ } else if (parser.getName() != null) {
+ return false;
+ }
+ return true;
+ }
+
+ private void writePermissions(XmlSerializer serializer, Set<String> permissions)
+ throws IOException {
+ for (String permission : permissions) {
+ serializer.startTag(null, TAG_ITEM);
+ serializer.attribute(null, ATTR_NAME, permission);
+ serializer.endTag(null, TAG_ITEM);
+ }
+ }
+
+ private final class MyHandler extends Handler {
+ public MyHandler() {
+ super(BackgroundThread.getHandler().getLooper());
+ }
+
+ @Override
+ public void handleMessage(Message message) {
+ final int userId = message.what;
+ Runnable callback = (Runnable) message.obj;
+ writePermissionsSync(userId);
+ if (callback != null) {
+ callback.run();
+ }
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/pm/SharedUserSetting.java b/services/core/java/com/android/server/pm/SharedUserSetting.java
index d95739c..06e020a 100644
--- a/services/core/java/com/android/server/pm/SharedUserSetting.java
+++ b/services/core/java/com/android/server/pm/SharedUserSetting.java
@@ -21,7 +21,7 @@ import android.util.ArraySet;
/**
* Settings data for a particular shared user ID we know about.
*/
-final class SharedUserSetting extends GrantedPermissions {
+final class SharedUserSetting extends SettingBase {
final String name;
int userId;
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index d8cb240..be6550c 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -4025,7 +4025,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
private void offsetInputMethodWindowLw(WindowState win) {
- int top = win.getDisplayFrameLw().top;
+ int top = Math.max(win.getDisplayFrameLw().top, win.getContentFrameLw().top);
top += win.getGivenContentInsetsLw().top;
if (mContentBottom > top) {
mContentBottom = top;
@@ -4044,7 +4044,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
private void offsetVoiceInputWindowLw(WindowState win) {
- int top = win.getDisplayFrameLw().top;
+ int top = Math.max(win.getDisplayFrameLw().top, win.getContentFrameLw().top);
top += win.getGivenContentInsetsLw().top;
if (mVoiceContentBottom > top) {
mVoiceContentBottom = top;
@@ -5956,7 +5956,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
intent = mHomeIntent;
}
- startActivityAsUser(mHomeIntent, UserHandle.CURRENT);
+ startActivityAsUser(intent, UserHandle.CURRENT);
}
/**
diff --git a/services/core/java/com/android/server/power/DeviceIdleController.java b/services/core/java/com/android/server/power/DeviceIdleController.java
new file mode 100644
index 0000000..dd00446
--- /dev/null
+++ b/services/core/java/com/android/server/power/DeviceIdleController.java
@@ -0,0 +1,461 @@
+/*
+ * Copyright (C) 2015 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.server.power;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.hardware.Sensor;
+import android.hardware.SensorManager;
+import android.hardware.TriggerEvent;
+import android.hardware.TriggerEventListener;
+import android.hardware.display.DisplayManager;
+import android.net.INetworkPolicyManager;
+import android.os.Binder;
+import android.os.PowerManager;
+import android.os.PowerManagerInternal;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.util.TimeUtils;
+import android.view.Display;
+import com.android.internal.app.IBatteryStats;
+import com.android.server.SystemService;
+import com.android.server.am.BatteryStatsService;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * Keeps track of device idleness and drives low power mode based on that.
+ */
+public class DeviceIdleController extends SystemService {
+ private static final String TAG = "DeviceIdleController";
+
+ private static final String ACTION_STEP_IDLE_STATE =
+ "com.android.server.device_idle.STEP_IDLE_STATE";
+
+ // TODO: These need to be moved to system settings.
+
+ /**
+ * This is the time, after becoming inactive, at which we start looking at the
+ * motion sensor to determine if the device is being left alone. We don't do this
+ * immediately after going inactive just because we don't want to be continually running
+ * the significant motion sensor whenever the screen is off.
+ */
+ private static final long DEFAULT_INACTIVE_TIMEOUT = 30*60*1000L;
+ /**
+ * This is the time, after seeing motion, that we wait after becoming inactive from
+ * that until we start looking for motion again.
+ */
+ private static final long DEFAULT_MOTION_INACTIVE_TIMEOUT = 10*60*1000L;
+ /**
+ * This is the time, after the inactive timeout elapses, that we will wait looking
+ * for significant motion until we truly consider the device to be idle.
+ */
+ private static final long DEFAULT_IDLE_AFTER_INACTIVE_TIMEOUT = 30*60*1000L;
+ /**
+ * This is the initial time, after being idle, that we will allow ourself to be back
+ * in the IDLE_PENDING state allowing the system to run normally until we return to idle.
+ */
+ private static final long DEFAULT_IDLE_PENDING_TIMEOUT = 5*60*1000L;
+ /**
+ * Maximum pending idle timeout (time spent running) we will be allowed to use.
+ */
+ private static final long DEFAULT_MAX_IDLE_PENDING_TIMEOUT = 10*60*1000L;
+ /**
+ * Scaling factor to apply to current pending idle timeout each time we cycle through
+ * that state.
+ */
+ private static final float DEFAULT_IDLE_PENDING_FACTOR = 2f;
+ /**
+ * This is the initial time that we want to sit in the idle state before waking up
+ * again to return to pending idle and allowing normal work to run.
+ */
+ private static final long DEFAULT_IDLE_TIMEOUT = 60*60*1000L;
+ /**
+ * Maximum idle duration we will be allowed to use.
+ */
+ private static final long DEFAULT_MAX_IDLE_TIMEOUT = 6*60*60*1000L;
+ /**
+ * Scaling factor to apply to current idle timeout each time we cycle through that state.
+ */
+ private static final float DEFAULT_IDLE_FACTOR = 2f;
+
+ private AlarmManager mAlarmManager;
+ private IBatteryStats mBatteryStats;
+ private PowerManagerInternal mLocalPowerManager;
+ private INetworkPolicyManager mNetworkPolicyManager;
+ private DisplayManager mDisplayManager;
+ private SensorManager mSensorManager;
+ private Sensor mSigMotionSensor;
+ private PendingIntent mAlarmIntent;
+ private Intent mIdleIntent;
+ private Display mCurDisplay;
+ private boolean mScreenOn;
+ private boolean mCharging;
+ private boolean mSigMotionActive;
+
+ /** Device is currently active. */
+ private static final int STATE_ACTIVE = 0;
+ /** Device is inactve (screen off, no motion) and we are waiting to for idle. */
+ private static final int STATE_INACTIVE = 1;
+ /** Device is past the initial inactive period, and waiting for the next idle period. */
+ private static final int STATE_IDLE_PENDING = 2;
+ /** Device is in the idle state, trying to stay asleep as much as possible. */
+ private static final int STATE_IDLE = 3;
+ private static String stateToString(int state) {
+ switch (state) {
+ case STATE_ACTIVE: return "ACTIVE";
+ case STATE_INACTIVE: return "INACTIVE";
+ case STATE_IDLE_PENDING: return "IDLE_PENDING";
+ case STATE_IDLE: return "IDLE";
+ default: return Integer.toString(state);
+ }
+ }
+
+ private int mState;
+
+ private long mInactiveTimeout;
+ private long mNextAlarmTime;
+ private long mNextIdlePendingDelay;
+ private long mNextIdleDelay;
+
+ private final Binder mBinder = new Binder() {
+ @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ DeviceIdleController.this.dump(fd, pw, args);
+ }
+ };
+
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override public void onReceive(Context context, Intent intent) {
+ if (Intent.ACTION_BATTERY_CHANGED.equals(intent.getAction())) {
+ int plugged = intent.getIntExtra("plugged", 0);
+ updateChargingLocked(plugged != 0);
+ } else if (ACTION_STEP_IDLE_STATE.equals(intent.getAction())) {
+ synchronized (DeviceIdleController.this) {
+ stepIdleStateLocked();
+ }
+ }
+ }
+ };
+
+ private final DisplayManager.DisplayListener mDisplayListener
+ = new DisplayManager.DisplayListener() {
+ @Override public void onDisplayAdded(int displayId) {
+ }
+
+ @Override public void onDisplayRemoved(int displayId) {
+ }
+
+ @Override public void onDisplayChanged(int displayId) {
+ if (displayId == Display.DEFAULT_DISPLAY) {
+ synchronized (DeviceIdleController.this) {
+ updateDisplayLocked();
+ }
+ }
+ }
+ };
+
+ private final TriggerEventListener mSigMotionListener = new TriggerEventListener() {
+ @Override public void onTrigger(TriggerEvent event) {
+ synchronized (DeviceIdleController.this) {
+ significantMotionLocked();
+ }
+ }
+ };
+
+ public DeviceIdleController(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void onStart() {
+ synchronized (this) {
+ mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE);
+ mBatteryStats = BatteryStatsService.getService();
+ mLocalPowerManager = getLocalService(PowerManagerInternal.class);
+ mNetworkPolicyManager = INetworkPolicyManager.Stub.asInterface(
+ ServiceManager.getService(Context.NETWORK_POLICY_SERVICE));
+ mDisplayManager = (DisplayManager) getContext().getSystemService(
+ Context.DISPLAY_SERVICE);
+ mSensorManager = (SensorManager) getContext().getSystemService(Context.SENSOR_SERVICE);
+ mSigMotionSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_SIGNIFICANT_MOTION);
+
+ Intent intent = new Intent(ACTION_STEP_IDLE_STATE)
+ .setPackage("android")
+ .setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+ mAlarmIntent = PendingIntent.getBroadcast(getContext(), 0, intent, 0);
+
+ mIdleIntent = new Intent(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
+ mIdleIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_BATTERY_CHANGED);
+ filter.addAction(ACTION_STEP_IDLE_STATE);
+ getContext().registerReceiver(mReceiver, filter);
+
+ mDisplayManager.registerDisplayListener(mDisplayListener, null);
+
+ mScreenOn = true;
+ // Start out assuming we are charging. If we aren't, we will at least get
+ // a battery update the next time the level drops.
+ mCharging = true;
+ mState = STATE_ACTIVE;
+ mInactiveTimeout = DEFAULT_INACTIVE_TIMEOUT;
+ updateDisplayLocked();
+ }
+
+ publishBinderService("deviceidle", mBinder);
+ }
+
+ void updateDisplayLocked() {
+ mCurDisplay = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY);
+ // We consider any situation where the display is showing something to be it on,
+ // because if there is anything shown we are going to be updating it at some
+ // frequency so can't be allowed to go into deep sleeps.
+ boolean screenOn = mCurDisplay.getState() != Display.STATE_OFF;;
+ if (!screenOn && mScreenOn) {
+ mScreenOn = false;
+ becomeInactiveIfAppropriateLocked();
+ } else if (screenOn) {
+ mScreenOn = true;
+ becomeActiveLocked();
+ }
+ }
+
+ void updateChargingLocked(boolean charging) {
+ if (!charging && mCharging) {
+ mCharging = false;
+ becomeInactiveIfAppropriateLocked();
+ } else if (charging) {
+ mCharging = charging;
+ becomeActiveLocked();
+ }
+ }
+
+ void becomeActiveLocked() {
+ if (mState != STATE_ACTIVE) {
+ mLocalPowerManager.setDeviceIdleMode(false);
+ try {
+ mNetworkPolicyManager.setDeviceIdleMode(false);
+ mBatteryStats.noteDeviceIdleMode(false, true, false);
+ } catch (RemoteException e) {
+ }
+ if (mState == STATE_IDLE) {
+ getContext().sendBroadcastAsUser(mIdleIntent, UserHandle.ALL);
+ }
+ mState = STATE_ACTIVE;
+ mInactiveTimeout = DEFAULT_INACTIVE_TIMEOUT;
+ mNextIdlePendingDelay = 0;
+ mNextIdleDelay = 0;
+ cancelAlarmLocked();
+ stopMonitoringSignificantMotion();
+ }
+ }
+
+ void becomeInactiveIfAppropriateLocked() {
+ if (!mScreenOn && !mCharging && mState == STATE_ACTIVE) {
+ // Screen has turned off; we are now going to become inactive and start
+ // waiting to see if we will ultimately go idle.
+ mState = STATE_INACTIVE;
+ mNextIdlePendingDelay = 0;
+ mNextIdleDelay = 0;
+ scheduleAlarmLocked(mInactiveTimeout, false);
+ }
+ }
+
+ void stepIdleStateLocked() {
+ switch (mState) {
+ case STATE_INACTIVE:
+ // We have now been inactive long enough, it is time to start looking
+ // for significant motion and sleep some more while doing so.
+ startMonitoringSignificantMotion();
+ scheduleAlarmLocked(DEFAULT_IDLE_AFTER_INACTIVE_TIMEOUT, false);
+ // Reset the upcoming idle delays.
+ mNextIdlePendingDelay = DEFAULT_IDLE_PENDING_TIMEOUT;
+ mNextIdleDelay = DEFAULT_IDLE_TIMEOUT;
+ mState = STATE_IDLE_PENDING;
+ break;
+ case STATE_IDLE_PENDING:
+ // We have been waiting to become idle, and now it is time! This is the
+ // only case where we want to use a wakeup alarm, because we do want to
+ // drag the device out of its sleep state in this case to do the next
+ // scheduled work.
+ scheduleAlarmLocked(mNextIdleDelay, true);
+ mNextIdleDelay = (long)(mNextIdleDelay*DEFAULT_IDLE_FACTOR);
+ if (mNextIdleDelay > DEFAULT_MAX_IDLE_TIMEOUT) {
+ mNextIdleDelay = DEFAULT_MAX_IDLE_TIMEOUT;
+ }
+ mState = STATE_IDLE;
+ mLocalPowerManager.setDeviceIdleMode(true);
+ try {
+ mNetworkPolicyManager.setDeviceIdleMode(true);
+ mBatteryStats.noteDeviceIdleMode(true, false, false);
+ } catch (RemoteException e) {
+ }
+ getContext().sendBroadcastAsUser(mIdleIntent, UserHandle.ALL);
+ break;
+ case STATE_IDLE:
+ // We have been idling long enough, now it is time to do some work.
+ scheduleAlarmLocked(mNextIdlePendingDelay, false);
+ mNextIdlePendingDelay = (long)(mNextIdlePendingDelay*DEFAULT_IDLE_PENDING_FACTOR);
+ if (mNextIdlePendingDelay > DEFAULT_MAX_IDLE_PENDING_TIMEOUT) {
+ mNextIdlePendingDelay = DEFAULT_MAX_IDLE_PENDING_TIMEOUT;
+ }
+ mState = STATE_IDLE_PENDING;
+ mLocalPowerManager.setDeviceIdleMode(false);
+ try {
+ mNetworkPolicyManager.setDeviceIdleMode(false);
+ mBatteryStats.noteDeviceIdleMode(false, false, false);
+ } catch (RemoteException e) {
+ }
+ getContext().sendBroadcastAsUser(mIdleIntent, UserHandle.ALL);
+ break;
+ }
+ }
+
+ void significantMotionLocked() {
+ // When the sensor goes off, its trigger is automatically removed.
+ mSigMotionActive = false;
+ // The device is not yet active, so we want to go back to the pending idle
+ // state to wait again for no motion. Note that we only monitor for significant
+ // motion after moving out of the inactive state, so no need to worry about that.
+ if (mState != STATE_ACTIVE) {
+ mLocalPowerManager.setDeviceIdleMode(false);
+ try {
+ mNetworkPolicyManager.setDeviceIdleMode(false);
+ mBatteryStats.noteDeviceIdleMode(false, false, true);
+ } catch (RemoteException e) {
+ }
+ if (mState == STATE_IDLE) {
+ getContext().sendBroadcastAsUser(mIdleIntent, UserHandle.ALL);
+ }
+ mState = STATE_ACTIVE;
+ mInactiveTimeout = DEFAULT_MOTION_INACTIVE_TIMEOUT;
+ becomeInactiveIfAppropriateLocked();
+ }
+ }
+
+ void startMonitoringSignificantMotion() {
+ if (mSigMotionSensor != null && !mSigMotionActive) {
+ mSensorManager.requestTriggerSensor(mSigMotionListener, mSigMotionSensor);
+ mSigMotionActive = true;
+ }
+ }
+
+ void stopMonitoringSignificantMotion() {
+ if (mSigMotionActive) {
+ mSensorManager.cancelTriggerSensor(mSigMotionListener, mSigMotionSensor);
+ mSigMotionActive = false;
+ }
+ }
+
+ void cancelAlarmLocked() {
+ if (mNextAlarmTime != 0) {
+ mNextAlarmTime = 0;
+ mAlarmManager.cancel(mAlarmIntent);
+ }
+ }
+
+ void scheduleAlarmLocked(long delay, boolean wakeup) {
+ if (mSigMotionSensor == null) {
+ // If there is no significant motion sensor on this device, then we won't schedule
+ // alarms, because we can't determine if the device is not moving. This effectively
+ // turns off normal exeuction of device idling, although it is still possible to
+ // manually poke it by pretending like the alarm is going off.
+ return;
+ }
+ mNextAlarmTime = SystemClock.elapsedRealtime() + delay;
+ mAlarmManager.set(wakeup ? AlarmManager.ELAPSED_REALTIME_WAKEUP
+ : AlarmManager.ELAPSED_REALTIME, mNextAlarmTime, mAlarmIntent);
+ }
+
+ private void dumpHelp(PrintWriter pw) {
+ pw.println("Device idle controller (deviceidle) dump options:");
+ pw.println(" [-h] [CMD]");
+ pw.println(" -h: print this help text.");
+ pw.println("Commands:");
+ pw.println(" step");
+ pw.println(" Immediately step to next state, without waiting for alarm.");
+ }
+
+ void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ if (getContext().checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
+ != PackageManager.PERMISSION_GRANTED) {
+ pw.println("Permission Denial: can't dump DeviceIdleController from from pid="
+ + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
+ + " without permission " + android.Manifest.permission.DUMP);
+ return;
+ }
+
+ if (args != null) {
+ for (int i=0; i<args.length; i++) {
+ String arg = args[i];
+ if ("-h".equals(arg)) {
+ dumpHelp(pw);
+ return;
+ } else if ("step".equals(arg)) {
+ synchronized (this) {
+ stepIdleStateLocked();
+ pw.print("Stepped to: "); pw.println(stateToString(mState));
+ }
+ return;
+ } else if (arg.length() > 0 && arg.charAt(0) == '-'){
+ pw.println("Unknown option: " + arg);
+ dumpHelp(pw);
+ return;
+ } else {
+ pw.println("Unknown command: " + arg);
+ dumpHelp(pw);
+ return;
+ }
+ }
+ }
+
+ synchronized (this) {
+ pw.print(" mSigMotionSensor="); pw.println(mSigMotionSensor);
+ pw.print(" mCurDisplay="); pw.println(mCurDisplay);
+ pw.print(" mScreenOn="); pw.println(mScreenOn);
+ pw.print(" mCharging="); pw.println(mCharging);
+ pw.print(" mSigMotionActive="); pw.println(mSigMotionActive);
+ pw.print(" mState="); pw.println(stateToString(mState));
+ pw.print(" mInactiveTimeout="); TimeUtils.formatDuration(mInactiveTimeout, pw);
+ pw.println();
+ if (mNextAlarmTime != 0) {
+ pw.print(" mNextAlarmTime=");
+ TimeUtils.formatDuration(mNextAlarmTime, SystemClock.elapsedRealtime(), pw);
+ pw.println();
+ }
+ if (mNextIdlePendingDelay != 0) {
+ pw.print(" mNextIdlePendingDelay=");
+ TimeUtils.formatDuration(mNextIdlePendingDelay, pw);
+ pw.println();
+ }
+ if (mNextIdleDelay != 0) {
+ pw.print(" mNextIdleDelay=");
+ TimeUtils.formatDuration(mNextIdleDelay, pw);
+ pw.println();
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 9e373b7..33b451d 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -420,6 +420,9 @@ public final class PowerManagerService extends SystemService
// True if the battery level is currently considered low.
private boolean mBatteryLevelLow;
+ // True if we are currently in device idle mode.
+ private boolean mDeviceIdleMode;
+
// True if theater mode is enabled
private boolean mTheaterModeEnabled;
@@ -2178,6 +2181,12 @@ public final class PowerManagerService extends SystemService
}
}
+ private boolean isDeviceIdleModeInternal() {
+ synchronized (mLock) {
+ return mDeviceIdleMode;
+ }
+ }
+
private void handleBatteryStateChangedLocked() {
mDirty |= DIRTY_BATTERY_STATE;
updatePowerStateLocked();
@@ -3050,6 +3059,16 @@ public final class PowerManagerService extends SystemService
}
}
+ @Override // Binder call
+ public boolean isDeviceIdleMode() {
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ return isDeviceIdleModeInternal();
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
/**
* Reboots the device.
*
@@ -3295,5 +3314,12 @@ public final class PowerManagerService extends SystemService
mLowPowerModeListeners.add(listener);
}
}
+
+ @Override
+ public void setDeviceIdleMode(boolean enabled) {
+ synchronized (mLock) {
+ mDeviceIdleMode = enabled;
+ }
+ }
}
}
diff --git a/services/core/java/com/android/server/wm/DimLayer.java b/services/core/java/com/android/server/wm/DimLayer.java
index c09ea5c..e385be3 100644
--- a/services/core/java/com/android/server/wm/DimLayer.java
+++ b/services/core/java/com/android/server/wm/DimLayer.java
@@ -140,10 +140,9 @@ public class DimLayer {
}
/**
- * @param layer The new layer value.
- * @param inTransaction Whether the call is made within a surface transaction.
+ * NOTE: Must be called with Surface transaction open.
*/
- void adjustSurface(int layer, boolean inTransaction) {
+ private void adjustBounds() {
final int dw, dh;
final float xPos, yPos;
if (!mStack.isFullscreen()) {
@@ -163,29 +162,24 @@ public class DimLayer {
yPos = -1 * dh / 6;
}
- try {
- if (!inTransaction) {
- SurfaceControl.openTransaction();
- }
- mDimSurface.setPosition(xPos, yPos);
- mDimSurface.setSize(dw, dh);
- mDimSurface.setLayer(layer);
- } catch (RuntimeException e) {
- Slog.w(TAG, "Failure setting size or layer", e);
- } finally {
- if (!inTransaction) {
- SurfaceControl.closeTransaction();
- }
- }
+ mDimSurface.setPosition(xPos, yPos);
+ mDimSurface.setSize(dw, dh);
+
mLastBounds.set(mBounds);
- mLayer = layer;
}
- // Assumes that surface transactions are currently closed.
+ /** @param bounds The new bounds to set */
void setBounds(Rect bounds) {
mBounds.set(bounds);
if (isDimming() && !mLastBounds.equals(bounds)) {
- adjustSurface(mLayer, false);
+ try {
+ SurfaceControl.openTransaction();
+ adjustBounds();
+ } catch (RuntimeException e) {
+ Slog.w(TAG, "Failure setting size", e);
+ } finally {
+ SurfaceControl.closeTransaction();
+ }
}
}
@@ -224,9 +218,10 @@ public class DimLayer {
return;
}
- if (!mLastBounds.equals(mBounds) || mLayer != layer) {
- adjustSurface(layer, true);
+ if (!mLastBounds.equals(mBounds)) {
+ adjustBounds();
}
+ setLayer(layer);
long curTime = SystemClock.uptimeMillis();
final boolean animating = isAnimating();
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 6d09f55..b61a6f7 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -373,10 +373,8 @@ public class TaskStack {
mService.requestTraversalLocked();
}
- mAnimationBackgroundSurface.destroySurface();
- mAnimationBackgroundSurface = null;
- mDimLayer.destroySurface();
- mDimLayer = null;
+ close();
+
mDisplayContent = null;
}
@@ -501,8 +499,14 @@ public class TaskStack {
}
void close() {
- mDimLayer.mDimSurface.destroy();
- mAnimationBackgroundSurface.mDimSurface.destroy();
+ if (mAnimationBackgroundSurface != null) {
+ mAnimationBackgroundSurface.destroySurface();
+ mAnimationBackgroundSurface = null;
+ }
+ if (mDimLayer != null) {
+ mDimLayer.destroySurface();
+ mDimLayer = null;
+ }
}
public void dump(String prefix, PrintWriter pw) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 9a97a2d..09bc2ab 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -178,7 +178,7 @@ public class WindowManagerService extends IWindowManager.Stub
static final boolean DEBUG_ORIENTATION = false;
static final boolean DEBUG_APP_ORIENTATION = false;
static final boolean DEBUG_CONFIGURATION = false;
- static final boolean DEBUG_APP_TRANSITIONS = false;
+ static final boolean DEBUG_APP_TRANSITIONS = true;
static final boolean DEBUG_STARTING_WINDOW = false;
static final boolean DEBUG_WALLPAPER = false;
static final boolean DEBUG_WALLPAPER_LIGHT = false || DEBUG_WALLPAPER;
@@ -189,7 +189,7 @@ public class WindowManagerService extends IWindowManager.Stub
static final boolean DEBUG_LAYOUT_REPEATS = true;
static final boolean DEBUG_SURFACE_TRACE = false;
static final boolean DEBUG_WINDOW_TRACE = false;
- static final boolean DEBUG_TASK_MOVEMENT = false;
+ static final boolean DEBUG_TASK_MOVEMENT = true;
static final boolean DEBUG_STACK = false;
static final boolean DEBUG_DISPLAY = false;
static final boolean DEBUG_POWER = false;
@@ -507,6 +507,7 @@ public class WindowManagerService extends IWindowManager.Stub
boolean mClientFreezingScreen = false;
int mAppsFreezingScreen = 0;
int mLastWindowForcedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+ int mLastKeyguardForcedOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
int mLayoutSeq = 0;
@@ -3115,6 +3116,10 @@ public class WindowManagerService extends IWindowManager.Stub
}
winAnimator.mEnteringAnimation = true;
if (toBeDisplayed) {
+ if ((win.mAttrs.softInputMode & SOFT_INPUT_MASK_ADJUST)
+ == SOFT_INPUT_ADJUST_RESIZE) {
+ win.mLayoutNeeded = true;
+ }
if (win.isDrawnLw() && okToDisplay()) {
winAnimator.applyEnterAnimationLocked();
}
@@ -3700,41 +3705,68 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
- public int getOrientationFromWindowsLocked() {
- if (mDisplayFrozen || mOpeningApps.size() > 0 || mClosingApps.size() > 0) {
- // If the display is frozen, some activities may be in the middle
- // of restarting, and thus have removed their old window. If the
- // window has the flag to hide the lock screen, then the lock screen
- // can re-appear and inflict its own orientation on us. Keep the
- // orientation stable until this all settles down.
- return mLastWindowForcedOrientation;
- }
-
- // TODO(multidisplay): Change to the correct display.
- final WindowList windows = getDefaultWindowListLocked();
- for (int pos = windows.size() - 1; pos >= 0; --pos) {
- WindowState win = windows.get(pos);
- if (win.mAppToken != null) {
- // We hit an application window. so the orientation will be determined by the
- // app window. No point in continuing further.
- return (mLastWindowForcedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
- }
- if (!win.isVisibleLw() || !win.mPolicyVisibilityAfterAnim) {
- continue;
+ public int getOrientationLocked() {
+ if (mDisplayFrozen) {
+ if (mLastWindowForcedOrientation != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) {
+ if (DEBUG_ORIENTATION) Slog.v(TAG, "Display is frozen, return "
+ + mLastWindowForcedOrientation);
+ // If the display is frozen, some activities may be in the middle
+ // of restarting, and thus have removed their old window. If the
+ // window has the flag to hide the lock screen, then the lock screen
+ // can re-appear and inflict its own orientation on us. Keep the
+ // orientation stable until this all settles down.
+ return mLastWindowForcedOrientation;
}
- int req = win.mAttrs.screenOrientation;
- if((req == ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) ||
- (req == ActivityInfo.SCREEN_ORIENTATION_BEHIND)){
- continue;
+ } else {
+ // TODO(multidisplay): Change to the correct display.
+ final WindowList windows = getDefaultWindowListLocked();
+ for (int pos = windows.size() - 1; pos >= 0; --pos) {
+ WindowState win = windows.get(pos);
+ if (win.mAppToken != null) {
+ // We hit an application window. so the orientation will be determined by the
+ // app window. No point in continuing further.
+ break;
+ }
+ if (!win.isVisibleLw() || !win.mPolicyVisibilityAfterAnim) {
+ continue;
+ }
+ int req = win.mAttrs.screenOrientation;
+ if((req == ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) ||
+ (req == ActivityInfo.SCREEN_ORIENTATION_BEHIND)){
+ continue;
+ }
+
+ if (DEBUG_ORIENTATION) Slog.v(TAG, win + " forcing orientation to " + req);
+ if (mPolicy.isKeyguardHostWindow(win.mAttrs)) {
+ mLastKeyguardForcedOrientation = req;
+ }
+ return (mLastWindowForcedOrientation = req);
}
+ mLastWindowForcedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
- if (DEBUG_ORIENTATION) Slog.v(TAG, win + " forcing orientation to " + req);
- return (mLastWindowForcedOrientation = req);
+ if (mPolicy.isKeyguardLocked()) {
+ // The screen is locked and no top system window is requesting an orientation.
+ // Return either the orientation of the show-when-locked app (if there is any) or
+ // the orientation of the keyguard. No point in searching from the rest of apps.
+ WindowState winShowWhenLocked = (WindowState) mPolicy.getWinShowWhenLockedLw();
+ AppWindowToken appShowWhenLocked = winShowWhenLocked == null ?
+ null : winShowWhenLocked.mAppToken;
+ if (appShowWhenLocked != null) {
+ int req = appShowWhenLocked.requestedOrientation;
+ if (req == ActivityInfo.SCREEN_ORIENTATION_BEHIND) {
+ req = mLastKeyguardForcedOrientation;
+ }
+ if (DEBUG_ORIENTATION) Slog.v(TAG, "Done at " + appShowWhenLocked
+ + " -- show when locked, return " + req);
+ return req;
+ }
+ if (DEBUG_ORIENTATION) Slog.v(TAG,
+ "No one is requesting an orientation when the screen is locked");
+ return mLastKeyguardForcedOrientation;
+ }
}
- return (mLastWindowForcedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
- }
- public int getOrientationFromAppTokensLocked() {
+ // Top system windows are not requesting an orientation. Start searching from apps.
int lastOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
boolean findingBehind = false;
boolean lastFullscreen = false;
@@ -3804,8 +3836,11 @@ public class WindowManagerService extends IWindowManager.Stub
findingBehind |= (or == ActivityInfo.SCREEN_ORIENTATION_BEHIND);
}
}
- if (DEBUG_ORIENTATION) Slog.v(TAG, "No app is requesting an orientation");
- return ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+ if (DEBUG_ORIENTATION) Slog.v(TAG, "No app is requesting an orientation, return "
+ + mForcedAppOrientation);
+ // The next app has not been requested to be visible, so we keep the current orientation
+ // to prevent freezing/unfreezing the display too early.
+ return mForcedAppOrientation;
}
@Override
@@ -3887,11 +3922,7 @@ public class WindowManagerService extends IWindowManager.Stub
boolean updateOrientationFromAppTokensLocked(boolean inTransaction) {
long ident = Binder.clearCallingIdentity();
try {
- int req = getOrientationFromWindowsLocked();
- if (req == ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) {
- req = getOrientationFromAppTokensLocked();
- }
-
+ int req = getOrientationLocked();
if (req != mForcedAppOrientation) {
mForcedAppOrientation = req;
//send a message to Policy indicating orientation change to take
@@ -7383,7 +7414,7 @@ public class WindowManagerService extends IWindowManager.Stub
outSurface.copyFrom(surface);
final IBinder winBinder = window.asBinder();
token = new Binder();
- mDragState = new DragState(this, token, surface, /*flags*/ 0, winBinder);
+ mDragState = new DragState(this, token, surface, flags, winBinder);
token = mDragState.mToken = new Binder();
// 5 second timeout for this window to actually begin the drag
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 663c919..d07cd98 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -166,6 +166,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
private static final String ATTR_PERMISSION_PROVIDER = "permission-provider";
private static final String ATTR_SETUP_COMPLETE = "setup-complete";
+ private static final String ATTR_DELEGATED_CERT_INSTALLER = "delegated-cert-installer";
+
private static final Set<String> DEVICE_OWNER_USER_RESTRICTIONS;
static {
DEVICE_OWNER_USER_RESTRICTIONS = new HashSet();
@@ -286,6 +288,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
ComponentName mRestrictionsProvider;
+ String mDelegatedCertInstallerPackage;
+
public DevicePolicyData(int userHandle) {
mUserHandle = userHandle;
}
@@ -948,6 +952,21 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
syncDeviceCapabilitiesLocked(policy);
saveSettingsLocked(policy.mUserHandle);
}
+
+ if (policy.mDelegatedCertInstallerPackage != null &&
+ (packageName == null
+ || packageName.equals(policy.mDelegatedCertInstallerPackage))) {
+ try {
+ // Check if delegated cert installer package is removed.
+ if (pm.getPackageInfo(
+ policy.mDelegatedCertInstallerPackage, 0, userHandle) == null) {
+ policy.mDelegatedCertInstallerPackage = null;
+ saveSettingsLocked(policy.mUserHandle);
+ }
+ } catch (RemoteException e) {
+ // Shouldn't happen
+ }
+ }
}
}
@@ -1332,6 +1351,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
out.attribute(null, ATTR_SETUP_COMPLETE,
Boolean.toString(true));
}
+ if (policy.mDelegatedCertInstallerPackage != null) {
+ out.attribute(null, ATTR_DELEGATED_CERT_INSTALLER,
+ policy.mDelegatedCertInstallerPackage);
+ }
+
final int N = policy.mAdminList.size();
for (int i=0; i<N; i++) {
@@ -1439,6 +1463,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
if (userSetupComplete != null && Boolean.toString(true).equals(userSetupComplete)) {
policy.mUserSetupComplete = true;
}
+ policy.mDelegatedCertInstallerPackage = parser.getAttributeValue(null,
+ ATTR_DELEGATED_CERT_INSTALLER);
type = parser.next();
int outerDepth = parser.getDepth();
@@ -2878,7 +2904,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
@Override
public void enforceCanManageCaCerts(ComponentName who) {
if (who == null) {
- mContext.enforceCallingOrSelfPermission(MANAGE_CA_CERTIFICATES, null);
+ if (!isCallerDelegatedCertInstaller()) {
+ mContext.enforceCallingOrSelfPermission(MANAGE_CA_CERTIFICATES, null);
+ }
} else {
synchronized (this) {
getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
@@ -2886,6 +2914,25 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
+ private boolean isCallerDelegatedCertInstaller() {
+ final int callingUid = Binder.getCallingUid();
+ final int userHandle = UserHandle.getUserId(callingUid);
+ synchronized (this) {
+ final DevicePolicyData policy = getUserData(userHandle);
+ if (policy.mDelegatedCertInstallerPackage == null) {
+ return false;
+ }
+
+ try {
+ int uid = mContext.getPackageManager().getPackageUid(
+ policy.mDelegatedCertInstallerPackage, userHandle);
+ return uid == callingUid;
+ } catch (NameNotFoundException e) {
+ return false;
+ }
+ }
+ }
+
@Override
public boolean installCaCert(ComponentName admin, byte[] certBuffer) throws RemoteException {
enforceCanManageCaCerts(admin);
@@ -3036,6 +3083,28 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}.execute();
}
+ @Override
+ public void setCertInstallerPackage(ComponentName who, String installerPackage)
+ throws SecurityException {
+ int userHandle = UserHandle.getCallingUserId();
+ synchronized (this) {
+ getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+ DevicePolicyData policy = getUserData(userHandle);
+ policy.mDelegatedCertInstallerPackage = installerPackage;
+ saveSettingsLocked(userHandle);
+ }
+ }
+
+ @Override
+ public String getCertInstallerPackage(ComponentName who) throws SecurityException {
+ int userHandle = UserHandle.getCallingUserId();
+ synchronized (this) {
+ getActiveAdminForCallerLocked(who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
+ DevicePolicyData policy = getUserData(userHandle);
+ return policy.mDelegatedCertInstallerPackage;
+ }
+ }
+
private void wipeDataLocked(boolean wipeExtRequested, String reason) {
// If the SD card is encrypted and non-removable, we have to force a wipe.
boolean forceExtWipe = !Environment.isExternalStorageRemovable() && isExtStorageEncrypted();
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index a0c7f86..65e3534 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -39,6 +39,7 @@ import android.os.StrictMode;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
+import android.os.storage.IMountService;
import android.util.DisplayMetrics;
import android.util.EventLog;
import android.util.Slog;
@@ -65,7 +66,6 @@ import com.android.server.lights.LightsService;
import com.android.server.media.MediaRouterService;
import com.android.server.media.MediaSessionService;
import com.android.server.media.projection.MediaProjectionManagerService;
-import com.android.server.MidiService;
import com.android.server.net.NetworkPolicyManagerService;
import com.android.server.net.NetworkStatsService;
import com.android.server.notification.NotificationManagerService;
@@ -75,6 +75,7 @@ import com.android.server.pm.Installer;
import com.android.server.pm.LauncherAppsService;
import com.android.server.pm.PackageManagerService;
import com.android.server.pm.UserManagerService;
+import com.android.server.power.DeviceIdleController;
import com.android.server.power.PowerManagerService;
import com.android.server.power.ShutdownThread;
import com.android.server.restrictions.RestrictionsManagerService;
@@ -131,6 +132,8 @@ public final class SystemServer {
"com.android.server.ethernet.EthernetService";
private static final String JOB_SCHEDULER_SERVICE_CLASS =
"com.android.server.job.JobSchedulerService";
+ private static final String MOUNT_SERVICE_CLASS =
+ "com.android.server.MountService$Lifecycle";
private static final String PERSISTENT_DATA_BLOCK_PROP = "ro.frp.pst";
private final int mFactoryTestMode;
@@ -384,7 +387,7 @@ public final class SystemServer {
ContentService contentService = null;
VibratorService vibrator = null;
IAlarmManager alarm = null;
- MountService mountService = null;
+ IMountService mountService = null;
NetworkManagementService networkManagement = null;
NetworkStatsService networkStats = null;
NetworkPolicyManagerService networkPolicy = null;
@@ -403,12 +406,9 @@ public final class SystemServer {
AudioService audioService = null;
MmsServiceBroker mmsService = null;
EntropyMixer entropyMixer = null;
- MidiService midiService = null;
boolean disableStorage = SystemProperties.getBoolean("config.disable_storage", false);
- boolean disableMedia = SystemProperties.getBoolean("config.disable_media", false);
boolean disableBluetooth = SystemProperties.getBoolean("config.disable_bluetooth", false);
- boolean disableTelephony = SystemProperties.getBoolean("config.disable_telephony", false);
boolean disableLocation = SystemProperties.getBoolean("config.disable_location", false);
boolean disableSystemUI = SystemProperties.getBoolean("config.disable_systemui", false);
boolean disableNonCoreServices = SystemProperties.getBoolean("config.disable_noncore", false);
@@ -517,7 +517,6 @@ public final class SystemServer {
LockSettingsService lockSettings = null;
AssetAtlasService atlas = null;
MediaRouterService mediaRouter = null;
- MidiService midi = null;
// Bring up services needed for UI.
if (mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL) {
@@ -552,15 +551,19 @@ public final class SystemServer {
* NotificationManagerService is dependant on MountService,
* (for media / usb notifications) so we must start MountService first.
*/
- Slog.i(TAG, "Mount Service");
- mountService = new MountService(context);
- ServiceManager.addService("mount", mountService);
+ mSystemServiceManager.startService(MOUNT_SERVICE_CLASS);
+ mountService = IMountService.Stub.asInterface(
+ ServiceManager.getService("mount"));
} catch (Throwable e) {
reportWtf("starting Mount Service", e);
}
}
}
+ // We start this here so that we update our configuration to set watch or television
+ // as appropriate.
+ mSystemServiceManager.startService(UiModeManagerService.class);
+
try {
mPackageManagerService.performBootDexOpt();
} catch (Throwable e) {
@@ -711,7 +714,10 @@ public final class SystemServer {
* first before continuing.
*/
if (mountService != null && !mOnlyCore) {
- mountService.waitForAsecScan();
+ try {
+ mountService.waitForAsecScan();
+ } catch (RemoteException ignored) {
+ }
}
try {
@@ -782,29 +788,25 @@ public final class SystemServer {
}
}
- if (!disableMedia && !"0".equals(SystemProperties.get("system_init.startaudioservice"))) {
- try {
- Slog.i(TAG, "Audio Service");
- audioService = new AudioService(context);
- ServiceManager.addService(Context.AUDIO_SERVICE, audioService);
- } catch (Throwable e) {
- reportWtf("starting Audio Service", e);
- }
+ try {
+ Slog.i(TAG, "Audio Service");
+ audioService = new AudioService(context);
+ ServiceManager.addService(Context.AUDIO_SERVICE, audioService);
+ } catch (Throwable e) {
+ reportWtf("starting Audio Service", e);
}
if (!disableNonCoreServices) {
mSystemServiceManager.startService(DockObserver.class);
}
- if (!disableMedia) {
- try {
- Slog.i(TAG, "Wired Accessory Manager");
- // Listen for wired headset changes
- inputManager.setWiredAccessoryCallbacks(
- new WiredAccessoryManager(context, inputManager));
- } catch (Throwable e) {
- reportWtf("starting WiredAccessoryManager", e);
- }
+ try {
+ Slog.i(TAG, "Wired Accessory Manager");
+ // Listen for wired headset changes
+ inputManager.setWiredAccessoryCallbacks(
+ new WiredAccessoryManager(context, inputManager));
+ } catch (Throwable e) {
+ reportWtf("starting WiredAccessoryManager", e);
}
if (!disableNonCoreServices) {
@@ -837,8 +839,6 @@ public final class SystemServer {
mSystemServiceManager.startService(TwilightService.class);
- mSystemServiceManager.startService(UiModeManagerService.class);
-
mSystemServiceManager.startService(JobSchedulerService.class);
if (!disableNonCoreServices) {
@@ -883,14 +883,12 @@ public final class SystemServer {
}
}
- if (!disableMedia) {
- try {
- Slog.i(TAG, "CommonTimeManagementService");
- commonTimeMgmtService = new CommonTimeManagementService(context);
- ServiceManager.addService("commontime_management", commonTimeMgmtService);
- } catch (Throwable e) {
- reportWtf("starting CommonTimeManagementService service", e);
- }
+ try {
+ Slog.i(TAG, "CommonTimeManagementService");
+ commonTimeMgmtService = new CommonTimeManagementService(context);
+ ServiceManager.addService("commontime_management", commonTimeMgmtService);
+ } catch (Throwable e) {
+ reportWtf("starting CommonTimeManagementService service", e);
}
if (!disableNetwork) {
@@ -960,6 +958,7 @@ public final class SystemServer {
if (!disableNonCoreServices) {
mSystemServiceManager.startService(MediaProjectionManagerService.class);
+ mSystemServiceManager.startService(DeviceIdleController.class);
}
// Before things start rolling, be sure we have decided whether
@@ -1038,7 +1037,6 @@ public final class SystemServer {
}
// These are needed to propagate to the runnable below.
- final MountService mountServiceF = mountService;
final NetworkManagementService networkManagementF = networkManagement;
final NetworkStatsService networkStatsF = networkStats;
final NetworkPolicyManagerService networkPolicyF = networkPolicy;
@@ -1086,11 +1084,6 @@ public final class SystemServer {
reportWtf("starting System UI", e);
}
try {
- if (mountServiceF != null) mountServiceF.systemReady();
- } catch (Throwable e) {
- reportWtf("making Mount Service ready", e);
- }
- try {
if (networkScoreF != null) networkScoreF.systemReady();
} catch (Throwable e) {
reportWtf("making Network Score Service ready", e);
diff --git a/services/net/Android.mk b/services/net/Android.mk
new file mode 100644
index 0000000..336bc45
--- /dev/null
+++ b/services/net/Android.mk
@@ -0,0 +1,10 @@
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := services.net
+
+LOCAL_SRC_FILES += \
+ $(call all-java-files-under,java)
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/services/net/java/android/net/dhcp/DhcpAckPacket.java b/services/net/java/android/net/dhcp/DhcpAckPacket.java
new file mode 100644
index 0000000..25b8093
--- /dev/null
+++ b/services/net/java/android/net/dhcp/DhcpAckPacket.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.dhcp;
+
+import java.net.Inet4Address;
+import java.nio.ByteBuffer;
+
+/**
+ * This class implements the DHCP-ACK packet.
+ */
+class DhcpAckPacket extends DhcpPacket {
+
+ /**
+ * The address of the server which sent this packet.
+ */
+ private final Inet4Address mSrcIp;
+
+ DhcpAckPacket(int transId, boolean broadcast, Inet4Address serverAddress,
+ Inet4Address clientIp, byte[] clientMac) {
+ super(transId, INADDR_ANY, clientIp, serverAddress, INADDR_ANY, clientMac, broadcast);
+ mBroadcast = broadcast;
+ mSrcIp = serverAddress;
+ }
+
+ public String toString() {
+ String s = super.toString();
+ String dnsServers = " DNS servers: ";
+
+ for (Inet4Address dnsServer: mDnsServers) {
+ dnsServers += dnsServer.toString() + " ";
+ }
+
+ return s + " ACK: your new IP " + mYourIp +
+ ", netmask " + mSubnetMask +
+ ", gateway " + mGateway + dnsServers +
+ ", lease time " + mLeaseTime;
+ }
+
+ /**
+ * Fills in a packet with the requested ACK parameters.
+ */
+ public ByteBuffer buildPacket(int encap, short destUdp, short srcUdp) {
+ ByteBuffer result = ByteBuffer.allocate(MAX_LENGTH);
+ Inet4Address destIp = mBroadcast ? INADDR_BROADCAST : mYourIp;
+ Inet4Address srcIp = mBroadcast ? INADDR_ANY : mSrcIp;
+
+ fillInPacket(encap, destIp, srcIp, destUdp, srcUdp, result,
+ DHCP_BOOTREPLY, mBroadcast);
+ result.flip();
+ return result;
+ }
+
+ /**
+ * Adds the optional parameters to the client-generated ACK packet.
+ */
+ void finishPacket(ByteBuffer buffer) {
+ addTlv(buffer, DHCP_MESSAGE_TYPE, DHCP_MESSAGE_TYPE_ACK);
+ addTlv(buffer, DHCP_SERVER_IDENTIFIER, mServerIdentifier);
+ addTlv(buffer, DHCP_LEASE_TIME, mLeaseTime);
+
+ // the client should renew at 1/2 the lease-expiry interval
+ if (mLeaseTime != null) {
+ addTlv(buffer, DHCP_RENEWAL_TIME,
+ Integer.valueOf(mLeaseTime.intValue() / 2));
+ }
+
+ addTlv(buffer, DHCP_SUBNET_MASK, mSubnetMask);
+ addTlv(buffer, DHCP_ROUTER, mGateway);
+ addTlv(buffer, DHCP_DOMAIN_NAME, mDomainName);
+ addTlv(buffer, DHCP_BROADCAST_ADDRESS, mBroadcastAddress);
+ addTlv(buffer, DHCP_DNS_SERVER, mDnsServers);
+ addTlvEnd(buffer);
+ }
+
+ /**
+ * Un-boxes an Integer, returning 0 if a null reference is supplied.
+ */
+ private static final int getInt(Integer v) {
+ if (v == null) {
+ return 0;
+ } else {
+ return v.intValue();
+ }
+ }
+}
diff --git a/services/net/java/android/net/dhcp/DhcpClient.java b/services/net/java/android/net/dhcp/DhcpClient.java
new file mode 100644
index 0000000..57cc251
--- /dev/null
+++ b/services/net/java/android/net/dhcp/DhcpClient.java
@@ -0,0 +1,807 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.dhcp;
+
+import com.android.internal.util.HexDump;
+import com.android.internal.util.Protocol;
+import com.android.internal.util.State;
+import com.android.internal.util.StateMachine;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.DhcpResults;
+import android.net.BaseDhcpStateMachine;
+import android.net.DhcpStateMachine;
+import android.net.InterfaceConfiguration;
+import android.net.LinkAddress;
+import android.net.NetworkUtils;
+import android.os.IBinder;
+import android.os.INetworkManagementService;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.PacketSocketAddress;
+import android.util.Log;
+import android.util.TimeUtils;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.lang.Thread;
+import java.net.Inet4Address;
+import java.net.InetSocketAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.Random;
+
+import libcore.io.IoUtils;
+
+import static android.system.OsConstants.*;
+import static android.net.dhcp.DhcpPacket.*;
+
+/**
+ * A DHCPv4 client.
+ *
+ * Written to behave similarly to the DhcpStateMachine + dhcpcd 5.5.6 combination used in Android
+ * 5.1 and below, as configured on Nexus 6. The interface is the same as DhcpStateMachine.
+ *
+ * TODO:
+ *
+ * - Exponential backoff when receiving NAKs (not specified by the RFC, but current behaviour).
+ * - Support persisting lease state and support INIT-REBOOT. Android 5.1 does this, but it does not
+ * do so correctly: instead of requesting the lease last obtained on a particular network (e.g., a
+ * given SSID), it requests the last-leased IP address on the same interface, causing a delay if
+ * the server NAKs or a timeout if it doesn't.
+ *
+ * Known differences from current behaviour:
+ *
+ * - Does not request the "static routes" option.
+ * - Does not support BOOTP servers. DHCP has been around since 1993, should be everywhere now.
+ * - Requests the "broadcast" option, but does nothing with it.
+ * - Rejects invalid subnet masks such as 255.255.255.1 (current code treats that as 255.255.255.0).
+ *
+ * @hide
+ */
+public class DhcpClient extends BaseDhcpStateMachine {
+
+ private static final String TAG = "DhcpClient";
+ private static final boolean DBG = true;
+ private static final boolean STATE_DBG = false;
+ private static final boolean MSG_DBG = false;
+
+ // Timers and timeouts.
+ private static final int SECONDS = 1000;
+ private static final int FIRST_TIMEOUT_MS = 2 * SECONDS;
+ private static final int MAX_TIMEOUT_MS = 128 * SECONDS;
+
+ // This is not strictly needed, since the client is asynchronous and implements exponential
+ // backoff. It's maintained for backwards compatibility with the previous DHCP code, which was
+ // a blocking operation with a 30-second timeout. We pick 36 seconds so we can send packets at
+ // t=0, t=2, t=6, t=14, t=30, allowing for 10% jitter.
+ private static final int DHCP_TIMEOUT_MS = 36 * SECONDS;
+
+ // Messages.
+ private static final int BASE = Protocol.BASE_DHCP + 100;
+ private static final int CMD_KICK = BASE + 1;
+ private static final int CMD_RECEIVED_PACKET = BASE + 2;
+ private static final int CMD_TIMEOUT = BASE + 3;
+
+ // DHCP parameters that we request.
+ private static final byte[] REQUESTED_PARAMS = new byte[] {
+ DHCP_SUBNET_MASK,
+ DHCP_ROUTER,
+ DHCP_DNS_SERVER,
+ DHCP_DOMAIN_NAME,
+ DHCP_MTU,
+ DHCP_BROADCAST_ADDRESS, // TODO: currently ignored.
+ DHCP_LEASE_TIME,
+ DHCP_RENEWAL_TIME,
+ DHCP_REBINDING_TIME,
+ };
+
+ // DHCP flag that means "yes, we support unicast."
+ private static final boolean DO_UNICAST = false;
+
+ // System services / libraries we use.
+ private final Context mContext;
+ private final AlarmManager mAlarmManager;
+ private final Random mRandom;
+ private final INetworkManagementService mNMService;
+
+ // Sockets.
+ // - We use a packet socket to receive, because servers send us packets bound for IP addresses
+ // which we have not yet configured, and the kernel protocol stack drops these.
+ // - We use a UDP socket to send, so the kernel handles ARP and routing for us (DHCP servers can
+ // be off-link as well as on-link).
+ private FileDescriptor mPacketSock;
+ private FileDescriptor mUdpSock;
+ private ReceiveThread mReceiveThread;
+
+ // State variables.
+ private final StateMachine mController;
+ private final PendingIntent mKickIntent;
+ private final PendingIntent mTimeoutIntent;
+ private final PendingIntent mRenewIntent;
+ private final String mIfaceName;
+
+ private boolean mRegisteredForPreDhcpNotification;
+ private NetworkInterface mIface;
+ private byte[] mHwAddr;
+ private PacketSocketAddress mInterfaceBroadcastAddr;
+ private int mTransactionId;
+ private DhcpResults mDhcpLease;
+ private long mDhcpLeaseExpiry;
+ private DhcpResults mOffer;
+
+ // States.
+ private State mStoppedState = new StoppedState();
+ private State mDhcpState = new DhcpState();
+ private State mDhcpInitState = new DhcpInitState();
+ private State mDhcpSelectingState = new DhcpSelectingState();
+ private State mDhcpRequestingState = new DhcpRequestingState();
+ private State mDhcpBoundState = new DhcpBoundState();
+ private State mDhcpRenewingState = new DhcpRenewingState();
+ private State mDhcpRebindingState = new DhcpRebindingState();
+ private State mDhcpInitRebootState = new DhcpInitRebootState();
+ private State mDhcpRebootingState = new DhcpRebootingState();
+ private State mWaitBeforeStartState = new WaitBeforeStartState(mDhcpInitState);
+ private State mWaitBeforeRenewalState = new WaitBeforeRenewalState(mDhcpRenewingState);
+
+ private DhcpClient(Context context, StateMachine controller, String iface) {
+ super(TAG);
+
+ mContext = context;
+ mController = controller;
+ mIfaceName = iface;
+
+ addState(mStoppedState);
+ addState(mDhcpState);
+ addState(mDhcpInitState, mDhcpState);
+ addState(mWaitBeforeStartState, mDhcpState);
+ addState(mDhcpSelectingState, mDhcpState);
+ addState(mDhcpRequestingState, mDhcpState);
+ addState(mDhcpBoundState, mDhcpState);
+ addState(mWaitBeforeRenewalState, mDhcpState);
+ addState(mDhcpRenewingState, mDhcpState);
+ addState(mDhcpRebindingState, mDhcpState);
+ addState(mDhcpInitRebootState, mDhcpState);
+ addState(mDhcpRebootingState, mDhcpState);
+
+ setInitialState(mStoppedState);
+
+ mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
+ IBinder b = ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE);
+ mNMService = INetworkManagementService.Stub.asInterface(b);
+
+ mRandom = new Random();
+
+ mKickIntent = createStateMachineCommandIntent("KICK", CMD_KICK);
+ mTimeoutIntent = createStateMachineCommandIntent("TIMEOUT", CMD_TIMEOUT);
+ mRenewIntent = createStateMachineCommandIntent("RENEW", DhcpStateMachine.CMD_RENEW_DHCP);
+ }
+
+ @Override
+ public void registerForPreDhcpNotification() {
+ mRegisteredForPreDhcpNotification = true;
+ }
+
+ public static BaseDhcpStateMachine makeDhcpStateMachine(
+ Context context, StateMachine controller, String intf) {
+ DhcpClient client = new DhcpClient(context, controller, intf);
+ client.start();
+ return client;
+ }
+
+ /**
+ * Constructs a PendingIntent that sends the specified command to the state machine. This is
+ * implemented by creating an Intent with the specified parameters, and creating and registering
+ * a BroadcastReceiver for it. The broadcast must be sent by a process that holds the
+ * {@code CONNECTIVITY_INTERNAL} permission.
+ *
+ * @param cmdName the name of the command. The intent's action will be
+ * {@code android.net.dhcp.DhcpClient.<cmdName>}
+ * @param cmd the command to send to the state machine when the PendingIntent is triggered.
+ * @return the PendingIntent
+ */
+ private PendingIntent createStateMachineCommandIntent(final String cmdName, final int cmd) {
+ String action = DhcpClient.class.getName() + "." + cmdName;
+
+ // TODO: figure out what values to pass to intent.setPackage() and intent.setClass() that
+ // result in the Intent being received by this class and nowhere else, and use them.
+ Intent intent = new Intent(action, null)
+ .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, cmd, intent, 0);
+
+ mContext.registerReceiver(
+ new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ sendMessage(cmd);
+ }
+ },
+ new IntentFilter(action),
+ android.Manifest.permission.CONNECTIVITY_INTERNAL,
+ null);
+
+ return pendingIntent;
+ }
+
+ private boolean initInterface() {
+ try {
+ mIface = NetworkInterface.getByName(mIfaceName);
+ mHwAddr = mIface.getHardwareAddress();
+ mInterfaceBroadcastAddr = new PacketSocketAddress(mIface.getIndex(),
+ DhcpPacket.ETHER_BROADCAST);
+ return true;
+ } catch(SocketException e) {
+ Log.wtf(TAG, "Can't determine ifindex or MAC address for " + mIfaceName);
+ return false;
+ }
+ }
+
+ private void initTransactionId() {
+ mTransactionId = mRandom.nextInt();
+ }
+
+ private boolean initSockets() {
+ try {
+ mPacketSock = Os.socket(AF_PACKET, SOCK_RAW, ETH_P_IP);
+ PacketSocketAddress addr = new PacketSocketAddress((short) ETH_P_IP, mIface.getIndex());
+ Os.bind(mPacketSock, addr);
+ NetworkUtils.attachDhcpFilter(mPacketSock);
+ } catch(SocketException|ErrnoException e) {
+ Log.e(TAG, "Error creating packet socket", e);
+ return false;
+ }
+ try {
+ mUdpSock = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+ Os.setsockoptInt(mUdpSock, SOL_SOCKET, SO_REUSEADDR, 1);
+ Os.setsockoptIfreq(mUdpSock, SOL_SOCKET, SO_BINDTODEVICE, mIfaceName);
+ Os.setsockoptInt(mUdpSock, SOL_SOCKET, SO_BROADCAST, 1);
+ Os.bind(mUdpSock, Inet4Address.ANY, DhcpPacket.DHCP_CLIENT);
+ NetworkUtils.protectFromVpn(mUdpSock);
+ } catch(SocketException|ErrnoException e) {
+ Log.e(TAG, "Error creating UDP socket", e);
+ return false;
+ }
+ return true;
+ }
+
+ private void closeSockets() {
+ IoUtils.closeQuietly(mUdpSock);
+ IoUtils.closeQuietly(mPacketSock);
+ }
+
+ private boolean setIpAddress(LinkAddress address) {
+ InterfaceConfiguration ifcg = new InterfaceConfiguration();
+ ifcg.setLinkAddress(address);
+ try {
+ mNMService.setInterfaceConfig(mIfaceName, ifcg);
+ } catch (RemoteException|IllegalStateException e) {
+ Log.e(TAG, "Error configuring IP address : " + e);
+ return false;
+ }
+ return true;
+ }
+
+ class ReceiveThread extends Thread {
+
+ private final byte[] mPacket = new byte[DhcpPacket.MAX_LENGTH];
+ private boolean stopped = false;
+
+ public void halt() {
+ stopped = true;
+ closeSockets(); // Interrupts the read() call the thread is blocked in.
+ }
+
+ @Override
+ public void run() {
+ maybeLog("Starting receive thread");
+ while (!stopped) {
+ try {
+ int length = Os.read(mPacketSock, mPacket, 0, mPacket.length);
+ DhcpPacket packet = null;
+ packet = DhcpPacket.decodeFullPacket(mPacket, length, DhcpPacket.ENCAP_L2);
+ if (packet != null) {
+ maybeLog("Received packet: " + packet);
+ sendMessage(CMD_RECEIVED_PACKET, packet);
+ }
+ } catch(IOException|ErrnoException e) {
+ Log.e(TAG, "Read error", e);
+ }
+ }
+ maybeLog("Stopping receive thread");
+ }
+ }
+
+ private boolean transmitPacket(ByteBuffer buf, String description, Inet4Address to) {
+ try {
+ if (to.equals(INADDR_BROADCAST)) {
+ maybeLog("Broadcasting " + description);
+ Os.sendto(mPacketSock, buf.array(), 0, buf.limit(), 0, mInterfaceBroadcastAddr);
+ } else {
+ maybeLog("Unicasting " + description + " to " + to.getHostAddress());
+ Os.sendto(mUdpSock, buf, 0, to, DhcpPacket.DHCP_SERVER);
+ }
+ } catch(ErrnoException|IOException e) {
+ Log.e(TAG, "Can't send packet: ", e);
+ return false;
+ }
+ return true;
+ }
+
+ private boolean sendDiscoverPacket() {
+ ByteBuffer packet = DhcpPacket.buildDiscoverPacket(
+ DhcpPacket.ENCAP_L2, mTransactionId, mHwAddr, DO_UNICAST, REQUESTED_PARAMS);
+ return transmitPacket(packet, "DHCPDISCOVER", INADDR_BROADCAST);
+ }
+
+ private boolean sendRequestPacket(
+ Inet4Address clientAddress, Inet4Address requestedAddress,
+ Inet4Address serverAddress, Inet4Address to) {
+ // TODO: should we use the transaction ID from the server?
+ int encap = to.equals(INADDR_BROADCAST) ? DhcpPacket.ENCAP_L2 : DhcpPacket.ENCAP_BOOTP;
+
+ ByteBuffer packet = DhcpPacket.buildRequestPacket(
+ encap, mTransactionId, clientAddress,
+ DO_UNICAST, mHwAddr, requestedAddress,
+ serverAddress, REQUESTED_PARAMS, null);
+ String description = "DHCPREQUEST ciaddr=" + clientAddress.getHostAddress() +
+ " request=" + requestedAddress.getHostAddress() +
+ " to=" + serverAddress.getHostAddress();
+ return transmitPacket(packet, description, to);
+ }
+
+ private void scheduleRenew() {
+ long now = SystemClock.elapsedRealtime();
+ long alarmTime = (now + mDhcpLeaseExpiry) / 2;
+ mAlarmManager.cancel(mRenewIntent);
+ mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, alarmTime, mRenewIntent);
+ Log.d(TAG, "Scheduling renewal in " + ((alarmTime - now) / 1000) + "s");
+ }
+
+ private void notifyLease() {
+ mController.sendMessage(DhcpStateMachine.CMD_POST_DHCP_ACTION,
+ DhcpStateMachine.DHCP_SUCCESS, 0, mDhcpLease);
+ }
+
+ private void notifyFailure() {
+ mController.sendMessage(DhcpStateMachine.CMD_POST_DHCP_ACTION,
+ DhcpStateMachine.DHCP_FAILURE, 0, null);
+ }
+
+ private void clearDhcpState() {
+ mDhcpLease = null;
+ mDhcpLeaseExpiry = 0;
+ mOffer = null;
+ }
+
+ /**
+ * Quit the DhcpStateMachine.
+ *
+ * @hide
+ */
+ @Override
+ public void doQuit() {
+ Log.d(TAG, "doQuit");
+ quit();
+ }
+
+ protected void onQuitting() {
+ Log.d(TAG, "onQuitting");
+ mController.sendMessage(DhcpStateMachine.CMD_ON_QUIT);
+ }
+
+ private void maybeLog(String msg) {
+ if (DBG) Log.d(TAG, msg);
+ }
+
+ abstract class LoggingState extends State {
+ public void enter() {
+ if (STATE_DBG) Log.d(TAG, "Entering state " + getName());
+ }
+
+ private String messageName(int what) {
+ switch (what) {
+ case DhcpStateMachine.CMD_START_DHCP:
+ return "CMD_START_DHCP";
+ case DhcpStateMachine.CMD_STOP_DHCP:
+ return "CMD_STOP_DHCP";
+ case DhcpStateMachine.CMD_RENEW_DHCP:
+ return "CMD_RENEW_DHCP";
+ case DhcpStateMachine.CMD_PRE_DHCP_ACTION:
+ return "CMD_PRE_DHCP_ACTION";
+ case DhcpStateMachine.CMD_PRE_DHCP_ACTION_COMPLETE:
+ return "CMD_PRE_DHCP_ACTION_COMPLETE";
+ case DhcpStateMachine.CMD_POST_DHCP_ACTION:
+ return "CMD_POST_DHCP_ACTION";
+ case CMD_KICK:
+ return "CMD_KICK";
+ case CMD_RECEIVED_PACKET:
+ return "CMD_RECEIVED_PACKET";
+ default:
+ return Integer.toString(what);
+ }
+ }
+
+ private String messageToString(Message message) {
+ long now = SystemClock.uptimeMillis();
+ StringBuilder b = new StringBuilder(" ");
+ TimeUtils.formatDuration(message.getWhen() - now, b);
+ b.append(" ").append(messageName(message.what))
+ .append(" ").append(message.arg1)
+ .append(" ").append(message.arg2)
+ .append(" ").append(message.obj);
+ return b.toString();
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ if (MSG_DBG) {
+ Log.d(TAG, getName() + messageToString(message));
+ }
+ return NOT_HANDLED;
+ }
+ }
+
+ // Sends CMD_PRE_DHCP_ACTION to the controller, waits for the controller to respond with
+ // CMD_PRE_DHCP_ACTION_COMPLETE, and then transitions to mOtherState.
+ abstract class WaitBeforeOtherState extends LoggingState {
+ protected State mOtherState;
+
+ @Override
+ public void enter() {
+ super.enter();
+ mController.sendMessage(DhcpStateMachine.CMD_PRE_DHCP_ACTION);
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ super.processMessage(message);
+ switch (message.what) {
+ case DhcpStateMachine.CMD_PRE_DHCP_ACTION_COMPLETE:
+ transitionTo(mOtherState);
+ return HANDLED;
+ default:
+ return NOT_HANDLED;
+ }
+ }
+ }
+
+ class StoppedState extends LoggingState {
+ @Override
+ public boolean processMessage(Message message) {
+ super.processMessage(message);
+ switch (message.what) {
+ case DhcpStateMachine.CMD_START_DHCP:
+ if (mRegisteredForPreDhcpNotification) {
+ transitionTo(mWaitBeforeStartState);
+ } else {
+ transitionTo(mDhcpInitState);
+ }
+ return HANDLED;
+ default:
+ return NOT_HANDLED;
+ }
+ }
+ }
+
+ class WaitBeforeStartState extends WaitBeforeOtherState {
+ public WaitBeforeStartState(State otherState) {
+ super();
+ mOtherState = otherState;
+ }
+ }
+
+ class WaitBeforeRenewalState extends WaitBeforeOtherState {
+ public WaitBeforeRenewalState(State otherState) {
+ super();
+ mOtherState = otherState;
+ }
+ }
+
+ class DhcpState extends LoggingState {
+ @Override
+ public void enter() {
+ super.enter();
+ clearDhcpState();
+ if (initInterface() && initSockets()) {
+ mReceiveThread = new ReceiveThread();
+ mReceiveThread.start();
+ } else {
+ notifyFailure();
+ transitionTo(mStoppedState);
+ }
+ }
+
+ @Override
+ public void exit() {
+ mReceiveThread.halt(); // Also closes sockets.
+ clearDhcpState();
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ super.processMessage(message);
+ switch (message.what) {
+ case DhcpStateMachine.CMD_STOP_DHCP:
+ transitionTo(mStoppedState);
+ return HANDLED;
+ default:
+ return NOT_HANDLED;
+ }
+ }
+ }
+
+ public boolean isValidPacket(DhcpPacket packet) {
+ // TODO: check checksum.
+ int xid = packet.getTransactionId();
+ if (xid != mTransactionId) {
+ Log.d(TAG, "Unexpected transaction ID " + xid + ", expected " + mTransactionId);
+ return false;
+ }
+ if (!Arrays.equals(packet.getClientMac(), mHwAddr)) {
+ Log.d(TAG, "MAC addr mismatch: got " +
+ HexDump.toHexString(packet.getClientMac()) + ", expected " +
+ HexDump.toHexString(packet.getClientMac()));
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Retransmits packets using jittered exponential backoff with an optional timeout. Packet
+ * transmission is triggered by CMD_KICK, which is sent by an AlarmManager alarm.
+ *
+ * Concrete subclasses must implement sendPacket, which is called when the alarm fires and a
+ * packet needs to be transmitted, and receivePacket, which is triggered by CMD_RECEIVED_PACKET
+ * sent by the receive thread. They may implement timeout, which is called when the timeout
+ * fires.
+ */
+ abstract class PacketRetransmittingState extends LoggingState {
+
+ private int mTimer;
+ protected int mTimeout = 0;
+
+ @Override
+ public void enter() {
+ super.enter();
+ initTimer();
+ maybeInitTimeout();
+ sendMessage(CMD_KICK);
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ super.processMessage(message);
+ switch (message.what) {
+ case CMD_KICK:
+ sendPacket();
+ scheduleKick();
+ return HANDLED;
+ case CMD_RECEIVED_PACKET:
+ receivePacket((DhcpPacket) message.obj);
+ return HANDLED;
+ case CMD_TIMEOUT:
+ timeout();
+ return HANDLED;
+ default:
+ return NOT_HANDLED;
+ }
+ }
+
+ public void exit() {
+ mAlarmManager.cancel(mKickIntent);
+ mAlarmManager.cancel(mTimeoutIntent);
+ }
+
+ abstract protected boolean sendPacket();
+ abstract protected void receivePacket(DhcpPacket packet);
+ protected void timeout() {}
+
+ protected void initTimer() {
+ mTimer = FIRST_TIMEOUT_MS;
+ }
+
+ protected int jitterTimer(int baseTimer) {
+ int maxJitter = baseTimer / 10;
+ int jitter = mRandom.nextInt(2 * maxJitter) - maxJitter;
+ return baseTimer + jitter;
+ }
+
+ protected void scheduleKick() {
+ long now = SystemClock.elapsedRealtime();
+ long timeout = jitterTimer(mTimer);
+ long alarmTime = now + timeout;
+ mAlarmManager.cancel(mKickIntent);
+ mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, alarmTime, mKickIntent);
+ mTimer *= 2;
+ if (mTimer > MAX_TIMEOUT_MS) {
+ mTimer = MAX_TIMEOUT_MS;
+ }
+ }
+
+ protected void maybeInitTimeout() {
+ if (mTimeout > 0) {
+ long alarmTime = SystemClock.elapsedRealtime() + mTimeout;
+ mAlarmManager.setExact(
+ AlarmManager.ELAPSED_REALTIME_WAKEUP, alarmTime, mTimeoutIntent);
+ }
+ }
+ }
+
+ class DhcpInitState extends PacketRetransmittingState {
+ public DhcpInitState() {
+ super();
+ mTimeout = DHCP_TIMEOUT_MS;
+ }
+
+ @Override
+ public void enter() {
+ super.enter();
+ initTransactionId();
+ }
+
+ protected boolean sendPacket() {
+ return sendDiscoverPacket();
+ }
+
+ protected void timeout() {
+ maybeLog("Timeout");
+ notifyFailure();
+ }
+
+ protected void receivePacket(DhcpPacket packet) {
+ if (!isValidPacket(packet)) return;
+ if (!(packet instanceof DhcpOfferPacket)) return;
+ mOffer = packet.toDhcpResults();
+ if (mOffer != null) {
+ Log.d(TAG, "Got pending lease: " + mOffer);
+ transitionTo(mDhcpRequestingState);
+ }
+ }
+ }
+
+ // Not implemented. We request the first offer we receive.
+ class DhcpSelectingState extends LoggingState {
+ }
+
+ class DhcpRequestingState extends PacketRetransmittingState {
+ public DhcpRequestingState() {
+ super();
+ mTimeout = DHCP_TIMEOUT_MS / 2;
+ }
+
+ protected boolean sendPacket() {
+ return sendRequestPacket(
+ INADDR_ANY, // ciaddr
+ (Inet4Address) mOffer.ipAddress.getAddress(), // DHCP_REQUESTED_IP
+ (Inet4Address) mOffer.serverAddress, // DHCP_SERVER_IDENTIFIER
+ INADDR_BROADCAST); // packet destination address
+ }
+
+ protected void receivePacket(DhcpPacket packet) {
+ if (!isValidPacket(packet)) return;
+ if ((packet instanceof DhcpAckPacket)) {
+ DhcpResults results = packet.toDhcpResults();
+ if (results != null) {
+ mDhcpLease = results;
+ Log.d(TAG, "Confirmed lease: " + mDhcpLease);
+ mDhcpLeaseExpiry = SystemClock.elapsedRealtime() +
+ mDhcpLease.leaseDuration * 1000;
+ mOffer = null;
+ transitionTo(mDhcpBoundState);
+ }
+ } else if (packet instanceof DhcpNakPacket) {
+ Log.d(TAG, "Received NAK, returning to INIT");
+ mOffer = null;
+ transitionTo(mDhcpInitState);
+ }
+ }
+
+ protected void timeout() {
+ notifyFailure();
+ transitionTo(mDhcpInitState);
+ }
+ }
+
+ class DhcpBoundState extends LoggingState {
+ @Override
+ public void enter() {
+ super.enter();
+ if (!setIpAddress(mDhcpLease.ipAddress)) {
+ notifyFailure();
+ transitionTo(mStoppedState);
+ }
+ notifyLease();
+ // TODO: DhcpStateMachine only supports renewing at 50% of the lease time, and does not
+ // support rebinding. Fix this.
+ scheduleRenew();
+ }
+
+ @Override
+ public boolean processMessage(Message message) {
+ super.processMessage(message);
+ switch (message.what) {
+ case DhcpStateMachine.CMD_RENEW_DHCP:
+ if (mRegisteredForPreDhcpNotification) {
+ transitionTo(mWaitBeforeRenewalState);
+ } else {
+ transitionTo(mDhcpRenewingState);
+ }
+ return HANDLED;
+ default:
+ return NOT_HANDLED;
+ }
+ }
+ }
+
+ // TODO: timeout.
+ class DhcpRenewingState extends PacketRetransmittingState {
+ public DhcpRenewingState() {
+ super();
+ mTimeout = DHCP_TIMEOUT_MS;
+ }
+
+ @Override
+ public void enter() {
+ super.enter();
+ initTransactionId();
+ }
+
+ protected boolean sendPacket() {
+ return sendRequestPacket(
+ (Inet4Address) mDhcpLease.ipAddress.getAddress(), // ciaddr
+ INADDR_ANY, // DHCP_REQUESTED_IP
+ INADDR_ANY, // DHCP_SERVER_IDENTIFIER
+ (Inet4Address) mDhcpLease.serverAddress); // packet destination address
+ }
+
+ protected void receivePacket(DhcpPacket packet) {
+ if (!isValidPacket(packet)) return;
+ if ((packet instanceof DhcpAckPacket)) {
+ DhcpResults results = packet.toDhcpResults();
+ mDhcpLease.leaseDuration = results.leaseDuration;
+ mDhcpLeaseExpiry = SystemClock.elapsedRealtime() +
+ mDhcpLease.leaseDuration * 1000;
+ transitionTo(mDhcpBoundState);
+ } else if (packet instanceof DhcpNakPacket) {
+ transitionTo(mDhcpInitState);
+ }
+ }
+ }
+
+ // Not implemented. DhcpStateMachine does not implement it either.
+ class DhcpRebindingState extends LoggingState {
+ }
+
+ class DhcpInitRebootState extends LoggingState {
+ }
+
+ class DhcpRebootingState extends LoggingState {
+ }
+}
diff --git a/services/net/java/android/net/dhcp/DhcpDeclinePacket.java b/services/net/java/android/net/dhcp/DhcpDeclinePacket.java
new file mode 100644
index 0000000..9d985ac
--- /dev/null
+++ b/services/net/java/android/net/dhcp/DhcpDeclinePacket.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.dhcp;
+
+import java.net.Inet4Address;
+import java.nio.ByteBuffer;
+
+/**
+ * This class implements the DHCP-DECLINE packet.
+ */
+class DhcpDeclinePacket extends DhcpPacket {
+ /**
+ * Generates a DECLINE packet with the specified parameters.
+ */
+ DhcpDeclinePacket(int transId, Inet4Address clientIp, Inet4Address yourIp,
+ Inet4Address nextIp, Inet4Address relayIp,
+ byte[] clientMac) {
+ super(transId, clientIp, yourIp, nextIp, relayIp, clientMac, false);
+ }
+
+ public String toString() {
+ String s = super.toString();
+ return s + " DECLINE";
+ }
+
+ /**
+ * Fills in a packet with the requested DECLINE attributes.
+ */
+ public ByteBuffer buildPacket(int encap, short destUdp, short srcUdp) {
+ ByteBuffer result = ByteBuffer.allocate(MAX_LENGTH);
+
+ fillInPacket(encap, mClientIp, mYourIp, destUdp, srcUdp, result,
+ DHCP_BOOTREQUEST, false);
+ result.flip();
+ return result;
+ }
+
+ /**
+ * Adds optional parameters to the DECLINE packet.
+ */
+ void finishPacket(ByteBuffer buffer) {
+ // None needed
+ }
+}
diff --git a/services/net/java/android/net/dhcp/DhcpDiscoverPacket.java b/services/net/java/android/net/dhcp/DhcpDiscoverPacket.java
new file mode 100644
index 0000000..a031080
--- /dev/null
+++ b/services/net/java/android/net/dhcp/DhcpDiscoverPacket.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.dhcp;
+
+import java.net.Inet4Address;
+import java.nio.ByteBuffer;
+
+/**
+ * This class implements the DHCP-DISCOVER packet.
+ */
+class DhcpDiscoverPacket extends DhcpPacket {
+ /**
+ * Generates a DISCOVER packet with the specified parameters.
+ */
+ DhcpDiscoverPacket(int transId, byte[] clientMac, boolean broadcast) {
+ super(transId, INADDR_ANY, INADDR_ANY, INADDR_ANY, INADDR_ANY, clientMac, broadcast);
+ }
+
+ public String toString() {
+ String s = super.toString();
+ return s + " DISCOVER " +
+ (mBroadcast ? "broadcast " : "unicast ");
+ }
+
+ /**
+ * Fills in a packet with the requested DISCOVER parameters.
+ */
+ public ByteBuffer buildPacket(int encap, short destUdp, short srcUdp) {
+ ByteBuffer result = ByteBuffer.allocate(MAX_LENGTH);
+ fillInPacket(encap, INADDR_BROADCAST, INADDR_ANY, destUdp,
+ srcUdp, result, DHCP_BOOTREQUEST, mBroadcast);
+ result.flip();
+ return result;
+ }
+
+ /**
+ * Adds optional parameters to a DISCOVER packet.
+ */
+ void finishPacket(ByteBuffer buffer) {
+ addTlv(buffer, DHCP_MESSAGE_TYPE, DHCP_MESSAGE_TYPE_DISCOVER);
+ addCommonClientTlvs(buffer);
+ addTlv(buffer, DHCP_PARAMETER_LIST, mRequestedParams);
+ addTlvEnd(buffer);
+ }
+}
diff --git a/services/net/java/android/net/dhcp/DhcpInformPacket.java b/services/net/java/android/net/dhcp/DhcpInformPacket.java
new file mode 100644
index 0000000..8bc7cdd
--- /dev/null
+++ b/services/net/java/android/net/dhcp/DhcpInformPacket.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.dhcp;
+
+import java.net.Inet4Address;
+import java.nio.ByteBuffer;
+
+/**
+ * This class implements the (unused) DHCP-INFORM packet.
+ */
+class DhcpInformPacket extends DhcpPacket {
+ /**
+ * Generates an INFORM packet with the specified parameters.
+ */
+ DhcpInformPacket(int transId, Inet4Address clientIp, Inet4Address yourIp,
+ Inet4Address nextIp, Inet4Address relayIp,
+ byte[] clientMac) {
+ super(transId, clientIp, yourIp, nextIp, relayIp, clientMac, false);
+ }
+
+ public String toString() {
+ String s = super.toString();
+ return s + " INFORM";
+ }
+
+ /**
+ * Builds an INFORM packet.
+ */
+ public ByteBuffer buildPacket(int encap, short destUdp, short srcUdp) {
+ ByteBuffer result = ByteBuffer.allocate(MAX_LENGTH);
+
+ fillInPacket(encap, mClientIp, mYourIp, destUdp, srcUdp, result,
+ DHCP_BOOTREQUEST, false);
+ result.flip();
+ return result;
+ }
+
+ /**
+ * Adds additional parameters to the INFORM packet.
+ */
+ void finishPacket(ByteBuffer buffer) {
+ byte[] clientId = new byte[7];
+
+ clientId[0] = CLIENT_ID_ETHER;
+ System.arraycopy(mClientMac, 0, clientId, 1, 6);
+
+ addTlv(buffer, DHCP_MESSAGE_TYPE, DHCP_MESSAGE_TYPE_REQUEST);
+ addTlv(buffer, DHCP_PARAMETER_LIST, mRequestedParams);
+ addTlvEnd(buffer);
+ }
+}
diff --git a/services/net/java/android/net/dhcp/DhcpNakPacket.java b/services/net/java/android/net/dhcp/DhcpNakPacket.java
new file mode 100644
index 0000000..1390ea7
--- /dev/null
+++ b/services/net/java/android/net/dhcp/DhcpNakPacket.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.dhcp;
+
+import java.net.Inet4Address;
+import java.nio.ByteBuffer;
+
+/**
+ * This class implements the DHCP-NAK packet.
+ */
+class DhcpNakPacket extends DhcpPacket {
+ /**
+ * Generates a NAK packet with the specified parameters.
+ */
+ DhcpNakPacket(int transId, Inet4Address clientIp, Inet4Address yourIp,
+ Inet4Address nextIp, Inet4Address relayIp,
+ byte[] clientMac) {
+ super(transId, INADDR_ANY, INADDR_ANY, nextIp, relayIp,
+ clientMac, false);
+ }
+
+ public String toString() {
+ String s = super.toString();
+ return s + " NAK, reason " + (mMessage == null ? "(none)" : mMessage);
+ }
+
+ /**
+ * Fills in a packet with the requested NAK attributes.
+ */
+ public ByteBuffer buildPacket(int encap, short destUdp, short srcUdp) {
+ ByteBuffer result = ByteBuffer.allocate(MAX_LENGTH);
+ Inet4Address destIp = mClientIp;
+ Inet4Address srcIp = mYourIp;
+
+ fillInPacket(encap, destIp, srcIp, destUdp, srcUdp, result,
+ DHCP_BOOTREPLY, mBroadcast);
+ result.flip();
+ return result;
+ }
+
+ /**
+ * Adds the optional parameters to the client-generated NAK packet.
+ */
+ void finishPacket(ByteBuffer buffer) {
+ addTlv(buffer, DHCP_MESSAGE_TYPE, DHCP_MESSAGE_TYPE_NAK);
+ addTlv(buffer, DHCP_SERVER_IDENTIFIER, mServerIdentifier);
+ addTlv(buffer, DHCP_MESSAGE, mMessage);
+ addTlvEnd(buffer);
+ }
+}
diff --git a/services/net/java/android/net/dhcp/DhcpOfferPacket.java b/services/net/java/android/net/dhcp/DhcpOfferPacket.java
new file mode 100644
index 0000000..b1f3bbd
--- /dev/null
+++ b/services/net/java/android/net/dhcp/DhcpOfferPacket.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.dhcp;
+
+import java.net.Inet4Address;
+import java.nio.ByteBuffer;
+
+/**
+ * This class implements the DHCP-OFFER packet.
+ */
+class DhcpOfferPacket extends DhcpPacket {
+ /**
+ * The IP address of the server which sent this packet.
+ */
+ private final Inet4Address mSrcIp;
+
+ /**
+ * Generates a OFFER packet with the specified parameters.
+ */
+ DhcpOfferPacket(int transId, boolean broadcast, Inet4Address serverAddress,
+ Inet4Address clientIp, byte[] clientMac) {
+ super(transId, INADDR_ANY, clientIp, INADDR_ANY, INADDR_ANY, clientMac, broadcast);
+ mSrcIp = serverAddress;
+ }
+
+ public String toString() {
+ String s = super.toString();
+ String dnsServers = ", DNS servers: ";
+
+ if (mDnsServers != null) {
+ for (Inet4Address dnsServer: mDnsServers) {
+ dnsServers += dnsServer + " ";
+ }
+ }
+
+ return s + " OFFER, ip " + mYourIp + ", mask " + mSubnetMask +
+ dnsServers + ", gateway " + mGateway +
+ " lease time " + mLeaseTime + ", domain " + mDomainName;
+ }
+
+ /**
+ * Fills in a packet with the specified OFFER attributes.
+ */
+ public ByteBuffer buildPacket(int encap, short destUdp, short srcUdp) {
+ ByteBuffer result = ByteBuffer.allocate(MAX_LENGTH);
+ Inet4Address destIp = mBroadcast ? INADDR_BROADCAST : mYourIp;
+ Inet4Address srcIp = mBroadcast ? INADDR_ANY : mSrcIp;
+
+ fillInPacket(encap, destIp, srcIp, destUdp, srcUdp, result,
+ DHCP_BOOTREPLY, mBroadcast);
+ result.flip();
+ return result;
+ }
+
+ /**
+ * Adds the optional parameters to the server-generated OFFER packet.
+ */
+ void finishPacket(ByteBuffer buffer) {
+ addTlv(buffer, DHCP_MESSAGE_TYPE, DHCP_MESSAGE_TYPE_OFFER);
+ addTlv(buffer, DHCP_SERVER_IDENTIFIER, mServerIdentifier);
+ addTlv(buffer, DHCP_LEASE_TIME, mLeaseTime);
+
+ // the client should renew at 1/2 the lease-expiry interval
+ if (mLeaseTime != null) {
+ addTlv(buffer, DHCP_RENEWAL_TIME,
+ Integer.valueOf(mLeaseTime.intValue() / 2));
+ }
+
+ addTlv(buffer, DHCP_SUBNET_MASK, mSubnetMask);
+ addTlv(buffer, DHCP_ROUTER, mGateway);
+ addTlv(buffer, DHCP_DOMAIN_NAME, mDomainName);
+ addTlv(buffer, DHCP_BROADCAST_ADDRESS, mBroadcastAddress);
+ addTlv(buffer, DHCP_DNS_SERVER, mDnsServers);
+ addTlvEnd(buffer);
+ }
+}
diff --git a/services/net/java/android/net/dhcp/DhcpPacket.java b/services/net/java/android/net/dhcp/DhcpPacket.java
new file mode 100644
index 0000000..a232a6e
--- /dev/null
+++ b/services/net/java/android/net/dhcp/DhcpPacket.java
@@ -0,0 +1,1065 @@
+package android.net.dhcp;
+
+import android.net.DhcpResults;
+import android.net.LinkAddress;
+import android.net.NetworkUtils;
+import android.os.Build;
+import android.os.SystemProperties;
+import android.system.OsConstants;
+
+import java.io.UnsupportedEncodingException;
+import java.net.Inet4Address;
+import java.net.UnknownHostException;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+import java.nio.ShortBuffer;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Defines basic data and operations needed to build and use packets for the
+ * DHCP protocol. Subclasses create the specific packets used at each
+ * stage of the negotiation.
+ */
+abstract class DhcpPacket {
+ protected static final String TAG = "DhcpPacket";
+
+ public static final Inet4Address INADDR_ANY = (Inet4Address) Inet4Address.ANY;
+ public static final Inet4Address INADDR_BROADCAST = (Inet4Address) Inet4Address.ALL;
+ public static final byte[] ETHER_BROADCAST = new byte[] {
+ (byte) 0xff, (byte) 0xff, (byte) 0xff,
+ (byte) 0xff, (byte) 0xff, (byte) 0xff,
+ };
+
+ /**
+ * Packet encapsulations.
+ */
+ public static final int ENCAP_L2 = 0; // EthernetII header included
+ public static final int ENCAP_L3 = 1; // IP/UDP header included
+ public static final int ENCAP_BOOTP = 2; // BOOTP contents only
+
+ /**
+ * Minimum length of a DHCP packet, excluding options, in the above encapsulations.
+ */
+ public static final int MIN_PACKET_LENGTH_BOOTP = 236; // See diagram in RFC 2131, section 2.
+ public static final int MIN_PACKET_LENGTH_L3 = MIN_PACKET_LENGTH_BOOTP + 20 + 8;
+ public static final int MIN_PACKET_LENGTH_L2 = MIN_PACKET_LENGTH_L3 + 14;
+
+ public static final int MAX_OPTION_LEN = 255;
+ /**
+ * IP layer definitions.
+ */
+ private static final byte IP_TYPE_UDP = (byte) 0x11;
+
+ /**
+ * IP: Version 4, Header Length 20 bytes
+ */
+ private static final byte IP_VERSION_HEADER_LEN = (byte) 0x45;
+
+ /**
+ * IP: Flags 0, Fragment Offset 0, Don't Fragment
+ */
+ private static final short IP_FLAGS_OFFSET = (short) 0x4000;
+
+ /**
+ * IP: TOS
+ */
+ private static final byte IP_TOS_LOWDELAY = (byte) 0x10;
+
+ /**
+ * IP: TTL -- use default 64 from RFC1340
+ */
+ private static final byte IP_TTL = (byte) 0x40;
+
+ /**
+ * The client DHCP port.
+ */
+ static final short DHCP_CLIENT = (short) 68;
+
+ /**
+ * The server DHCP port.
+ */
+ static final short DHCP_SERVER = (short) 67;
+
+ /**
+ * The message op code indicating a request from a client.
+ */
+ protected static final byte DHCP_BOOTREQUEST = (byte) 1;
+
+ /**
+ * The message op code indicating a response from the server.
+ */
+ protected static final byte DHCP_BOOTREPLY = (byte) 2;
+
+ /**
+ * The code type used to identify an Ethernet MAC address in the
+ * Client-ID field.
+ */
+ protected static final byte CLIENT_ID_ETHER = (byte) 1;
+
+ /**
+ * The maximum length of a packet that can be constructed.
+ */
+ protected static final int MAX_LENGTH = 1500;
+
+ /**
+ * DHCP Optional Type: DHCP Subnet Mask
+ */
+ protected static final byte DHCP_SUBNET_MASK = 1;
+ protected Inet4Address mSubnetMask;
+
+ /**
+ * DHCP Optional Type: DHCP Router
+ */
+ protected static final byte DHCP_ROUTER = 3;
+ protected Inet4Address mGateway;
+
+ /**
+ * DHCP Optional Type: DHCP DNS Server
+ */
+ protected static final byte DHCP_DNS_SERVER = 6;
+ protected List<Inet4Address> mDnsServers;
+
+ /**
+ * DHCP Optional Type: DHCP Host Name
+ */
+ protected static final byte DHCP_HOST_NAME = 12;
+ protected String mHostName;
+
+ /**
+ * DHCP Optional Type: DHCP DOMAIN NAME
+ */
+ protected static final byte DHCP_DOMAIN_NAME = 15;
+ protected String mDomainName;
+
+ /**
+ * DHCP Optional Type: DHCP Interface MTU
+ */
+ protected static final byte DHCP_MTU = 26;
+ protected Short mMtu;
+
+ /**
+ * DHCP Optional Type: DHCP BROADCAST ADDRESS
+ */
+ protected static final byte DHCP_BROADCAST_ADDRESS = 28;
+ protected Inet4Address mBroadcastAddress;
+
+ /**
+ * DHCP Optional Type: DHCP Requested IP Address
+ */
+ protected static final byte DHCP_REQUESTED_IP = 50;
+ protected Inet4Address mRequestedIp;
+
+ /**
+ * DHCP Optional Type: DHCP Lease Time
+ */
+ protected static final byte DHCP_LEASE_TIME = 51;
+ protected Integer mLeaseTime;
+
+ /**
+ * DHCP Optional Type: DHCP Message Type
+ */
+ protected static final byte DHCP_MESSAGE_TYPE = 53;
+ // the actual type values
+ protected static final byte DHCP_MESSAGE_TYPE_DISCOVER = 1;
+ protected static final byte DHCP_MESSAGE_TYPE_OFFER = 2;
+ protected static final byte DHCP_MESSAGE_TYPE_REQUEST = 3;
+ protected static final byte DHCP_MESSAGE_TYPE_DECLINE = 4;
+ protected static final byte DHCP_MESSAGE_TYPE_ACK = 5;
+ protected static final byte DHCP_MESSAGE_TYPE_NAK = 6;
+ protected static final byte DHCP_MESSAGE_TYPE_INFORM = 8;
+
+ /**
+ * DHCP Optional Type: DHCP Server Identifier
+ */
+ protected static final byte DHCP_SERVER_IDENTIFIER = 54;
+ protected Inet4Address mServerIdentifier;
+
+ /**
+ * DHCP Optional Type: DHCP Parameter List
+ */
+ protected static final byte DHCP_PARAMETER_LIST = 55;
+ protected byte[] mRequestedParams;
+
+ /**
+ * DHCP Optional Type: DHCP MESSAGE
+ */
+ protected static final byte DHCP_MESSAGE = 56;
+ protected String mMessage;
+
+ /**
+ * DHCP Optional Type: Maximum DHCP Message Size
+ */
+ protected static final byte DHCP_MAX_MESSAGE_SIZE = 57;
+ protected Short mMaxMessageSize;
+
+ /**
+ * DHCP Optional Type: DHCP Renewal Time Value
+ */
+ protected static final byte DHCP_RENEWAL_TIME = 58;
+ protected Integer mT1;
+
+ /**
+ * DHCP Optional Type: Rebinding Time Value
+ */
+ protected static final byte DHCP_REBINDING_TIME = 59;
+ protected Integer mT2;
+
+ /**
+ * DHCP Optional Type: Vendor Class Identifier
+ */
+ protected static final byte DHCP_VENDOR_CLASS_ID = 60;
+ protected String mVendorId;
+
+ /**
+ * DHCP Optional Type: DHCP Client Identifier
+ */
+ protected static final byte DHCP_CLIENT_IDENTIFIER = 61;
+
+ /**
+ * The transaction identifier used in this particular DHCP negotiation
+ */
+ protected final int mTransId;
+
+ /**
+ * The IP address of the client host. This address is typically
+ * proposed by the client (from an earlier DHCP negotiation) or
+ * supplied by the server.
+ */
+ protected final Inet4Address mClientIp;
+ protected final Inet4Address mYourIp;
+ private final Inet4Address mNextIp;
+ private final Inet4Address mRelayIp;
+
+ /**
+ * Does the client request a broadcast response?
+ */
+ protected boolean mBroadcast;
+
+ /**
+ * The six-octet MAC of the client.
+ */
+ protected final byte[] mClientMac;
+
+ /**
+ * Asks the packet object to create a ByteBuffer serialization of
+ * the packet for transmission.
+ */
+ public abstract ByteBuffer buildPacket(int encap, short destUdp,
+ short srcUdp);
+
+ /**
+ * Allows the concrete class to fill in packet-type-specific details,
+ * typically optional parameters at the end of the packet.
+ */
+ abstract void finishPacket(ByteBuffer buffer);
+
+ protected DhcpPacket(int transId, Inet4Address clientIp, Inet4Address yourIp,
+ Inet4Address nextIp, Inet4Address relayIp,
+ byte[] clientMac, boolean broadcast) {
+ mTransId = transId;
+ mClientIp = clientIp;
+ mYourIp = yourIp;
+ mNextIp = nextIp;
+ mRelayIp = relayIp;
+ mClientMac = clientMac;
+ mBroadcast = broadcast;
+ }
+
+ /**
+ * Returns the transaction ID.
+ */
+ public int getTransactionId() {
+ return mTransId;
+ }
+
+ /**
+ * Returns the client MAC.
+ */
+ public byte[] getClientMac() {
+ return mClientMac;
+ }
+
+ /**
+ * Creates a new L3 packet (including IP header) containing the
+ * DHCP udp packet. This method relies upon the delegated method
+ * finishPacket() to insert the per-packet contents.
+ */
+ protected void fillInPacket(int encap, Inet4Address destIp,
+ Inet4Address srcIp, short destUdp, short srcUdp, ByteBuffer buf,
+ byte requestCode, boolean broadcast) {
+ byte[] destIpArray = destIp.getAddress();
+ byte[] srcIpArray = srcIp.getAddress();
+ int ipHeaderOffset = 0;
+ int ipLengthOffset = 0;
+ int ipChecksumOffset = 0;
+ int endIpHeader = 0;
+ int udpHeaderOffset = 0;
+ int udpLengthOffset = 0;
+ int udpChecksumOffset = 0;
+
+ buf.clear();
+ buf.order(ByteOrder.BIG_ENDIAN);
+
+ if (encap == ENCAP_L2) {
+ buf.put(ETHER_BROADCAST);
+ buf.put(mClientMac);
+ buf.putShort((short) OsConstants.ETH_P_IP);
+ }
+
+ // if a full IP packet needs to be generated, put the IP & UDP
+ // headers in place, and pre-populate with artificial values
+ // needed to seed the IP checksum.
+ if (encap <= ENCAP_L3) {
+ ipHeaderOffset = buf.position();
+ buf.put(IP_VERSION_HEADER_LEN);
+ buf.put(IP_TOS_LOWDELAY); // tos: IPTOS_LOWDELAY
+ ipLengthOffset = buf.position();
+ buf.putShort((short)0); // length
+ buf.putShort((short)0); // id
+ buf.putShort(IP_FLAGS_OFFSET); // ip offset: don't fragment
+ buf.put(IP_TTL); // TTL: use default 64 from RFC1340
+ buf.put(IP_TYPE_UDP);
+ ipChecksumOffset = buf.position();
+ buf.putShort((short) 0); // checksum
+
+ buf.put(srcIpArray);
+ buf.put(destIpArray);
+ endIpHeader = buf.position();
+
+ // UDP header
+ udpHeaderOffset = buf.position();
+ buf.putShort(srcUdp);
+ buf.putShort(destUdp);
+ udpLengthOffset = buf.position();
+ buf.putShort((short) 0); // length
+ udpChecksumOffset = buf.position();
+ buf.putShort((short) 0); // UDP checksum -- initially zero
+ }
+
+ // DHCP payload
+ buf.put(requestCode);
+ buf.put((byte) 1); // Hardware Type: Ethernet
+ buf.put((byte) mClientMac.length); // Hardware Address Length
+ buf.put((byte) 0); // Hop Count
+ buf.putInt(mTransId); // Transaction ID
+ buf.putShort((short) 0); // Elapsed Seconds
+
+ if (broadcast) {
+ buf.putShort((short) 0x8000); // Flags
+ } else {
+ buf.putShort((short) 0x0000); // Flags
+ }
+
+ buf.put(mClientIp.getAddress());
+ buf.put(mYourIp.getAddress());
+ buf.put(mNextIp.getAddress());
+ buf.put(mRelayIp.getAddress());
+ buf.put(mClientMac);
+ buf.position(buf.position() +
+ (16 - mClientMac.length) // pad addr to 16 bytes
+ + 64 // empty server host name (64 bytes)
+ + 128); // empty boot file name (128 bytes)
+ buf.putInt(0x63825363); // magic number
+ finishPacket(buf);
+
+ // round up to an even number of octets
+ if ((buf.position() & 1) == 1) {
+ buf.put((byte) 0);
+ }
+
+ // If an IP packet is being built, the IP & UDP checksums must be
+ // computed.
+ if (encap <= ENCAP_L3) {
+ // fix UDP header: insert length
+ short udpLen = (short)(buf.position() - udpHeaderOffset);
+ buf.putShort(udpLengthOffset, udpLen);
+ // fix UDP header: checksum
+ // checksum for UDP at udpChecksumOffset
+ int udpSeed = 0;
+
+ // apply IPv4 pseudo-header. Read IP address src and destination
+ // values from the IP header and accumulate checksum.
+ udpSeed += intAbs(buf.getShort(ipChecksumOffset + 2));
+ udpSeed += intAbs(buf.getShort(ipChecksumOffset + 4));
+ udpSeed += intAbs(buf.getShort(ipChecksumOffset + 6));
+ udpSeed += intAbs(buf.getShort(ipChecksumOffset + 8));
+
+ // accumulate extra data for the pseudo-header
+ udpSeed += IP_TYPE_UDP;
+ udpSeed += udpLen;
+ // and compute UDP checksum
+ buf.putShort(udpChecksumOffset, (short) checksum(buf, udpSeed,
+ udpHeaderOffset,
+ buf.position()));
+ // fix IP header: insert length
+ buf.putShort(ipLengthOffset, (short)(buf.position() - ipHeaderOffset));
+ // fixup IP-header checksum
+ buf.putShort(ipChecksumOffset,
+ (short) checksum(buf, 0, ipHeaderOffset, endIpHeader));
+ }
+ }
+
+ /**
+ * Converts a signed short value to an unsigned int value. Needed
+ * because Java does not have unsigned types.
+ */
+ private static int intAbs(short v) {
+ return v & 0xFFFF;
+ }
+
+ /**
+ * Performs an IP checksum (used in IP header and across UDP
+ * payload) on the specified portion of a ByteBuffer. The seed
+ * allows the checksum to commence with a specified value.
+ */
+ private int checksum(ByteBuffer buf, int seed, int start, int end) {
+ int sum = seed;
+ int bufPosition = buf.position();
+
+ // set position of original ByteBuffer, so that the ShortBuffer
+ // will be correctly initialized
+ buf.position(start);
+ ShortBuffer shortBuf = buf.asShortBuffer();
+
+ // re-set ByteBuffer position
+ buf.position(bufPosition);
+
+ short[] shortArray = new short[(end - start) / 2];
+ shortBuf.get(shortArray);
+
+ for (short s : shortArray) {
+ sum += intAbs(s);
+ }
+
+ start += shortArray.length * 2;
+
+ // see if a singleton byte remains
+ if (end != start) {
+ short b = buf.get(start);
+
+ // make it unsigned
+ if (b < 0) {
+ b += 256;
+ }
+
+ sum += b * 256;
+ }
+
+ sum = ((sum >> 16) & 0xFFFF) + (sum & 0xFFFF);
+ sum = ((sum + ((sum >> 16) & 0xFFFF)) & 0xFFFF);
+ int negated = ~sum;
+ return intAbs((short) negated);
+ }
+
+ /**
+ * Adds an optional parameter containing a single byte value.
+ */
+ protected static void addTlv(ByteBuffer buf, byte type, byte value) {
+ buf.put(type);
+ buf.put((byte) 1);
+ buf.put(value);
+ }
+
+ /**
+ * Adds an optional parameter containing an array of bytes.
+ */
+ protected static void addTlv(ByteBuffer buf, byte type, byte[] payload) {
+ if (payload != null) {
+ if (payload.length > MAX_OPTION_LEN) {
+ throw new IllegalArgumentException("DHCP option too long: "
+ + payload.length + " vs. " + MAX_OPTION_LEN);
+ }
+ buf.put(type);
+ buf.put((byte) payload.length);
+ buf.put(payload);
+ }
+ }
+
+ /**
+ * Adds an optional parameter containing an IP address.
+ */
+ protected static void addTlv(ByteBuffer buf, byte type, Inet4Address addr) {
+ if (addr != null) {
+ addTlv(buf, type, addr.getAddress());
+ }
+ }
+
+ /**
+ * Adds an optional parameter containing a list of IP addresses.
+ */
+ protected static void addTlv(ByteBuffer buf, byte type, List<Inet4Address> addrs) {
+ if (addrs == null || addrs.size() == 0) return;
+
+ int optionLen = 4 * addrs.size();
+ if (optionLen > MAX_OPTION_LEN) {
+ throw new IllegalArgumentException("DHCP option too long: "
+ + optionLen + " vs. " + MAX_OPTION_LEN);
+ }
+
+ buf.put(type);
+ buf.put((byte)(optionLen));
+
+ for (Inet4Address addr : addrs) {
+ buf.put(addr.getAddress());
+ }
+ }
+
+ /**
+ * Adds an optional parameter containing a short integer
+ */
+ protected static void addTlv(ByteBuffer buf, byte type, Short value) {
+ if (value != null) {
+ buf.put(type);
+ buf.put((byte) 2);
+ buf.putShort(value.shortValue());
+ }
+ }
+
+ /**
+ * Adds an optional parameter containing a simple integer
+ */
+ protected static void addTlv(ByteBuffer buf, byte type, Integer value) {
+ if (value != null) {
+ buf.put(type);
+ buf.put((byte) 4);
+ buf.putInt(value.intValue());
+ }
+ }
+
+ /**
+ * Adds an optional parameter containing an ASCII string.
+ */
+ protected static void addTlv(ByteBuffer buf, byte type, String str) {
+ try {
+ addTlv(buf, type, str.getBytes("US-ASCII"));
+ } catch (UnsupportedEncodingException e) {
+ throw new IllegalArgumentException("String is not US-ASCII: " + str);
+ }
+ }
+
+ /**
+ * Adds the special end-of-optional-parameters indicator.
+ */
+ protected static void addTlvEnd(ByteBuffer buf) {
+ buf.put((byte) 0xFF);
+ }
+
+ /**
+ * Adds common client TLVs.
+ *
+ * TODO: Does this belong here? The alternative would be to modify all the buildXyzPacket
+ * methods to take them.
+ */
+ protected void addCommonClientTlvs(ByteBuffer buf) {
+ addTlv(buf, DHCP_MAX_MESSAGE_SIZE, (short) MAX_LENGTH);
+ addTlv(buf, DHCP_VENDOR_CLASS_ID, "android-dhcp-" + Build.VERSION.RELEASE);
+ addTlv(buf, DHCP_HOST_NAME, SystemProperties.get("net.hostname"));
+ }
+
+ /**
+ * Converts a MAC from an array of octets to an ASCII string.
+ */
+ public static String macToString(byte[] mac) {
+ String macAddr = "";
+
+ for (int i = 0; i < mac.length; i++) {
+ String hexString = "0" + Integer.toHexString(mac[i]);
+
+ // substring operation grabs the last 2 digits: this
+ // allows signed bytes to be converted correctly.
+ macAddr += hexString.substring(hexString.length() - 2);
+
+ if (i != (mac.length - 1)) {
+ macAddr += ":";
+ }
+ }
+
+ return macAddr;
+ }
+
+ public String toString() {
+ String macAddr = macToString(mClientMac);
+
+ return macAddr;
+ }
+
+ /**
+ * Reads a four-octet value from a ByteBuffer and construct
+ * an IPv4 address from that value.
+ */
+ private static Inet4Address readIpAddress(ByteBuffer packet) {
+ Inet4Address result = null;
+ byte[] ipAddr = new byte[4];
+ packet.get(ipAddr);
+
+ try {
+ result = (Inet4Address) Inet4Address.getByAddress(ipAddr);
+ } catch (UnknownHostException ex) {
+ // ipAddr is numeric, so this should not be
+ // triggered. However, if it is, just nullify
+ result = null;
+ }
+
+ return result;
+ }
+
+ /**
+ * Reads a string of specified length from the buffer.
+ */
+ private static String readAsciiString(ByteBuffer buf, int byteCount) {
+ byte[] bytes = new byte[byteCount];
+ buf.get(bytes);
+ return new String(bytes, 0, bytes.length, StandardCharsets.US_ASCII);
+ }
+
+ /**
+ * Creates a concrete DhcpPacket from the supplied ByteBuffer. The
+ * buffer may have an L2 encapsulation (which is the full EthernetII
+ * format starting with the source-address MAC) or an L3 encapsulation
+ * (which starts with the IP header).
+ * <br>
+ * A subset of the optional parameters are parsed and are stored
+ * in object fields.
+ */
+ public static DhcpPacket decodeFullPacket(ByteBuffer packet, int pktType)
+ {
+ // bootp parameters
+ int transactionId;
+ Inet4Address clientIp;
+ Inet4Address yourIp;
+ Inet4Address nextIp;
+ Inet4Address relayIp;
+ byte[] clientMac;
+ List<Inet4Address> dnsServers = new ArrayList<Inet4Address>();
+ Inet4Address gateway = null; // aka router
+ Inet4Address serverIdentifier = null;
+ Inet4Address netMask = null;
+ String message = null;
+ String vendorId = null;
+ byte[] expectedParams = null;
+ String hostName = null;
+ String domainName = null;
+ Inet4Address ipSrc = null;
+ Inet4Address ipDst = null;
+ Inet4Address bcAddr = null;
+ Inet4Address requestedIp = null;
+
+ // The following are all unsigned integers. Internally we store them as signed integers of
+ // the same length because that way we're guaranteed that they can't be out of the range of
+ // the unsigned field in the packet. Callers wanting to pass in an unsigned value will need
+ // to cast it.
+ Short mtu = null;
+ Short maxMessageSize = null;
+ Integer leaseTime = null;
+ Integer T1 = null;
+ Integer T2 = null;
+
+ // dhcp options
+ byte dhcpType = (byte) 0xFF;
+
+ packet.order(ByteOrder.BIG_ENDIAN);
+
+ // check to see if we need to parse L2, IP, and UDP encaps
+ if (pktType == ENCAP_L2) {
+ if (packet.remaining() < MIN_PACKET_LENGTH_L2) {
+ return null;
+ }
+
+ byte[] l2dst = new byte[6];
+ byte[] l2src = new byte[6];
+
+ packet.get(l2dst);
+ packet.get(l2src);
+
+ short l2type = packet.getShort();
+
+ if (l2type != OsConstants.ETH_P_IP)
+ return null;
+ }
+
+ if (pktType <= ENCAP_L3) {
+ if (packet.remaining() < MIN_PACKET_LENGTH_L3) {
+ return null;
+ }
+
+ byte ipTypeAndLength = packet.get();
+ int ipVersion = (ipTypeAndLength & 0xf0) >> 4;
+ if (ipVersion != 4) {
+ return null;
+ }
+
+ // System.out.println("ipType is " + ipType);
+ byte ipDiffServicesField = packet.get();
+ short ipTotalLength = packet.getShort();
+ short ipIdentification = packet.getShort();
+ byte ipFlags = packet.get();
+ byte ipFragOffset = packet.get();
+ byte ipTTL = packet.get();
+ byte ipProto = packet.get();
+ short ipChksm = packet.getShort();
+
+ ipSrc = readIpAddress(packet);
+ ipDst = readIpAddress(packet);
+
+ if (ipProto != IP_TYPE_UDP) // UDP
+ return null;
+
+ // Skip options. This cannot cause us to read beyond the end of the buffer because the
+ // IPv4 header cannot be more than (0x0f * 4) = 60 bytes long, and that is less than
+ // MIN_PACKET_LENGTH_L3.
+ int optionWords = ((ipTypeAndLength & 0x0f) - 5);
+ for (int i = 0; i < optionWords; i++) {
+ packet.getInt();
+ }
+
+ // assume UDP
+ short udpSrcPort = packet.getShort();
+ short udpDstPort = packet.getShort();
+ short udpLen = packet.getShort();
+ short udpChkSum = packet.getShort();
+
+ if ((udpSrcPort != DHCP_SERVER) && (udpSrcPort != DHCP_CLIENT))
+ return null;
+ }
+
+ // We need to check the length even for ENCAP_L3 because the IPv4 header is variable-length.
+ if (pktType > ENCAP_BOOTP || packet.remaining() < MIN_PACKET_LENGTH_BOOTP) {
+ return null;
+ }
+
+ byte type = packet.get();
+ byte hwType = packet.get();
+ byte addrLen = packet.get();
+ byte hops = packet.get();
+ transactionId = packet.getInt();
+ short elapsed = packet.getShort();
+ short bootpFlags = packet.getShort();
+ boolean broadcast = (bootpFlags & 0x8000) != 0;
+ byte[] ipv4addr = new byte[4];
+
+ try {
+ packet.get(ipv4addr);
+ clientIp = (Inet4Address) Inet4Address.getByAddress(ipv4addr);
+ packet.get(ipv4addr);
+ yourIp = (Inet4Address) Inet4Address.getByAddress(ipv4addr);
+ packet.get(ipv4addr);
+ nextIp = (Inet4Address) Inet4Address.getByAddress(ipv4addr);
+ packet.get(ipv4addr);
+ relayIp = (Inet4Address) Inet4Address.getByAddress(ipv4addr);
+ } catch (UnknownHostException ex) {
+ return null;
+ }
+
+ clientMac = new byte[addrLen];
+ packet.get(clientMac);
+
+ // skip over address padding (16 octets allocated)
+ packet.position(packet.position() + (16 - addrLen)
+ + 64 // skip server host name (64 chars)
+ + 128); // skip boot file name (128 chars)
+
+ int dhcpMagicCookie = packet.getInt();
+
+ if (dhcpMagicCookie != 0x63825363)
+ return null;
+
+ // parse options
+ boolean notFinishedOptions = true;
+
+ while ((packet.position() < packet.limit()) && notFinishedOptions) {
+ try {
+ byte optionType = packet.get();
+
+ if (optionType == (byte) 0xFF) {
+ notFinishedOptions = false;
+ } else {
+ int optionLen = packet.get() & 0xFF;
+ int expectedLen = 0;
+
+ switch(optionType) {
+ case DHCP_SUBNET_MASK:
+ netMask = readIpAddress(packet);
+ expectedLen = 4;
+ break;
+ case DHCP_ROUTER:
+ gateway = readIpAddress(packet);
+ expectedLen = 4;
+ break;
+ case DHCP_DNS_SERVER:
+ for (expectedLen = 0; expectedLen < optionLen; expectedLen += 4) {
+ dnsServers.add(readIpAddress(packet));
+ }
+ break;
+ case DHCP_HOST_NAME:
+ expectedLen = optionLen;
+ hostName = readAsciiString(packet, optionLen);
+ break;
+ case DHCP_MTU:
+ expectedLen = 2;
+ mtu = Short.valueOf(packet.getShort());
+ break;
+ case DHCP_DOMAIN_NAME:
+ expectedLen = optionLen;
+ domainName = readAsciiString(packet, optionLen);
+ break;
+ case DHCP_BROADCAST_ADDRESS:
+ bcAddr = readIpAddress(packet);
+ expectedLen = 4;
+ break;
+ case DHCP_REQUESTED_IP:
+ requestedIp = readIpAddress(packet);
+ expectedLen = 4;
+ break;
+ case DHCP_LEASE_TIME:
+ leaseTime = Integer.valueOf(packet.getInt());
+ expectedLen = 4;
+ break;
+ case DHCP_MESSAGE_TYPE:
+ dhcpType = packet.get();
+ expectedLen = 1;
+ break;
+ case DHCP_SERVER_IDENTIFIER:
+ serverIdentifier = readIpAddress(packet);
+ expectedLen = 4;
+ break;
+ case DHCP_PARAMETER_LIST:
+ expectedParams = new byte[optionLen];
+ packet.get(expectedParams);
+ expectedLen = optionLen;
+ break;
+ case DHCP_MESSAGE:
+ expectedLen = optionLen;
+ message = readAsciiString(packet, optionLen);
+ break;
+ case DHCP_MAX_MESSAGE_SIZE:
+ expectedLen = 2;
+ maxMessageSize = Short.valueOf(packet.getShort());
+ break;
+ case DHCP_RENEWAL_TIME:
+ expectedLen = 4;
+ T1 = Integer.valueOf(packet.getInt());
+ break;
+ case DHCP_REBINDING_TIME:
+ expectedLen = 4;
+ T2 = Integer.valueOf(packet.getInt());
+ break;
+ case DHCP_VENDOR_CLASS_ID:
+ expectedLen = optionLen;
+ vendorId = readAsciiString(packet, optionLen);
+ break;
+ case DHCP_CLIENT_IDENTIFIER: { // Client identifier
+ byte[] id = new byte[optionLen];
+ packet.get(id);
+ expectedLen = optionLen;
+ } break;
+ default:
+ // ignore any other parameters
+ for (int i = 0; i < optionLen; i++) {
+ expectedLen++;
+ byte throwaway = packet.get();
+ }
+ }
+
+ if (expectedLen != optionLen) {
+ return null;
+ }
+ }
+ } catch (BufferUnderflowException e) {
+ return null;
+ }
+ }
+
+ DhcpPacket newPacket;
+
+ switch(dhcpType) {
+ case -1: return null;
+ case DHCP_MESSAGE_TYPE_DISCOVER:
+ newPacket = new DhcpDiscoverPacket(
+ transactionId, clientMac, broadcast);
+ break;
+ case DHCP_MESSAGE_TYPE_OFFER:
+ newPacket = new DhcpOfferPacket(
+ transactionId, broadcast, ipSrc, yourIp, clientMac);
+ break;
+ case DHCP_MESSAGE_TYPE_REQUEST:
+ newPacket = new DhcpRequestPacket(
+ transactionId, clientIp, clientMac, broadcast);
+ break;
+ case DHCP_MESSAGE_TYPE_DECLINE:
+ newPacket = new DhcpDeclinePacket(
+ transactionId, clientIp, yourIp, nextIp, relayIp,
+ clientMac);
+ break;
+ case DHCP_MESSAGE_TYPE_ACK:
+ newPacket = new DhcpAckPacket(
+ transactionId, broadcast, ipSrc, yourIp, clientMac);
+ break;
+ case DHCP_MESSAGE_TYPE_NAK:
+ newPacket = new DhcpNakPacket(
+ transactionId, clientIp, yourIp, nextIp, relayIp,
+ clientMac);
+ break;
+ case DHCP_MESSAGE_TYPE_INFORM:
+ newPacket = new DhcpInformPacket(
+ transactionId, clientIp, yourIp, nextIp, relayIp,
+ clientMac);
+ break;
+ default:
+ System.out.println("Unimplemented type: " + dhcpType);
+ return null;
+ }
+
+ newPacket.mBroadcastAddress = bcAddr;
+ newPacket.mDnsServers = dnsServers;
+ newPacket.mDomainName = domainName;
+ newPacket.mGateway = gateway;
+ newPacket.mHostName = hostName;
+ newPacket.mLeaseTime = leaseTime;
+ newPacket.mMessage = message;
+ newPacket.mMtu = mtu;
+ newPacket.mRequestedIp = requestedIp;
+ newPacket.mRequestedParams = expectedParams;
+ newPacket.mServerIdentifier = serverIdentifier;
+ newPacket.mSubnetMask = netMask;
+ newPacket.mMaxMessageSize = maxMessageSize;
+ newPacket.mT1 = T1;
+ newPacket.mT2 = T2;
+ newPacket.mVendorId = vendorId;
+ return newPacket;
+ }
+
+ /**
+ * Parse a packet from an array of bytes, stopping at the given length.
+ */
+ public static DhcpPacket decodeFullPacket(byte[] packet, int length, int pktType)
+ {
+ ByteBuffer buffer = ByteBuffer.wrap(packet, 0, length).order(ByteOrder.BIG_ENDIAN);
+ return decodeFullPacket(buffer, pktType);
+ }
+
+ /**
+ * Construct a DhcpResults object from a DHCP reply packet.
+ */
+ public DhcpResults toDhcpResults() {
+ Inet4Address ipAddress = mYourIp;
+ if (ipAddress == Inet4Address.ANY) {
+ ipAddress = mClientIp;
+ if (ipAddress == Inet4Address.ANY) {
+ return null;
+ }
+ }
+
+ int prefixLength;
+ if (mSubnetMask != null) {
+ try {
+ prefixLength = NetworkUtils.netmaskToPrefixLength(mSubnetMask);
+ } catch (IllegalArgumentException e) {
+ // Non-contiguous netmask.
+ return null;
+ }
+ } else {
+ prefixLength = NetworkUtils.getImplicitNetmask(ipAddress);
+ }
+
+ DhcpResults results = new DhcpResults();
+ try {
+ results.ipAddress = new LinkAddress(ipAddress, prefixLength);
+ } catch (IllegalArgumentException e) {
+ return null;
+ }
+ results.gateway = mGateway;
+ results.dnsServers.addAll(mDnsServers);
+ results.domains = mDomainName;
+ results.serverAddress = mServerIdentifier;
+ results.vendorInfo = mVendorId;
+ results.leaseDuration = mLeaseTime;
+ return results;
+ }
+
+ /**
+ * Builds a DHCP-DISCOVER packet from the required specified
+ * parameters.
+ */
+ public static ByteBuffer buildDiscoverPacket(int encap, int transactionId,
+ byte[] clientMac, boolean broadcast, byte[] expectedParams) {
+ DhcpPacket pkt = new DhcpDiscoverPacket(
+ transactionId, clientMac, broadcast);
+ pkt.mRequestedParams = expectedParams;
+ return pkt.buildPacket(encap, DHCP_SERVER, DHCP_CLIENT);
+ }
+
+ /**
+ * Builds a DHCP-OFFER packet from the required specified
+ * parameters.
+ */
+ public static ByteBuffer buildOfferPacket(int encap, int transactionId,
+ boolean broadcast, Inet4Address serverIpAddr, Inet4Address clientIpAddr,
+ byte[] mac, Integer timeout, Inet4Address netMask, Inet4Address bcAddr,
+ Inet4Address gateway, List<Inet4Address> dnsServers,
+ Inet4Address dhcpServerIdentifier, String domainName) {
+ DhcpPacket pkt = new DhcpOfferPacket(
+ transactionId, broadcast, serverIpAddr, clientIpAddr, mac);
+ pkt.mGateway = gateway;
+ pkt.mDnsServers = dnsServers;
+ pkt.mLeaseTime = timeout;
+ pkt.mDomainName = domainName;
+ pkt.mServerIdentifier = dhcpServerIdentifier;
+ pkt.mSubnetMask = netMask;
+ pkt.mBroadcastAddress = bcAddr;
+ return pkt.buildPacket(encap, DHCP_CLIENT, DHCP_SERVER);
+ }
+
+ /**
+ * Builds a DHCP-ACK packet from the required specified parameters.
+ */
+ public static ByteBuffer buildAckPacket(int encap, int transactionId,
+ boolean broadcast, Inet4Address serverIpAddr, Inet4Address clientIpAddr,
+ byte[] mac, Integer timeout, Inet4Address netMask, Inet4Address bcAddr,
+ Inet4Address gateway, List<Inet4Address> dnsServers,
+ Inet4Address dhcpServerIdentifier, String domainName) {
+ DhcpPacket pkt = new DhcpAckPacket(
+ transactionId, broadcast, serverIpAddr, clientIpAddr, mac);
+ pkt.mGateway = gateway;
+ pkt.mDnsServers = dnsServers;
+ pkt.mLeaseTime = timeout;
+ pkt.mDomainName = domainName;
+ pkt.mSubnetMask = netMask;
+ pkt.mServerIdentifier = dhcpServerIdentifier;
+ pkt.mBroadcastAddress = bcAddr;
+ return pkt.buildPacket(encap, DHCP_CLIENT, DHCP_SERVER);
+ }
+
+ /**
+ * Builds a DHCP-NAK packet from the required specified parameters.
+ */
+ public static ByteBuffer buildNakPacket(int encap, int transactionId,
+ Inet4Address serverIpAddr, Inet4Address clientIpAddr, byte[] mac) {
+ DhcpPacket pkt = new DhcpNakPacket(transactionId, clientIpAddr,
+ serverIpAddr, serverIpAddr, serverIpAddr, mac);
+ pkt.mMessage = "requested address not available";
+ pkt.mRequestedIp = clientIpAddr;
+ return pkt.buildPacket(encap, DHCP_CLIENT, DHCP_SERVER);
+ }
+
+ /**
+ * Builds a DHCP-REQUEST packet from the required specified parameters.
+ */
+ public static ByteBuffer buildRequestPacket(int encap,
+ int transactionId, Inet4Address clientIp, boolean broadcast,
+ byte[] clientMac, Inet4Address requestedIpAddress,
+ Inet4Address serverIdentifier, byte[] requestedParams, String hostName) {
+ DhcpPacket pkt = new DhcpRequestPacket(transactionId, clientIp,
+ clientMac, broadcast);
+ pkt.mRequestedIp = requestedIpAddress;
+ pkt.mServerIdentifier = serverIdentifier;
+ pkt.mHostName = hostName;
+ pkt.mRequestedParams = requestedParams;
+ ByteBuffer result = pkt.buildPacket(encap, DHCP_SERVER, DHCP_CLIENT);
+ return result;
+ }
+}
diff --git a/services/net/java/android/net/dhcp/DhcpRequestPacket.java b/services/net/java/android/net/dhcp/DhcpRequestPacket.java
new file mode 100644
index 0000000..42b7b0c
--- /dev/null
+++ b/services/net/java/android/net/dhcp/DhcpRequestPacket.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.dhcp;
+
+import android.util.Log;
+
+import java.net.Inet4Address;
+import java.nio.ByteBuffer;
+
+/**
+ * This class implements the DHCP-REQUEST packet.
+ */
+class DhcpRequestPacket extends DhcpPacket {
+ /**
+ * Generates a REQUEST packet with the specified parameters.
+ */
+ DhcpRequestPacket(int transId, Inet4Address clientIp, byte[] clientMac,
+ boolean broadcast) {
+ super(transId, clientIp, INADDR_ANY, INADDR_ANY, INADDR_ANY, clientMac, broadcast);
+ }
+
+ public String toString() {
+ String s = super.toString();
+ return s + " REQUEST, desired IP " + mRequestedIp + " from host '"
+ + mHostName + "', param list length "
+ + (mRequestedParams == null ? 0 : mRequestedParams.length);
+ }
+
+ /**
+ * Fills in a packet with the requested REQUEST attributes.
+ */
+ public ByteBuffer buildPacket(int encap, short destUdp, short srcUdp) {
+ ByteBuffer result = ByteBuffer.allocate(MAX_LENGTH);
+
+ fillInPacket(encap, INADDR_BROADCAST, INADDR_ANY, destUdp, srcUdp,
+ result, DHCP_BOOTREQUEST, mBroadcast);
+ result.flip();
+ return result;
+ }
+
+ /**
+ * Adds the optional parameters to the client-generated REQUEST packet.
+ */
+ void finishPacket(ByteBuffer buffer) {
+ byte[] clientId = new byte[7];
+
+ // assemble client identifier
+ clientId[0] = CLIENT_ID_ETHER;
+ System.arraycopy(mClientMac, 0, clientId, 1, 6);
+
+ addTlv(buffer, DHCP_MESSAGE_TYPE, DHCP_MESSAGE_TYPE_REQUEST);
+ if (!INADDR_ANY.equals(mRequestedIp)) {
+ addTlv(buffer, DHCP_REQUESTED_IP, mRequestedIp);
+ }
+ if (!INADDR_ANY.equals(mServerIdentifier)) {
+ addTlv(buffer, DHCP_SERVER_IDENTIFIER, mServerIdentifier);
+ }
+ addTlv(buffer, DHCP_CLIENT_IDENTIFIER, clientId);
+ addCommonClientTlvs(buffer);
+ addTlv(buffer, DHCP_PARAMETER_LIST, mRequestedParams);
+ addTlvEnd(buffer);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
index b631331..4dc1131 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -32,7 +32,6 @@ import java.io.FileOutputStream;
import java.io.IOException;
public class PackageManagerSettingsTests extends AndroidTestCase {
-
private static final String PACKAGE_NAME_2 = "com.google.app2";
private static final String PACKAGE_NAME_3 = "com.android.app3";
private static final String PACKAGE_NAME_1 = "com.google.app1";
@@ -56,7 +55,7 @@ public class PackageManagerSettingsTests extends AndroidTestCase {
writeFile(new File(getContext().getFilesDir(), "system/packages.xml"),
("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>"
+ "<packages>"
- + "<last-platform-version internal=\"15\" external=\"0\" />"
+ + "<last-platform-version internal=\"15\" external=\"0\" fingerprint=\"foo\" />"
+ "<permission-trees>"
+ "<item name=\"com.google.android.permtree\" package=\"com.google.android.permpackage\" />"
+ "</permission-trees>"
@@ -110,28 +109,32 @@ public class PackageManagerSettingsTests extends AndroidTestCase {
.getBytes());
}
- @Override
- protected void setUp() throws Exception {
- super.setUp();
+ private void deleteSystemFolder() {
+ File systemFolder = new File(getContext().getFilesDir(), "system");
+ deleteFolder(systemFolder);
+ }
+
+ private static void deleteFolder(File folder) {
+ File[] files = folder.listFiles();
+ if (files != null) {
+ for (File file : files) {
+ deleteFolder(file);
+ }
+ }
+ folder.delete();
}
private void writeOldFiles() {
+ deleteSystemFolder();
writePackagesXml();
writeStoppedPackagesXml();
writePackagesList();
}
- @Override
- protected void tearDown() throws Exception {
- super.tearDown();
- }
-
public void testSettingsReadOld() {
- // Debug.waitForDebugger();
-
// Write the package files and make sure they're parsed properly the first time
writeOldFiles();
- Settings settings = new Settings(getContext(), getContext().getFilesDir());
+ Settings settings = new Settings(getContext(), getContext().getFilesDir(), new Object());
assertEquals(true, settings.readLPw(null, null, 0, false));
assertNotNull(settings.peekPackageLPr(PACKAGE_NAME_3));
assertNotNull(settings.peekPackageLPr(PACKAGE_NAME_1));
@@ -149,11 +152,12 @@ public class PackageManagerSettingsTests extends AndroidTestCase {
public void testNewPackageRestrictionsFile() {
// Write the package files and make sure they're parsed properly the first time
writeOldFiles();
- Settings settings = new Settings(getContext(), getContext().getFilesDir());
+ Settings settings = new Settings(getContext(), getContext().getFilesDir(), new Object());
assertEquals(true, settings.readLPw(null, null, 0, false));
+ settings.writeLPr();
// Create Settings again to make it read from the new files
- settings = new Settings(getContext(), getContext().getFilesDir());
+ settings = new Settings(getContext(), getContext().getFilesDir(), new Object());
assertEquals(true, settings.readLPw(null, null, 0, false));
PackageSetting ps = settings.peekPackageLPr(PACKAGE_NAME_2);
@@ -164,7 +168,7 @@ public class PackageManagerSettingsTests extends AndroidTestCase {
public void testEnableDisable() {
// Write the package files and make sure they're parsed properly the first time
writeOldFiles();
- Settings settings = new Settings(getContext(), getContext().getFilesDir());
+ Settings settings = new Settings(getContext(), getContext().getFilesDir(), new Object());
assertEquals(true, settings.readLPw(null, null, 0, false));
// Enable/Disable a package