diff options
Diffstat (limited to 'services')
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 |