diff options
Diffstat (limited to 'services')
87 files changed, 6480 insertions, 1565 deletions
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java index b5fcb5cb6a3b..4ae6e479798d 100644 --- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java +++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java @@ -125,6 +125,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; +import java.util.concurrent.atomic.AtomicLong; class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBackupProvider, OnCrossProfileWidgetProvidersChangeListener { @@ -151,6 +152,8 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku // Bump if the stored widgets need to be upgraded. private static final int CURRENT_VERSION = 1; + private static final AtomicLong REQUEST_COUNTER = new AtomicLong(); + private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -767,7 +770,8 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku LongSparseArray<PendingHostUpdate> updatesMap = new LongSparseArray<>(); for (int i = 0; i < N; i++) { if (host.getPendingUpdatesForId(appWidgetIds[i], updatesMap)) { - // We key the updates based on time, so that the values are sorted by time. + // We key the updates based on request id, so that the values are sorted in the + // order they were received. int M = updatesMap.size(); for (int j = 0; j < M; j++) { outUpdates.add(updatesMap.valueAt(j)); @@ -1590,14 +1594,6 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku // Make sure the package runs under the caller uid. mSecurityPolicy.enforceCallFromPackage(callingPackage); - - final int bitmapMemoryUsage = (views != null) ? views.estimateMemoryUsage() : 0; - if (bitmapMemoryUsage > mMaxWidgetBitmapMemory) { - throw new IllegalArgumentException("RemoteViews for widget update exceeds" - + " maximum bitmap memory usage (used: " + bitmapMemoryUsage - + ", max: " + mMaxWidgetBitmapMemory + ")"); - } - synchronized (mLock) { ensureGroupStateLoadedLocked(userId); @@ -1809,6 +1805,15 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku // For a full update we replace the RemoteViews completely. widget.views = views; } + int memoryUsage; + if ((UserHandle.getAppId(Binder.getCallingUid()) != Process.SYSTEM_UID) && + (widget.views != null) && + ((memoryUsage = widget.views.estimateMemoryUsage()) > mMaxWidgetBitmapMemory)) { + widget.views = null; + throw new IllegalArgumentException("RemoteViews for widget update exceeds" + + " maximum bitmap memory usage (used: " + memoryUsage + + ", max: " + mMaxWidgetBitmapMemory + ")"); + } scheduleNotifyUpdateAppWidgetLocked(widget, widget.getEffectiveViewsLocked()); } } @@ -1819,9 +1824,9 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku // method with a wrong id. In that case, ignore the call. return; } - long requestTime = SystemClock.uptimeMillis(); + long requestId = REQUEST_COUNTER.incrementAndGet(); if (widget != null) { - widget.updateTimes.put(viewId, requestTime); + widget.updateRequestIds.put(viewId, requestId); } if (widget == null || widget.host == null || widget.host.zombie || widget.host.callbacks == null || widget.provider == null @@ -1832,7 +1837,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku SomeArgs args = SomeArgs.obtain(); args.arg1 = widget.host; args.arg2 = widget.host.callbacks; - args.arg3 = requestTime; + args.arg3 = requestId; args.argi1 = widget.appWidgetId; args.argi2 = viewId; @@ -1843,10 +1848,10 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku private void handleNotifyAppWidgetViewDataChanged(Host host, IAppWidgetHost callbacks, - int appWidgetId, int viewId, long requestTime) { + int appWidgetId, int viewId, long requestId) { try { callbacks.viewDataChanged(appWidgetId, viewId); - host.lastWidgetUpdateTime = requestTime; + host.lastWidgetUpdateRequestId = requestId; } catch (RemoteException re) { // It failed; remove the callback. No need to prune because // we know that this host is still referenced by this instance. @@ -1893,9 +1898,9 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku } private void scheduleNotifyUpdateAppWidgetLocked(Widget widget, RemoteViews updateViews) { - long requestTime = SystemClock.uptimeMillis(); + long requestId = REQUEST_COUNTER.incrementAndGet(); if (widget != null) { - widget.updateTimes.put(ID_VIEWS_UPDATE, requestTime); + widget.updateRequestIds.put(ID_VIEWS_UPDATE, requestId); } if (widget == null || widget.provider == null || widget.provider.zombie || widget.host.callbacks == null || widget.host.zombie) { @@ -1906,7 +1911,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku args.arg1 = widget.host; args.arg2 = widget.host.callbacks; args.arg3 = (updateViews != null) ? updateViews.clone() : null; - args.arg4 = requestTime; + args.arg4 = requestId; args.argi1 = widget.appWidgetId; mCallbackHandler.obtainMessage( @@ -1915,10 +1920,10 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku } private void handleNotifyUpdateAppWidget(Host host, IAppWidgetHost callbacks, - int appWidgetId, RemoteViews views, long requestTime) { + int appWidgetId, RemoteViews views, long requestId) { try { callbacks.updateAppWidget(appWidgetId, views); - host.lastWidgetUpdateTime = requestTime; + host.lastWidgetUpdateRequestId = requestId; } catch (RemoteException re) { synchronized (mLock) { Slog.e(TAG, "Widget host dead: " + host.id, re); @@ -1928,11 +1933,11 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku } private void scheduleNotifyProviderChangedLocked(Widget widget) { - long requestTime = SystemClock.uptimeMillis(); + long requestId = REQUEST_COUNTER.incrementAndGet(); if (widget != null) { // When the provider changes, reset everything else. - widget.updateTimes.clear(); - widget.updateTimes.append(ID_PROVIDER_CHANGED, requestTime); + widget.updateRequestIds.clear(); + widget.updateRequestIds.append(ID_PROVIDER_CHANGED, requestId); } if (widget == null || widget.provider == null || widget.provider.zombie || widget.host.callbacks == null || widget.host.zombie) { @@ -1943,7 +1948,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku args.arg1 = widget.host; args.arg2 = widget.host.callbacks; args.arg3 = widget.provider.info; - args.arg4 = requestTime; + args.arg4 = requestId; args.argi1 = widget.appWidgetId; mCallbackHandler.obtainMessage( @@ -1952,10 +1957,10 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku } private void handleNotifyProviderChanged(Host host, IAppWidgetHost callbacks, - int appWidgetId, AppWidgetProviderInfo info, long requestTime) { + int appWidgetId, AppWidgetProviderInfo info, long requestId) { try { callbacks.providerChanged(appWidgetId, info); - host.lastWidgetUpdateTime = requestTime; + host.lastWidgetUpdateRequestId = requestId; } catch (RemoteException re) { synchronized (mLock){ Slog.e(TAG, "Widget host dead: " + host.id, re); @@ -3428,11 +3433,11 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku Host host = (Host) args.arg1; IAppWidgetHost callbacks = (IAppWidgetHost) args.arg2; RemoteViews views = (RemoteViews) args.arg3; - long requestTime = (Long) args.arg4; + long requestId = (Long) args.arg4; final int appWidgetId = args.argi1; args.recycle(); - handleNotifyUpdateAppWidget(host, callbacks, appWidgetId, views, requestTime); + handleNotifyUpdateAppWidget(host, callbacks, appWidgetId, views, requestId); } break; case MSG_NOTIFY_PROVIDER_CHANGED: { @@ -3440,11 +3445,11 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku Host host = (Host) args.arg1; IAppWidgetHost callbacks = (IAppWidgetHost) args.arg2; AppWidgetProviderInfo info = (AppWidgetProviderInfo)args.arg3; - long requestTime = (Long) args.arg4; + long requestId = (Long) args.arg4; final int appWidgetId = args.argi1; args.recycle(); - handleNotifyProviderChanged(host, callbacks, appWidgetId, info, requestTime); + handleNotifyProviderChanged(host, callbacks, appWidgetId, info, requestId); } break; case MSG_NOTIFY_PROVIDERS_CHANGED: { @@ -3460,13 +3465,13 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku SomeArgs args = (SomeArgs) message.obj; Host host = (Host) args.arg1; IAppWidgetHost callbacks = (IAppWidgetHost) args.arg2; - long requestTime = (Long) args.arg3; + long requestId = (Long) args.arg3; final int appWidgetId = args.argi1; final int viewId = args.argi2; args.recycle(); handleNotifyAppWidgetViewDataChanged(host, callbacks, appWidgetId, viewId, - requestTime); + requestId); } break; } } @@ -3782,7 +3787,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku boolean zombie; // if we're in safe mode, don't prune this just because nobody references it int tag = TAG_UNDEFINED; // for use while saving state (the index) - long lastWidgetUpdateTime; // last time we were successfully able to send an update. + long lastWidgetUpdateRequestId; // request id for the last update successfully sent public int getUserId() { return UserHandle.getUserId(id.uid); @@ -3809,18 +3814,18 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku */ public boolean getPendingUpdatesForId(int appWidgetId, LongSparseArray<PendingHostUpdate> outUpdates) { - long updateTime = lastWidgetUpdateTime; + long updateRequestId = lastWidgetUpdateRequestId; int N = widgets.size(); for (int i = 0; i < N; i++) { Widget widget = widgets.get(i); if (widget.appWidgetId == appWidgetId) { outUpdates.clear(); - for (int j = widget.updateTimes.size() - 1; j >= 0; j--) { - long time = widget.updateTimes.valueAt(j); - if (time <= updateTime) { + for (int j = widget.updateRequestIds.size() - 1; j >= 0; j--) { + long requestId = widget.updateRequestIds.valueAt(j); + if (requestId <= updateRequestId) { continue; } - int id = widget.updateTimes.keyAt(j); + int id = widget.updateRequestIds.keyAt(j); final PendingHostUpdate update; switch (id) { case ID_PROVIDER_CHANGED: @@ -3834,7 +3839,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku default: update = PendingHostUpdate.viewDataChanged(appWidgetId, id); } - outUpdates.put(time, update); + outUpdates.put(requestId, update); } return true; } @@ -3916,8 +3921,8 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku RemoteViews maskedViews; Bundle options; Host host; - // timestamps for various operations - SparseLongArray updateTimes = new SparseLongArray(2); + // Request ids for various operations + SparseLongArray updateRequestIds = new SparseLongArray(2); @Override public String toString() { diff --git a/services/core/Android.mk b/services/core/Android.mk index 32105429575d..58f207499888 100644 --- a/services/core/Android.mk +++ b/services/core/Android.mk @@ -12,7 +12,7 @@ LOCAL_SRC_FILES += \ java/com/android/server/EventLogTags.logtags \ java/com/android/server/am/EventLogTags.logtags \ ../../../../system/netd/server/binder/android/net/INetd.aidl \ - ../../../../system/netd/server/binder/android/net/metrics/IDnsEventListener.aidl \ + ../../../../system/netd/server/binder/android/net/metrics/INetdEventListener.aidl \ LOCAL_AIDL_INCLUDES += \ system/netd/server/binder diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java index 97437c9579d4..2a6f9d2b8569 100644 --- a/services/core/java/com/android/server/BluetoothManagerService.java +++ b/services/core/java/com/android/server/BluetoothManagerService.java @@ -55,13 +55,16 @@ import android.os.UserManager; import android.provider.Settings; import android.provider.Settings.SettingNotFoundException; import android.util.Slog; -import java.util.concurrent.locks.ReentrantReadWriteLock; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.HashMap; +import java.util.LinkedList; import java.util.Map; + class BluetoothManagerService extends IBluetoothManager.Stub { private static final String TAG = "BluetoothManagerService"; private static final boolean DBG = true; @@ -101,6 +104,7 @@ class BluetoothManagerService extends IBluetoothManager.Stub { private static final int MESSAGE_USER_UNLOCKED = 301; private static final int MESSAGE_ADD_PROXY_DELAYED = 400; private static final int MESSAGE_BIND_PROFILE_SERVICE = 401; + private static final int MAX_SAVE_RETRIES = 3; private static final int MAX_ERROR_RESTART_RETRIES = 6; @@ -118,7 +122,6 @@ class BluetoothManagerService extends IBluetoothManager.Stub { private static final int SERVICE_IBLUETOOTHGATT = 2; private final Context mContext; - private static int mBleAppCount = 0; // Locks are not provided for mName and mAddress. // They are accessed in handler or broadcast receiver, same thread context. @@ -134,16 +137,46 @@ class BluetoothManagerService extends IBluetoothManager.Stub { new ReentrantReadWriteLock(); private boolean mBinding; private boolean mUnbinding; + // used inside handler thread private boolean mQuietEnable = false; - // configuarion from external IBinder call which is used to + private boolean mEnable; + + /** + * Used for tracking apps that enabled / disabled Bluetooth. + */ + private class ActiveLog { + private String mPackageName; + private boolean mEnable; + private long mTimestamp; + + public ActiveLog(String packageName, boolean enable, long timestamp) { + mPackageName = packageName; + mEnable = enable; + mTimestamp = timestamp; + } + + public long getTime() { + return mTimestamp; + } + + public String toString() { + return android.text.format.DateFormat.format("MM-dd hh:mm:ss ", mTimestamp) + + (mEnable ? " Enabled " : " Disabled ") + " by " + mPackageName; + } + + } + + private LinkedList<ActiveLog> mActiveLogs; + + // configuration from external IBinder call which is used to // synchronize with broadcast receiver. private boolean mQuietEnableExternal; - // configuarion from external IBinder call which is used to - // synchronize with broadcast receiver. private boolean mEnableExternal; - // used inside handler thread - private boolean mEnable; + + // Map of apps registered to keep BLE scanning on. + private Map<IBinder, ClientDeathRecipient> mBleApps = new ConcurrentHashMap<IBinder, ClientDeathRecipient>(); + private int mState; private final BluetoothHandler mHandler; private int mErrorRecoveryRetryCounter; @@ -169,7 +202,7 @@ class BluetoothManagerService extends IBluetoothManager.Stub { } } - private final IBluetoothCallback mBluetoothCallback = new IBluetoothCallback.Stub() { + private final IBluetoothCallback mBluetoothCallback = new IBluetoothCallback.Stub() { @Override public void onBluetoothStateChange(int prevState, int newState) throws RemoteException { Message msg = mHandler.obtainMessage(MESSAGE_BLUETOOTH_STATE_CHANGE,prevState,newState); @@ -187,14 +220,6 @@ class BluetoothManagerService extends IBluetoothManager.Stub { if (newName != null) { storeNameAndAddress(newName, null); } - } else if (BluetoothAdapter.ACTION_BLUETOOTH_ADDRESS_CHANGED.equals(action)) { - String newAddress = intent.getStringExtra(BluetoothAdapter.EXTRA_BLUETOOTH_ADDRESS); - if (newAddress != null) { - if (DBG) Slog.d(TAG, "Bluetooth Adapter address changed to " + newAddress); - storeNameAndAddress(null, newAddress); - } else { - if (DBG) Slog.e(TAG, "No Bluetooth Adapter address parameter found"); - } } else if (Intent.ACTION_AIRPLANE_MODE_CHANGED.equals(action)) { synchronized(mReceiver) { if (isBluetoothPersistedStateOn()) { @@ -216,14 +241,11 @@ class BluetoothManagerService extends IBluetoothManager.Stub { } finally { mBluetoothLock.readLock().unlock(); } - Slog.d(TAG, "Airplane Mode change - current state: " + st); + Slog.d(TAG, "State " + BluetoothAdapter.nameForState(st)); if (isAirplaneModeOn()) { // Clear registered LE apps to force shut-off - synchronized (this) { - mBleAppCount = 0; - mBleApps.clear(); - } + clearBleApps(); if (st == BluetoothAdapter.STATE_BLE_ON) { //if state is BLE_ON make sure you trigger disableBLE part try { @@ -241,12 +263,12 @@ class BluetoothManagerService extends IBluetoothManager.Stub { } else if (st == BluetoothAdapter.STATE_ON){ // disable without persisting the setting Slog.d(TAG, "Calling disable"); - sendDisableMsg(); + sendDisableMsg("airplane mode"); } } else if (mEnableExternal) { // enable without persisting the setting Slog.d(TAG, "Calling enable"); - sendEnableMsg(mQuietEnableExternal); + sendEnableMsg(mQuietEnableExternal, "airplane mode"); } } } @@ -262,6 +284,7 @@ class BluetoothManagerService extends IBluetoothManager.Stub { || context.getResources().getBoolean( com.android.internal.R.bool.config_permissionReviewRequired); + mActiveLogs = new LinkedList<ActiveLog>(); mBluetooth = null; mBluetoothBinder = null; mBluetoothGatt = null; @@ -283,24 +306,21 @@ class BluetoothManagerService extends IBluetoothManager.Stub { registerForAirplaneMode(filter); filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); mContext.registerReceiver(mReceiver, filter); - filter = new IntentFilter(BluetoothAdapter.ACTION_BLUETOOTH_ADDRESS_CHANGED); - filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); - mContext.registerReceiver(mReceiver, filter); loadStoredNameAndAddress(); if (isBluetoothPersistedStateOn()) { if (DBG) Slog.d(TAG, "Startup: Bluetooth persisted state is ON."); mEnableExternal = true; } - int sysUiUid = -1; + int systemUiUid = -1; try { - sysUiUid = mContext.getPackageManager().getPackageUidAsUser("com.android.systemui", + systemUiUid = mContext.getPackageManager().getPackageUidAsUser("com.android.systemui", PackageManager.MATCH_SYSTEM_ONLY, UserHandle.USER_SYSTEM); } catch (PackageManager.NameNotFoundException e) { // Some platforms, such as wearables do not have a system ui. Slog.w(TAG, "Unable to resolve SystemUI's UID.", e); } - mSystemUiUid = sysUiUid; + mSystemUiUid = systemUiUid; } /** @@ -331,13 +351,15 @@ class BluetoothManagerService extends IBluetoothManager.Stub { /** * Save the Bluetooth on/off state - * */ private void persistBluetoothSetting(int value) { if (DBG) Slog.d(TAG, "Persisting Bluetooth Setting: " + value); + // waive WRITE_SECURE_SETTINGS permission check + long callingIdentity = Binder.clearCallingIdentity(); Settings.Global.putInt(mContext.getContentResolver(), Settings.Global.BLUETOOTH_ON, value); + Binder.restoreCallingIdentity(callingIdentity); } /** @@ -420,6 +442,10 @@ class BluetoothManagerService extends IBluetoothManager.Stub { public void registerStateChangeCallback(IBluetoothStateChangeCallback callback) { mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); + if (callback == null) { + Slog.w(TAG, "registerStateChangeCallback: Callback is null!"); + return; + } Message msg = mHandler.obtainMessage(MESSAGE_REGISTER_STATE_CHANGE_CALLBACK); msg.obj = callback; mHandler.sendMessage(msg); @@ -428,6 +454,10 @@ class BluetoothManagerService extends IBluetoothManager.Stub { public void unregisterStateChangeCallback(IBluetoothStateChangeCallback callback) { mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); + if (callback == null) { + Slog.w(TAG, "unregisterStateChangeCallback: Callback is null!"); + return; + } Message msg = mHandler.obtainMessage(MESSAGE_UNREGISTER_STATE_CHANGE_CALLBACK); msg.obj = callback; mHandler.sendMessage(msg); @@ -454,7 +484,7 @@ class BluetoothManagerService extends IBluetoothManager.Stub { public int getState() { if ((Binder.getCallingUid() != Process.SYSTEM_UID) && (!checkIfCallerIsForegroundUser())) { - Slog.w(TAG, "getState(): not allowed for non-active and non system user"); + Slog.w(TAG, "getState(): report OFF for non-active and non system user"); return BluetoothAdapter.STATE_OFF; } @@ -470,30 +500,37 @@ class BluetoothManagerService extends IBluetoothManager.Stub { } class ClientDeathRecipient implements IBinder.DeathRecipient { - public void binderDied() { - if (DBG) Slog.d(TAG, "Binder is dead - unregister Ble App"); - if (mBleAppCount > 0) --mBleAppCount; + private String mPackageName; - if (mBleAppCount == 0) { - if (DBG) Slog.d(TAG, "Disabling LE only mode after application crash"); - try { - mBluetoothLock.readLock().lock(); - if (mBluetooth != null && - mBluetooth.getState() == BluetoothAdapter.STATE_BLE_ON) { - mEnable = false; - mBluetooth.onBrEdrDown(); - } - } catch (RemoteException e) { - Slog.e(TAG,"Unable to call onBrEdrDown", e); - } finally { - mBluetoothLock.readLock().unlock(); + public ClientDeathRecipient(String packageName) { + mPackageName = packageName; + } + + public void binderDied() { + if (DBG) Slog.d(TAG, "Binder is dead - unregister " + mPackageName); + if (isBleAppPresent()) { + // Nothing to do, another app is here. + return; + } + if (DBG) Slog.d(TAG, "Disabling LE only mode after application crash"); + try { + mBluetoothLock.readLock().lock(); + if (mBluetooth != null && + mBluetooth.getState() == BluetoothAdapter.STATE_BLE_ON) { + mEnable = false; + mBluetooth.onBrEdrDown(); } + } catch (RemoteException e) { + Slog.e(TAG,"Unable to call onBrEdrDown", e); + } finally { + mBluetoothLock.readLock().unlock(); } } - } - /** Internal death rec list */ - Map<IBinder, ClientDeathRecipient> mBleApps = new HashMap<IBinder, ClientDeathRecipient>(); + public String getPackageName() { + return mPackageName; + } + } @Override public boolean isBleScanAlwaysAvailable() { @@ -513,17 +550,20 @@ class BluetoothManagerService extends IBluetoothManager.Stub { ContentObserver contentObserver = new ContentObserver(null) { @Override public void onChange(boolean selfChange) { - if (!isBleScanAlwaysAvailable()) { - disableBleScanMode(); - clearBleApps(); - try { - mBluetoothLock.readLock().lock(); - if (mBluetooth != null) mBluetooth.onBrEdrDown(); - } catch (RemoteException e) { - Slog.e(TAG, "error when disabling bluetooth", e); - } finally { - mBluetoothLock.readLock().unlock(); - } + if (isBleScanAlwaysAvailable()) { + // Nothing to do + return; + } + // BLE scan is not available. + disableBleScanMode(); + clearBleApps(); + try { + mBluetoothLock.readLock().lock(); + if (mBluetooth != null) mBluetooth.onBrEdrDown(); + } catch (RemoteException e) { + Slog.e(TAG, "error when disabling bluetooth", e); + } finally { + mBluetoothLock.readLock().unlock(); } } }; @@ -548,71 +588,63 @@ class BluetoothManagerService extends IBluetoothManager.Stub { } } - public int updateBleAppCount(IBinder token, boolean enable) { - if (enable) { - ClientDeathRecipient r = mBleApps.get(token); - if (r == null) { - ClientDeathRecipient deathRec = new ClientDeathRecipient(); - try { - token.linkToDeath(deathRec, 0); - } catch (RemoteException ex) { - throw new IllegalArgumentException("Wake lock is already dead."); - } - mBleApps.put(token, deathRec); - synchronized (this) { - ++mBleAppCount; - } - if (DBG) Slog.d(TAG, "Registered for death Notification"); - } - - } else { - ClientDeathRecipient r = mBleApps.get(token); - if (r != null) { - // Unregister death recipient as the app goes away. - token.unlinkToDeath(r, 0); - mBleApps.remove(token); - synchronized (this) { - if (mBleAppCount > 0) --mBleAppCount; - } - if (DBG) Slog.d(TAG, "Unregistered for death Notification"); + public int updateBleAppCount(IBinder token, boolean enable, String packageName) { + ClientDeathRecipient r = mBleApps.get(token); + if (r == null && enable) { + ClientDeathRecipient deathRec = new ClientDeathRecipient(packageName); + try { + token.linkToDeath(deathRec, 0); + } catch (RemoteException ex) { + throw new IllegalArgumentException("BLE app (" + packageName + ") already dead!"); } - } - if (DBG) Slog.d(TAG, "Updated BleAppCount" + mBleAppCount); - if (mBleAppCount == 0 && mEnable) { + mBleApps.put(token, deathRec); + if (DBG) Slog.d(TAG, "Registered for death of " + packageName); + } else if (!enable && r != null) { + // Unregister death recipient as the app goes away. + token.unlinkToDeath(r, 0); + mBleApps.remove(token); + if (DBG) Slog.d(TAG, "Unregistered for death of " + packageName); + } + int appCount = mBleApps.size(); + if (DBG) Slog.d(TAG, appCount + " registered Ble Apps"); + if (appCount == 0 && mEnable) { disableBleScanMode(); } - return mBleAppCount; + return appCount; } // Clear all apps using BLE scan only mode. private void clearBleApps() { - synchronized (this) { - mBleApps.clear(); - mBleAppCount = 0; - } + mBleApps.clear(); } - /** @hide*/ + /** @hide */ public boolean isBleAppPresent() { - if (DBG) Slog.d(TAG, "isBleAppPresent() count: " + mBleAppCount); - return (mBleAppCount > 0); + if (DBG) Slog.d(TAG, "isBleAppPresent() count: " + mBleApps.size()); + return mBleApps.size() > 0; } /** - * Action taken when GattService is turned off + * Action taken when GattService is turned on */ private void onBluetoothGattServiceUp() { if (DBG) Slog.d(TAG,"BluetoothGatt Service is Up"); try { mBluetoothLock.readLock().lock(); - if (isBleAppPresent() == false && mBluetooth != null - && mBluetooth.getState() == BluetoothAdapter.STATE_BLE_ON) { + if (mBluetooth == null) { + if (DBG) Slog.w(TAG, "onBluetoothServiceUp: mBluetooth is null!"); + return; + } + int st = mBluetooth.getState(); + if (st != BluetoothAdapter.STATE_BLE_ON) { + if (DBG) Slog.v(TAG, "onBluetoothServiceUp: state isn't BLE_ON: " + + BluetoothAdapter.nameForState(st)); + return; + } + if (isBluetoothPersistedStateOnBluetooth() || !isBleAppPresent()) { + // This triggers transition to STATE_ON mBluetooth.onLeServiceUp(); - - // waive WRITE_SECURE_SETTINGS permission check - long callingIdentity = Binder.clearCallingIdentity(); persistBluetoothSetting(BLUETOOTH_ON_BLUETOOTH); - Binder.restoreCallingIdentity(callingIdentity); } } catch (RemoteException e) { Slog.e(TAG,"Unable to call onServiceUp", e); @@ -652,7 +684,7 @@ class BluetoothManagerService extends IBluetoothManager.Stub { } } - public boolean enableNoAutoConnect() + public boolean enableNoAutoConnect(String packageName) { mContext.enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH ADMIN permission"); @@ -670,7 +702,7 @@ class BluetoothManagerService extends IBluetoothManager.Stub { synchronized(mReceiver) { mQuietEnableExternal = true; mEnableExternal = true; - sendEnableMsg(true); + sendEnableMsg(true, packageName); } return true; } @@ -696,15 +728,16 @@ class BluetoothManagerService extends IBluetoothManager.Stub { } if (DBG) { - Slog.d(TAG,"enable(): mBluetooth =" + mBluetooth + - " mBinding = " + mBinding + " mState = " + mState); + Slog.d(TAG,"enable(" + packageName + "): mBluetooth =" + mBluetooth + + " mBinding = " + mBinding + " mState = " + + BluetoothAdapter.nameForState(mState)); } synchronized(mReceiver) { mQuietEnableExternal = false; mEnableExternal = true; // waive WRITE_SECURE_SETTINGS permission check - sendEnableMsg(false); + sendEnableMsg(false, packageName); } if (DBG) Slog.d(TAG, "enable returning"); return true; @@ -737,13 +770,10 @@ class BluetoothManagerService extends IBluetoothManager.Stub { synchronized(mReceiver) { if (persist) { - // waive WRITE_SECURE_SETTINGS permission check - long callingIdentity = Binder.clearCallingIdentity(); persistBluetoothSetting(BLUETOOTH_OFF); - Binder.restoreCallingIdentity(callingIdentity); } mEnableExternal = false; - sendDisableMsg(); + sendDisableMsg(packageName); } return true; } @@ -781,7 +811,7 @@ class BluetoothManagerService extends IBluetoothManager.Stub { public void unbindAndFinish() { if (DBG) { Slog.d(TAG,"unbindAndFinish(): " + mBluetooth + - " mBinding = " + mBinding); + " mBinding = " + mBinding + " mUnbinding = " + mUnbinding); } try { @@ -797,16 +827,13 @@ class BluetoothManagerService extends IBluetoothManager.Stub { } catch (RemoteException re) { Slog.e(TAG, "Unable to unregister BluetoothCallback",re); } - - if (DBG) Slog.d(TAG, "Sending unbind request."); mBluetoothBinder = null; mBluetooth = null; - //Unbind mContext.unbindService(mConnection); mUnbinding = false; mBinding = false; } else { - mUnbinding=false; + mUnbinding = false; } mBluetoothGatt = null; } finally { @@ -890,7 +917,7 @@ class BluetoothManagerService extends IBluetoothManager.Stub { if (DBG) Slog.d(TAG, "Bluetooth boot completed"); if (mEnableExternal && isBluetoothPersistedStateOnBluetooth()) { if (DBG) Slog.d(TAG, "Auto-enabling Bluetooth."); - sendEnableMsg(mQuietEnableExternal); + sendEnableMsg(mQuietEnableExternal, "system boot"); } else if (!isNameAndAddressSet()) { if (DBG) Slog.d(TAG, "Getting adapter name and address"); Message getMsg = mHandler.obtainMessage(MESSAGE_GET_NAME_AND_ADDRESS); @@ -1076,7 +1103,6 @@ class BluetoothManagerService extends IBluetoothManager.Stub { * Inform BluetoothAdapter instances that Adapter service is up */ private void sendBluetoothServiceUpCallback() { - if (DBG) Slog.d(TAG,"Calling onBluetoothServiceUp callbacks"); try { int n = mCallbacks.beginBroadcast(); Slog.d(TAG,"Broadcasting onBluetoothServiceUp() to " + n + " receivers."); @@ -1095,7 +1121,6 @@ class BluetoothManagerService extends IBluetoothManager.Stub { * Inform BluetoothAdapter instances that Adapter service is down */ private void sendBluetoothServiceDownCallback() { - if (DBG) Slog.d(TAG,"Calling onBluetoothServiceDown callbacks"); try { int n = mCallbacks.beginBroadcast(); Slog.d(TAG,"Broadcasting onBluetoothServiceDown() to " + n + " receivers."); @@ -1167,34 +1192,33 @@ class BluetoothManagerService extends IBluetoothManager.Stub { } private class BluetoothServiceConnection implements ServiceConnection { - public void onServiceConnected(ComponentName className, IBinder service) { - if (DBG) Slog.d(TAG, "BluetoothServiceConnection: " + className.getClassName()); + public void onServiceConnected(ComponentName componentName, IBinder service) { + String name = componentName.getClassName(); + if (DBG) Slog.d(TAG, "BluetoothServiceConnection: " + name); Message msg = mHandler.obtainMessage(MESSAGE_BLUETOOTH_SERVICE_CONNECTED); - // TBD if (className.getClassName().equals(IBluetooth.class.getName())) { - if (className.getClassName().equals("com.android.bluetooth.btservice.AdapterService")) { + if (name.equals("com.android.bluetooth.btservice.AdapterService")) { msg.arg1 = SERVICE_IBLUETOOTH; - // } else if (className.getClassName().equals(IBluetoothGatt.class.getName())) { - } else if (className.getClassName().equals("com.android.bluetooth.gatt.GattService")) { + } else if (name.equals("com.android.bluetooth.gatt.GattService")) { msg.arg1 = SERVICE_IBLUETOOTHGATT; } else { - Slog.e(TAG, "Unknown service connected: " + className.getClassName()); + Slog.e(TAG, "Unknown service connected: " + name); return; } msg.obj = service; mHandler.sendMessage(msg); } - public void onServiceDisconnected(ComponentName className) { - // Called if we unexpected disconnected. - if (DBG) Slog.d(TAG, "BluetoothServiceConnection, disconnected: " + - className.getClassName()); + public void onServiceDisconnected(ComponentName componentName) { + // Called if we unexpectedly disconnect. + String name = componentName.getClassName(); + if (DBG) Slog.d(TAG, "BluetoothServiceConnection, disconnected: " + name); Message msg = mHandler.obtainMessage(MESSAGE_BLUETOOTH_SERVICE_DISCONNECTED); - if (className.getClassName().equals("com.android.bluetooth.btservice.AdapterService")) { + if (name.equals("com.android.bluetooth.btservice.AdapterService")) { msg.arg1 = SERVICE_IBLUETOOTH; - } else if (className.getClassName().equals("com.android.bluetooth.gatt.GattService")) { + } else if (name.equals("com.android.bluetooth.gatt.GattService")) { msg.arg1 = SERVICE_IBLUETOOTHGATT; } else { - Slog.e(TAG, "Unknown service disconnected: " + className.getClassName()); + Slog.e(TAG, "Unknown service disconnected: " + name); return; } mHandler.sendMessage(msg); @@ -1212,7 +1236,6 @@ class BluetoothManagerService extends IBluetoothManager.Stub { @Override public void handleMessage(Message msg) { - if (DBG) Slog.d (TAG, "Message: " + msg.what); switch (msg.what) { case MESSAGE_GET_NAME_AND_ADDRESS: if (DBG) Slog.d(TAG, "MESSAGE_GET_NAME_AND_ADDRESS"); @@ -1250,7 +1273,7 @@ class BluetoothManagerService extends IBluetoothManager.Stub { case MESSAGE_ENABLE: if (DBG) { - Slog.d(TAG, "MESSAGE_ENABLE: mBluetooth = " + mBluetooth); + Slog.d(TAG, "MESSAGE_ENABLE(" + msg.arg1 + "): mBluetooth = " + mBluetooth); } mHandler.removeMessages(MESSAGE_RESTART_BLUETOOTH_SERVICE); mEnable = true; @@ -1261,8 +1284,9 @@ class BluetoothManagerService extends IBluetoothManager.Stub { if (mBluetooth != null) { int state = mBluetooth.getState(); if (state == BluetoothAdapter.STATE_BLE_ON) { - Slog.w(TAG, "BT is in BLE_ON State"); + Slog.w(TAG, "BT Enable in BLE_ON State, going to ON"); mBluetooth.onLeServiceUp(); + persistBluetoothSetting(BLUETOOTH_ON_BLUETOOTH); break; } } @@ -1300,6 +1324,7 @@ class BluetoothManagerService extends IBluetoothManager.Stub { break; case MESSAGE_DISABLE: + if (DBG) Slog.d(TAG, "MESSAGE_DISABLE: mBluetooth = " + mBluetooth); mHandler.removeMessages(MESSAGE_RESTART_BLUETOOTH_SERVICE); if (mEnable && mBluetooth != null) { waitForOnOff(true, false); @@ -1315,31 +1340,25 @@ class BluetoothManagerService extends IBluetoothManager.Stub { case MESSAGE_REGISTER_ADAPTER: { IBluetoothManagerCallback callback = (IBluetoothManagerCallback) msg.obj; - boolean added = mCallbacks.register(callback); - Slog.d(TAG,"Added callback: " + (callback == null? "null": callback) +":" +added ); - } + mCallbacks.register(callback); break; + } case MESSAGE_UNREGISTER_ADAPTER: { IBluetoothManagerCallback callback = (IBluetoothManagerCallback) msg.obj; - boolean removed = mCallbacks.unregister(callback); - Slog.d(TAG,"Removed callback: " + (callback == null? "null": callback) +":" + removed); + mCallbacks.unregister(callback); break; } case MESSAGE_REGISTER_STATE_CHANGE_CALLBACK: { IBluetoothStateChangeCallback callback = (IBluetoothStateChangeCallback) msg.obj; - if (callback != null) { - mStateChangeCallbacks.register(callback); - } + mStateChangeCallbacks.register(callback); break; } case MESSAGE_UNREGISTER_STATE_CHANGE_CALLBACK: { IBluetoothStateChangeCallback callback = (IBluetoothStateChangeCallback) msg.obj; - if (callback != null) { - mStateChangeCallbacks.unregister(callback); - } + mStateChangeCallbacks.unregister(callback); break; } case MESSAGE_ADD_PROXY_DELAYED: @@ -1412,13 +1431,11 @@ class BluetoothManagerService extends IBluetoothManager.Stub { //Do enable request try { if (mQuietEnable == false) { - if(!mBluetooth.enable()) { + if (!mBluetooth.enable()) { Slog.e(TAG,"IBluetooth.enable() returned false"); } - } - else - { - if(!mBluetooth.enableNoAutoConnect()) { + } else { + if (!mBluetooth.enableNoAutoConnect()) { Slog.e(TAG,"IBluetooth.enableNoAutoConnect() returned false"); } } @@ -1436,19 +1453,14 @@ class BluetoothManagerService extends IBluetoothManager.Stub { } break; } - case MESSAGE_TIMEOUT_BIND: { - Slog.e(TAG, "MESSAGE_TIMEOUT_BIND"); - mBluetoothLock.writeLock().lock(); - mBinding = false; - mBluetoothLock.writeLock().unlock(); - - break; - } case MESSAGE_BLUETOOTH_STATE_CHANGE: { int prevState = msg.arg1; int newState = msg.arg2; - if (DBG) Slog.d(TAG, "MESSAGE_BLUETOOTH_STATE_CHANGE: prevState = " + prevState + ", newState =" + newState); + if (DBG) { + Slog.d(TAG, "MESSAGE_BLUETOOTH_STATE_CHANGE: " + BluetoothAdapter.nameForState(prevState) + " > " + + BluetoothAdapter.nameForState(newState)); + } mState = newState; bluetoothStateChangeHandler(prevState, newState); // handle error state transition case from TURNING_ON to OFF @@ -1456,12 +1468,12 @@ class BluetoothManagerService extends IBluetoothManager.Stub { if ((prevState == BluetoothAdapter.STATE_BLE_TURNING_ON) && (newState == BluetoothAdapter.STATE_OFF) && (mBluetooth != null) && mEnable) { - recoverBluetoothServiceFromError(); + recoverBluetoothServiceFromError(false); } if ((prevState == BluetoothAdapter.STATE_TURNING_ON) && (newState == BluetoothAdapter.STATE_BLE_ON) && (mBluetooth != null) && mEnable) { - recoverBluetoothServiceFromError(); + recoverBluetoothServiceFromError(true); } // If we tried to enable BT while BT was in the process of shutting down, // wait for the BT process to fully tear down and then force a restart @@ -1488,7 +1500,7 @@ class BluetoothManagerService extends IBluetoothManager.Stub { } case MESSAGE_BLUETOOTH_SERVICE_DISCONNECTED: { - Slog.e(TAG, "MESSAGE_BLUETOOTH_SERVICE_DISCONNECTED: " + msg.arg1); + Slog.e(TAG, "MESSAGE_BLUETOOTH_SERVICE_DISCONNECTED(" + msg.arg1 + ")"); try { mBluetoothLock.writeLock().lock(); if (msg.arg1 == SERVICE_IBLUETOOTH) { @@ -1499,7 +1511,7 @@ class BluetoothManagerService extends IBluetoothManager.Stub { mBluetoothGatt = null; break; } else { - Slog.e(TAG, "Bad msg.arg1: " + msg.arg1); + Slog.e(TAG, "Unknown argument for service disconnect!"); break; } } finally { @@ -1536,8 +1548,7 @@ class BluetoothManagerService extends IBluetoothManager.Stub { } case MESSAGE_RESTART_BLUETOOTH_SERVICE: { - Slog.d(TAG, "MESSAGE_RESTART_BLUETOOTH_SERVICE:" - +" Restart IBluetooth service"); + Slog.d(TAG, "MESSAGE_RESTART_BLUETOOTH_SERVICE"); /* Enable without persisting the setting as it doesnt change when IBluetooth service restarts */ @@ -1545,7 +1556,13 @@ class BluetoothManagerService extends IBluetoothManager.Stub { handleEnable(mQuietEnable); break; } - + case MESSAGE_TIMEOUT_BIND: { + Slog.e(TAG, "MESSAGE_TIMEOUT_BIND"); + mBluetoothLock.writeLock().lock(); + mBinding = false; + mBluetoothLock.writeLock().unlock(); + break; + } case MESSAGE_TIMEOUT_UNBIND: { Slog.e(TAG, "MESSAGE_TIMEOUT_UNBIND"); @@ -1631,11 +1648,10 @@ class BluetoothManagerService extends IBluetoothManager.Stub { } else if (mBinding || mBluetooth != null) { Message userMsg = mHandler.obtainMessage(MESSAGE_USER_SWITCHED); userMsg.arg2 = 1 + msg.arg2; - // if user is switched when service is being binding - // delay sending MESSAGE_USER_SWITCHED + // if user is switched when service is binding retry after a delay mHandler.sendMessageDelayed(userMsg, USER_SWITCHED_TIME_MS); if (DBG) { - Slog.d(TAG, "delay MESSAGE_USER_SWITCHED " + userMsg.arg2); + Slog.d(TAG, "Retry MESSAGE_USER_SWITCHED " + userMsg.arg2); } } break; @@ -1736,7 +1752,7 @@ class BluetoothManagerService extends IBluetoothManager.Stub { parentUser == foregroundUser || callingAppId == Process.NFC_UID || callingAppId == mSystemUiUid; - if (DBG) { + if (DBG && !valid) { Slog.d(TAG, "checkIfCallerIsForegroundUser: valid=" + valid + " callingUser=" + callingUser + " parentUser=" + parentUser @@ -1749,7 +1765,8 @@ class BluetoothManagerService extends IBluetoothManager.Stub { } private void sendBleStateChanged(int prevState, int newState) { - if (DBG) Slog.d(TAG,"BLE State Change Intent: " + prevState + " -> " + newState); + if (DBG) Slog.d(TAG,"Sending BLE State Change: " + BluetoothAdapter.nameForState(prevState) + + " > " + BluetoothAdapter.nameForState(newState)); // Send broadcast message to everyone else Intent intent = new Intent(BluetoothAdapter.ACTION_BLE_STATE_CHANGED); intent.putExtra(BluetoothAdapter.EXTRA_PREVIOUS_STATE, prevState); @@ -1760,76 +1777,76 @@ class BluetoothManagerService extends IBluetoothManager.Stub { private void bluetoothStateChangeHandler(int prevState, int newState) { boolean isStandardBroadcast = true; - if (DBG) Slog.d(TAG, "bluetoothStateChangeHandler: " + prevState + " -> " + newState); - if (prevState != newState) { - //Notify all proxy objects first of adapter state change - if (newState == BluetoothAdapter.STATE_BLE_ON || - newState == BluetoothAdapter.STATE_OFF) { - boolean intermediate_off = (prevState == BluetoothAdapter.STATE_TURNING_OFF - && newState == BluetoothAdapter.STATE_BLE_ON); - - if (newState == BluetoothAdapter.STATE_OFF) { - // If Bluetooth is off, send service down event to proxy objects, and unbind - if (DBG) Slog.d(TAG, "Bluetooth is complete turn off"); - sendBluetoothServiceDownCallback(); - unbindAndFinish(); - sendBleStateChanged(prevState, newState); - // Don't broadcast as it has already been broadcast before - isStandardBroadcast = false; - - } else if (!intermediate_off) { - // connect to GattService - if (DBG) Slog.d(TAG, "Bluetooth is in LE only mode"); - if (mBluetoothGatt != null) { - if (DBG) Slog.d(TAG, "Calling BluetoothGattServiceUp"); - onBluetoothGattServiceUp(); - } else { - if (DBG) Slog.d(TAG, "Binding Bluetooth GATT service"); - if (mContext.getPackageManager().hasSystemFeature( - PackageManager.FEATURE_BLUETOOTH_LE)) { - Intent i = new Intent(IBluetoothGatt.class.getName()); - doBind(i, mConnection, Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT, UserHandle.CURRENT); - } - } - sendBleStateChanged(prevState, newState); - //Don't broadcase this as std intent - isStandardBroadcast = false; - - } else if (intermediate_off){ - if (DBG) Slog.d(TAG, "Intermediate off, back to LE only mode"); - // For LE only mode, broadcast as is - sendBleStateChanged(prevState, newState); - sendBluetoothStateCallback(false); // BT is OFF for general users - // Broadcast as STATE_OFF - newState = BluetoothAdapter.STATE_OFF; - sendBrEdrDownCallback(); - } - } else if (newState == BluetoothAdapter.STATE_ON) { - boolean isUp = (newState==BluetoothAdapter.STATE_ON); - sendBluetoothStateCallback(isUp); + if (prevState == newState) { // No change. Nothing to do. + return; + } + // Notify all proxy objects first of adapter state change + if (newState == BluetoothAdapter.STATE_BLE_ON || + newState == BluetoothAdapter.STATE_OFF) { + boolean intermediate_off = (prevState == BluetoothAdapter.STATE_TURNING_OFF + && newState == BluetoothAdapter.STATE_BLE_ON); + + if (newState == BluetoothAdapter.STATE_OFF) { + // If Bluetooth is off, send service down event to proxy objects, and unbind + if (DBG) Slog.d(TAG, "Bluetooth is complete send Service Down"); + sendBluetoothServiceDownCallback(); + unbindAndFinish(); sendBleStateChanged(prevState, newState); + // Don't broadcast as it has already been broadcast before + isStandardBroadcast = false; - } else if (newState == BluetoothAdapter.STATE_BLE_TURNING_ON || - newState == BluetoothAdapter.STATE_BLE_TURNING_OFF ) { + } else if (!intermediate_off) { + // connect to GattService + if (DBG) Slog.d(TAG, "Bluetooth is in LE only mode"); + if (mBluetoothGatt != null) { + if (DBG) Slog.d(TAG, "Calling BluetoothGattServiceUp"); + onBluetoothGattServiceUp(); + } else { + if (DBG) Slog.d(TAG, "Binding Bluetooth GATT service"); + if (mContext.getPackageManager().hasSystemFeature( + PackageManager.FEATURE_BLUETOOTH_LE)) { + Intent i = new Intent(IBluetoothGatt.class.getName()); + doBind(i, mConnection, Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT, UserHandle.CURRENT); + } + } sendBleStateChanged(prevState, newState); + //Don't broadcase this as std intent isStandardBroadcast = false; - } else if (newState == BluetoothAdapter.STATE_TURNING_ON || - newState == BluetoothAdapter.STATE_TURNING_OFF) { + } else if (intermediate_off) { + if (DBG) Slog.d(TAG, "Intermediate off, back to LE only mode"); + // For LE only mode, broadcast as is sendBleStateChanged(prevState, newState); + sendBluetoothStateCallback(false); // BT is OFF for general users + // Broadcast as STATE_OFF + newState = BluetoothAdapter.STATE_OFF; + sendBrEdrDownCallback(); } + } else if (newState == BluetoothAdapter.STATE_ON) { + boolean isUp = (newState == BluetoothAdapter.STATE_ON); + sendBluetoothStateCallback(isUp); + sendBleStateChanged(prevState, newState); - if (isStandardBroadcast) { - if (prevState == BluetoothAdapter.STATE_BLE_ON) { - // Show prevState of BLE_ON as OFF to standard users - prevState = BluetoothAdapter.STATE_OFF; - } - Intent intent = new Intent(BluetoothAdapter.ACTION_STATE_CHANGED); - intent.putExtra(BluetoothAdapter.EXTRA_PREVIOUS_STATE, prevState); - intent.putExtra(BluetoothAdapter.EXTRA_STATE, newState); - intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); - mContext.sendBroadcastAsUser(intent, UserHandle.ALL, BLUETOOTH_PERM); + } else if (newState == BluetoothAdapter.STATE_BLE_TURNING_ON || + newState == BluetoothAdapter.STATE_BLE_TURNING_OFF ) { + sendBleStateChanged(prevState, newState); + isStandardBroadcast = false; + + } else if (newState == BluetoothAdapter.STATE_TURNING_ON || + newState == BluetoothAdapter.STATE_TURNING_OFF) { + sendBleStateChanged(prevState, newState); + } + + if (isStandardBroadcast) { + if (prevState == BluetoothAdapter.STATE_BLE_ON) { + // Show prevState of BLE_ON as OFF to standard users + prevState = BluetoothAdapter.STATE_OFF; } + Intent intent = new Intent(BluetoothAdapter.ACTION_STATE_CHANGED); + intent.putExtra(BluetoothAdapter.EXTRA_PREVIOUS_STATE, prevState); + intent.putExtra(BluetoothAdapter.EXTRA_STATE, newState); + intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + mContext.sendBroadcastAsUser(intent, UserHandle.ALL, BLUETOOTH_PERM); } } @@ -1868,16 +1885,27 @@ class BluetoothManagerService extends IBluetoothManager.Stub { return false; } - private void sendDisableMsg() { + private void sendDisableMsg(String packageName) { mHandler.sendMessage(mHandler.obtainMessage(MESSAGE_DISABLE)); + addActiveLog(packageName, false); } - private void sendEnableMsg(boolean quietMode) { + private void sendEnableMsg(boolean quietMode, String packageName) { mHandler.sendMessage(mHandler.obtainMessage(MESSAGE_ENABLE, quietMode ? 1 : 0, 0)); + addActiveLog(packageName, true); + } + + private void addActiveLog(String packageName, boolean enable) { + synchronized (mActiveLogs) { + if (mActiveLogs.size() > 10) { + mActiveLogs.remove(); + } + mActiveLogs.add(new ActiveLog(packageName, enable, System.currentTimeMillis())); + } } - private void recoverBluetoothServiceFromError() { + private void recoverBluetoothServiceFromError(boolean clearBle) { Slog.e(TAG,"recoverBluetoothServiceFromError"); try { mBluetoothLock.readLock().lock(); @@ -1915,6 +1943,10 @@ class BluetoothManagerService extends IBluetoothManager.Stub { mHandler.removeMessages(MESSAGE_BLUETOOTH_STATE_CHANGE); mState = BluetoothAdapter.STATE_OFF; + if (clearBle) { + clearBleApps(); + } + mEnable = false; if (mErrorRecoveryRetryCounter++ < MAX_ERROR_RESTART_RETRIES) { @@ -1931,19 +1963,50 @@ class BluetoothManagerService extends IBluetoothManager.Stub { public void dump(FileDescriptor fd, PrintWriter writer, String[] args) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG); String errorMsg = null; + + boolean protoOut = (args.length > 0) && args[0].startsWith("--proto"); + + if (!protoOut) { + writer.println("Bluetooth Status"); + writer.println(" enabled: " + isEnabled()); + writer.println(" state: " + BluetoothAdapter.nameForState(mState)); + writer.println(" address: " + mAddress); + writer.println(" name: " + mName); + if (mEnable) { + long onDuration = System.currentTimeMillis() - mActiveLogs.getLast().getTime(); + String onDurationString = String.format("%02d:%02d:%02d.%03d", + (int)(onDuration / (1000 * 60 * 60)), + (int)((onDuration / (1000 * 60)) % 60), + (int)((onDuration / 1000) % 60), + (int)(onDuration % 1000)); + writer.println(" time since enabled: " + onDurationString + "\n"); + } + + writer.println("Enable log:"); + for (ActiveLog log : mActiveLogs) { + writer.println(log); + } + + writer.println("\n" + mBleApps.size() + " BLE Apps registered:"); + for (ClientDeathRecipient app : mBleApps.values()) { + writer.println(app.getPackageName()); + } + + writer.flush(); + } + if (mBluetoothBinder == null) { errorMsg = "Bluetooth Service not connected"; } else { try { mBluetoothBinder.dump(fd, args); } catch (RemoteException re) { - errorMsg = "RemoteException while calling Bluetooth Service"; + errorMsg = "RemoteException while dumping Bluetooth Service"; } } if (errorMsg != null) { // Silently return if we are extracting metrics in Protobuf format - if ((args.length > 0) && args[0].startsWith("--proto")) - return; + if (protoOut) return; writer.println(errorMsg); } } diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index a0707d5ce156..269327201d50 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -373,6 +373,11 @@ public class ConnectivityService extends IConnectivityManager.Stub private static final int EVENT_SET_ACCEPT_UNVALIDATED = 28; /** + * used to specify whether a network should not be penalized when it becomes unvalidated. + */ + private static final int EVENT_SET_AVOID_UNVALIDATED = 35; + + /** * used to ask the user to confirm a connection to an unvalidated network. * obj = network */ @@ -399,16 +404,6 @@ public class ConnectivityService extends IConnectivityManager.Stub private static final int EVENT_REQUEST_LINKPROPERTIES = 32; private static final int EVENT_REQUEST_NETCAPABILITIES = 33; - /* - * used to specify whether a network should not be penalized when it becomes unvalidated. - */ - private static final int EVENT_SET_AVOID_UNVALIDATED = 35; - - /** - * used to trigger revalidation of a network. - */ - private static final int EVENT_REVALIDATE_NETWORK = 36; - /** Handler thread used for both of the handlers below. */ @VisibleForTesting protected final HandlerThread mHandlerThread; @@ -869,6 +864,7 @@ public class ConnectivityService extends IConnectivityManager.Stub mAvoidBadWifiTracker = createAvoidBadWifiTracker( mContext, mHandler, () -> rematchForAvoidBadWifiUpdate()); + mAvoidBadWifiTracker.start(); } private NetworkRequest createInternetRequestForTransport( @@ -1332,16 +1328,13 @@ public class ConnectivityService extends IConnectivityManager.Stub @Override public LinkProperties getLinkProperties(Network network) { enforceAccessPermission(); - return getLinkProperties(getNetworkAgentInfoForNetwork(network)); - } - - private LinkProperties getLinkProperties(NetworkAgentInfo nai) { - if (nai == null) { - return null; - } - synchronized (nai) { - return new LinkProperties(nai.linkProperties); + NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network); + if (nai != null) { + synchronized (nai) { + return new LinkProperties(nai.linkProperties); + } } + return null; } private NetworkCapabilities getNetworkCapabilitiesInternal(NetworkAgentInfo nai) { @@ -3004,11 +2997,6 @@ public class ConnectivityService extends IConnectivityManager.Stub } break; } - case EVENT_REVALIDATE_NETWORK: { - boolean hasConnectivity = (msg.arg2 == 1); - handleReportNetworkConnectivity((Network) msg.obj, msg.arg1, hasConnectivity); - break; - } } } } @@ -3181,15 +3169,8 @@ public class ConnectivityService extends IConnectivityManager.Stub public void reportNetworkConnectivity(Network network, boolean hasConnectivity) { enforceAccessPermission(); enforceInternetPermission(); - final int uid = Binder.getCallingUid(); - final int connectivityInfo = hasConnectivity ? 1 : 0; - mHandler.sendMessage( - mHandler.obtainMessage(EVENT_REVALIDATE_NETWORK, uid, connectivityInfo, network)); - } - private void handleReportNetworkConnectivity( - Network network, int uid, boolean hasConnectivity) { - final NetworkAgentInfo nai; + NetworkAgentInfo nai; if (network == null) { nai = getDefaultNetwork(); } else { @@ -3200,23 +3181,21 @@ public class ConnectivityService extends IConnectivityManager.Stub return; } // Revalidate if the app report does not match our current validated state. - if (hasConnectivity == nai.lastValidated) { - return; - } + if (hasConnectivity == nai.lastValidated) return; + final int uid = Binder.getCallingUid(); if (DBG) { - int netid = nai.network.netId; - log("reportNetworkConnectivity(" + netid + ", " + hasConnectivity + ") by " + uid); - } - // Validating a network that has not yet connected could result in a call to - // rematchNetworkAndRequests() which is not meant to work on such networks. - if (!nai.everConnected) { - return; + log("reportNetworkConnectivity(" + nai.network.netId + ", " + hasConnectivity + + ") by " + uid); } - LinkProperties lp = getLinkProperties(nai); - if (isNetworkWithLinkPropertiesBlocked(lp, uid, false)) { - return; + synchronized (nai) { + // Validating a network that has not yet connected could result in a call to + // rematchNetworkAndRequests() which is not meant to work on such networks. + if (!nai.everConnected) return; + + if (isNetworkWithLinkPropertiesBlocked(nai.linkProperties, uid, false)) return; + + nai.networkMonitor.sendMessage(NetworkMonitor.CMD_FORCE_REEVALUATION, uid); } - nai.networkMonitor.sendMessage(NetworkMonitor.CMD_FORCE_REEVALUATION, uid); } private ProxyInfo getDefaultProxy() { diff --git a/services/core/java/com/android/server/DiskStatsService.java b/services/core/java/com/android/server/DiskStatsService.java index 8ca675a904c4..962ac6fc540f 100644 --- a/services/core/java/com/android/server/DiskStatsService.java +++ b/services/core/java/com/android/server/DiskStatsService.java @@ -22,6 +22,15 @@ import android.os.Environment; import android.os.StatFs; import android.os.SystemClock; import android.os.storage.StorageManager; +import android.util.Log; + +import com.android.server.storage.DiskStatsFileLogger; +import com.android.server.storage.DiskStatsLoggingService; + +import libcore.io.IoUtils; + +import org.json.JSONException; +import org.json.JSONObject; import java.io.File; import java.io.FileDescriptor; @@ -35,11 +44,13 @@ import java.io.PrintWriter; */ public class DiskStatsService extends Binder { private static final String TAG = "DiskStatsService"; + private static final String DISKSTATS_DUMP_FILE = "/data/system/diskstats_cache.json"; private final Context mContext; public DiskStatsService(Context context) { mContext = context; + DiskStatsLoggingService.schedule(context); } @Override @@ -84,6 +95,8 @@ public class DiskStatsService extends Binder { pw.println("File-based Encryption: true"); } + reportCachedValues(pw); + // TODO: Read /proc/yaffs and report interesting values; // add configurable (through args) performance test parameters. } @@ -114,4 +127,36 @@ public class DiskStatsService extends Binder { return; } } + + private void reportCachedValues(PrintWriter pw) { + try { + String jsonString = IoUtils.readFileAsString(DISKSTATS_DUMP_FILE); + JSONObject json = new JSONObject(jsonString); + pw.print("App Size: "); + pw.println(json.getLong(DiskStatsFileLogger.APP_SIZE_AGG_KEY)); + pw.print("App Cache Size: "); + pw.println(json.getLong(DiskStatsFileLogger.APP_CACHE_AGG_KEY)); + pw.print("Photos Size: "); + pw.println(json.getLong(DiskStatsFileLogger.PHOTOS_KEY)); + pw.print("Videos Size: "); + pw.println(json.getLong(DiskStatsFileLogger.VIDEOS_KEY)); + pw.print("Audio Size: "); + pw.println(json.getLong(DiskStatsFileLogger.AUDIO_KEY)); + pw.print("Downloads Size: "); + pw.println(json.getLong(DiskStatsFileLogger.DOWNLOADS_KEY)); + pw.print("System Size: "); + pw.println(json.getLong(DiskStatsFileLogger.SYSTEM_KEY)); + pw.print("Other Size: "); + pw.println(json.getLong(DiskStatsFileLogger.MISC_KEY)); + pw.print("Package Names: "); + pw.println(json.getJSONArray(DiskStatsFileLogger.PACKAGE_NAMES_KEY)); + pw.print("App Sizes: "); + pw.println(json.getJSONArray(DiskStatsFileLogger.APP_SIZES_KEY)); + pw.print("Cache Sizes: "); + pw.println(json.getJSONArray(DiskStatsFileLogger.APP_CACHES_KEY)); + } catch (IOException | JSONException e) { + Log.w(TAG, "exception reading diskstats cache file", e); + } + } + } diff --git a/services/core/java/com/android/server/HardwarePropertiesManagerService.java b/services/core/java/com/android/server/HardwarePropertiesManagerService.java index 23cf64a031af..36a16cd0a18b 100644 --- a/services/core/java/com/android/server/HardwarePropertiesManagerService.java +++ b/services/core/java/com/android/server/HardwarePropertiesManagerService.java @@ -16,6 +16,8 @@ package com.android.server; +import android.Manifest; +import android.app.ActivityManager; import android.app.admin.DevicePolicyManager; import android.content.Context; import android.content.pm.PackageManager; @@ -80,8 +82,9 @@ public class HardwarePropertiesManagerService extends IHardwarePropertiesManager * * @param callingPackage The calling package name. * - * @throws SecurityException if something other than the profile or device owner, or the - * current VR service tries to retrieve information provided by this service. + * @throws SecurityException if something other than the profile or device owner, the + * current VR service, or a caller holding the {@link Manifest.permission#DEVICE_POWER} + * permission tries to retrieve information provided by this service. */ private void enforceHardwarePropertiesRetrievalAllowed(String callingPackage) throws SecurityException { @@ -100,9 +103,11 @@ public class HardwarePropertiesManagerService extends IHardwarePropertiesManager final VrManagerInternal vrService = LocalServices.getService(VrManagerInternal.class); final DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); if (!dpm.isDeviceOwnerApp(callingPackage) && !dpm.isProfileOwnerApp(callingPackage) - && !vrService.isCurrentVrListener(callingPackage, userId)) { - throw new SecurityException("The caller is not a device or profile owner or bound " - + "VrListenerService."); + && !vrService.isCurrentVrListener(callingPackage, userId) + && mContext.checkCallingOrSelfPermission(Manifest.permission.DEVICE_POWER) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("The caller is not a device or profile owner, bound " + + "VrListenerService, or holding the DEVICE_POWER permission."); } } } diff --git a/services/core/java/com/android/server/PinnerService.java b/services/core/java/com/android/server/PinnerService.java index 356ccb3596a9..fa5a52c712cd 100644 --- a/services/core/java/com/android/server/PinnerService.java +++ b/services/core/java/com/android/server/PinnerService.java @@ -63,7 +63,7 @@ public final class PinnerService extends SystemService { private BinderService mBinderService; - private final long MAX_CAMERA_PIN_SIZE = 50 * (1 << 20); //50MB max + private final long MAX_CAMERA_PIN_SIZE = 80 * (1 << 20); //80MB max private PinnerHandler mPinnerHandler = null; @@ -192,6 +192,9 @@ public final class PinnerService extends SystemService { if (isResolverActivity(cameraResolveInfo.activityInfo)) { + if (DEBUG) { + Slog.v(TAG, "cameraIntent returned resolverActivity"); + } return null; } diff --git a/services/core/java/com/android/server/SystemServiceManager.java b/services/core/java/com/android/server/SystemServiceManager.java index 90f507c146bf..a0c80fe666e1 100644 --- a/services/core/java/com/android/server/SystemServiceManager.java +++ b/services/core/java/com/android/server/SystemServiceManager.java @@ -35,6 +35,7 @@ public class SystemServiceManager { private final Context mContext; private boolean mSafeMode; + private boolean mRuntimeRestarted; // Services that should receive lifecycle events. private final ArrayList<SystemService> mServices = new ArrayList<SystemService>(); @@ -246,6 +247,17 @@ public class SystemServiceManager { } /** + * @return true if runtime was restarted, false if it's normal boot + */ + public boolean isRuntimeRestarted() { + return mRuntimeRestarted; + } + + void setRuntimeRestarted(boolean runtimeRestarted) { + mRuntimeRestarted = runtimeRestarted; + } + + /** * Outputs the state of this manager to the System log. */ public void dump() { diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java index 58ccac4f74ef..ab036c7163d3 100644 --- a/services/core/java/com/android/server/VibratorService.java +++ b/services/core/java/com/android/server/VibratorService.java @@ -86,7 +86,6 @@ public class VibratorService extends IVibratorService.Stub private int mCurVibUid = -1; private boolean mLowPowerMode; - private boolean mAllowPriorityVibrationsInLowPowerMode; private SettingsObserver mSettingObserver; native static boolean vibratorExists(); @@ -216,9 +215,6 @@ public class VibratorService extends IVibratorService.Stub mPreviousVibrationsLimit = mContext.getResources().getInteger( com.android.internal.R.integer.config_previousVibrationsDumpLimit); - mAllowPriorityVibrationsInLowPowerMode = mContext.getResources().getBoolean( - com.android.internal.R.bool.config_allowPriorityVibrationsInLowPowerMode); - mVibrations = new LinkedList<>(); mPreviousVibrations = new LinkedList<>(); @@ -451,7 +447,8 @@ public class VibratorService extends IVibratorService.Stub // Lock held on mVibrations private void startVibrationLocked(final Vibration vib) { try { - if (!isAllowedToVibrate(vib)) { + if (mLowPowerMode + && vib.mUsageHint != AudioAttributes.USAGE_NOTIFICATION_RINGTONE) { return; } @@ -488,23 +485,6 @@ public class VibratorService extends IVibratorService.Stub } } - private boolean isAllowedToVibrate(Vibration vib) { - if (mLowPowerMode) { - if (vib.mUsageHint == AudioAttributes.USAGE_NOTIFICATION_RINGTONE) { - return true; - } else if (mAllowPriorityVibrationsInLowPowerMode) { - if (vib.mUsageHint == AudioAttributes.USAGE_ALARM || - vib.mUsageHint == AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY || - vib.mUsageHint == AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST) { - return true; - } - } - return false; - } else { - return true; - } - } - private boolean shouldVibrateForRingtone() { AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); int ringerMode = audioManager.getRingerModeInternal(); diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index e5729208768f..91c4571ee491 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -1212,6 +1212,15 @@ public final class ActivityManagerService extends ActivityManagerNative /** * Set while we are wanting to sleep, to prevent any * activities from being started/resumed. + * + * TODO(b/33594039): Clarify the actual state transitions represented by mSleeping. + * + * Currently mSleeping is set to true when transitioning into the sleep state, and remains true + * while in the sleep state until there is a pending transition out of sleep, in which case + * mSleeping is set to false, and remains false while awake. + * + * Whether mSleeping can quickly toggled between true/false without the device actually + * display changing states is undefined. */ private boolean mSleeping = false; @@ -1537,8 +1546,8 @@ public final class ActivityManagerService extends ActivityManagerNative static final int NOTIFY_PINNED_STACK_ANIMATION_ENDED_LISTENERS_MSG = 66; static final int NOTIFY_FORCED_RESIZABLE_MSG = 67; static final int NOTIFY_ACTIVITY_DISMISSING_DOCKED_STACK_MSG = 68; - static final int VR_MODE_APPLY_IF_NEEDED_MSG = 69; - static final int SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG_MSG = 70; + static final int SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG_MSG = 69; + static final int NOTIFY_VR_SLEEPING_MSG = 70; static final int FIRST_ACTIVITY_STACK_MSG = 100; static final int FIRST_BROADCAST_QUEUE_MSG = 200; @@ -2354,14 +2363,8 @@ public final class ActivityManagerService extends ActivityManagerNative } } vrService.setVrMode(vrMode, requestedPackage, userId, callingPackage); - } break; - case VR_MODE_APPLY_IF_NEEDED_MSG: { - final ActivityRecord r = (ActivityRecord) msg.obj; - final boolean needsVrMode = r != null && r.requestedVrComponent != null; - if (needsVrMode) { - applyVrMode(msg.arg1 == 1, r.requestedVrComponent, r.userId, - r.info.getComponentName(), false); - } + } case NOTIFY_VR_SLEEPING_MSG: { + notifyVrManagerOfSleepState(msg.arg1 != 0); } break; } } @@ -3164,20 +3167,17 @@ public final class ActivityManagerService extends ActivityManagerNative mHandler.obtainMessage(VR_MODE_CHANGE_MSG, 0, 0, r)); } - private void applyVrModeIfNeededLocked(ActivityRecord r, boolean enable) { + private void sendNotifyVrManagerOfSleepState(boolean isSleeping) { mHandler.sendMessage( - mHandler.obtainMessage(VR_MODE_APPLY_IF_NEEDED_MSG, enable ? 1 : 0, 0, r)); + mHandler.obtainMessage(NOTIFY_VR_SLEEPING_MSG, isSleeping ? 1 : 0, 0)); } - private void applyVrMode(boolean enabled, ComponentName packageName, int userId, - ComponentName callingPackage, boolean immediate) { - VrManagerInternal vrService = - LocalServices.getService(VrManagerInternal.class); - if (immediate) { - vrService.setVrModeImmediate(enabled, packageName, userId, callingPackage); - } else { - vrService.setVrMode(enabled, packageName, userId, callingPackage); + private void notifyVrManagerOfSleepState(boolean isSleeping) { + final VrManagerInternal vrService = LocalServices.getService(VrManagerInternal.class); + if (vrService == null) { + return; } + vrService.onSleepStateChanged(isSleeping); } final void showAskCompatModeDialogLocked(ActivityRecord r) { @@ -6455,6 +6455,7 @@ public final class ActivityManagerService extends ActivityManagerNative // the pid if we are running in multiple processes, or just pull the // next app record if we are emulating process with anonymous threads. ProcessRecord app; + long startTime = SystemClock.uptimeMillis(); if (pid != MY_PID && pid >= 0) { synchronized (mPidsSelfLocked) { app = mPidsSelfLocked.get(pid); @@ -6531,6 +6532,8 @@ public final class ActivityManagerService extends ActivityManagerNative mHandler.sendMessageDelayed(msg, CONTENT_PROVIDER_PUBLISH_TIMEOUT); } + checkTime(startTime, "attachApplicationLocked: before bindApplication"); + if (!normalMode) { Slog.i(TAG, "Launching preboot mode app: " + app); } @@ -6589,7 +6592,8 @@ public final class ActivityManagerService extends ActivityManagerNative profileFd = profileFd.dup(); } ProfilerInfo profilerInfo = profileFile == null ? null - : new ProfilerInfo(profileFile, profileFd, samplingInterval, profileAutoStop); + : new ProfilerInfo(profileFile, profileFd, samplingInterval, profileAutoStop); + checkTime(startTime, "attachApplicationLocked: immediately before bindApplication"); thread.bindApplication(processName, appInfo, providers, app.instrumentationClass, profilerInfo, app.instrumentationArguments, app.instrumentationWatcher, app.instrumentationUiAutomationConnection, testMode, @@ -6597,8 +6601,10 @@ public final class ActivityManagerService extends ActivityManagerNative isRestrictedBackupMode || !normalMode, app.persistent, new Configuration(mConfiguration), app.compat, getCommonServicesLocked(app.isolated), - mCoreSettingsObserver.getCoreSettingsLocked()); + mCoreSettingsObserver.getCoreSettingsLocked()); + checkTime(startTime, "attachApplicationLocked: immediately after bindApplication"); updateLruProcessLocked(app, false, null); + checkTime(startTime, "attachApplicationLocked: after updateLruProcessLocked"); app.lastRequestedGc = app.lastLowMemory = SystemClock.uptimeMillis(); } catch (Exception e) { // todo: Yikes! What should we do? For now we will try to @@ -6637,6 +6643,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (!badApp) { try { didSomething |= mServices.attachApplicationLocked(app, processName); + checkTime(startTime, "attachApplicationLocked: after mServices.attachApplicationLocked"); } catch (Exception e) { Slog.wtf(TAG, "Exception thrown starting services in " + app, e); badApp = true; @@ -6647,6 +6654,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (!badApp && isPendingBroadcastProcessLocked(pid)) { try { didSomething |= sendPendingBroadcastsLocked(app); + checkTime(startTime, "attachApplicationLocked: after sendPendingBroadcastsLocked"); } catch (Exception e) { // If the app died trying to launch the receiver we declare it 'bad' Slog.wtf(TAG, "Exception thrown dispatching broadcasts in " + app, e); @@ -6678,6 +6686,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (!didSomething) { updateOomAdjLocked(); + checkTime(startTime, "attachApplicationLocked: after updateOomAdjLocked"); } return true; @@ -6770,7 +6779,6 @@ public final class ActivityManagerService extends ActivityManagerNative // Some stack visibility might change (e.g. docked stack) mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS); - applyVrModeIfNeededLocked(mFocusedActivity, true); } } } finally { @@ -11699,6 +11707,7 @@ public final class ActivityManagerService extends ActivityManagerNative startTimeTrackingFocusedActivityLocked(); mTopProcessState = ActivityManager.PROCESS_STATE_TOP; mStackSupervisor.comeOutOfSleepIfNeededLocked(); + sendNotifyVrManagerOfSleepState(false); updateOomAdjLocked(); } else if (!mSleeping && shouldSleepLocked()) { mSleeping = true; @@ -11707,6 +11716,7 @@ public final class ActivityManagerService extends ActivityManagerNative } mTopProcessState = ActivityManager.PROCESS_STATE_TOP_SLEEPING; mStackSupervisor.goingToSleepLocked(); + sendNotifyVrManagerOfSleepState(true); updateOomAdjLocked(); // Initialize the wake times of all processes. @@ -12171,6 +12181,9 @@ public final class ActivityManagerService extends ActivityManagerNative case ActivityManager.BUGREPORT_OPTION_WEAR: service = "bugreportwear"; break; + case ActivityManager.BUGREPORT_OPTION_TELEPHONY: + service = "bugreportelefony"; + break; } if (service == null) { throw new IllegalArgumentException("Provided bugreport type is not correct, value: " @@ -21947,21 +21960,6 @@ public final class ActivityManagerService extends ActivityManagerNative public SleepToken acquireSleepToken(String tag) { Preconditions.checkNotNull(tag); - ComponentName requestedVrService = null; - ComponentName callingVrActivity = null; - int userId = -1; - synchronized (ActivityManagerService.this) { - if (mFocusedActivity != null) { - requestedVrService = mFocusedActivity.requestedVrComponent; - callingVrActivity = mFocusedActivity.info.getComponentName(); - userId = mFocusedActivity.userId; - } - } - - if (requestedVrService != null) { - applyVrMode(false, requestedVrService, userId, callingVrActivity, true); - } - synchronized (ActivityManagerService.this) { SleepTokenImpl token = new SleepTokenImpl(tag); mSleepTokens.add(token); diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java index 7ada47abfada..55c4698528a8 100755 --- a/services/core/java/com/android/server/am/ActivityRecord.java +++ b/services/core/java/com/android/server/am/ActivityRecord.java @@ -1384,7 +1384,8 @@ final class ActivityRecord { if (_taskDescription.getIconFilename() == null && (icon = _taskDescription.getIcon()) != null) { final String iconFilename = createImageFilename(createTime, task.taskId); - final File iconFile = new File(TaskPersister.getUserImagesDir(userId), iconFilename); + final File iconFile = new File(TaskPersister.getUserImagesDir(task.userId), + iconFilename); final String iconFilePath = iconFile.getAbsolutePath(); service.mRecentTasks.saveImage(icon, iconFilePath); _taskDescription.setIconFilename(iconFilePath); diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java index c234ac5c0dfd..aacc9de8896d 100644 --- a/services/core/java/com/android/server/am/ActivityStarter.java +++ b/services/core/java/com/android/server/am/ActivityStarter.java @@ -1624,9 +1624,9 @@ class ActivityStarter { == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK)) { // The caller has requested to completely replace any existing task with its new // activity. Well that should not be too hard... + intentActivity.task.performClearTaskLocked(); + intentActivity.task.setIntent(mStartActivity); mReuseTask = intentActivity.task; - mReuseTask.performClearTaskLocked(); - mReuseTask.setIntent(mStartActivity); // When we clear the task - focus will be adjusted, which will bring another task // to top before we launch the activity we need. This will temporary swap their // mTaskToReturnTo values and we don't want to overwrite them accidentally. diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index 4bc148bef290..4fd26b38b185 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -46,6 +46,7 @@ import static com.android.server.am.UserState.STATE_RUNNING_UNLOCKING; import android.annotation.NonNull; import android.annotation.UserIdInt; import android.app.ActivityManager; +import android.app.AppGlobals; import android.app.AppOpsManager; import android.app.Dialog; import android.app.IStopUserCallback; @@ -54,6 +55,7 @@ import android.app.KeyguardManager; import android.content.Context; import android.content.IIntentReceiver; import android.content.Intent; +import android.content.pm.IPackageManager; import android.content.pm.PackageManager; import android.content.pm.UserInfo; import android.os.BatteryStats; @@ -112,7 +114,7 @@ final class UserController { // Amount of time we wait for observers to handle a user switch before // giving up on them and unfreezing the screen. - static final int USER_SWITCH_TIMEOUT = 2 * 1000; + static final int USER_SWITCH_TIMEOUT = 3 * 1000; private final ActivityManagerService mService; private final Handler mHandler; @@ -239,11 +241,14 @@ final class UserController { // storage is already unlocked. if (uss.setState(STATE_BOOTING, STATE_RUNNING_LOCKED)) { getUserManagerInternal().setUserState(userId, uss.state); - - int uptimeSeconds = (int)(SystemClock.elapsedRealtime() / 1000); - MetricsLogger.histogram(mService.mContext, "framework_locked_boot_completed", - uptimeSeconds); - + // Do not report secondary users, runtime restarts or first boot/upgrade + if (userId == UserHandle.USER_SYSTEM + && !mService.mSystemServiceManager.isRuntimeRestarted() + && !isFirstBootOrUpgrade()) { + int uptimeSeconds = (int)(SystemClock.elapsedRealtime() / 1000); + MetricsLogger.histogram(mService.mContext, "framework_locked_boot_completed", + uptimeSeconds); + } Intent intent = new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED, null); intent.putExtra(Intent.EXTRA_USER_HANDLE, userId); intent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT @@ -415,8 +420,14 @@ final class UserController { } Slog.d(TAG, "Sending BOOT_COMPLETE user #" + userId); - int uptimeSeconds = (int)(SystemClock.elapsedRealtime() / 1000); - MetricsLogger.histogram(mService.mContext, "framework_boot_completed", uptimeSeconds); + // Do not report secondary users, runtime restarts or first boot/upgrade + if (userId == UserHandle.USER_SYSTEM + && !mService.mSystemServiceManager.isRuntimeRestarted() + && !isFirstBootOrUpgrade()) { + int uptimeSeconds = (int) (SystemClock.elapsedRealtime() / 1000); + MetricsLogger + .histogram(mService.mContext, "framework_boot_completed", uptimeSeconds); + } final Intent bootIntent = new Intent(Intent.ACTION_BOOT_COMPLETED, null); bootIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId); bootIntent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT @@ -427,6 +438,15 @@ final class UserController { } } + private static boolean isFirstBootOrUpgrade() { + IPackageManager pm = AppGlobals.getPackageManager(); + try { + return pm.isFirstBoot() || pm.isUpgrade(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + int stopUser(final int userId, final boolean force, final IStopUserCallback callback) { if (mService.checkCallingPermission(INTERACT_ACROSS_USERS_FULL) != PackageManager.PERMISSION_GRANTED) { @@ -1085,6 +1105,7 @@ final class UserController { mCurWaitingUserSwitchCallbacks = curWaitingUserSwitchCallbacks; } final AtomicInteger waitingCallbacksCount = new AtomicInteger(observerCount); + final long dispatchStartedTime = SystemClock.elapsedRealtime(); for (int i = 0; i < observerCount; i++) { try { // Prepend with unique prefix to guarantee that keys are unique @@ -1096,6 +1117,11 @@ final class UserController { @Override public void sendResult(Bundle data) throws RemoteException { synchronized (mService) { + long delay = SystemClock.elapsedRealtime() - dispatchStartedTime; + if (delay > USER_SWITCH_TIMEOUT) { + Slog.wtf(TAG, "User switch timeout: observer " + name + + " sent result after " + delay + " ms"); + } // Early return if this session is no longer valid if (curWaitingUserSwitchCallbacks != mCurWaitingUserSwitchCallbacks) { diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index a9277cedd4fd..c70a87c36d22 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -4035,21 +4035,23 @@ public class AudioService extends IAudioService.Stub { mIndexMap.put(device, index); changed = oldIndex != index; - if (changed) { - // Apply change to all streams using this one as alias - // if changing volume of current device, also change volume of current - // device on aliased stream - boolean currentDevice = (device == getDeviceForStream(mStreamType)); - int numStreamTypes = AudioSystem.getNumStreamTypes(); - for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) { - if (streamType != mStreamType && - mStreamVolumeAlias[streamType] == mStreamType) { - int scaledIndex = rescaleIndex(index, mStreamType, streamType); - mStreamStates[streamType].setIndex(scaledIndex, device, caller); - if (currentDevice) { - mStreamStates[streamType].setIndex(scaledIndex, - getDeviceForStream(streamType), caller); - } + // Apply change to all streams using this one as alias if: + // - the index actually changed OR + // - there is no volume index stored for this device on alias stream. + // If changing volume of current device, also change volume of current + // device on aliased stream + final boolean currentDevice = (device == getDeviceForStream(mStreamType)); + final int numStreamTypes = AudioSystem.getNumStreamTypes(); + for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) { + final VolumeStreamState aliasStreamState = mStreamStates[streamType]; + if (streamType != mStreamType && + mStreamVolumeAlias[streamType] == mStreamType && + (changed || !aliasStreamState.hasIndexForDevice(device))) { + final int scaledIndex = rescaleIndex(index, mStreamType, streamType); + aliasStreamState.setIndex(scaledIndex, device, caller); + if (currentDevice) { + aliasStreamState.setIndex(scaledIndex, + getDeviceForStream(streamType), caller); } } } @@ -4086,6 +4088,12 @@ public class AudioService extends IAudioService.Stub { } } + public boolean hasIndexForDevice(int device) { + synchronized (VolumeStreamState.class) { + return (mIndexMap.get(device, -1) != -1); + } + } + public int getMaxIndex() { return mIndexMax; } diff --git a/services/core/java/com/android/server/connectivity/ConnectStats.java b/services/core/java/com/android/server/connectivity/ConnectStats.java new file mode 100644 index 000000000000..d6de815c8ef4 --- /dev/null +++ b/services/core/java/com/android/server/connectivity/ConnectStats.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2016 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.connectivity; + +import android.system.OsConstants; +import android.util.IntArray; +import android.util.SparseIntArray; +import com.android.internal.util.TokenBucket; +import com.android.server.connectivity.metrics.IpConnectivityLogClass.ConnectStatistics; +import com.android.server.connectivity.metrics.IpConnectivityLogClass.Pair; + +/** + * A class that aggregates connect() statistics and helps build + * IpConnectivityLogClass.ConnectStatistics instances. + * + * {@hide} + */ +public class ConnectStats { + private final static int EALREADY = OsConstants.EALREADY; + private final static int EINPROGRESS = OsConstants.EINPROGRESS; + + /** How many events resulted in a given errno. */ + private final SparseIntArray mErrnos = new SparseIntArray(); + /** Latencies of blocking connects. TODO: add non-blocking connects latencies. */ + private final IntArray mLatencies = new IntArray(); + /** TokenBucket for rate limiting latency recording. */ + private final TokenBucket mLatencyTb; + /** Maximum number of latency values recorded. */ + private final int mMaxLatencyRecords; + /** Total count of successful connects. */ + private int mConnectCount = 0; + /** Total count of successful connects with IPv6 socket address. */ + private int mIpv6ConnectCount = 0; + + public ConnectStats(TokenBucket tb, int maxLatencyRecords) { + mLatencyTb = tb; + mMaxLatencyRecords = maxLatencyRecords; + } + + public ConnectStatistics toProto() { + ConnectStatistics stats = new ConnectStatistics(); + stats.connectCount = mConnectCount; + stats.ipv6AddrCount = mIpv6ConnectCount; + stats.latenciesMs = mLatencies.toArray(); + stats.errnosCounters = toPairArrays(mErrnos); + return stats; + } + + public void addEvent(int errno, int latencyMs, String ipAddr) { + if (isSuccess(errno)) { + countConnect(ipAddr); + countLatency(errno, latencyMs); + } else { + countError(errno); + } + } + + private void countConnect(String ipAddr) { + mConnectCount++; + if (isIPv6(ipAddr)) mIpv6ConnectCount++; + } + + private void countLatency(int errno, int ms) { + if (isNonBlocking(errno)) { + // Ignore connect() on non-blocking sockets + return; + } + if (!mLatencyTb.get()) { + // Rate limited + return; + } + if (mLatencies.size() >= mMaxLatencyRecords) { + // Hard limit the total number of latency measurements. + return; + } + mLatencies.add(ms); + } + + private void countError(int errno) { + final int newcount = mErrnos.get(errno, 0) + 1; + mErrnos.put(errno, newcount); + } + + private static boolean isSuccess(int errno) { + return (errno == 0) || isNonBlocking(errno); + } + + private static boolean isNonBlocking(int errno) { + // On non-blocking TCP sockets, connect() immediately returns EINPROGRESS. + // On non-blocking TCP sockets that are connecting, connect() immediately returns EALREADY. + return (errno == EINPROGRESS) || (errno == EALREADY); + } + + private static boolean isIPv6(String ipAddr) { + return ipAddr.contains(":"); + } + + private static Pair[] toPairArrays(SparseIntArray counts) { + final int s = counts.size(); + Pair[] pairs = new Pair[s]; + for (int i = 0; i < s; i++) { + Pair p = new Pair(); + p.key = counts.keyAt(i); + p.value = counts.valueAt(i); + pairs[i] = p; + } + return pairs; + } +} diff --git a/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java b/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java index f1ef947f3472..2a2d1abc6def 100644 --- a/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java +++ b/services/core/java/com/android/server/connectivity/IpConnectivityEventBuilder.java @@ -43,15 +43,19 @@ final public class IpConnectivityEventBuilder { private IpConnectivityEventBuilder() { } - public static byte[] serialize(int dropped, List<ConnectivityMetricsEvent> events) + public static byte[] serialize(int dropped, List<IpConnectivityEvent> events) throws IOException { final IpConnectivityLog log = new IpConnectivityLog(); - log.events = toProto(events); + log.events = events.toArray(new IpConnectivityEvent[events.size()]); log.droppedEvents = dropped; + if ((log.events.length > 0) || (dropped > 0)) { + // Only write version number if log has some information at all. + log.version = IpConnectivityMetrics.VERSION; + } return IpConnectivityLog.toByteArray(log); } - public static IpConnectivityEvent[] toProto(List<ConnectivityMetricsEvent> eventsIn) { + public static List<IpConnectivityEvent> toProto(List<ConnectivityMetricsEvent> eventsIn) { final ArrayList<IpConnectivityEvent> eventsOut = new ArrayList<>(eventsIn.size()); for (ConnectivityMetricsEvent in : eventsIn) { final IpConnectivityEvent out = toProto(in); @@ -60,7 +64,7 @@ final public class IpConnectivityEventBuilder { } eventsOut.add(out); } - return eventsOut.toArray(new IpConnectivityEvent[eventsOut.size()]); + return eventsOut; } public static IpConnectivityEvent toProto(ConnectivityMetricsEvent ev) { diff --git a/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java b/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java index bcbcf54b03d2..76c3528856fd 100644 --- a/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java +++ b/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java @@ -19,31 +19,53 @@ package com.android.server.connectivity; import android.content.Context; import android.net.ConnectivityMetricsEvent; import android.net.IIpConnectivityMetrics; +import android.net.INetdEventCallback; +import android.net.metrics.ApfProgramEvent; import android.net.metrics.IpConnectivityLog; +import android.os.Binder; import android.os.IBinder; import android.os.Parcelable; +import android.os.Process; +import android.provider.Settings; import android.text.TextUtils; +import android.text.format.DateUtils; +import android.util.ArrayMap; import android.util.Base64; import android.util.Log; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.TokenBucket; import com.android.server.SystemService; +import com.android.server.connectivity.metrics.IpConnectivityLogClass.IpConnectivityEvent; import java.io.FileDescriptor; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; - -import static com.android.server.connectivity.metrics.IpConnectivityLogClass.IpConnectivityEvent; +import java.util.List; +import java.util.function.ToIntFunction; /** {@hide} */ final public class IpConnectivityMetrics extends SystemService { private static final String TAG = IpConnectivityMetrics.class.getSimpleName(); private static final boolean DBG = false; + // The logical version numbers of ipconnectivity.proto, corresponding to the + // "version" field of IpConnectivityLog. + private static final int NYC = 0; + private static final int NYC_MR1 = 1; + private static final int NYC_MR2 = 2; + public static final int VERSION = NYC_MR2; + private static final String SERVICE_NAME = IpConnectivityLog.SERVICE_NAME; // Default size of the event buffer. Once the buffer is full, incoming events are dropped. private static final int DEFAULT_BUFFER_SIZE = 2000; + // Maximum size of the event buffer. + private static final int MAXIMUM_BUFFER_SIZE = DEFAULT_BUFFER_SIZE * 10; + + private static final int MAXIMUM_CONNECT_LATENCY_RECORDS = 20000; + + private static final int ERROR_RATE_LIMITED = -1; // Lock ensuring that concurrent manipulations of the event buffer are correct. // There are three concurrent operations to synchronize: @@ -54,7 +76,7 @@ final public class IpConnectivityMetrics extends SystemService { @VisibleForTesting public final Impl impl = new Impl(); - private DnsEventListenerService mDnsListener; + private NetdEventListenerService mNetdListener; @GuardedBy("mLock") private ArrayList<ConnectivityMetricsEvent> mBuffer; @@ -62,12 +84,21 @@ final public class IpConnectivityMetrics extends SystemService { private int mDropped; @GuardedBy("mLock") private int mCapacity; + @GuardedBy("mLock") + private final ArrayMap<Class<?>, TokenBucket> mBuckets = makeRateLimitingBuckets(); - public IpConnectivityMetrics(Context ctx) { + private final ToIntFunction<Context> mCapacityGetter; + + public IpConnectivityMetrics(Context ctx, ToIntFunction<Context> capacityGetter) { super(ctx); + mCapacityGetter = capacityGetter; initBuffer(); } + public IpConnectivityMetrics(Context ctx) { + this(ctx, READ_BUFFER_SIZE); + } + @Override public void onStart() { if (DBG) Log.d(TAG, "onStart"); @@ -77,16 +108,16 @@ final public class IpConnectivityMetrics extends SystemService { public void onBootPhase(int phase) { if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) { if (DBG) Log.d(TAG, "onBootPhase"); - mDnsListener = new DnsEventListenerService(getContext()); + mNetdListener = new NetdEventListenerService(getContext()); publishBinderService(SERVICE_NAME, impl); - publishBinderService(mDnsListener.SERVICE_NAME, mDnsListener); + publishBinderService(mNetdListener.SERVICE_NAME, mNetdListener); } } @VisibleForTesting public int bufferCapacity() { - return DEFAULT_BUFFER_SIZE; // TODO: read from config + return mCapacityGetter.applyAsInt(getContext()); } private void initBuffer() { @@ -104,6 +135,10 @@ final public class IpConnectivityMetrics extends SystemService { if (event == null) { return left; } + if (isRateLimited(event)) { + // Do not count as a dropped event. TODO: consider adding separate counter + return ERROR_RATE_LIMITED; + } if (left == 0) { mDropped++; return 0; @@ -113,6 +148,11 @@ final public class IpConnectivityMetrics extends SystemService { } } + private boolean isRateLimited(ConnectivityMetricsEvent event) { + TokenBucket tb = mBuckets.get(event.data.getClass()); + return (tb != null) && !tb.get(); + } + private String flushEncodedOutput() { final ArrayList<ConnectivityMetricsEvent> events; final int dropped; @@ -122,9 +162,15 @@ final public class IpConnectivityMetrics extends SystemService { initBuffer(); } + final List<IpConnectivityEvent> protoEvents = IpConnectivityEventBuilder.toProto(events); + + if (mNetdListener != null) { + mNetdListener.flushStatistics(protoEvents); + } + final byte[] data; try { - data = IpConnectivityEventBuilder.serialize(dropped, events); + data = IpConnectivityEventBuilder.serialize(dropped, protoEvents); } catch (IOException e) { Log.e(TAG, "could not serialize events", e); return ""; @@ -169,8 +215,8 @@ final public class IpConnectivityMetrics extends SystemService { pw.println("Buffer capacity: " + mCapacity); pw.println("Dropped events: " + mDropped); } - if (mDnsListener != null) { - mDnsListener.dump(pw); + if (mNetdListener != null) { + mNetdListener.dump(pw); } } @@ -225,5 +271,48 @@ final public class IpConnectivityMetrics extends SystemService { private void enforcePermission(String what) { getContext().enforceCallingOrSelfPermission(what, "IpConnectivityMetrics"); } + + private void enforceNetdEventListeningPermission() { + final int uid = Binder.getCallingUid(); + if (uid != Process.SYSTEM_UID) { + throw new SecurityException(String.format("Uid %d has no permission to listen for" + + " netd events.", uid)); + } + } + + @Override + public boolean registerNetdEventCallback(INetdEventCallback callback) { + enforceNetdEventListeningPermission(); + if (mNetdListener == null) { + return false; + } + return mNetdListener.registerNetdEventCallback(callback); + } + + @Override + public boolean unregisterNetdEventCallback() { + enforceNetdEventListeningPermission(); + if (mNetdListener == null) { + // if the service is null, we aren't registered anyway + return true; + } + return mNetdListener.unregisterNetdEventCallback(); + } + }; + + private static final ToIntFunction<Context> READ_BUFFER_SIZE = (ctx) -> { + int size = Settings.Global.getInt(ctx.getContentResolver(), + Settings.Global.CONNECTIVITY_METRICS_BUFFER_SIZE, DEFAULT_BUFFER_SIZE); + if (size <= 0) { + return DEFAULT_BUFFER_SIZE; + } + return Math.min(size, MAXIMUM_BUFFER_SIZE); }; + + private static ArrayMap<Class<?>, TokenBucket> makeRateLimitingBuckets() { + ArrayMap<Class<?>, TokenBucket> map = new ArrayMap<>(); + // one token every minute, 50 tokens max: burst of ~50 events every hour. + map.put(ApfProgramEvent.class, new TokenBucket((int)DateUtils.MINUTE_IN_MILLIS, 50)); + return map; + } } diff --git a/services/core/java/com/android/server/connectivity/DnsEventListenerService.java b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java index 8d206ef90b94..3f056a54d1f4 100644 --- a/services/core/java/com/android/server/connectivity/DnsEventListenerService.java +++ b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java @@ -19,37 +19,47 @@ package com.android.server.connectivity; import android.content.Context; import android.net.ConnectivityManager; import android.net.ConnectivityManager.NetworkCallback; +import android.net.INetdEventCallback; import android.net.Network; import android.net.NetworkRequest; import android.net.metrics.DnsEvent; -import android.net.metrics.IDnsEventListener; +import android.net.metrics.INetdEventListener; import android.net.metrics.IpConnectivityLog; +import android.os.RemoteException; +import android.text.format.DateUtils; import android.util.Log; - import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.IndentingPrintWriter; - +import com.android.internal.util.TokenBucket; +import com.android.server.connectivity.metrics.IpConnectivityLogClass.ConnectStatistics; +import com.android.server.connectivity.metrics.IpConnectivityLogClass.IpConnectivityEvent; import java.io.PrintWriter; import java.util.Arrays; +import java.util.List; import java.util.SortedMap; import java.util.TreeMap; - /** - * Implementation of the IDnsEventListener interface. + * Implementation of the INetdEventListener interface. */ -public class DnsEventListenerService extends IDnsEventListener.Stub { +public class NetdEventListenerService extends INetdEventListener.Stub { - public static final String SERVICE_NAME = "dns_listener"; + public static final String SERVICE_NAME = "netd_listener"; - private static final String TAG = DnsEventListenerService.class.getSimpleName(); - private static final boolean DBG = true; + private static final String TAG = NetdEventListenerService.class.getSimpleName(); + private static final boolean DBG = false; private static final boolean VDBG = false; // TODO: read this constant from system property private static final int MAX_LOOKUPS_PER_DNS_EVENT = 100; + // Rate limit connect latency logging to 1 measurement per 15 seconds (5760 / day) with maximum + // bursts of 5000 measurements. + private static final int CONNECT_LATENCY_BURST_LIMIT = 5000; + private static final int CONNECT_LATENCY_FILL_RATE = 15 * (int) DateUtils.SECOND_IN_MILLIS; + private static final int CONNECT_LATENCY_MAXIMUM_RECORDS = 20000; + // Stores the results of a number of consecutive DNS lookups on the same network. // This class is not thread-safe and it is the responsibility of the service to call its methods // on one thread at a time. @@ -86,7 +96,7 @@ public class DnsEventListenerService extends IDnsEventListener.Stub { byte[] returnCodes = Arrays.copyOf(mReturnCodes, mEventCount); int[] latenciesMs = Arrays.copyOf(mLatenciesMs, mEventCount); mMetricsLog.log(new DnsEvent(mNetId, eventTypes, returnCodes, latenciesMs)); - maybeLog(String.format("Logging %d results for netId %d", mEventCount, mNetId)); + maybeLog("Logging %d results for netId %d", mEventCount, mNetId); mEventCount = 0; } @@ -110,7 +120,7 @@ public class DnsEventListenerService extends IDnsEventListener.Stub { private final NetworkCallback mNetworkCallback = new NetworkCallback() { @Override public void onLost(Network network) { - synchronized (DnsEventListenerService.this) { + synchronized (NetdEventListenerService.this) { DnsEventBatch batch = mEventBatches.remove(network.netId); if (batch != null) { batch.logAndClear(); @@ -119,12 +129,33 @@ public class DnsEventListenerService extends IDnsEventListener.Stub { } }; - public DnsEventListenerService(Context context) { + @GuardedBy("this") + private final TokenBucket mConnectTb = + new TokenBucket(CONNECT_LATENCY_FILL_RATE, CONNECT_LATENCY_BURST_LIMIT); + @GuardedBy("this") + private ConnectStats mConnectStats = makeConnectStats(); + + // Callback should only be registered/unregistered when logging is being enabled/disabled in DPM + // by the device owner. It's DevicePolicyManager's responsibility to ensure that. + @GuardedBy("this") + private INetdEventCallback mNetdEventCallback; + + public synchronized boolean registerNetdEventCallback(INetdEventCallback callback) { + mNetdEventCallback = callback; + return true; + } + + public synchronized boolean unregisterNetdEventCallback() { + mNetdEventCallback = null; + return true; + } + + public NetdEventListenerService(Context context) { this(context.getSystemService(ConnectivityManager.class), new IpConnectivityLog()); } @VisibleForTesting - public DnsEventListenerService(ConnectivityManager cm, IpConnectivityLog log) { + public NetdEventListenerService(ConnectivityManager cm, IpConnectivityLog log) { // We are started when boot is complete, so ConnectivityService should already be running. mCm = cm; mMetricsLog = log; @@ -134,9 +165,11 @@ public class DnsEventListenerService extends IDnsEventListener.Stub { @Override // Called concurrently by multiple binder threads. - public synchronized void onDnsEvent(int netId, int eventType, int returnCode, int latencyMs) { - maybeVerboseLog(String.format("onDnsEvent(%d, %d, %d, %d)", - netId, eventType, returnCode, latencyMs)); + // This method must not block or perform long-running operations. + public synchronized void onDnsEvent(int netId, int eventType, int returnCode, int latencyMs, + String hostname, String[] ipAddresses, int ipAddressesCount, int uid) + throws RemoteException { + maybeVerboseLog("onDnsEvent(%d, %d, %d, %dms)", netId, eventType, returnCode, latencyMs); DnsEventBatch batch = mEventBatches.get(netId); if (batch == null) { @@ -144,6 +177,38 @@ public class DnsEventListenerService extends IDnsEventListener.Stub { mEventBatches.put(netId, batch); } batch.addResult((byte) eventType, (byte) returnCode, latencyMs); + + if (mNetdEventCallback != null) { + mNetdEventCallback.onDnsEvent(hostname, ipAddresses, ipAddressesCount, + System.currentTimeMillis(), uid); + } + } + + @Override + // Called concurrently by multiple binder threads. + // This method must not block or perform long-running operations. + public synchronized void onConnectEvent(int netId, int error, int latencyMs, String ipAddr, int port, + int uid) throws RemoteException { + maybeVerboseLog("onConnectEvent(%d, %d)", netId, latencyMs); + + mConnectStats.addEvent(error, latencyMs, ipAddr); + + if (mNetdEventCallback != null) { + mNetdEventCallback.onConnectEvent(ipAddr, port, System.currentTimeMillis(), uid); + } + } + + public synchronized void flushStatistics(List<IpConnectivityEvent> events) { + events.add(flushConnectStats()); + // TODO: migrate DnsEventBatch to IpConnectivityLogClass.DNSLatencies + } + + private IpConnectivityEvent flushConnectStats() { + IpConnectivityEvent ev = new IpConnectivityEvent(); + ev.connectStatistics = mConnectStats.toProto(); + // TODO: add transport information + mConnectStats = makeConnectStats(); + return ev; } public synchronized void dump(PrintWriter writer) { @@ -153,14 +218,19 @@ public class DnsEventListenerService extends IDnsEventListener.Stub { for (DnsEventBatch batch : mEventBatches.values()) { pw.println(batch.toString()); } + // TODO: also dump ConnectStats pw.decreaseIndent(); } - private static void maybeLog(String s) { - if (DBG) Log.d(TAG, s); + private ConnectStats makeConnectStats() { + return new ConnectStats(mConnectTb, CONNECT_LATENCY_MAXIMUM_RECORDS); + } + + private static void maybeLog(String s, Object... args) { + if (DBG) Log.d(TAG, String.format(s, args)); } - private static void maybeVerboseLog(String s) { - if (VDBG) Log.d(TAG, s); + private static void maybeVerboseLog(String s, Object... args) { + if (VDBG) Log.d(TAG, String.format(s, args)); } } diff --git a/services/core/java/com/android/server/connectivity/NetworkMonitor.java b/services/core/java/com/android/server/connectivity/NetworkMonitor.java index c73d1dd95437..c40780e0d588 100644 --- a/services/core/java/com/android/server/connectivity/NetworkMonitor.java +++ b/services/core/java/com/android/server/connectivity/NetworkMonitor.java @@ -80,7 +80,8 @@ import java.util.concurrent.TimeUnit; */ public class NetworkMonitor extends StateMachine { private static final String TAG = NetworkMonitor.class.getSimpleName(); - private static final boolean DBG = false; + private static final boolean DBG = true; + private static final boolean VDBG = false; // Default configuration values for captive portal detection probes. // TODO: append a random length parameter to the default HTTPS url. @@ -96,6 +97,24 @@ public class NetworkMonitor extends StateMachine { private static final int SOCKET_TIMEOUT_MS = 10000; private static final int PROBE_TIMEOUT_MS = 3000; + static enum EvaluationResult { + VALIDATED(true), + CAPTIVE_PORTAL(false); + final boolean isValidated; + EvaluationResult(boolean isValidated) { + this.isValidated = isValidated; + } + } + + static enum ValidationStage { + FIRST_VALIDATION(true), + REVALIDATION(false); + final boolean isFirstValidation; + ValidationStage(boolean isFirstValidation) { + this.isFirstValidation = isFirstValidation; + } + } + public static final String ACTION_NETWORK_CONDITIONS_MEASURED = "android.net.conn.NETWORK_CONDITIONS_MEASURED"; public static final String EXTRA_CONNECTIVITY_TYPE = "extra_connectivity_type"; @@ -215,6 +234,8 @@ public class NetworkMonitor extends StateMachine { protected boolean mIsCaptivePortalCheckEnabled; private boolean mUseHttps; + // The total number of captive portal detection attempts for this NetworkMonitor instance. + private int mValidations = 0; // Set if the user explicitly selected "Do not use this network" in captive portal sign-in app. private boolean mUserDoesNotWant = false; @@ -289,6 +310,10 @@ public class NetworkMonitor extends StateMachine { return validationLogs.readOnlyLocalLog(); } + private ValidationStage validationStage() { + return 0 == mValidations ? ValidationStage.FIRST_VALIDATION : ValidationStage.REVALIDATION; + } + // DefaultState is the parent of all States. It exists only to handle CMD_* messages but // does not entail any real state (hence no enter() or exit() routines). private class DefaultState extends State { @@ -365,9 +390,11 @@ public class NetworkMonitor extends StateMachine { private class ValidatedState extends State { @Override public void enter() { - maybeLogEvaluationResult(NetworkEvent.NETWORK_VALIDATED); + maybeLogEvaluationResult( + networkEventType(validationStage(), EvaluationResult.VALIDATED)); mConnectivityServiceHandler.sendMessage(obtainMessage(EVENT_NETWORK_TESTED, NETWORK_TEST_RESULT_VALID, mNetworkAgentInfo.network.netId, null)); + mValidations++; } @Override @@ -406,6 +433,8 @@ public class NetworkMonitor extends StateMachine { })); intent.putExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL_URL, mLastPortalProbeResult.detectUrl); + intent.putExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL_USER_AGENT, + getCaptivePortalUserAgent(mContext)); intent.setFlags( Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | Intent.FLAG_ACTIVITY_NEW_TASK); mContext.startActivityAsUser(intent, UserHandle.CURRENT); @@ -583,7 +612,8 @@ public class NetworkMonitor extends StateMachine { @Override public void enter() { - maybeLogEvaluationResult(NetworkEvent.NETWORK_CAPTIVE_PORTAL_FOUND); + maybeLogEvaluationResult( + networkEventType(validationStage(), EvaluationResult.CAPTIVE_PORTAL)); // Don't annoy user with sign-in notifications. if (mDontDisplaySigninNotification) return; // Create a CustomIntentReceiver that sends us a @@ -603,6 +633,7 @@ public class NetworkMonitor extends StateMachine { // Retest for captive portal occasionally. sendMessageDelayed(CMD_CAPTIVE_PORTAL_RECHECK, 0 /* no UID */, CAPTIVE_PORTAL_REEVALUATE_DELAY_MS); + mValidations++; } @Override @@ -678,48 +709,13 @@ public class NetworkMonitor extends StateMachine { long startTime = SystemClock.elapsedRealtime(); - // Pre-resolve the captive portal server host so we can log it. - // Only do this if HttpURLConnection is about to, to avoid any potentially - // unnecessary resolution. - String hostToResolve = null; + final CaptivePortalProbeResult result; if (pacUrl != null) { - hostToResolve = pacUrl.getHost(); - } else if (proxyInfo != null) { - hostToResolve = proxyInfo.getHost(); - } else { - hostToResolve = httpUrl.getHost(); - } - - if (!TextUtils.isEmpty(hostToResolve)) { - String probeName = ValidationProbeEvent.getProbeName(ValidationProbeEvent.PROBE_DNS); - final Stopwatch dnsTimer = new Stopwatch().start(); - int dnsResult; - long dnsLatency; - try { - InetAddress[] addresses = mNetworkAgentInfo.network.getAllByName(hostToResolve); - dnsResult = ValidationProbeEvent.DNS_SUCCESS; - dnsLatency = dnsTimer.stop(); - final StringBuffer connectInfo = new StringBuffer(", " + hostToResolve + "="); - for (InetAddress address : addresses) { - connectInfo.append(address.getHostAddress()); - if (address != addresses[addresses.length-1]) connectInfo.append(","); - } - validationLog(probeName + " OK " + dnsLatency + "ms" + connectInfo); - } catch (UnknownHostException e) { - dnsResult = ValidationProbeEvent.DNS_FAILURE; - dnsLatency = dnsTimer.stop(); - validationLog(probeName + " FAIL " + dnsLatency + "ms, " + hostToResolve); - } - logValidationProbe(dnsLatency, ValidationProbeEvent.PROBE_DNS, dnsResult); - } - - CaptivePortalProbeResult result; - if (pacUrl != null) { - result = sendHttpProbe(pacUrl, ValidationProbeEvent.PROBE_PAC); + result = sendDnsAndHttpProbes(null, pacUrl, ValidationProbeEvent.PROBE_PAC); } else if (mUseHttps) { - result = sendParallelHttpProbes(httpsUrl, httpUrl, fallbackUrl); + result = sendParallelHttpProbes(proxyInfo, httpsUrl, httpUrl, fallbackUrl); } else { - result = sendHttpProbe(httpUrl, ValidationProbeEvent.PROBE_HTTP); + result = sendDnsAndHttpProbes(proxyInfo, httpUrl, ValidationProbeEvent.PROBE_HTTP); } long endTime = SystemClock.elapsedRealtime(); @@ -732,8 +728,50 @@ public class NetworkMonitor extends StateMachine { } /** - * Do a URL fetch on a known server to see if we get the data we expect. - * Returns HTTP response code. + * Do a DNS resolution and URL fetch on a known web server to see if we get the data we expect. + * @return a CaptivePortalProbeResult inferred from the HTTP response. + */ + private CaptivePortalProbeResult sendDnsAndHttpProbes(ProxyInfo proxy, URL url, int probeType) { + // Pre-resolve the captive portal server host so we can log it. + // Only do this if HttpURLConnection is about to, to avoid any potentially + // unnecessary resolution. + final String host = (proxy != null) ? proxy.getHost() : url.getHost(); + sendDnsProbe(host); + return sendHttpProbe(url, probeType); + } + + /** Do a DNS resolution of the given server. */ + private void sendDnsProbe(String host) { + if (TextUtils.isEmpty(host)) { + return; + } + + final String name = ValidationProbeEvent.getProbeName(ValidationProbeEvent.PROBE_DNS); + final Stopwatch watch = new Stopwatch().start(); + int result; + String connectInfo; + try { + InetAddress[] addresses = mNetworkAgentInfo.network.getAllByName(host); + result = ValidationProbeEvent.DNS_SUCCESS; + StringBuffer buffer = new StringBuffer(host).append("="); + for (InetAddress address : addresses) { + buffer.append(address.getHostAddress()); + if (address != addresses[addresses.length-1]) buffer.append(","); + } + connectInfo = buffer.toString(); + } catch (UnknownHostException e) { + result = ValidationProbeEvent.DNS_FAILURE; + connectInfo = host; + } + final long latency = watch.stop(); + String resultString = (ValidationProbeEvent.DNS_SUCCESS == result) ? "OK" : "FAIL"; + validationLog(String.format("%s %s %dms, %s", name, resultString, latency, connectInfo)); + logValidationProbe(latency, ValidationProbeEvent.PROBE_DNS, result); + } + + /** + * Do a URL fetch on a known web server to see if we get the data we expect. + * @return a CaptivePortalProbeResult inferred from the HTTP response. */ @VisibleForTesting protected CaptivePortalProbeResult sendHttpProbe(URL url, int probeType) { @@ -771,19 +809,26 @@ public class NetworkMonitor extends StateMachine { // portal. If it is considered a captive portal, a different sign-in URL // is needed (i.e. can't browse a 204). This could be the result of an HTTP // proxy server. - - // Consider 200 response with "Content-length=0" to not be a captive portal. - // There's no point in considering this a captive portal as the user cannot - // sign-in to an empty page. Probably the result of a broken transparent proxy. - // See http://b/9972012. - if (httpResponseCode == 200 && urlConnection.getContentLength() == 0) { - validationLog("Empty 200 response interpreted as 204 response."); - httpResponseCode = 204; - } - - if (httpResponseCode == 200 && probeType == ValidationProbeEvent.PROBE_PAC) { - validationLog("PAC fetch 200 response interpreted as 204 response."); - httpResponseCode = 204; + if (httpResponseCode == 200) { + if (probeType == ValidationProbeEvent.PROBE_PAC) { + validationLog("PAC fetch 200 response interpreted as 204 response."); + httpResponseCode = 204; + } else if (urlConnection.getContentLengthLong() == 0) { + // Consider 200 response with "Content-length=0" to not be a captive portal. + // There's no point in considering this a captive portal as the user cannot + // sign-in to an empty page. Probably the result of a broken transparent proxy. + // See http://b/9972012. + validationLog( + "200 response with Content-length=0 interpreted as 204 response."); + httpResponseCode = 204; + } else if (urlConnection.getContentLengthLong() == -1) { + // When no Content-length (default value == -1), attempt to read a byte from the + // response. Do not use available() as it is unreliable. See http://b/33498325. + if (urlConnection.getInputStream().read() == -1) { + validationLog("Empty 200 response interpreted as 204 response."); + httpResponseCode = 204; + } + } } } catch (IOException e) { validationLog("Probably not a portal: exception " + e); @@ -800,7 +845,7 @@ public class NetworkMonitor extends StateMachine { } private CaptivePortalProbeResult sendParallelHttpProbes( - URL httpsUrl, URL httpUrl, URL fallbackUrl) { + ProxyInfo proxy, URL httpsUrl, URL httpUrl, URL fallbackUrl) { // Number of probes to wait for. If a probe completes with a conclusive answer // it shortcuts the latch immediately by forcing the count to 0. final CountDownLatch latch = new CountDownLatch(2); @@ -820,9 +865,10 @@ public class NetworkMonitor extends StateMachine { @Override public void run() { if (mIsHttps) { - mResult = sendHttpProbe(httpsUrl, ValidationProbeEvent.PROBE_HTTPS); + mResult = + sendDnsAndHttpProbes(proxy, httpsUrl, ValidationProbeEvent.PROBE_HTTPS); } else { - mResult = sendHttpProbe(httpUrl, ValidationProbeEvent.PROBE_HTTP); + mResult = sendDnsAndHttpProbes(proxy, httpUrl, ValidationProbeEvent.PROBE_HTTP); } if ((mIsHttps && mResult.isSuccessful()) || (!mIsHttps && mResult.isPortal())) { // Stop waiting immediately if https succeeds or if http finds a portal. @@ -918,7 +964,7 @@ public class NetworkMonitor extends StateMachine { latencyBroadcast.putExtra(EXTRA_SSID, currentWifiInfo.getSSID()); latencyBroadcast.putExtra(EXTRA_BSSID, currentWifiInfo.getBSSID()); } else { - if (DBG) logw("network info is TYPE_WIFI but no ConnectionInfo found"); + if (VDBG) logw("network info is TYPE_WIFI but no ConnectionInfo found"); return; } break; @@ -931,8 +977,8 @@ public class NetworkMonitor extends StateMachine { if (cellInfo.isRegistered()) { numRegisteredCellInfo++; if (numRegisteredCellInfo > 1) { - log("more than one registered CellInfo. Can't " + - "tell which is active. Bailing."); + if (VDBG) logw("more than one registered CellInfo." + + " Can't tell which is active. Bailing."); return; } if (cellInfo instanceof CellInfoCdma) { @@ -948,7 +994,7 @@ public class NetworkMonitor extends StateMachine { CellIdentityWcdma cellId = ((CellInfoWcdma) cellInfo).getCellIdentity(); latencyBroadcast.putExtra(EXTRA_CELL_ID, cellId); } else { - if (DBG) logw("Registered cellinfo is unrecognized"); + if (VDBG) logw("Registered cellinfo is unrecognized"); return; } } @@ -973,6 +1019,22 @@ public class NetworkMonitor extends StateMachine { mMetricsLog.log(new NetworkEvent(mNetId, evtype)); } + private int networkEventType(ValidationStage s, EvaluationResult r) { + if (s.isFirstValidation) { + if (r.isValidated) { + return NetworkEvent.NETWORK_FIRST_VALIDATION_SUCCESS; + } else { + return NetworkEvent.NETWORK_FIRST_VALIDATION_PORTAL_FOUND; + } + } else { + if (r.isValidated) { + return NetworkEvent.NETWORK_REVALIDATION_SUCCESS; + } else { + return NetworkEvent.NETWORK_REVALIDATION_PORTAL_FOUND; + } + } + } + private void maybeLogEvaluationResult(int evtype) { if (mEvaluationTimer.isRunning()) { mMetricsLog.log(new NetworkEvent(mNetId, evtype, mEvaluationTimer.stop())); @@ -981,6 +1043,8 @@ public class NetworkMonitor extends StateMachine { } private void logValidationProbe(long durationMs, int probeType, int probeResult) { + probeType = + ValidationProbeEvent.makeProbeType(probeType, validationStage().isFirstValidation); mMetricsLog.log(new ValidationProbeEvent(mNetId, durationMs, probeType, probeResult)); } } diff --git a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java index f7b01be48d88..4ff665787c45 100644 --- a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java +++ b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java @@ -19,7 +19,6 @@ package com.android.server.connectivity; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; -import android.widget.Toast; import android.content.Context; import android.content.Intent; import android.content.res.Resources; @@ -27,17 +26,40 @@ import android.net.NetworkCapabilities; import android.os.UserHandle; import android.telephony.TelephonyManager; import android.util.Slog; - +import android.util.SparseArray; +import android.util.SparseIntArray; +import android.widget.Toast; import com.android.internal.R; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.logging.MetricsProto.MetricsEvent; -import static android.net.NetworkCapabilities.*; - +import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; +import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; +import static android.net.NetworkCapabilities.TRANSPORT_WIFI; public class NetworkNotificationManager { - public static enum NotificationType { SIGN_IN, NO_INTERNET, LOST_INTERNET, NETWORK_SWITCH }; + public static enum NotificationType { + LOST_INTERNET(MetricsEvent.NOTIFICATION_NETWORK_LOST_INTERNET), + NETWORK_SWITCH(MetricsEvent.NOTIFICATION_NETWORK_SWITCH), + NO_INTERNET(MetricsEvent.NOTIFICATION_NETWORK_NO_INTERNET), + SIGN_IN(MetricsEvent.NOTIFICATION_NETWORK_SIGN_IN); + + public final int eventId; + + NotificationType(int eventId) { + this.eventId = eventId; + Holder.sIdToTypeMap.put(eventId, this); + } + + private static class Holder { + private static SparseArray<NotificationType> sIdToTypeMap = new SparseArray<>(); + } - private static final String NOTIFICATION_ID = "Connectivity.Notification"; + public static NotificationType getFromId(int id) { + return Holder.sIdToTypeMap.get(id); + } + }; private static final String TAG = NetworkNotificationManager.class.getSimpleName(); private static final boolean DBG = true; @@ -46,11 +68,14 @@ public class NetworkNotificationManager { private final Context mContext; private final TelephonyManager mTelephonyManager; private final NotificationManager mNotificationManager; + // Tracks the types of notifications managed by this instance, from creation to cancellation. + private final SparseIntArray mNotificationTypeMap; public NetworkNotificationManager(Context c, TelephonyManager t, NotificationManager n) { mContext = c; mTelephonyManager = t; mNotificationManager = n; + mNotificationTypeMap = new SparseIntArray(); } // TODO: deal more gracefully with multi-transport networks. @@ -100,8 +125,10 @@ public class NetworkNotificationManager { */ public void showNotification(int id, NotificationType notifyType, NetworkAgentInfo nai, NetworkAgentInfo switchToNai, PendingIntent intent, boolean highPriority) { - int transportType; - String extraInfo; + final String tag = tagFor(id); + final int eventId = notifyType.eventId; + final int transportType; + final String extraInfo; if (nai != null) { transportType = getFirstTransportType(nai); extraInfo = nai.networkInfo.getExtraInfo(); @@ -114,9 +141,10 @@ public class NetworkNotificationManager { } if (DBG) { - Slog.d(TAG, "showNotification " + notifyType - + " transportType=" + getTransportName(transportType) - + " extraInfo=" + extraInfo + " highPriority=" + highPriority); + Slog.d(TAG, String.format( + "showNotification tag=%s event=%s transport=%s extraInfo=%s highPrioriy=%s", + tag, nameOf(eventId), getTransportName(transportType), extraInfo, + highPriority)); } Resources r = Resources.getSystem(); @@ -154,7 +182,7 @@ public class NetworkNotificationManager { details = r.getString(R.string.network_switch_metered_detail, toTransport, fromTransport); } else { - Slog.wtf(TAG, "Unknown notification type " + notifyType + "on network transport " + Slog.wtf(TAG, "Unknown notification type " + notifyType + " on network transport " + getTransportName(transportType)); return; } @@ -184,22 +212,31 @@ public class NetworkNotificationManager { Notification notification = builder.build(); + mNotificationTypeMap.put(id, eventId); try { - mNotificationManager.notifyAsUser(NOTIFICATION_ID, id, notification, UserHandle.ALL); + mNotificationManager.notifyAsUser(tag, eventId, notification, UserHandle.ALL); } catch (NullPointerException npe) { - Slog.d(TAG, "setNotificationVisible: visible notificationManager npe=" + npe); + Slog.d(TAG, "setNotificationVisible: visible notificationManager error", npe); } } public void clearNotification(int id) { + if (mNotificationTypeMap.indexOfKey(id) < 0) { + return; + } + final String tag = tagFor(id); + final int eventId = mNotificationTypeMap.get(id); if (DBG) { - Slog.d(TAG, "clearNotification id=" + id); + Slog.d(TAG, String.format("clearing notification tag=%s event=%s", tag, + nameOf(eventId))); } try { - mNotificationManager.cancelAsUser(NOTIFICATION_ID, id, UserHandle.ALL); + mNotificationManager.cancelAsUser(tag, eventId, UserHandle.ALL); } catch (NullPointerException npe) { - Slog.d(TAG, "setNotificationVisible: cancel notificationManager npe=" + npe); + Slog.d(TAG, String.format( + "failed to clear notification tag=%s event=%s", tag, nameOf(eventId)), npe); } + mNotificationTypeMap.delete(id); } /** @@ -222,4 +259,15 @@ public class NetworkNotificationManager { R.string.network_switch_metered_toast, fromTransport, toTransport); Toast.makeText(mContext, text, Toast.LENGTH_LONG).show(); } + + @VisibleForTesting + static String tagFor(int id) { + return String.format("ConnectivityNotification:%d", id); + } + + @VisibleForTesting + static String nameOf(int eventId) { + NotificationType t = NotificationType.getFromId(eventId); + return (t != null) ? t.name() : "UNKNOWN"; + } } diff --git a/services/core/java/com/android/server/connectivity/Tethering.java b/services/core/java/com/android/server/connectivity/Tethering.java index de919a3f1aa6..156e278204a5 100644 --- a/services/core/java/com/android/server/connectivity/Tethering.java +++ b/services/core/java/com/android/server/connectivity/Tethering.java @@ -897,7 +897,7 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering } } else { mUsbTetherRequested = true; - usbManager.setCurrentFunction(UsbManager.USB_FUNCTION_RNDIS); + usbManager.setCurrentFunction(UsbManager.USB_FUNCTION_RNDIS, false); } } else { final long ident = Binder.clearCallingIdentity(); @@ -907,7 +907,7 @@ public class Tethering extends BaseNetworkObserver implements IControlsTethering Binder.restoreCallingIdentity(ident); } if (mRndisEnabled) { - usbManager.setCurrentFunction(null); + usbManager.setCurrentFunction(null, false); } mUsbTetherRequested = false; } diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index ede3bda5bea2..610a2ab5b53b 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -1544,9 +1544,6 @@ public class Vpn { public void exit() { // We assume that everything is reset after stopping the daemons. interrupt(); - for (LocalSocket socket : mSockets) { - IoUtils.closeQuietly(socket); - } agentDisconnect(); try { mContext.unregisterReceiver(mBroadcastReceiver); @@ -1559,8 +1556,26 @@ public class Vpn { Log.v(TAG, "Waiting"); synchronized (TAG) { Log.v(TAG, "Executing"); - execute(); - monitorDaemons(); + try { + execute(); + monitorDaemons(); + interrupted(); // Clear interrupt flag if execute called exit. + } catch (InterruptedException e) { + } finally { + for (LocalSocket socket : mSockets) { + IoUtils.closeQuietly(socket); + } + // This sleep is necessary for racoon to successfully complete sending delete + // message to server. + try { + Thread.sleep(50); + } catch (InterruptedException e) { + } + for (String daemon : mDaemons) { + SystemService.stop(daemon); + } + } + agentDisconnect(); } } @@ -1759,18 +1774,6 @@ public class Vpn { Log.i(TAG, "Aborting", e); updateState(DetailedState.FAILED, e.getMessage()); exit(); - } finally { - // Kill the daemons if they fail to stop. - if (!initFinished) { - for (String daemon : mDaemons) { - SystemService.stop(daemon); - } - } - - // Do not leave an unstable state. - if (!initFinished || mNetworkInfo.getDetailedState() == DetailedState.CONNECTING) { - agentDisconnect(); - } } } @@ -1778,28 +1781,17 @@ public class Vpn { * Monitor the daemons we started, moving to disconnected state if the * underlying services fail. */ - private void monitorDaemons() { + private void monitorDaemons() throws InterruptedException{ if (!mNetworkInfo.isConnected()) { return; } - - try { - while (true) { - Thread.sleep(2000); - for (int i = 0; i < mDaemons.length; i++) { - if (mArguments[i] != null && SystemService.isStopped(mDaemons[i])) { - return; - } + while (true) { + Thread.sleep(2000); + for (int i = 0; i < mDaemons.length; i++) { + if (mArguments[i] != null && SystemService.isStopped(mDaemons[i])) { + return; } } - } catch (InterruptedException e) { - Log.d(TAG, "interrupted during monitorDaemons(); stopping services"); - } finally { - for (String daemon : mDaemons) { - SystemService.stop(daemon); - } - - agentDisconnect(); } } } diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java index 66b5e3772974..772d81aec20a 100644 --- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java +++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java @@ -18,9 +18,6 @@ package com.android.server.display; import com.android.server.EventLogTags; import com.android.server.LocalServices; -import com.android.server.twilight.TwilightListener; -import com.android.server.twilight.TwilightManager; -import com.android.server.twilight.TwilightState; import android.annotation.Nullable; import android.hardware.Sensor; @@ -57,9 +54,6 @@ class AutomaticBrightnessController { // non-zero, which in turn ensures that the total weight is non-zero. private static final long AMBIENT_LIGHT_PREDICTION_TIME_MILLIS = 100; - // Specifies the maximum magnitude of the time of day adjustment. - private static final float TWILIGHT_ADJUSTMENT_MAX_GAMMA = 1f; - // Debounce for sampling user-initiated changes in display brightness to ensure // the user is satisfied with the result before storing the sample. private static final int BRIGHTNESS_ADJUSTMENT_SAMPLE_DEBOUNCE_MILLIS = 10000; @@ -76,9 +70,6 @@ class AutomaticBrightnessController { // The light sensor, or null if not available or needed. private final Sensor mLightSensor; - // The twilight service. - private final TwilightManager mTwilight; - // The auto-brightness spline adjustment. // The brightness values have been scaled to a range of 0..1. private final Spline mScreenAutoBrightnessSpline; @@ -120,7 +111,7 @@ class AutomaticBrightnessController { // weighting values positive. private final int mWeightingIntercept; - // Accessor object for determining lux levels. + // accessor object for determining lux levels private final LuxLevels mLuxLevels; // Amount of time to delay auto-brightness after screen on while waiting for @@ -195,8 +186,10 @@ class AutomaticBrightnessController { // Are we going to adjust brightness while dozing. private boolean mDozing; - // True if we are collecting one last light sample when dozing to set the screen brightness. - private boolean mActiveDozeLightSensor = false; + // True if we are collecting light samples when dozing to set the screen brightness. A single + // light sample is collected when entering doze mode. If autobrightness is enabled, calls to + // DisplayPowerController#updatePowerState in doze mode will also collect light samples. + private final boolean mUseActiveDozeLightSensorConfig; // True if the ambient light sensor ring buffer should be cleared when entering doze mode. private final boolean mUseNewSensorSamplesForDoze; @@ -209,18 +202,15 @@ class AutomaticBrightnessController { private int mBrightnessAdjustmentSampleOldBrightness; private float mBrightnessAdjustmentSampleOldGamma; - private boolean mUseTwilight; - public AutomaticBrightnessController(Callbacks callbacks, Looper looper, SensorManager sensorManager, Spline autoBrightnessSpline, int lightSensorWarmUpTime, int brightnessMin, int brightnessMax, float dozeScaleFactor, int lightSensorRate, int initialLightSensorRate, long brighteningLightDebounceConfig, long darkeningLightDebounceConfig, boolean resetAmbientLuxAfterWarmUpConfig, int ambientLightHorizon, float autoBrightnessAdjustmentMaxGamma, - boolean useNewSensorSamplesForDoze, float darkHorizonThresholdFactor, - int darkAmbientLightHorizon, LuxLevels luxLevels) { + boolean activeDozeLightSensor, boolean useNewSensorSamplesForDoze, + float darkHorizonThresholdFactor, int darkAmbientLightHorizon, LuxLevels luxLevels) { mCallbacks = callbacks; - mTwilight = LocalServices.getService(TwilightManager.class); mSensorManager = sensorManager; mScreenAutoBrightnessSpline = autoBrightnessSpline; mScreenBrightnessRangeMinimum = brightnessMin; @@ -237,10 +227,10 @@ class AutomaticBrightnessController { mWeightingIntercept = ambientLightHorizon; mScreenAutoBrightnessAdjustmentMaxGamma = autoBrightnessAdjustmentMaxGamma; mUseNewSensorSamplesForDoze = useNewSensorSamplesForDoze; + mUseActiveDozeLightSensorConfig = activeDozeLightSensor; mDarkHorizonThresholdFactor = darkHorizonThresholdFactor; mDarkAmbientLightHorizon = darkAmbientLightHorizon; mLuxLevels = luxLevels; - mActiveDozeLightSensor = mLuxLevels.hasDynamicDozeBrightness(); mHandler = new AutomaticBrightnessHandler(looper); mAmbientLightRingBuffer = @@ -265,30 +255,26 @@ class AutomaticBrightnessController { } public void configure(boolean enable, float adjustment, boolean dozing, - boolean userInitiatedChange, boolean useTwilight) { + boolean userInitiatedChange) { // While dozing, the application processor may be suspended which will prevent us from // receiving new information from the light sensor. On some devices, we may be able to // switch to a wake-up light sensor instead but for now we will simply disable the sensor // and hold onto the last computed screen auto brightness. We save the dozing flag for // debugging purposes. - mDozing = dozing; - boolean enableSensor = enable && !dozing; - if (enableSensor && !mLightSensorEnabled && mActiveDozeLightSensor) { - mActiveDozeLightSensor = false; - } else if (!enableSensor && mLightSensorEnabled && mLuxLevels.hasDynamicDozeBrightness()) { - // keep the light sensor active until another light sample is taken while dozing - mActiveDozeLightSensor = true; - adjustLightSensorRate(mInitialLightSensorRate); - if (mUseNewSensorSamplesForDoze) { - mAmbientLightRingBuffer.clear(); - mInitialHorizonAmbientLightRingBuffer.clear(); - mAmbientLuxValid = false; - return; + boolean enableSensor = enable && (dozing ? mUseActiveDozeLightSensorConfig : true); + if (enableSensor && dozing && !mDozing && mLightSensorEnabled + && mUseNewSensorSamplesForDoze) { + mAmbientLightRingBuffer.clear(); + mInitialHorizonAmbientLightRingBuffer.clear(); + if (DEBUG) { + Slog.d(TAG, "configure: Clearing ambient light ring buffers when entering doze."); } + mAmbientLuxValid = false; + adjustLightSensorRate(mInitialLightSensorRate); } + mDozing = dozing; boolean changed = setLightSensorEnabled(enableSensor); changed |= setScreenAutoBrightnessAdjustment(adjustment); - changed |= setUseTwilight(useTwilight); if (changed) { updateAutoBrightness(false /*sendUpdate*/); } @@ -297,17 +283,6 @@ class AutomaticBrightnessController { } } - private boolean setUseTwilight(boolean useTwilight) { - if (mUseTwilight == useTwilight) return false; - if (useTwilight) { - mTwilight.registerListener(mTwilightListener, mHandler); - } else { - mTwilight.unregisterListener(mTwilightListener); - } - mUseTwilight = useTwilight; - return true; - } - public void dump(PrintWriter pw) { pw.println(); pw.println("Automatic Brightness Controller Configuration:"); @@ -322,7 +297,6 @@ class AutomaticBrightnessController { pw.println(); pw.println("Automatic Brightness Controller State:"); pw.println(" mLightSensor=" + mLightSensor); - pw.println(" mTwilight.getLastTwilightState()=" + mTwilight.getLastTwilightState()); pw.println(" mLightSensorEnabled=" + mLightSensorEnabled); pw.println(" mLightSensorEnableTime=" + TimeUtils.formatUptime(mLightSensorEnableTime)); pw.println(" mAmbientLux=" + mAmbientLux); @@ -383,7 +357,7 @@ class AutomaticBrightnessController { } applyLightSensorMeasurement(time, lux); updateAmbientLux(time); - if (mActiveDozeLightSensor) { + if (mUseActiveDozeLightSensorConfig && mDozing) { // disable the ambient light sensor and update the screen brightness if (DEBUG) { Slog.d(TAG, "handleLightSensorEvent: doze ambient light sensor reading: " + lux); @@ -612,19 +586,6 @@ class AutomaticBrightnessController { } } - if (mUseTwilight) { - TwilightState state = mTwilight.getLastTwilightState(); - if (state != null && state.isNight()) { - final long duration = state.sunriseTimeMillis() - state.sunsetTimeMillis(); - final long progress = System.currentTimeMillis() - state.sunsetTimeMillis(); - final float amount = (float) Math.pow(2.0 * progress / duration - 1.0, 2.0); - gamma *= 1 + amount * TWILIGHT_ADJUSTMENT_MAX_GAMMA; - if (DEBUG) { - Slog.d(TAG, "updateAutoBrightness: twilight amount=" + amount); - } - } - } - if (gamma != 1.0f) { final float in = value; value = MathUtils.pow(value, gamma); @@ -635,7 +596,7 @@ class AutomaticBrightnessController { } int newScreenAutoBrightness; - if (mActiveDozeLightSensor) { + if (mUseActiveDozeLightSensorConfig && mDozing) { newScreenAutoBrightness = mLuxLevels.getDozeBrightness(mAmbientLux); } else { newScreenAutoBrightness = @@ -745,13 +706,6 @@ class AutomaticBrightnessController { } }; - private final TwilightListener mTwilightListener = new TwilightListener() { - @Override - public void onTwilightStateChanged(@Nullable TwilightState state) { - updateAutoBrightness(true /*sendUpdate*/); - } - }; - /** Callbacks to request updates to the display's power state. */ interface Callbacks { void updateBrightness(); diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index a5c2a0932728..9e51b092b8d7 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -102,11 +102,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call // Trigger proximity if distance is less than 5 cm. private static final float TYPICAL_PROXIMITY_THRESHOLD = 5.0f; - // State machine constants for tracking initial brightness ramp skipping when enabled. - private static final int RAMP_STATE_SKIP_NONE = 0; - private static final int RAMP_STATE_SKIP_INITIAL = 1; - private static final int RAMP_STATE_SKIP_AUTOBRIGHT = 2; - private static final int REPORTED_TO_POLICY_SCREEN_OFF = 0; private static final int REPORTED_TO_POLICY_SCREEN_TURNING_ON = 1; private static final int REPORTED_TO_POLICY_SCREEN_ON = 2; @@ -158,6 +153,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call // True if should use light sensor to automatically determine doze screen brightness. private final boolean mAllowAutoBrightnessWhileDozingConfig; + // True if collecting light sensor samples in doze mode. + private boolean mUseActiveDozeLightSensorConfig; + // True if we should fade the screen while turning it off, false if we should play // a stylish color fade animation instead. private boolean mColorFadeFadesConfig; @@ -239,9 +237,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call // Screen state we reported to policy. Must be one of REPORTED_TO_POLICY_SCREEN_* fields. private int mReportedScreenStateToPolicy; - // If the last recorded screen state was dozing or not. - private boolean mDozing; - // Remembers whether certain kinds of brightness adjustments // were recently applied so that we can decide how to transition. private boolean mAppliedAutoBrightness; @@ -252,18 +247,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call private final int mBrightnessRampRateFast; private final int mBrightnessRampRateSlow; - // Whether or not to skip the initial brightness ramps into STATE_ON. - private final boolean mSkipScreenOnBrightnessRamp; - - // A record of state for skipping brightness ramps - private int mSkipRampState = RAMP_STATE_SKIP_NONE; - - // The first autobrightness value set when entering RAMP_STATE_SKIP_INITIAL. - private int mInitialAutoBrightness; - - // Accessor object for determining lux levels. - private LuxLevels mLuxLevels; - // The controller for the automatic brightness level. private AutomaticBrightnessController mAutomaticBrightnessController; @@ -327,8 +310,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call com.android.internal.R.integer.config_brightness_ramp_rate_fast); mBrightnessRampRateSlow = resources.getInteger( com.android.internal.R.integer.config_brightness_ramp_rate_slow); - mSkipScreenOnBrightnessRamp = resources.getBoolean( - com.android.internal.R.bool.config_skipScreenOnBrightnessRamp); int lightSensorRate = resources.getInteger( com.android.internal.R.integer.config_autoBrightnessLightSensorRate); @@ -383,7 +364,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call com.android.internal.R.array.config_dozeBrightnessBacklightValues); boolean useNewSensorSamplesForDoze = resources.getBoolean( com.android.internal.R.bool.config_useNewSensorSamplesForDoze); - mLuxLevels = new LuxLevels(brightHysteresisLevels, darkHysteresisLevels, + mUseActiveDozeLightSensorConfig = resources.getBoolean( + com.android.internal.R.bool.config_allowAutoBrightnessActiveDozeLightSensor); + LuxLevels luxLevels = new LuxLevels(brightHysteresisLevels, darkHysteresisLevels, luxHysteresisLevels, dozeSensorLuxLevels, dozeBrightnessBacklightValues); Spline screenAutoBrightnessSpline = createAutoBrightnessSpline(lux, screenBrightness); @@ -412,8 +395,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mScreenBrightnessRangeMaximum, dozeScaleFactor, lightSensorRate, initialLightSensorRate, brighteningLightDebounce, darkeningLightDebounce, autoBrightnessResetAmbientLuxAfterWarmUp, ambientLightHorizon, - autoBrightnessAdjustmentMaxGamma, useNewSensorSamplesForDoze, - darkHorizonThresholdFactor, darkAmbientLightHorizon, mLuxLevels); + autoBrightnessAdjustmentMaxGamma, mUseActiveDozeLightSensorConfig, + useNewSensorSamplesForDoze, darkHorizonThresholdFactor, + darkAmbientLightHorizon, luxLevels); } } @@ -613,6 +597,9 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call brightness = mPowerRequest.dozeScreenBrightness; } break; + case DisplayPowerRequest.POLICY_VR: + state = Display.STATE_VR; + break; case DisplayPowerRequest.POLICY_DIM: case DisplayPowerRequest.POLICY_BRIGHT: default: @@ -654,6 +641,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call // Animate the screen state change unless already animating. // The transition may be deferred, so after this point we will use the // actual state instead of the desired one. + final int oldState = mPowerState.getScreenState(); animateScreenStateChange(state, performScreenOffTransition); state = mPowerState.getScreenState(); @@ -667,14 +655,15 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call if (mAutomaticBrightnessController != null) { final boolean autoBrightnessEnabledInDoze = mAllowAutoBrightnessWhileDozingConfig && (state == Display.STATE_DOZE || state == Display.STATE_DOZE_SUSPEND); - autoBrightnessEnabled = mPowerRequest.useAutoBrightness + autoBrightnessEnabled = (mPowerRequest.useAutoBrightness && (state == Display.STATE_ON || autoBrightnessEnabledInDoze) + || mUseActiveDozeLightSensorConfig && autoBrightnessEnabledInDoze) && brightness < 0; final boolean userInitiatedChange = autoBrightnessAdjustmentChanged && mPowerRequest.brightnessSetByUser; mAutomaticBrightnessController.configure(autoBrightnessEnabled, mPowerRequest.screenAutoBrightnessAdjustment, state != Display.STATE_ON, - userInitiatedChange, mPowerRequest.useTwilight); + userInitiatedChange); } // Apply brightness boost. @@ -707,13 +696,11 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mAppliedAutoBrightness = false; } - // Use default brightness when dozing unless overridden or if using dynamic doze brightness. + // Use default brightness when dozing unless overridden or if collecting sensor samples. if (state == Display.STATE_DOZE || state == Display.STATE_DOZE_SUSPEND) { if (brightness < 0) { brightness = mScreenBrightnessDozeConfig; - } else if (autoBrightnessEnabled && mLuxLevels.hasDynamicDozeBrightness()) { - brightness = mAutomaticBrightnessController.getAutomaticScreenBrightness(); - } else { + } else if (mUseActiveDozeLightSensorConfig) { brightness = Math.min(brightness, mScreenBrightnessDozeConfig); if (DEBUG) { Slog.d(TAG, "updatePowerState: ALS-based doze brightness: " + brightness); @@ -761,32 +748,10 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call } // Animate the screen brightness when the screen is on or dozing. - // Skip the animation when the screen is off, suspended, or if configs say otherwise. - // N.B. There are two ramps that can occur when the skip ramp config is enabled. The first - // is from a dozing or off state value to the previous screen brightness value. The second - // occurs when autobrightness is enabled and the screen adjusts from the previous - // brightness value to a new one based off of new ambient light sensor readings. + // Skip the animation when the screen is off or suspended or transition to/from VR. if (!mPendingScreenOff) { - if (mSkipScreenOnBrightnessRamp) { - - if (state == Display.STATE_ON) { - if (mSkipRampState == RAMP_STATE_SKIP_NONE && mDozing) { - mInitialAutoBrightness = brightness; - mSkipRampState = RAMP_STATE_SKIP_INITIAL; - } else if (mSkipRampState == RAMP_STATE_SKIP_INITIAL - && mUseSoftwareAutoBrightnessConfig - && brightness != mInitialAutoBrightness) { - mSkipRampState = RAMP_STATE_SKIP_AUTOBRIGHT; - } else if (mSkipRampState == RAMP_STATE_SKIP_AUTOBRIGHT) { - mSkipRampState = RAMP_STATE_SKIP_NONE; - } - } else { - mSkipRampState = RAMP_STATE_SKIP_NONE; - } - } - - if (state == Display.STATE_ON && mSkipRampState == RAMP_STATE_SKIP_NONE - || state == Display.STATE_DOZE) { + boolean wasOrWillBeInVr = (state == Display.STATE_VR || oldState == Display.STATE_VR); + if ((state == Display.STATE_ON || state == Display.STATE_DOZE) && !wasOrWillBeInVr) { animateScreenBrightness(brightness, slowChange ? mBrightnessRampRateSlow : mBrightnessRampRateFast); } else { @@ -844,9 +809,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mUnfinishedBusiness = false; mCallbacks.releaseSuspendBlocker(); } - - // Record if dozing for future comparison. - mDozing = state != Display.STATE_ON; } @Override @@ -973,6 +935,23 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call mPowerState.setColorFadeLevel(1.0f); mPowerState.dismissColorFade(); } + } else if (target == Display.STATE_VR) { + // Wait for brightness animation to complete beforehand when entering VR + // from screen on to prevent a perceptible jump because brightness may operate + // differently when the display is configured for dozing. + if (mScreenBrightnessRampAnimator.isAnimating() + && mPowerState.getScreenState() == Display.STATE_ON) { + return; + } + + // Set screen state. + if (!setScreenState(Display.STATE_VR)) { + return; // screen on blocked + } + + // Dismiss the black surface without fanfare. + mPowerState.setColorFadeLevel(1.0f); + mPowerState.dismissColorFade(); } else if (target == Display.STATE_DOZE) { // Want screen dozing. // Wait for brightness animation to complete beforehand when entering doze diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java index 61c2eacaa8f9..867322578a66 100644 --- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java +++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java @@ -471,6 +471,16 @@ final class LocalDisplayAdapter extends DisplayAdapter { } } + // If the state change was from or to VR, then we need to tell the light + // so that it can apply appropriate VR brightness settings. This should + // happen prior to changing the brightness but also if there is no + // brightness change at all. + if ((state == Display.STATE_VR || currentState == Display.STATE_VR) && + currentState != state) { + setVrMode(state == Display.STATE_VR); + } + + // Apply brightness changes given that we are in a non-suspended state. if (brightnessChanged) { setDisplayBrightness(brightness); @@ -482,6 +492,15 @@ final class LocalDisplayAdapter extends DisplayAdapter { } } + private void setVrMode(boolean isVrEnabled) { + if (DEBUG) { + Slog.d(TAG, "setVrMode(" + + "id=" + displayId + + ", state=" + Display.stateToString(state) + ")"); + } + mBacklight.setVrMode(isVrEnabled); + } + private void setDisplayState(int state) { if (DEBUG) { Slog.d(TAG, "setDisplayState(" diff --git a/services/core/java/com/android/server/display/LuxLevels.java b/services/core/java/com/android/server/display/LuxLevels.java index 41169cb35aee..a796871874c4 100644 --- a/services/core/java/com/android/server/display/LuxLevels.java +++ b/services/core/java/com/android/server/display/LuxLevels.java @@ -29,7 +29,7 @@ final class LuxLevels { private final float[] mBrightLevels; private final float[] mDarkLevels; private final float[] mLuxHysteresisLevels; - private final float[] mDozeBacklightLevels; + private final float[] mDozeBrightnessBacklightValues; private final float[] mDozeSensorLuxLevels; /** @@ -39,29 +39,29 @@ final class LuxLevels { * {@code luxLevels} has length n+1. * * {@code dozeSensorLuxLevels} has length r. - * {@code dozeBacklightLevels} has length r+1. + * {@code dozeBrightnessBacklightValues} has length r+1. * * @param brightLevels an array of brightening hysteresis constraint constants * @param darkLevels an array of darkening hysteresis constraint constants * @param luxHysteresisLevels a monotonically increasing array of illuminance thresholds in lux * @param dozeSensorLuxLevels a monotonically increasing array of ALS thresholds in lux - * @param dozeBacklightLevels an array of screen brightness values for doze mode in lux + * @param dozeBrightnessBacklightValues an array of screen brightness values for doze mode in lux */ public LuxLevels(int[] brightLevels, int[] darkLevels, int[] luxHysteresisLevels, - int[] dozeSensorLuxLevels, int[] dozeBacklightLevels) { + int[] dozeSensorLuxLevels, int[] dozeBrightnessBacklightValues) { if (brightLevels.length != darkLevels.length || darkLevels.length !=luxHysteresisLevels.length + 1) { throw new IllegalArgumentException("Mismatch between hysteresis array lengths."); } - if (dozeBacklightLevels.length > 0 && dozeSensorLuxLevels.length > 0 - && dozeBacklightLevels.length != dozeSensorLuxLevels.length + 1) { + if (dozeBrightnessBacklightValues.length > 0 && dozeSensorLuxLevels.length > 0 + && dozeBrightnessBacklightValues.length != dozeSensorLuxLevels.length + 1) { throw new IllegalArgumentException("Mismatch between doze lux array lengths."); } mBrightLevels = setArrayFormat(brightLevels, 1000.0f); mDarkLevels = setArrayFormat(darkLevels, 1000.0f); mLuxHysteresisLevels = setArrayFormat(luxHysteresisLevels, 1.0f); mDozeSensorLuxLevels = setArrayFormat(dozeSensorLuxLevels, 1.0f); - mDozeBacklightLevels = setArrayFormat(dozeBacklightLevels, 1.0f); + mDozeBrightnessBacklightValues = setArrayFormat(dozeBrightnessBacklightValues, 1.0f); } /** @@ -94,7 +94,7 @@ final class LuxLevels { * Return the doze backlight brightness level for the given ambient sensor lux level. */ public int getDozeBrightness(float lux) { - int dozeBrightness = (int) getReferenceLevel(lux, mDozeBacklightLevels, + int dozeBrightness = (int) getReferenceLevel(lux, mDozeBrightnessBacklightValues, mDozeSensorLuxLevels); if (DEBUG) { Slog.d(TAG, "doze brightness: " + dozeBrightness + ", lux=" + lux); diff --git a/services/core/java/com/android/server/emergency/EmergencyAffordanceService.java b/services/core/java/com/android/server/emergency/EmergencyAffordanceService.java index 353f4506e1e9..98771dfa1f33 100644 --- a/services/core/java/com/android/server/emergency/EmergencyAffordanceService.java +++ b/services/core/java/com/android/server/emergency/EmergencyAffordanceService.java @@ -245,23 +245,25 @@ public class EmergencyAffordanceService extends SystemService { } } } - if (neededNow != neededBefore) { - setSimNeedsEmergencyAffordance(neededNow); - } + setSimNeedsEmergencyAffordance(neededNow); return neededNow; } private void setSimNeedsEmergencyAffordance(boolean simNeedsEmergencyAffordance) { - mSimNeedsEmergencyAffordance = simNeedsEmergencyAffordance; - Settings.Global.putInt(mContext.getContentResolver(), - EMERGENCY_SIM_INSERTED_SETTING, - simNeedsEmergencyAffordance ? 1 : 0); - updateEmergencyAffordanceNeeded(); + if (simNeededAffordanceBefore() != simNeedsEmergencyAffordance) { + Settings.Global.putInt(mContext.getContentResolver(), + EMERGENCY_SIM_INSERTED_SETTING, + simNeedsEmergencyAffordance ? 1 : 0); + } + if (simNeedsEmergencyAffordance != mSimNeedsEmergencyAffordance) { + mSimNeedsEmergencyAffordance = simNeedsEmergencyAffordance; + updateEmergencyAffordanceNeeded(); + } } private boolean simNeededAffordanceBefore() { return Settings.Global.getInt(mContext.getContentResolver(), - "emergency_sim_inserted_before", 0) != 0; + EMERGENCY_SIM_INSERTED_SETTING, 0) != 0; } private boolean handleUpdateCellInfo() { diff --git a/services/core/java/com/android/server/fingerprint/FingerprintService.java b/services/core/java/com/android/server/fingerprint/FingerprintService.java index 6c11794b189d..e6408238e4c2 100644 --- a/services/core/java/com/android/server/fingerprint/FingerprintService.java +++ b/services/core/java/com/android/server/fingerprint/FingerprintService.java @@ -74,6 +74,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; +import java.util.Map; /** * A service to manage multiple clients that want to access the fingerprint HAL API. @@ -101,6 +102,8 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe private final ArrayList<FingerprintServiceLockoutResetMonitor> mLockoutMonitors = new ArrayList<>(); + private final Map<Integer, Long> mAuthenticatorIds = + Collections.synchronizedMap(new HashMap<>()); private final AppOpsManager mAppOps; private static final long FAIL_LOCKOUT_TIMEOUT_MS = 30*1000; private static final int MAX_FAILED_ATTEMPTS = 5; @@ -117,7 +120,6 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe private final UserManager mUserManager; private ClientMonitor mCurrentClient; private ClientMonitor mPendingClient; - private long mCurrentAuthenticatorId; private PerformanceStats mPerformanceStats; // Normal fingerprint authentications are tracked by mPerformanceMap. @@ -209,6 +211,7 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe mDaemon.init(mDaemonCallback); mHalDeviceId = mDaemon.openHal(); if (mHalDeviceId != 0) { + loadAuthenticatorIds(); updateActiveGroup(ActivityManager.getCurrentUser(), null); } else { Slog.w(TAG, "Failed to open Fingerprint HAL!"); @@ -226,6 +229,26 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe return mDaemon; } + /** Populates existing authenticator ids. To be used only during the start of the service. */ + private void loadAuthenticatorIds() { + // This operation can be expensive, so keep track of the elapsed time. Might need to move to + // background if it takes too long. + long t = System.currentTimeMillis(); + + mAuthenticatorIds.clear(); + for (UserInfo user : UserManager.get(mContext).getUsers(true /* excludeDying */)) { + int userId = getUserOrWorkProfileId(null, user.id); + if (!mAuthenticatorIds.containsKey(userId)) { + updateActiveGroup(userId, null); + } + } + + t = System.currentTimeMillis() - t; + if (t > 1000) { + Slog.w(TAG, "loadAuthenticatorIds() taking too long: " + t + "ms"); + } + } + protected void handleEnumerate(long deviceId, int[] fingerIds, int[] groupIds) { if (fingerIds.length != groupIds.length) { Slog.w(TAG, "fingerIds and groupIds differ in length: f[]=" @@ -443,14 +466,23 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe boolean isCurrentUserOrProfile(int userId) { UserManager um = UserManager.get(mContext); + if (um == null) { + Slog.e(TAG, "Unable to acquire UserManager"); + return false; + } - // Allow current user or profiles of the current user... - for (int profileId : um.getEnabledProfileIds(userId)) { - if (profileId == userId) { - return true; + final long token = Binder.clearCallingIdentity(); + try { + // Allow current user or profiles of the current user... + for (int profileId : um.getEnabledProfileIds(mCurrentUserId)) { + if (profileId == userId) { + return true; + } } + return false; + } finally { + Binder.restoreCallingIdentity(token); } - return false; } private boolean isForegroundActivity(int uid, int pid) { @@ -1035,7 +1067,7 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe daemon.setActiveGroup(userId, fpDir.getAbsolutePath().getBytes()); mCurrentUserId = userId; } - mCurrentAuthenticatorId = daemon.getAuthenticatorId(); + mAuthenticatorIds.put(userId, daemon.getAuthenticatorId()); } catch (RemoteException e) { Slog.e(TAG, "Failed to setActiveGroup():", e); } @@ -1058,8 +1090,14 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe * @return true if this is a work profile */ private boolean isWorkProfile(int userId) { - UserInfo info = mUserManager.getUserInfo(userId); - return info != null && info.isManagedProfile(); + UserInfo userInfo = null; + final long token = Binder.clearCallingIdentity(); + try { + userInfo = mUserManager.getUserInfo(userId); + } finally { + Binder.restoreCallingIdentity(token); + } + return userInfo != null && userInfo.isManagedProfile(); } private void listenForUserSwitches() { @@ -1085,12 +1123,14 @@ public class FingerprintService extends SystemService implements IBinder.DeathRe } } - /*** + /** * @param opPackageName the name of the calling package - * @return authenticator id for the current user + * @return authenticator id for the calling user */ public long getAuthenticatorId(String opPackageName) { - return mCurrentAuthenticatorId; + final int userId = getUserOrWorkProfileId(opPackageName, UserHandle.getCallingUserId()); + Long authenticatorId = mAuthenticatorIds.get(userId); + return authenticatorId != null ? authenticatorId : 0; } } diff --git a/services/core/java/com/android/server/job/JobServiceContext.java b/services/core/java/com/android/server/job/JobServiceContext.java index 5e495fab3f6e..fb54d3bb9468 100644 --- a/services/core/java/com/android/server/job/JobServiceContext.java +++ b/services/core/java/com/android/server/job/JobServiceContext.java @@ -445,7 +445,10 @@ public class JobServiceContext extends IJobCallback.Stub implements ServiceConne mVerb = VERB_STARTING; scheduleOpTimeOut(); service.startJob(mParams); - } catch (RemoteException e) { + } catch (Exception e) { + // We catch 'Exception' because client-app malice or bugs might induce a wide + // range of possible exception-throw outcomes from startJob() and its handling + // of the client's ParcelableBundle extras. Slog.e(TAG, "Error sending onStart message to '" + mRunningJob.getServiceComponent().getShortClassName() + "' ", e); } diff --git a/services/core/java/com/android/server/lights/Light.java b/services/core/java/com/android/server/lights/Light.java index b18a18129248..6d0a51040c73 100644 --- a/services/core/java/com/android/server/lights/Light.java +++ b/services/core/java/com/android/server/lights/Light.java @@ -43,4 +43,5 @@ public abstract class Light { public abstract void pulse(); public abstract void pulse(int color, int onMS); public abstract void turnOff(); + public abstract void setVrMode(boolean enabled); } diff --git a/services/core/java/com/android/server/lights/LightsService.java b/services/core/java/com/android/server/lights/LightsService.java index ca6481792dfe..eead11464d64 100644 --- a/services/core/java/com/android/server/lights/LightsService.java +++ b/services/core/java/com/android/server/lights/LightsService.java @@ -1,5 +1,4 @@ -/* - * Copyright (C) 2008 The Android Open Source Project +/* * Copyright (C) 2008 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. @@ -17,18 +16,13 @@ package com.android.server.lights; import com.android.server.SystemService; -import com.android.server.vr.VrManagerService; import android.app.ActivityManager; import android.content.Context; import android.os.Handler; import android.os.Message; -import android.os.RemoteException; import android.os.Trace; -import android.os.UserHandle; import android.provider.Settings; -import android.service.vr.IVrManager; -import android.service.vr.IVrStateCallbacks; import android.util.Slog; public class LightsService extends SystemService { @@ -36,7 +30,6 @@ public class LightsService extends SystemService { static final boolean DEBUG = false; final LightImpl mLights[] = new LightImpl[LightsManager.LIGHT_ID_COUNT]; - private boolean mVrModeEnabled; private final class LightImpl extends Light { @@ -52,6 +45,13 @@ public class LightsService extends SystemService { @Override public void setBrightness(int brightness, int brightnessMode) { synchronized (this) { + // LOW_PERSISTENCE cannot be manually set + if (brightnessMode == BRIGHTNESS_MODE_LOW_PERSISTENCE) { + Slog.w(TAG, "setBrightness with LOW_PERSISTENCE unexpected #" + mId + + ": brightness=0x" + Integer.toHexString(brightness)); + return; + } + int color = brightness & 0x000000ff; color = 0xff000000 | (color << 16) | (color << 8) | color; setLightLocked(color, LIGHT_FLASH_NONE, 0, 0, brightnessMode); @@ -80,11 +80,9 @@ public class LightsService extends SystemService { @Override public void pulse(int color, int onMS) { synchronized (this) { - if (mBrightnessMode == BRIGHTNESS_MODE_LOW_PERSISTENCE) { - return; - } if (mColor == 0 && !mFlashing) { - setLightLocked(color, LIGHT_FLASH_HARDWARE, onMS, 1000, BRIGHTNESS_MODE_USER); + setLightLocked(color, LIGHT_FLASH_HARDWARE, onMS, 1000, + BRIGHTNESS_MODE_USER); mColor = 0; mH.sendMessageDelayed(Message.obtain(mH, 1, this), onMS); } @@ -98,17 +96,23 @@ public class LightsService extends SystemService { } } - void enableLowPersistence() { - synchronized(this) { - setLightLocked(0, LIGHT_FLASH_NONE, 0, 0, BRIGHTNESS_MODE_LOW_PERSISTENCE); - mLocked = true; - } - } - - void disableLowPersistence() { - synchronized(this) { - mLocked = false; - setLightLocked(mLastColor, LIGHT_FLASH_NONE, 0, 0, mLastBrightnessMode); + @Override + public void setVrMode(boolean enabled) { + synchronized (this) { + if (mVrModeEnabled != enabled) { + mVrModeEnabled = enabled; + + mUseLowPersistenceForVR = + (getVrDisplayMode() == Settings.Secure.VR_DISPLAY_MODE_LOW_PERSISTENCE); + if (shouldBeInLowPersistenceMode()) { + mLastBrightnessMode = mBrightnessMode; + } + + // NOTE: We do not trigger a call to setLightLocked here. We do not know the + // current brightness or other values when leaving VR so we avoid any incorrect + // jumps. The code that calls this method will immediately issue a brightness + // update which is when the change will occur. + } } } @@ -119,7 +123,13 @@ public class LightsService extends SystemService { } private void setLightLocked(int color, int mode, int onMS, int offMS, int brightnessMode) { - if (!mLocked && (color != mColor || mode != mMode || onMS != mOnMS || offMS != mOffMS || + if (shouldBeInLowPersistenceMode()) { + brightnessMode = BRIGHTNESS_MODE_LOW_PERSISTENCE; + } else if (brightnessMode == BRIGHTNESS_MODE_LOW_PERSISTENCE) { + brightnessMode = mLastBrightnessMode; + } + + if ((color != mColor || mode != mMode || onMS != mOnMS || offMS != mOffMS || mBrightnessMode != brightnessMode)) { if (DEBUG) Slog.v(TAG, "setLight #" + mId + ": color=#" + Integer.toHexString(color) + ": brightnessMode=" + brightnessMode); @@ -128,7 +138,6 @@ public class LightsService extends SystemService { mMode = mode; mOnMS = onMS; mOffMS = offMS; - mLastBrightnessMode = mBrightnessMode; mBrightnessMode = brightnessMode; Trace.traceBegin(Trace.TRACE_TAG_POWER, "setLight(" + mId + ", 0x" + Integer.toHexString(color) + ")"); @@ -140,6 +149,10 @@ public class LightsService extends SystemService { } } + private boolean shouldBeInLowPersistenceMode() { + return mVrModeEnabled && mUseLowPersistenceForVR; + } + private int mId; private int mColor; private int mMode; @@ -149,7 +162,8 @@ public class LightsService extends SystemService { private int mBrightnessMode; private int mLastBrightnessMode; private int mLastColor; - private boolean mLocked; + private boolean mVrModeEnabled; + private boolean mUseLowPersistenceForVR; } public LightsService(Context context) { @@ -169,17 +183,6 @@ public class LightsService extends SystemService { @Override public void onBootPhase(int phase) { - if (phase == PHASE_SYSTEM_SERVICES_READY) { - IVrManager vrManager = - (IVrManager) getBinderService(VrManagerService.VR_MANAGER_BINDER_SERVICE); - if (vrManager != null) { - try { - vrManager.registerListener(mVrStateCallbacks); - } catch (RemoteException e) { - Slog.e(TAG, "Failed to register VR mode state listener: " + e); - } - } - } } private int getVrDisplayMode() { @@ -190,30 +193,6 @@ public class LightsService extends SystemService { currentUser); } - private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() { - @Override - public void onVrStateChanged(boolean enabled) throws RemoteException { - LightImpl l = mLights[LightsManager.LIGHT_ID_BACKLIGHT]; - int vrDisplayMode = getVrDisplayMode(); - - // User leaves VR mode before altering display settings. - if (enabled && vrDisplayMode == Settings.Secure.VR_DISPLAY_MODE_LOW_PERSISTENCE) { - if (!mVrModeEnabled) { - if (DEBUG) - Slog.v(TAG, "VR mode enabled, setting brightness to low persistence"); - l.enableLowPersistence(); - mVrModeEnabled = true; - } - } else { - if (mVrModeEnabled) { - if (DEBUG) Slog.v(TAG, "VR mode disabled, resetting brightnes"); - l.disableLowPersistence(); - mVrModeEnabled = false; - } - } - } - }; - private final LightsManager mService = new LightsManager() { @Override public Light getLight(int id) { diff --git a/services/core/java/com/android/server/location/GnssLocationProvider.java b/services/core/java/com/android/server/location/GnssLocationProvider.java index 17c100067ab1..5f58c5d9e56a 100644 --- a/services/core/java/com/android/server/location/GnssLocationProvider.java +++ b/services/core/java/com/android/server/location/GnssLocationProvider.java @@ -30,7 +30,6 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.content.pm.PackageManager; import android.database.Cursor; import android.hardware.location.GeofenceHardware; import android.hardware.location.GeofenceHardwareImpl; @@ -295,9 +294,6 @@ public class GnssLocationProvider implements LocationProviderInterface { // current setting - 4 hours private static final long MAX_RETRY_INTERVAL = 4*60*60*1000; - // Timeout when holding wakelocks for downloading XTRA data. - private static final long DOWNLOAD_XTRA_DATA_TIMEOUT_MS = 60 * 1000; - private BackOff mNtpBackOff = new BackOff(RETRY_INTERVAL, MAX_RETRY_INTERVAL); private BackOff mXtraBackOff = new BackOff(RETRY_INTERVAL, MAX_RETRY_INTERVAL); @@ -389,8 +385,6 @@ public class GnssLocationProvider implements LocationProviderInterface { // Wakelocks private final static String WAKELOCK_KEY = "GnssLocationProvider"; private final PowerManager.WakeLock mWakeLock; - private static final String DOWNLOAD_EXTRA_WAKELOCK_KEY = "GnssLocationProviderXtraDownload"; - private final PowerManager.WakeLock mDownloadXtraWakeLock; // Alarms private final static String ALARM_WAKEUP = "com.android.internal.location.ALARM_WAKEUP"; @@ -423,6 +417,12 @@ public class GnssLocationProvider implements LocationProviderInterface { private int mYearOfHardware = 0; + // Set lower than the current ITAR limit of 600m/s to allow this to trigger even if GPS HAL + // stops output right at 600m/s, depriving this of the information of a device that reaches + // greater than 600m/s, and higher than the speed of sound to avoid impacting most use cases. + private static final float ITAR_SPEED_LIMIT_METERS_PER_SECOND = 400.0F; + private boolean mItarSpeedLimitExceeded = false; + private final IGnssStatusProvider mGnssStatusProvider = new IGnssStatusProvider.Stub() { @Override public void registerGnssStatusCallback(IGnssStatusListener callback) { @@ -464,12 +464,6 @@ public class GnssLocationProvider implements LocationProviderInterface { if (mDownloadXtraDataPending == STATE_PENDING_NETWORK) { xtraDownloadRequest(); } - sendMessage(UPDATE_NETWORK_STATE, 0 /*arg*/, network); - } - - @Override - public void onLost(Network network) { - sendMessage(UPDATE_NETWORK_STATE, 0 /*arg*/, network); } }; @@ -481,6 +475,11 @@ public class GnssLocationProvider implements LocationProviderInterface { private final ConnectivityManager.NetworkCallback mSuplConnectivityCallback = new ConnectivityManager.NetworkCallback() { @Override + public void onAvailable(Network network) { + sendMessage(UPDATE_NETWORK_STATE, 0 /*arg*/, network); + } + + @Override public void onLost(Network network) { releaseSuplConnection(GPS_RELEASE_AGPS_DATA_CONN); } @@ -726,11 +725,6 @@ public class GnssLocationProvider implements LocationProviderInterface { mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_KEY); mWakeLock.setReferenceCounted(true); - // Create a separate wake lock for xtra downloader as it may be released due to timeout. - mDownloadXtraWakeLock = mPowerManager.newWakeLock( - PowerManager.PARTIAL_WAKE_LOCK, DOWNLOAD_EXTRA_WAKELOCK_KEY); - mDownloadXtraWakeLock.setReferenceCounted(true); - mAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE); mWakeupIntent = PendingIntent.getBroadcast(mContext, 0, new Intent(ALARM_WAKEUP), 0); mTimeoutIntent = PendingIntent.getBroadcast(mContext, 0, new Intent(ALARM_TIMEOUT), 0); @@ -834,21 +828,11 @@ public class GnssLocationProvider implements LocationProviderInterface { private void handleUpdateNetworkState(Network network) { // retrieve NetworkInfo for this UID NetworkInfo info = mConnMgr.getNetworkInfo(network); - - boolean networkAvailable = false; - boolean isConnected = false; - int type = ConnectivityManager.TYPE_NONE; - boolean isRoaming = false; - String apnName = null; - - if (info != null) { - networkAvailable = info.isAvailable() && TelephonyManager.getDefault().getDataEnabled(); - isConnected = info.isConnected(); - type = info.getType(); - isRoaming = info.isRoaming(); - apnName = info.getExtraInfo(); + if (info == null) { + return; } + boolean isConnected = info.isConnected(); if (DEBUG) { String message = String.format( "UpdateNetworkState, state=%s, connected=%s, info=%s, capabilities=%S", @@ -860,6 +844,8 @@ public class GnssLocationProvider implements LocationProviderInterface { } if (native_is_agps_ril_supported()) { + boolean dataEnabled = TelephonyManager.getDefault().getDataEnabled(); + boolean networkAvailable = info.isAvailable() && dataEnabled; String defaultApn = getSelectedApn(); if (defaultApn == null) { defaultApn = "dummy-apn"; @@ -867,10 +853,10 @@ public class GnssLocationProvider implements LocationProviderInterface { native_update_network_state( isConnected, - type, - isRoaming, + info.getType(), + info.isRoaming(), networkAvailable, - apnName, + info.getExtraInfo(), defaultApn); } else if (DEBUG) { Log.d(TAG, "Skipped network state update because GPS HAL AGPS-RIL is not supported"); @@ -878,6 +864,7 @@ public class GnssLocationProvider implements LocationProviderInterface { if (mAGpsDataConnectionState == AGPS_DATA_CONNECTION_OPENING) { if (isConnected) { + String apnName = info.getExtraInfo(); if (apnName == null) { // assign a dummy value in the case of C2K as otherwise we will have a runtime // exception in the following call to native_agps_data_conn_open @@ -1042,7 +1029,7 @@ public class GnssLocationProvider implements LocationProviderInterface { mDownloadXtraDataPending = STATE_DOWNLOADING; // hold wake lock while task runs - mDownloadXtraWakeLock.acquire(DOWNLOAD_XTRA_DATA_TIMEOUT_MS); + mWakeLock.acquire(); Log.i(TAG, "WakeLock acquired by handleDownloadXtraData()"); AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() { @Override @@ -1064,24 +1051,9 @@ public class GnssLocationProvider implements LocationProviderInterface { mXtraBackOff.nextBackoffMillis()); } - // Release wake lock held by task, synchronize on mLock in case multiple - // download tasks overrun. - synchronized (mLock) { - if (mDownloadXtraWakeLock.isHeld()) { - // This wakelock may have time-out, if a timeout was specified. - // Catch (and ignore) any timeout exceptions. - try { - mDownloadXtraWakeLock.release(); - if (DEBUG) Log.d(TAG, "WakeLock released by handleDownloadXtraData()"); - } catch (Exception e) { - Log.i(TAG, "Wakelock timeout & release race exception in " - + "handleDownloadXtraData()", e); - } - } else { - Log.e(TAG, "WakeLock expired before release in " - + "handleDownloadXtraData()"); - } - } + // release wake lock held by task + mWakeLock.release(); + Log.i(TAG, "WakeLock released by handleDownloadXtraData()"); } }); } @@ -1448,6 +1420,12 @@ public class GnssLocationProvider implements LocationProviderInterface { mStarted = true; mSingleShot = singleShot; mPositionMode = GPS_POSITION_MODE_STANDALONE; + // Notify about suppressed output, if speed limit was previously exceeded. + // Elsewhere, we check again with every speed output reported. + if (mItarSpeedLimitExceeded) { + Log.i(TAG, "startNavigating with ITAR limit in place. Output limited " + + "until slow enough speed reported."); + } boolean agpsEnabled = (Settings.Global.getInt(mContext.getContentResolver(), @@ -1534,7 +1512,17 @@ public class GnssLocationProvider implements LocationProviderInterface { * called from native code to update our position. */ private void reportLocation(int flags, double latitude, double longitude, double altitude, - float speed, float bearing, float accuracy, long timestamp) { + float speedMetersPerSecond, float bearing, float accuracy, long timestamp) { + if ((flags & LOCATION_HAS_SPEED) == LOCATION_HAS_SPEED) { + mItarSpeedLimitExceeded = speedMetersPerSecond > ITAR_SPEED_LIMIT_METERS_PER_SECOND; + } + + if (mItarSpeedLimitExceeded) { + Log.i(TAG, "Hal reported a speed in excess of ITAR limit." + + " GPS/GNSS Navigation output blocked."); + return; // No output of location allowed + } + if (VERBOSE) Log.v(TAG, "reportLocation lat: " + latitude + " long: " + longitude + " timestamp: " + timestamp); @@ -1554,7 +1542,7 @@ public class GnssLocationProvider implements LocationProviderInterface { mLocation.removeAltitude(); } if ((flags & LOCATION_HAS_SPEED) == LOCATION_HAS_SPEED) { - mLocation.setSpeed(speed); + mLocation.setSpeed(speedMetersPerSecond); } else { mLocation.removeSpeed(); } @@ -1738,23 +1726,29 @@ public class GnssLocationProvider implements LocationProviderInterface { * called from native code to report NMEA data received */ private void reportNmea(long timestamp) { - int length = native_read_nmea(mNmeaBuffer, mNmeaBuffer.length); - String nmea = new String(mNmeaBuffer, 0 /* offset */, length); - mListenerHelper.onNmeaReceived(timestamp, nmea); + if (!mItarSpeedLimitExceeded) { + int length = native_read_nmea(mNmeaBuffer, mNmeaBuffer.length); + String nmea = new String(mNmeaBuffer, 0 /* offset */, length); + mListenerHelper.onNmeaReceived(timestamp, nmea); + } } /** * called from native code - Gps measurements callback */ private void reportMeasurementData(GnssMeasurementsEvent event) { - mGnssMeasurementsProvider.onMeasurementsAvailable(event); + if (!mItarSpeedLimitExceeded) { + mGnssMeasurementsProvider.onMeasurementsAvailable(event); + } } /** * called from native code - GPS navigation message callback */ private void reportNavigationMessage(GnssNavigationMessage event) { - mGnssNavigationMessageProvider.onNavigationMessageAvailable(event); + if (!mItarSpeedLimitExceeded) { + mGnssNavigationMessageProvider.onNavigationMessageAvailable(event); + } } /** @@ -2251,12 +2245,6 @@ public class GnssLocationProvider implements LocationProviderInterface { NetworkRequest.Builder networkRequestBuilder = new NetworkRequest.Builder(); networkRequestBuilder.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR); networkRequestBuilder.addTransportType(NetworkCapabilities.TRANSPORT_WIFI); - // On watches, Bluetooth is the most important network type. - boolean isWatch = - mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH); - if (isWatch) { - networkRequestBuilder.addTransportType(NetworkCapabilities.TRANSPORT_BLUETOOTH); - } NetworkRequest networkRequest = networkRequestBuilder.build(); mConnMgr.registerNetworkCallback(networkRequest, mNetworkConnectivityCallback); diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index b1468f182fd3..b1fd26583685 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -2529,7 +2529,6 @@ public class NotificationManagerService extends SystemService { final int userId = ActivityManager.handleIncomingUser(callingPid, callingUid, incomingUserId, true, false, "enqueueNotification", pkg); final UserHandle user = new UserHandle(userId); - // Fix the notification as best we can. try { final ApplicationInfo ai = getContext().getPackageManager().getApplicationInfoAsUser( @@ -2543,13 +2542,16 @@ public class NotificationManagerService extends SystemService { mUsageStats.registerEnqueuedByApp(pkg); - if (pkg == null || notification == null) { throw new IllegalArgumentException("null not allowed: pkg=" + pkg + " id=" + id + " notification=" + notification); } + + // The system can post notifications for any package, let us resolve that. + final int notificationUid = resolveNotificationUid(opPkg, callingUid, userId); + final StatusBarNotification n = new StatusBarNotification( - pkg, opPkg, id, tag, callingUid, callingPid, 0, notification, + pkg, opPkg, id, tag, notificationUid, callingPid, 0, notification, user); // Limit the number of notifications that any given package except the android @@ -2619,6 +2621,19 @@ public class NotificationManagerService extends SystemService { idOut[0] = id; } + private int resolveNotificationUid(String opPackageName, int callingUid, int userId) { + // The system can post notifications on behalf of any package it wants + if (isCallerSystem() && opPackageName != null && !"android".equals(opPackageName)) { + try { + return getContext().getPackageManager() + .getPackageUidAsUser(opPackageName, userId); + } catch (NameNotFoundException e) { + /* ignore */ + } + } + return callingUid; + } + private class EnqueueNotificationRunnable implements Runnable { private final NotificationRecord r; private final int userId; @@ -3576,7 +3591,8 @@ public class NotificationManagerService extends SystemService { NotificationRecord childR = mNotificationList.get(i); StatusBarNotification childSbn = childR.sbn; if ((childSbn.isGroup() && !childSbn.getNotification().isGroupSummary()) && - childR.getGroupKey().equals(r.getGroupKey())) { + childR.getGroupKey().equals(r.getGroupKey()) + && (childR.getFlags() & Notification.FLAG_FOREGROUND_SERVICE) == 0) { EventLogTags.writeNotificationCancel(callingUid, callingPid, pkg, childSbn.getId(), childSbn.getTag(), userId, 0, 0, reason, listenerName); mNotificationList.remove(i); diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 93f5f47a79e3..c46a9a7fca6b 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -1646,11 +1646,9 @@ public class PackageManagerService extends IPackageManager.Stub { } // Now that we successfully installed the package, grant runtime - // permissions if requested before broadcasting the install. Also - // for legacy apps in permission review mode we clear the permission - // review flag which is used to emulate runtime permissions for - // legacy apps. - if (grantPermissions) { + // permissions if requested before broadcasting the install. + if (grantPermissions && res.pkg.applicationInfo.targetSdkVersion + >= Build.VERSION_CODES.M) { grantRequestedRuntimePermissions(res.pkg, res.newUsers, grantedPermissions); } @@ -1887,6 +1885,11 @@ public class PackageManagerService extends IPackageManager.Stub { for (int userId : userIds) { grantRequestedRuntimePermissionsForUser(pkg, userId, grantedPermissions); } + + // We could have touched GID membership, so flush out packages.list + synchronized (mPackages) { + mSettings.writePackageListLPr(); + } } private void grantRequestedRuntimePermissionsForUser(PackageParser.Package pkg, int userId, @@ -1901,9 +1904,6 @@ public class PackageManagerService extends IPackageManager.Stub { final int immutableFlags = PackageManager.FLAG_PERMISSION_SYSTEM_FIXED | PackageManager.FLAG_PERMISSION_POLICY_FIXED; - final boolean supportsRuntimePermissions = pkg.applicationInfo.targetSdkVersion - >= Build.VERSION_CODES.M; - for (String permission : pkg.requestedPermissions) { final BasePermission bp; synchronized (mPackages) { @@ -1913,18 +1913,9 @@ public class PackageManagerService extends IPackageManager.Stub { && (grantedPermissions == null || ArrayUtils.contains(grantedPermissions, permission))) { final int flags = permissionsState.getPermissionFlags(permission, userId); - if (supportsRuntimePermissions) { - // Installer cannot change immutable permissions. - if ((flags & immutableFlags) == 0) { - grantRuntimePermission(pkg.packageName, permission, userId); - } - } else if (mPermissionReviewRequired) { - // In permission review mode we clear the review flag when we - // are asked to install the app with all permissions granted. - if ((flags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) != 0) { - updatePermissionFlags(permission, pkg.packageName, - PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED, 0 /*value*/, userId); - } + // Installer cannot change immutable permissions. + if ((flags & immutableFlags) == 0) { + grantRuntimePermission(pkg.packageName, permission, userId); } } } @@ -5411,15 +5402,23 @@ public class PackageManagerService extends IPackageManager.Stub { result.remove(xpResolveInfo); } if (result.size() == 0 && !addEphemeral) { + // No result in current profile, but found candidate in parent user. + // And we are not going to add emphemeral app, so we can return the + // result straight away. result.add(xpDomainInfo.resolveInfo); return result; } - } - if (result.size() > 1 || addEphemeral) { - result = filterCandidatesWithDomainPreferredActivitiesLPr( - intent, flags, result, xpDomainInfo, userId); - sortResult = true; - } + } else if (result.size() <= 1 && !addEphemeral) { + // No result in parent user and <= 1 result in current profile, and we + // are not going to add emphemeral app, so we can return the result without + // further processing. + return result; + } + // We have more than one candidate (combining results from current and parent + // profile), so we need filtering and sorting. + result = filterCandidatesWithDomainPreferredActivitiesLPr( + intent, flags, result, xpDomainInfo, userId); + sortResult = true; } } else { final PackageParser.Package pkg = mPackages.get(pkgName); @@ -21144,6 +21143,11 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); return mSettings.wasPackageEverLaunchedLPr(packageName, userId); } } + + @Override + public String getNameForUid(int uid) { + return PackageManagerService.this.getNameForUid(uid); + } } @Override diff --git a/services/core/java/com/android/server/pm/ShortcutPackageItem.java b/services/core/java/com/android/server/pm/ShortcutPackageItem.java index 1f195a7a544b..e59d69f4c563 100644 --- a/services/core/java/com/android/server/pm/ShortcutPackageItem.java +++ b/services/core/java/com/android/server/pm/ShortcutPackageItem.java @@ -106,27 +106,31 @@ abstract class ShortcutPackageItem { } return; // Not installed, no need to restore yet. } + boolean blockRestore = false; if (!mPackageInfo.hasSignatures()) { s.wtf("Attempted to restore package " + mPackageName + ", user=" + mPackageUserId + " but signatures not found in the restore data."); - onRestoreBlocked(); - return; + blockRestore = true; } - - final PackageInfo pi = s.getPackageInfoWithSignatures(mPackageName, mPackageUserId); - if (!mPackageInfo.canRestoreTo(s, pi)) { - // Package is now installed, but can't restore. Let the subclass do the cleanup. - onRestoreBlocked(); - return; - } - if (ShortcutService.DEBUG) { - Slog.d(TAG, String.format("Restored package: %s/%d on user %d", mPackageName, - mPackageUserId, getOwnerUserId())); + if (!blockRestore) { + final PackageInfo pi = s.getPackageInfoWithSignatures(mPackageName, mPackageUserId); + if (!mPackageInfo.canRestoreTo(s, pi)) { + // Package is now installed, but can't restore. Let the subclass do the cleanup. + blockRestore = true; + } } + if (blockRestore) { + onRestoreBlocked(); + } else { + if (ShortcutService.DEBUG) { + Slog.d(TAG, String.format("Restored package: %s/%d on user %d", mPackageName, + mPackageUserId, getOwnerUserId())); + } - onRestored(); + onRestored(); + } - // Now the package is not shadow. + // Either way, it's no longer a shadow. mPackageInfo.setShadow(false); s.scheduleSaveUser(mPackageUserId); diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index 500af0ca73d3..6e8799e81326 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -3739,6 +3739,16 @@ public class ShortcutService extends IShortcutService.Stub { } } + @VisibleForTesting + ShortcutLauncher getLauncherShortcutForTest(String packageName, int userId) { + synchronized (mLock) { + final ShortcutUser user = mUsers.get(userId); + if (user == null) return null; + + return user.getAllLaunchersForTest().get(PackageWithUser.of(userId, packageName)); + } + } + /** * Control whether {@link #verifyStates} should be performed. We always perform it during unit * tests. diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index af055da2b9e6..24f877e26b7f 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -33,12 +33,10 @@ import android.app.IStopUserCallback; import android.app.KeyguardManager; import android.app.PendingIntent; import android.content.BroadcastReceiver; -import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.IntentSender; -import android.content.pm.IPackageManager; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.UserInfo; @@ -573,7 +571,7 @@ public class UserManagerService extends IUserManager.Stub { @Override public int[] getProfileIds(int userId, boolean enabledOnly) { if (userId != UserHandle.getCallingUserId()) { - checkManageUsersPermission("getting profiles related to user " + userId); + checkManageOrCreateUsersPermission("getting profiles related to user " + userId); } final long ident = Binder.clearCallingIdentity(); try { @@ -654,12 +652,10 @@ public class UserManagerService extends IUserManager.Stub { public boolean isSameProfileGroup(int userId, int otherUserId) { if (userId == otherUserId) return true; checkManageUsersPermission("check if in the same profile group"); - synchronized (mPackagesLock) { - return isSameProfileGroupLP(userId, otherUserId); - } + return isSameProfileGroupNoChecks(userId, otherUserId); } - private boolean isSameProfileGroupLP(int userId, int otherUserId) { + private boolean isSameProfileGroupNoChecks(int userId, int otherUserId) { synchronized (mUsersLock) { UserInfo userInfo = getUserInfoLU(userId); if (userInfo == null || userInfo.profileGroupId == UserInfo.NO_PROFILE_GROUP_ID) { @@ -861,12 +857,10 @@ public class UserManagerService extends IUserManager.Stub { public boolean isManagedProfile(int userId) { int callingUserId = UserHandle.getCallingUserId(); if (callingUserId != userId && !hasManageUsersPermission()) { - synchronized (mPackagesLock) { - if (!isSameProfileGroupLP(callingUserId, userId)) { - throw new SecurityException( - "You need MANAGE_USERS permission to: check if specified user a " + - "managed profile outside your profile group"); - } + if (!isSameProfileGroupNoChecks(callingUserId, userId)) { + throw new SecurityException( + "You need MANAGE_USERS permission to: check if specified user a " + + "managed profile outside your profile group"); } } synchronized (mUsersLock) { @@ -876,6 +870,37 @@ public class UserManagerService extends IUserManager.Stub { } @Override + public boolean isUserUnlockingOrUnlocked(int userId) { + checkManageOrInteractPermIfCallerInOtherProfileGroup(userId, "isUserUnlockingOrUnlocked"); + return mLocalService.isUserUnlockingOrUnlocked(userId); + } + + @Override + public boolean isUserUnlocked(int userId) { + checkManageOrInteractPermIfCallerInOtherProfileGroup(userId, "isUserUnlocked"); + return mLocalService.isUserUnlockingOrUnlocked(userId); + } + + @Override + public boolean isUserRunning(int userId) { + checkManageOrInteractPermIfCallerInOtherProfileGroup(userId, "isUserRunning"); + return mLocalService.isUserRunning(userId); + } + + private void checkManageOrInteractPermIfCallerInOtherProfileGroup(int userId, String name) { + int callingUserId = UserHandle.getCallingUserId(); + if (callingUserId == userId || isSameProfileGroupNoChecks(callingUserId, userId) || + hasManageUsersPermission()) { + return; + } + if (ActivityManager.checkComponentPermission(Manifest.permission.INTERACT_ACROSS_USERS, + Binder.getCallingUid(), -1, true) != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("You need INTERACT_ACROSS_USERS or MANAGE_USERS permission " + + "to: check " + name); + } + } + + @Override public boolean isDemoUser(int userId) { int callingUserId = UserHandle.getCallingUserId(); if (callingUserId != userId && !hasManageUsersPermission()) { @@ -2889,8 +2914,6 @@ public class UserManagerService extends IUserManager.Stub { applyUserRestrictionsLR(userId); } } - - maybeInitializeDemoMode(userId); } /** @@ -2923,29 +2946,6 @@ public class UserManagerService extends IUserManager.Stub { scheduleWriteUser(userData); } - private void maybeInitializeDemoMode(int userId) { - if (UserManager.isDeviceInDemoMode(mContext) && userId != UserHandle.USER_SYSTEM) { - String demoLauncher = - mContext.getResources().getString( - com.android.internal.R.string.config_demoModeLauncherComponent); - if (!TextUtils.isEmpty(demoLauncher)) { - ComponentName componentToEnable = ComponentName.unflattenFromString(demoLauncher); - String demoLauncherPkg = componentToEnable.getPackageName(); - try { - final IPackageManager iPm = AppGlobals.getPackageManager(); - iPm.setComponentEnabledSetting(componentToEnable, - PackageManager.COMPONENT_ENABLED_STATE_ENABLED, /* flags= */ 0, - /* userId= */ userId); - iPm.setApplicationEnabledSetting(demoLauncherPkg, - PackageManager.COMPONENT_ENABLED_STATE_ENABLED, /* flags= */ 0, - /* userId= */ userId, null); - } catch (RemoteException re) { - // Internal, shouldn't happen - } - } - } - } - /** * Returns the next available user id, filling in any holes in the ids. * TODO: May not be a good idea to recycle ids, in case it results in confusion @@ -3485,6 +3485,14 @@ public class UserManagerService extends IUserManager.Stub { || (state == UserState.STATE_RUNNING_UNLOCKED); } } + + @Override + public boolean isUserUnlocked(int userId) { + synchronized (mUserStates) { + int state = mUserStates.get(userId, -1); + return state == UserState.STATE_RUNNING_UNLOCKED; + } + } } /* Remove all the users except of the system one. */ diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index a8788d16296e..889c52afa379 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -152,6 +152,7 @@ import com.android.server.LocalServices; import com.android.server.policy.keyguard.KeyguardServiceDelegate; import com.android.server.policy.keyguard.KeyguardServiceDelegate.DrawnListener; import com.android.server.statusbar.StatusBarManagerInternal; +import com.android.server.vr.VrManagerInternal; import java.io.File; import java.io.FileReader; @@ -189,7 +190,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { static final int SHORT_PRESS_POWER_REALLY_GO_TO_SLEEP = 2; static final int SHORT_PRESS_POWER_REALLY_GO_TO_SLEEP_AND_GO_HOME = 3; static final int SHORT_PRESS_POWER_GO_HOME = 4; - static final int SHORT_PRESS_POWER_CLOSE_IME_OR_GO_HOME = 5; static final int LONG_PRESS_POWER_NOTHING = 0; static final int LONG_PRESS_POWER_GLOBAL_ACTIONS = 1; @@ -1301,28 +1301,12 @@ public class PhoneWindowManager implements WindowManagerPolicy { launchHomeFromHotKey(); break; case SHORT_PRESS_POWER_GO_HOME: - shortPressPowerGoHome(); - break; - case SHORT_PRESS_POWER_CLOSE_IME_OR_GO_HOME: - if (mWindowManagerFuncs.isInputMethodWindowVisible()) { - mWindowManagerFuncs.hideCurrentInputMethod(); - } else { - shortPressPowerGoHome(); - } + launchHomeFromHotKey(true /* awakenFromDreams */, false /*respectKeyguard*/); break; } } } - private void shortPressPowerGoHome() { - launchHomeFromHotKey(true /* awakenFromDreams */, false /*respectKeyguard*/); - if (isKeyguardShowingAndNotOccluded()) { - // Notify keyguard so it can do any special handling for the power button since the - // device will not power off and only launch home. - mKeyguardDelegate.onShortPowerPressedGoHome(); - } - } - private void powerMultiPressAction(long eventTime, boolean interactive, int behavior) { switch (behavior) { case MULTI_PRESS_POWER_NOTHING: @@ -6530,6 +6514,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { mKeyguardDelegate.onScreenTurnedOff(); } } + reportScreenStateToVrManager(false); } // Called on the DisplayManager's DisplayPowerController thread. @@ -6565,6 +6550,15 @@ public class PhoneWindowManager implements WindowManagerPolicy { mKeyguardDelegate.onScreenTurnedOn(); } } + reportScreenStateToVrManager(true); + } + + private void reportScreenStateToVrManager(boolean isScreenOn) { + VrManagerInternal vrService = LocalServices.getService(VrManagerInternal.class); + if (vrService == null) { + return; + } + vrService.onScreenStateChanged(isScreenOn); } private void finishWindowsDrawn() { diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java index 142095583c55..29a1f0710c34 100644 --- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java +++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceDelegate.java @@ -427,12 +427,6 @@ public class KeyguardServiceDelegate { } } - public void onShortPowerPressedGoHome() { - if (mKeyguardService != null) { - mKeyguardService.onShortPowerPressedGoHome(); - } - } - public void dump(String prefix, PrintWriter pw) { pw.println(prefix + TAG); prefix += " "; diff --git a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java index 1fe7a3c435ef..de906e67519e 100644 --- a/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java +++ b/services/core/java/com/android/server/policy/keyguard/KeyguardServiceWrapper.java @@ -228,15 +228,6 @@ public class KeyguardServiceWrapper implements IKeyguardService { } } - @Override - public void onShortPowerPressedGoHome() { - try { - mService.onShortPowerPressedGoHome(); - } catch (RemoteException e) { - Slog.w(TAG , "Remote Exception", e); - } - } - @Override // Binder interface public IBinder asBinder() { return mService.asBinder(); diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java index 554696d666c5..b33f3cf8391f 100644 --- a/services/core/java/com/android/server/power/PowerManagerService.java +++ b/services/core/java/com/android/server/power/PowerManagerService.java @@ -134,6 +134,8 @@ public final class PowerManagerService extends SystemService private static final int DIRTY_DOCK_STATE = 1 << 10; // Dirty bit: brightness boost changed private static final int DIRTY_SCREEN_BRIGHTNESS_BOOST = 1 << 11; + // Dirty bit: VR Mode enabled changed + private static final int DIRTY_VR_MODE_CHANGED = 1 << 12; // Summarizes the state of all active wakelocks. private static final int WAKE_LOCK_CPU = 1 << 0; @@ -409,11 +411,15 @@ public final class PowerManagerService extends SystemService private int mScreenBrightnessSettingMinimum; private int mScreenBrightnessSettingMaximum; private int mScreenBrightnessSettingDefault; + private int mScreenBrightnessForVrSettingDefault; // The screen brightness setting, from 0 to 255. // Use -1 if no value has been set. private int mScreenBrightnessSetting; + // The screen brightness setting, from 0 to 255, to be used while in VR Mode. + private int mScreenBrightnessForVrSetting; + // The screen auto-brightness adjustment setting, from -1 to 1. // Use 0 if there is no adjustment. private float mScreenAutoBrightnessAdjustmentSetting; @@ -498,8 +504,8 @@ public final class PowerManagerService extends SystemService private final ArrayList<PowerManagerInternal.LowPowerModeListener> mLowPowerModeListeners = new ArrayList<PowerManagerInternal.LowPowerModeListener>(); - // True if brightness should be affected by twilight. - private boolean mBrightnessUseTwilight; + // True if we are currently in VR Mode. + private boolean mIsVrModeEnabled; private native void nativeInit(); @@ -582,6 +588,7 @@ public final class PowerManagerService extends SystemService mScreenBrightnessSettingMinimum = pm.getMinimumScreenBrightnessSetting(); mScreenBrightnessSettingMaximum = pm.getMaximumScreenBrightnessSetting(); mScreenBrightnessSettingDefault = pm.getDefaultScreenBrightnessSetting(); + mScreenBrightnessForVrSettingDefault = pm.getDefaultScreenBrightnessForVrSetting(); SensorManager sensorManager = new SystemSensorManager(mContext, mHandler.getLooper()); @@ -647,6 +654,9 @@ public final class PowerManagerService extends SystemService Settings.System.SCREEN_BRIGHTNESS), false, mSettingsObserver, UserHandle.USER_ALL); resolver.registerContentObserver(Settings.System.getUriFor( + Settings.System.SCREEN_BRIGHTNESS_FOR_VR), + false, mSettingsObserver, UserHandle.USER_ALL); + resolver.registerContentObserver(Settings.System.getUriFor( Settings.System.SCREEN_BRIGHTNESS_MODE), false, mSettingsObserver, UserHandle.USER_ALL); resolver.registerContentObserver(Settings.System.getUriFor( @@ -664,9 +674,6 @@ public final class PowerManagerService extends SystemService resolver.registerContentObserver(Settings.Secure.getUriFor( Settings.Secure.DOUBLE_TAP_TO_WAKE), false, mSettingsObserver, UserHandle.USER_ALL); - resolver.registerContentObserver(Settings.Secure.getUriFor( - Secure.BRIGHTNESS_USE_TWILIGHT), - false, mSettingsObserver, UserHandle.USER_ALL); IVrManager vrManager = (IVrManager) getBinderService(VrManagerService.VR_MANAGER_BINDER_SERVICE); if (vrManager != null) { @@ -761,11 +768,17 @@ public final class PowerManagerService extends SystemService } } - final int oldScreenBrightnessSetting = mScreenBrightnessSetting; + final int oldScreenBrightnessSetting = getCurrentBrightnessSettingLocked(); + + mScreenBrightnessForVrSetting = Settings.System.getIntForUser(resolver, + Settings.System.SCREEN_BRIGHTNESS_FOR_VR, mScreenBrightnessForVrSettingDefault, + UserHandle.USER_CURRENT); + mScreenBrightnessSetting = Settings.System.getIntForUser(resolver, Settings.System.SCREEN_BRIGHTNESS, mScreenBrightnessSettingDefault, UserHandle.USER_CURRENT); - if (oldScreenBrightnessSetting != mScreenBrightnessSetting) { + + if (oldScreenBrightnessSetting != getCurrentBrightnessSettingLocked()) { mTemporaryScreenBrightnessSettingOverride = -1; } @@ -782,9 +795,6 @@ public final class PowerManagerService extends SystemService Settings.System.SCREEN_BRIGHTNESS_MODE, Settings.System.SCREEN_BRIGHTNESS_MODE_MANUAL, UserHandle.USER_CURRENT); - mBrightnessUseTwilight = Settings.Secure.getIntForUser(resolver, - Secure.BRIGHTNESS_USE_TWILIGHT, 0, UserHandle.USER_CURRENT) != 0; - final boolean lowPowerModeEnabled = Settings.Global.getInt(resolver, Settings.Global.LOW_POWER_MODE, 0) != 0; final boolean autoLowPowerModeConfigured = Settings.Global.getInt(resolver, @@ -799,6 +809,10 @@ public final class PowerManagerService extends SystemService mDirty |= DIRTY_SETTINGS; } + private int getCurrentBrightnessSettingLocked() { + return mIsVrModeEnabled ? mScreenBrightnessForVrSetting : mScreenBrightnessSetting; + } + private void postAfterBootCompleted(Runnable r) { if (mBootCompleted) { BackgroundThread.getHandler().post(r); @@ -2035,6 +2049,7 @@ public final class PowerManagerService extends SystemService || !mDreamsSupportedConfig || !mDreamsEnabledSetting || !mDisplayPowerRequest.isBrightOrDim() + || mDisplayPowerRequest.isVr() || (mUserActivitySummary & (USER_ACTIVITY_SCREEN_BRIGHT | USER_ACTIVITY_SCREEN_DIM | USER_ACTIVITY_SCREEN_DREAM)) == 0 || !mBootCompleted) { @@ -2079,7 +2094,7 @@ public final class PowerManagerService extends SystemService final boolean oldDisplayReady = mDisplayReady; if ((dirty & (DIRTY_WAKE_LOCKS | DIRTY_USER_ACTIVITY | DIRTY_WAKEFULNESS | DIRTY_ACTUAL_DISPLAY_POWER_STATE_UPDATED | DIRTY_BOOT_COMPLETED - | DIRTY_SETTINGS | DIRTY_SCREEN_BRIGHTNESS_BOOST)) != 0) { + | DIRTY_SETTINGS | DIRTY_SCREEN_BRIGHTNESS_BOOST | DIRTY_VR_MODE_CHANGED)) != 0) { mDisplayPowerRequest.policy = getDesiredScreenPolicyLocked(); // Determine appropriate screen brightness and auto-brightness adjustments. @@ -2093,6 +2108,9 @@ public final class PowerManagerService extends SystemService // bootloader brightness and the default brightness to be identical. autoBrightness = false; brightnessSetByUser = false; + } else if (mIsVrModeEnabled) { + screenBrightness = mScreenBrightnessForVrSetting; + autoBrightness = false; } else if (isValidBrightness(mScreenBrightnessOverrideFromWindowManager)) { screenBrightness = mScreenBrightnessOverrideFromWindowManager; autoBrightness = false; @@ -2126,8 +2144,7 @@ public final class PowerManagerService extends SystemService mDisplayPowerRequest.useAutoBrightness = autoBrightness; mDisplayPowerRequest.useProximitySensor = shouldUseProximitySensorLocked(); mDisplayPowerRequest.lowPowerMode = mLowPowerModeEnabled; - mDisplayPowerRequest.boostScreenBrightness = mScreenBrightnessBoostInProgress; - mDisplayPowerRequest.useTwilight = mBrightnessUseTwilight; + mDisplayPowerRequest.boostScreenBrightness = shouldBoostScreenBrightness(); if (mDisplayPowerRequest.policy == DisplayPowerRequest.POLICY_DOZE) { mDisplayPowerRequest.dozeScreenState = mDozeScreenStateOverrideFromDreamManager; @@ -2153,6 +2170,7 @@ public final class PowerManagerService extends SystemService + ", mWakeLockSummary=0x" + Integer.toHexString(mWakeLockSummary) + ", mUserActivitySummary=0x" + Integer.toHexString(mUserActivitySummary) + ", mBootCompleted=" + mBootCompleted + + ", mIsVrModeEnabled= " + mIsVrModeEnabled + ", mScreenBrightnessBoostInProgress=" + mScreenBrightnessBoostInProgress); } @@ -2183,6 +2201,10 @@ public final class PowerManagerService extends SystemService } } + private boolean shouldBoostScreenBrightness() { + return !mIsVrModeEnabled && mScreenBrightnessBoostInProgress; + } + private static boolean isValidBrightness(int value) { return value >= 0 && value <= 255; } @@ -2193,6 +2215,10 @@ public final class PowerManagerService extends SystemService } private int getDesiredScreenPolicyLocked() { + if (mIsVrModeEnabled) { + return DisplayPowerRequest.POLICY_VR; + } + if (mWakefulness == WAKEFULNESS_ASLEEP) { return DisplayPowerRequest.POLICY_OFF; } @@ -2296,7 +2322,7 @@ public final class PowerManagerService extends SystemService }; private boolean shouldUseProximitySensorLocked() { - return (mWakeLockSummary & WAKE_LOCK_PROXIMITY_SCREEN_OFF) != 0; + return !mIsVrModeEnabled && (mWakeLockSummary & WAKE_LOCK_PROXIMITY_SCREEN_OFF) != 0; } /** @@ -2956,7 +2982,11 @@ public final class PowerManagerService extends SystemService pw.println(" mScreenBrightnessSettingMinimum=" + mScreenBrightnessSettingMinimum); pw.println(" mScreenBrightnessSettingMaximum=" + mScreenBrightnessSettingMaximum); pw.println(" mScreenBrightnessSettingDefault=" + mScreenBrightnessSettingDefault); + pw.println(" mScreenBrightnessForVrSettingDefault=" + + mScreenBrightnessForVrSettingDefault); + pw.println(" mScreenBrightnessForVrSetting=" + mScreenBrightnessForVrSetting); pw.println(" mDoubleTapWakeEnabled=" + mDoubleTapWakeEnabled); + pw.println(" mIsVrModeEnabled=" + mIsVrModeEnabled); final int sleepTimeout = getSleepTimeoutLocked(); final int screenOffTimeout = getScreenOffTimeoutLocked(sleepTimeout); @@ -3083,6 +3113,14 @@ public final class PowerManagerService extends SystemService @Override public void onVrStateChanged(boolean enabled) { powerHintInternal(POWER_HINT_VR_MODE, enabled ? 1 : 0); + + synchronized (mLock) { + if (mIsVrModeEnabled != enabled) { + mIsVrModeEnabled = enabled; + mDirty |= DIRTY_VR_MODE_CHANGED; + updatePowerStateLocked(); + } + } } }; @@ -3821,6 +3859,7 @@ public final class PowerManagerService extends SystemService case Display.STATE_DOZE: case Display.STATE_DOZE_SUSPEND: case Display.STATE_ON: + case Display.STATE_VR: break; default: screenState = Display.STATE_UNKNOWN; diff --git a/services/core/java/com/android/server/storage/AppCollector.java b/services/core/java/com/android/server/storage/AppCollector.java new file mode 100644 index 000000000000..ee9c5bf2775d --- /dev/null +++ b/services/core/java/com/android/server/storage/AppCollector.java @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2016 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.storage; + +import android.annotation.NonNull; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.IPackageStatsObserver; +import android.content.pm.PackageManager; +import android.content.pm.PackageStats; +import android.content.pm.UserInfo; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.Message; +import android.os.Process; +import android.os.RemoteException; +import android.os.UserManager; +import android.os.storage.VolumeInfo; +import android.util.Log; +import com.android.internal.os.BackgroundThread; +import com.android.internal.util.Preconditions; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * AppCollector asynchronously collects package sizes. + */ +public class AppCollector { + private static String TAG = "AppCollector"; + + private CompletableFuture<List<PackageStats>> mStats; + private final BackgroundHandler mBackgroundHandler; + + /** + * Constrcuts a new AppCollector which runs on the provided volume. + * @param context Android context used to get + * @param volume Volume to check for apps. + */ + public AppCollector(Context context, @NonNull VolumeInfo volume) { + Preconditions.checkNotNull(volume); + + mBackgroundHandler = new BackgroundHandler(BackgroundThread.get().getLooper(), + volume, + context.getPackageManager(), + (UserManager) context.getSystemService(Context.USER_SERVICE)); + } + + /** + * Returns a list of package stats for the context and volume. Note that in a multi-user + * environment, this may return stats for the same package multiple times. These "duplicate" + * entries will have the package stats for the package for a given user, not the package in + * aggregate. + * @param timeoutMillis Milliseconds before timing out and returning early with null. + */ + public List<PackageStats> getPackageStats(long timeoutMillis) { + synchronized(this) { + if (mStats == null) { + mStats = new CompletableFuture<>(); + mBackgroundHandler.sendEmptyMessage(BackgroundHandler.MSG_START_LOADING_SIZES); + } + } + + List<PackageStats> value = null; + try { + value = mStats.get(timeoutMillis, TimeUnit.MILLISECONDS); + } catch (InterruptedException | ExecutionException e) { + Log.e(TAG, "An exception occurred while getting app storage", e); + } catch (TimeoutException e) { + Log.e(TAG, "AppCollector timed out"); + } + return value; + } + + private class StatsObserver extends IPackageStatsObserver.Stub { + private AtomicInteger mCount; + private final ArrayList<PackageStats> mPackageStats; + + public StatsObserver(int count) { + mCount = new AtomicInteger(count); + mPackageStats = new ArrayList<>(count); + } + + @Override + public void onGetStatsCompleted(PackageStats packageStats, boolean succeeded) + throws RemoteException { + if (succeeded) { + mPackageStats.add(packageStats); + } + + if (mCount.decrementAndGet() == 0) { + mStats.complete(mPackageStats); + } + } + } + + private class BackgroundHandler extends Handler { + static final int MSG_START_LOADING_SIZES = 0; + private final VolumeInfo mVolume; + private final PackageManager mPm; + private final UserManager mUm; + + BackgroundHandler(Looper looper, @NonNull VolumeInfo volume, PackageManager pm, UserManager um) { + super(looper); + mVolume = volume; + mPm = pm; + mUm = um; + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_START_LOADING_SIZES: { + final List<ApplicationInfo> apps = mPm.getInstalledApplications( + PackageManager.GET_UNINSTALLED_PACKAGES + | PackageManager.GET_DISABLED_COMPONENTS); + + final List<ApplicationInfo> volumeApps = new ArrayList<>(); + for (ApplicationInfo app : apps) { + if (Objects.equals(app.volumeUuid, mVolume.getFsUuid())) { + volumeApps.add(app); + } + } + + List<UserInfo> users = mUm.getUsers(); + final int count = users.size() * volumeApps.size(); + if (count == 0) { + mStats.complete(new ArrayList<>()); + } + + // Kick off the async package size query for all apps. + final StatsObserver observer = new StatsObserver(count); + for (UserInfo user : users) { + for (ApplicationInfo app : volumeApps) { + mPm.getPackageSizeInfoAsUser(app.packageName, user.id, + observer); + } + } + } + } + } + } +} diff --git a/services/core/java/com/android/server/storage/DiskStatsFileLogger.java b/services/core/java/com/android/server/storage/DiskStatsFileLogger.java new file mode 100644 index 000000000000..22299df93ef5 --- /dev/null +++ b/services/core/java/com/android/server/storage/DiskStatsFileLogger.java @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2016 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/LICENSE2.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.storage; + +import android.content.pm.PackageStats; +import android.os.Environment; +import android.util.ArrayMap; +import android.util.Log; + +import com.android.server.storage.FileCollector.MeasurementResult; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.PrintWriter; +import java.util.List; +import java.util.Map; + +/** + * DiskStatsFileLogger logs collected storage information to a file in a JSON format. + * + * The following information is cached in the file: + * 1. Size of images on disk. + * 2. Size of videos on disk. + * 3. Size of audio on disk. + * 4. Size of the downloads folder. + * 5. System size. + * 6. Aggregate and individual app and app cache sizes. + * 7. How much storage couldn't be categorized in one of the above categories. + */ +public class DiskStatsFileLogger { + private static final String TAG = "DiskStatsLogger"; + + public static final String PHOTOS_KEY = "photosSize"; + public static final String VIDEOS_KEY = "videosSize"; + public static final String AUDIO_KEY = "audioSize"; + public static final String DOWNLOADS_KEY = "downloadsSize"; + public static final String SYSTEM_KEY = "systemSize"; + public static final String MISC_KEY = "otherSize"; + public static final String APP_SIZE_AGG_KEY = "appSize"; + public static final String APP_CACHE_AGG_KEY = "cacheSize"; + public static final String PACKAGE_NAMES_KEY = "packageNames"; + public static final String APP_SIZES_KEY = "appSizes"; + public static final String APP_CACHES_KEY = "cacheSizes"; + public static final String LAST_QUERY_TIMESTAMP_KEY = "queryTime"; + + private MeasurementResult mResult; + private long mDownloadsSize; + private long mSystemSize; + private List<PackageStats> mPackageStats; + + /** + * Constructs a DiskStatsFileLogger with calculated measurement results. + */ + public DiskStatsFileLogger(MeasurementResult result, MeasurementResult downloadsResult, + List<PackageStats> stats, long systemSize) { + mResult = result; + mDownloadsSize = downloadsResult.totalAccountedSize(); + mSystemSize = systemSize; + mPackageStats = stats; + } + + /** + * Dumps the storage collection output to a file. + * @param file File to write the output into. + * @throws FileNotFoundException + */ + public void dumpToFile(File file) throws FileNotFoundException { + PrintWriter pw = new PrintWriter(file); + JSONObject representation = getJsonRepresentation(); + if (representation != null) { + pw.println(representation); + } + pw.close(); + } + + private JSONObject getJsonRepresentation() { + JSONObject json = new JSONObject(); + try { + json.put(LAST_QUERY_TIMESTAMP_KEY, System.currentTimeMillis()); + json.put(PHOTOS_KEY, mResult.imagesSize); + json.put(VIDEOS_KEY, mResult.videosSize); + json.put(AUDIO_KEY, mResult.audioSize); + json.put(DOWNLOADS_KEY, mDownloadsSize); + json.put(SYSTEM_KEY, mSystemSize); + json.put(MISC_KEY, mResult.miscSize); + addAppsToJson(json); + } catch (JSONException e) { + Log.e(TAG, e.toString()); + return null; + } + + return json; + } + + private void addAppsToJson(JSONObject json) throws JSONException { + JSONArray names = new JSONArray(); + JSONArray appSizeList = new JSONArray(); + JSONArray cacheSizeList = new JSONArray(); + + long appSizeSum = 0L; + long cacheSizeSum = 0L; + boolean isExternal = Environment.isExternalStorageEmulated(); + for (Map.Entry<String, PackageStats> entry : mergePackagesAcrossUsers().entrySet()) { + PackageStats stat = entry.getValue(); + long appSize = stat.codeSize + stat.dataSize; + long cacheSize = stat.cacheSize; + if (isExternal) { + appSize += stat.externalCodeSize + stat.externalDataSize; + cacheSize += stat.externalCacheSize; + } + appSizeSum += appSize; + cacheSizeSum += cacheSize; + + names.put(stat.packageName); + appSizeList.put(appSize); + cacheSizeList.put(cacheSize); + } + json.put(PACKAGE_NAMES_KEY, names); + json.put(APP_SIZES_KEY, appSizeList); + json.put(APP_CACHES_KEY, cacheSizeList); + json.put(APP_SIZE_AGG_KEY, appSizeSum); + json.put(APP_CACHE_AGG_KEY, cacheSizeSum); + } + + /** + * A given package may exist for multiple users with distinct sizes. This function merges + * the duplicated packages together and sums up their sizes to get the actual totals for the + * package. + * @return A mapping of package name to merged package stats. + */ + private ArrayMap<String, PackageStats> mergePackagesAcrossUsers() { + ArrayMap<String, PackageStats> packageMap = new ArrayMap<>(); + for (PackageStats stat : mPackageStats) { + PackageStats existingStats = packageMap.get(stat.packageName); + if (existingStats != null) { + existingStats.cacheSize += stat.cacheSize; + existingStats.codeSize += stat.codeSize; + existingStats.dataSize += stat.dataSize; + existingStats.externalCacheSize += stat.externalCacheSize; + existingStats.externalCodeSize += stat.externalCodeSize; + existingStats.externalDataSize += stat.externalDataSize; + } else { + packageMap.put(stat.packageName, new PackageStats(stat)); + } + } + return packageMap; + } +}
\ No newline at end of file diff --git a/services/core/java/com/android/server/storage/DiskStatsLoggingService.java b/services/core/java/com/android/server/storage/DiskStatsLoggingService.java new file mode 100644 index 000000000000..4035adedafe1 --- /dev/null +++ b/services/core/java/com/android/server/storage/DiskStatsLoggingService.java @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2016 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/LICENSE2.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.storage; + +import android.app.job.JobInfo; +import android.app.job.JobParameters; +import android.app.job.JobScheduler; +import android.app.job.JobService; +import android.content.ComponentName; +import android.content.ContentResolver; +import android.content.Context; +import android.content.pm.PackageStats; +import android.os.AsyncTask; +import android.os.BatteryManager; +import android.os.Environment; +import android.os.Environment.UserEnvironment; +import android.os.UserHandle; +import android.os.storage.VolumeInfo; +import android.provider.Settings; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.storage.FileCollector.MeasurementResult; + +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.concurrent.TimeUnit; + +/** + * DiskStatsLoggingService is a JobService which collects storage categorization information and + * app size information on a roughly daily cadence. + */ +public class DiskStatsLoggingService extends JobService { + private static final String TAG = "DiskStatsLogService"; + public static final String DUMPSYS_CACHE_PATH = "/data/system/diskstats_cache.json"; + private static final int JOB_DISKSTATS_LOGGING = 0x4449534b; // DISK + private static ComponentName sDiskStatsLoggingService = new ComponentName( + "android", + DiskStatsLoggingService.class.getName()); + + @Override + public boolean onStartJob(JobParameters params) { + // We need to check the preconditions again because they may not be enforced for + // subsequent runs. + if (!isCharging(this) || !isDumpsysTaskEnabled(getContentResolver())) { + jobFinished(params, true); + return false; + } + + + VolumeInfo volume = getPackageManager().getPrimaryStorageCurrentVolume(); + // volume is null if the primary storage is not yet mounted. + if (volume == null) { + return false; + } + AppCollector collector = new AppCollector(this, volume); + + final int userId = UserHandle.myUserId(); + UserEnvironment environment = new UserEnvironment(userId); + LogRunnable task = new LogRunnable(); + task.setRootDirectory(environment.getExternalStorageDirectory()); + task.setDownloadsDirectory( + environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)); + task.setSystemSize(FileCollector.getSystemSize(this)); + task.setLogOutputFile(new File(DUMPSYS_CACHE_PATH)); + task.setAppCollector(collector); + task.setJobService(this, params); + AsyncTask.execute(task); + return true; + } + + @Override + public boolean onStopJob(JobParameters params) { + // TODO: Try to stop being handled. + return false; + } + + /** + * Schedules a DiskStats collection task. This task only runs on device idle while charging + * once every 24 hours. + * @param context Context to use to get a job scheduler. + */ + public static void schedule(Context context) { + JobScheduler js = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE); + + js.schedule(new JobInfo.Builder(JOB_DISKSTATS_LOGGING, sDiskStatsLoggingService) + .setRequiresDeviceIdle(true) + .setRequiresCharging(true) + .setPeriodic(TimeUnit.DAYS.toMillis(1)) + .build()); + } + + private static boolean isCharging(Context context) { + BatteryManager batteryManager = context.getSystemService(BatteryManager.class); + if (batteryManager != null) { + return batteryManager.isCharging(); + } + return false; + } + + @VisibleForTesting + static boolean isDumpsysTaskEnabled(ContentResolver resolver) { + // The default is to treat the task as enabled. + return Settings.Global.getInt(resolver, Settings.Global.ENABLE_DISKSTATS_LOGGING, 1) != 0; + } + + @VisibleForTesting + static class LogRunnable implements Runnable { + private static final long TIMEOUT_MILLIS = TimeUnit.MINUTES.toMillis(10); + + private JobService mJobService; + private JobParameters mParams; + private AppCollector mCollector; + private File mOutputFile; + private File mRootDirectory; + private File mDownloadsDirectory; + private long mSystemSize; + + public void setRootDirectory(File file) { + mRootDirectory = file; + } + + public void setDownloadsDirectory(File file) { + mDownloadsDirectory = file; + } + + public void setAppCollector(AppCollector collector) { + mCollector = collector; + } + + public void setLogOutputFile(File file) { + mOutputFile = file; + } + + public void setSystemSize(long size) { + mSystemSize = size; + } + + public void setJobService(JobService jobService, JobParameters params) { + mJobService = jobService; + mParams = params; + } + + public void run() { + FileCollector.MeasurementResult mainCategories = + FileCollector.getMeasurementResult(mRootDirectory); + FileCollector.MeasurementResult downloads = + FileCollector.getMeasurementResult(mDownloadsDirectory); + + boolean needsReschedule = true; + List<PackageStats> stats = mCollector.getPackageStats(TIMEOUT_MILLIS); + if (stats != null) { + needsReschedule = false; + logToFile(mainCategories, downloads, stats, mSystemSize); + } else { + Log.w("TAG", "Timed out while fetching package stats."); + } + + if (mJobService != null) { + mJobService.jobFinished(mParams, needsReschedule); + } + } + + private void logToFile(MeasurementResult mainCategories, MeasurementResult downloads, + List<PackageStats> stats, long systemSize) { + DiskStatsFileLogger logger = new DiskStatsFileLogger(mainCategories, downloads, stats, + systemSize); + try { + mOutputFile.createNewFile(); + logger.dumpToFile(mOutputFile); + } catch (IOException e) { + Log.e(TAG, "Exception while writing opportunistic disk file cache.", e); + } + } + } +}
\ No newline at end of file diff --git a/services/core/java/com/android/server/storage/FileCollector.java b/services/core/java/com/android/server/storage/FileCollector.java new file mode 100644 index 000000000000..90f9f1391679 --- /dev/null +++ b/services/core/java/com/android/server/storage/FileCollector.java @@ -0,0 +1,247 @@ +/* + * Copyright (C) 2016 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/LICENSE2.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.storage; + +import android.annotation.IntDef; +import android.content.Context; +import android.content.pm.PackageManager; +import android.os.storage.StorageManager; +import android.os.storage.VolumeInfo; +import android.util.ArrayMap; + +import java.io.File; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Map; + +/** + * FileCollector walks over a directory and categorizes storage usage by their type. + */ +public class FileCollector { + private static final int UNRECOGNIZED = -1; + private static final int IMAGES = 0; + private static final int VIDEO = 1; + private static final int AUDIO = 2; + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + UNRECOGNIZED, + IMAGES, + VIDEO, + AUDIO }) + private @interface FileTypes {} + + + private static final Map<String, Integer> EXTENSION_MAP = new ArrayMap<String, Integer>(); + static { + // Audio + EXTENSION_MAP.put("aac", AUDIO); + EXTENSION_MAP.put("amr", AUDIO); + EXTENSION_MAP.put("awb", AUDIO); + EXTENSION_MAP.put("snd", AUDIO); + EXTENSION_MAP.put("flac", AUDIO); + EXTENSION_MAP.put("mp3", AUDIO); + EXTENSION_MAP.put("mpga", AUDIO); + EXTENSION_MAP.put("mpega", AUDIO); + EXTENSION_MAP.put("mp2", AUDIO); + EXTENSION_MAP.put("m4a", AUDIO); + EXTENSION_MAP.put("aif", AUDIO); + EXTENSION_MAP.put("aiff", AUDIO); + EXTENSION_MAP.put("aifc", AUDIO); + EXTENSION_MAP.put("gsm", AUDIO); + EXTENSION_MAP.put("mka", AUDIO); + EXTENSION_MAP.put("m3u", AUDIO); + EXTENSION_MAP.put("wma", AUDIO); + EXTENSION_MAP.put("wax", AUDIO); + EXTENSION_MAP.put("ra", AUDIO); + EXTENSION_MAP.put("rm", AUDIO); + EXTENSION_MAP.put("ram", AUDIO); + EXTENSION_MAP.put("pls", AUDIO); + EXTENSION_MAP.put("sd2", AUDIO); + EXTENSION_MAP.put("wav", AUDIO); + EXTENSION_MAP.put("ogg", AUDIO); + EXTENSION_MAP.put("oga", AUDIO); + // Video + EXTENSION_MAP.put("3gpp", VIDEO); + EXTENSION_MAP.put("3gp", VIDEO); + EXTENSION_MAP.put("3gpp2", VIDEO); + EXTENSION_MAP.put("3g2", VIDEO); + EXTENSION_MAP.put("avi", VIDEO); + EXTENSION_MAP.put("dl", VIDEO); + EXTENSION_MAP.put("dif", VIDEO); + EXTENSION_MAP.put("dv", VIDEO); + EXTENSION_MAP.put("fli", VIDEO); + EXTENSION_MAP.put("m4v", VIDEO); + EXTENSION_MAP.put("ts", VIDEO); + EXTENSION_MAP.put("mpeg", VIDEO); + EXTENSION_MAP.put("mpg", VIDEO); + EXTENSION_MAP.put("mpe", VIDEO); + EXTENSION_MAP.put("mp4", VIDEO); + EXTENSION_MAP.put("vob", VIDEO); + EXTENSION_MAP.put("qt", VIDEO); + EXTENSION_MAP.put("mov", VIDEO); + EXTENSION_MAP.put("mxu", VIDEO); + EXTENSION_MAP.put("webm", VIDEO); + EXTENSION_MAP.put("lsf", VIDEO); + EXTENSION_MAP.put("lsx", VIDEO); + EXTENSION_MAP.put("mkv", VIDEO); + EXTENSION_MAP.put("mng", VIDEO); + EXTENSION_MAP.put("asf", VIDEO); + EXTENSION_MAP.put("asx", VIDEO); + EXTENSION_MAP.put("wm", VIDEO); + EXTENSION_MAP.put("wmv", VIDEO); + EXTENSION_MAP.put("wmx", VIDEO); + EXTENSION_MAP.put("wvx", VIDEO); + EXTENSION_MAP.put("movie", VIDEO); + EXTENSION_MAP.put("wrf", VIDEO); + // Images + EXTENSION_MAP.put("bmp", IMAGES); + EXTENSION_MAP.put("gif", IMAGES); + EXTENSION_MAP.put("jpg", IMAGES); + EXTENSION_MAP.put("jpeg", IMAGES); + EXTENSION_MAP.put("jpe", IMAGES); + EXTENSION_MAP.put("pcx", IMAGES); + EXTENSION_MAP.put("png", IMAGES); + EXTENSION_MAP.put("svg", IMAGES); + EXTENSION_MAP.put("svgz", IMAGES); + EXTENSION_MAP.put("tiff", IMAGES); + EXTENSION_MAP.put("tif", IMAGES); + EXTENSION_MAP.put("wbmp", IMAGES); + EXTENSION_MAP.put("webp", IMAGES); + EXTENSION_MAP.put("dng", IMAGES); + EXTENSION_MAP.put("cr2", IMAGES); + EXTENSION_MAP.put("ras", IMAGES); + EXTENSION_MAP.put("art", IMAGES); + EXTENSION_MAP.put("jng", IMAGES); + EXTENSION_MAP.put("nef", IMAGES); + EXTENSION_MAP.put("nrw", IMAGES); + EXTENSION_MAP.put("orf", IMAGES); + EXTENSION_MAP.put("rw2", IMAGES); + EXTENSION_MAP.put("pef", IMAGES); + EXTENSION_MAP.put("psd", IMAGES); + EXTENSION_MAP.put("pnm", IMAGES); + EXTENSION_MAP.put("pbm", IMAGES); + EXTENSION_MAP.put("pgm", IMAGES); + EXTENSION_MAP.put("ppm", IMAGES); + EXTENSION_MAP.put("srw", IMAGES); + EXTENSION_MAP.put("arw", IMAGES); + EXTENSION_MAP.put("rgb", IMAGES); + EXTENSION_MAP.put("xbm", IMAGES); + EXTENSION_MAP.put("xpm", IMAGES); + EXTENSION_MAP.put("xwd", IMAGES); + } + + /** + * Returns the file categorization measurement result. + * @param path Directory to collect and categorize storage in. + */ + public static MeasurementResult getMeasurementResult(File path) { + return collectFiles(StorageManager.maybeTranslateEmulatedPathToInternal(path), + new MeasurementResult()); + } + + /** + * Returns the size of a system for a given context. This is done by finding the difference + * between the shared data and the total primary storage size. + * @param context Context to use to get storage information. + */ + public static long getSystemSize(Context context) { + PackageManager pm = context.getPackageManager(); + VolumeInfo primaryVolume = pm.getPrimaryStorageCurrentVolume(); + + StorageManager sm = context.getSystemService(StorageManager.class); + VolumeInfo shared = sm.findEmulatedForPrivate(primaryVolume); + if (shared == null) { + return 0; + } + + final long sharedDataSize = shared.getPath().getTotalSpace(); + long systemSize = sm.getPrimaryStorageSize() - sharedDataSize; + + // This case is not exceptional -- we just fallback to the shared data volume in this case. + if (systemSize <= 0) { + return 0; + } + + return systemSize; + } + + private static MeasurementResult collectFiles(File file, MeasurementResult result) { + File[] files = file.listFiles(); + + if (files == null) { + return result; + } + + for (File f : files) { + if (f.isDirectory()) { + try { + collectFiles(f, result); + } catch (StackOverflowError e) { + return result; + } + } else { + handleFile(result, f); + } + } + + return result; + } + + private static void handleFile(MeasurementResult result, File f) { + long fileSize = f.length(); + int fileType = EXTENSION_MAP.getOrDefault(getExtensionForFile(f), UNRECOGNIZED); + switch (fileType) { + case AUDIO: + result.audioSize += fileSize; + break; + case VIDEO: + result.videosSize += fileSize; + break; + case IMAGES: + result.imagesSize += fileSize; + break; + default: + result.miscSize += fileSize; + } + } + + private static String getExtensionForFile(File file) { + String fileName = file.getName(); + int index = fileName.lastIndexOf('.'); + if (index == -1) { + return ""; + } + return fileName.substring(index + 1).toLowerCase(); + } + + /** + * MeasurementResult contains a storage categorization result. + */ + public static class MeasurementResult { + public long imagesSize; + public long videosSize; + public long miscSize; + public long audioSize; + + /** + * Sums up the storage taken by all of the categorizable sizes in the measurement. + */ + public long totalAccountedSize() { + return imagesSize + videosSize + miscSize + audioSize; + } + } +} diff --git a/services/core/java/com/android/server/twilight/TwilightService.java b/services/core/java/com/android/server/twilight/TwilightService.java index db7df25da3de..bb4d67e0309f 100644 --- a/services/core/java/com/android/server/twilight/TwilightService.java +++ b/services/core/java/com/android/server/twilight/TwilightService.java @@ -59,17 +59,17 @@ public final class TwilightService extends SystemService private final Handler mHandler; - private AlarmManager mAlarmManager; + protected AlarmManager mAlarmManager; private LocationManager mLocationManager; private boolean mBootCompleted; private boolean mHasListeners; private BroadcastReceiver mTimeChangedReceiver; - private Location mLastLocation; + protected Location mLastLocation; @GuardedBy("mListeners") - private TwilightState mLastTwilightState; + protected TwilightState mLastTwilightState; public TwilightService(Context context) { super(context); @@ -247,7 +247,11 @@ public final class TwilightService extends SystemService @Override public void onLocationChanged(Location location) { - if (location != null) { + // Location providers may erroneously return (0.0, 0.0) when they fail to determine the + // device's location. These location updates can be safely ignored since the chance of a + // user actually being at these coordinates is quite low. + if (location != null + && !(location.getLongitude() == 0.0 && location.getLatitude() == 0.0)) { Slog.d(TAG, "onLocationChanged:" + " provider=" + location.getProvider() + " accuracy=" + location.getAccuracy() diff --git a/services/core/java/com/android/server/vr/VrManagerInternal.java b/services/core/java/com/android/server/vr/VrManagerInternal.java index ad87a885348e..0fc1900166e2 100644 --- a/services/core/java/com/android/server/vr/VrManagerInternal.java +++ b/services/core/java/com/android/server/vr/VrManagerInternal.java @@ -56,25 +56,27 @@ public abstract class VrManagerInternal { int userId, @NonNull ComponentName calling); /** - * Set the current VR mode state immediately. + * Set whether the system has acquired a sleep token. * - * @param enabled {@code true} to enable VR mode. - * @param packageName The package name of the requested VrListenerService to bind. - * @param userId the user requesting the VrListenerService component. - * @param calling the component currently using VR mode, or null to leave unchanged. + * @param isAsleep is {@code true} if the device is asleep, or {@code false} otherwise. */ - public abstract void setVrModeImmediate(boolean enabled, @NonNull ComponentName packageName, - int userId, @NonNull ComponentName calling); + public abstract void onSleepStateChanged(boolean isAsleep); + /** + * Set whether the display used for VR output is on. + * + * @param isScreenOn is {@code true} if the display is on and can receive commands, + * or {@code false} otherwise. + */ + public abstract void onScreenStateChanged(boolean isScreenOn); - /** - * Return NO_ERROR if the given package is installed on the device and enabled as a - * VrListenerService for the given current user, or a negative error code indicating a failure. - * - * @param packageName the name of the package to check, or null to select the default package. - * @return NO_ERROR if the given package is installed and is enabled, or a negative error code - * given in {@link android.service.vr.VrModeException} on failure. - */ + /** + * Return NO_ERROR if the given package is installed on the device and enabled as a + * VrListenerService for the given current user, or a negative error code indicating a failure. + * + * @param packageName the name of the package to check, or null to select the default package. + * @return NO_ERROR if the given package is installed and is enabled, or a negative error code + * given in {@link android.service.vr.VrModeException} on failure. + */ public abstract int hasVrPackage(@NonNull ComponentName packageName, int userId); - } diff --git a/services/core/java/com/android/server/vr/VrManagerService.java b/services/core/java/com/android/server/vr/VrManagerService.java index 75c9e2184fb9..a9a6aa1000cf 100644 --- a/services/core/java/com/android/server/vr/VrManagerService.java +++ b/services/core/java/com/android/server/vr/VrManagerService.java @@ -101,6 +101,14 @@ public class VrManagerService extends SystemService implements EnabledComponentC private static final int PENDING_STATE_DELAY_MS = 300; private static final int EVENT_LOG_SIZE = 32; private static final int INVALID_APPOPS_MODE = -1; + /** Null set of sleep sleep flags. */ + private static final int FLAG_NONE = 0; + /** Flag set when the device is not sleeping. */ + private static final int FLAG_AWAKE = 1; + /** Flag set when the screen has been turned on. */ + private static final int FLAG_SCREEN_ON = 2; + /** Flag indicating that all system sleep flags have been set.*/ + private static final int FLAG_ALL = FLAG_AWAKE | FLAG_SCREEN_ON; private static native void initializeNative(); private static native void setVrModeNative(boolean enabled); @@ -110,6 +118,7 @@ public class VrManagerService extends SystemService implements EnabledComponentC private final IBinder mOverlayToken = new Binder(); // State protected by mLock + private boolean mVrModeAllowed; private boolean mVrModeEnabled; private EnabledComponentsObserver mComponentObserver; private ManagedApplicationService mCurrentVrService; @@ -125,10 +134,64 @@ public class VrManagerService extends SystemService implements EnabledComponentC private VrState mPendingState; private final ArrayDeque<VrState> mLoggingDeque = new ArrayDeque<>(EVENT_LOG_SIZE); private final NotificationAccessManager mNotifAccessManager = new NotificationAccessManager(); + /** Tracks the state of the screen and keyguard UI.*/ + private int mSystemSleepFlags = FLAG_NONE; private static final int MSG_VR_STATE_CHANGE = 0; private static final int MSG_PENDING_VR_STATE_CHANGE = 1; + /** + * Set whether VR mode may be enabled. + * <p/> + * If VR mode is not allowed to be enabled, calls to set VR mode will be cached. When VR mode + * is again allowed to be enabled, the most recent cached state will be applied. + * + * @param allowed {@code true} if calling any of the setVrMode methods may cause the device to + * enter VR mode. + */ + private void setVrModeAllowedLocked(boolean allowed) { + if (mVrModeAllowed != allowed) { + mVrModeAllowed = allowed; + Slog.i(TAG, "VR mode is " + ((allowed) ? "allowed" : "disallowed")); + if (mVrModeAllowed) { + consumeAndApplyPendingStateLocked(); + } else { + // Set pending state to current state. + mPendingState = (mVrModeEnabled && mCurrentVrService != null) + ? new VrState(mVrModeEnabled, mCurrentVrService.getComponent(), + mCurrentVrService.getUserId(), mCurrentVrModeComponent) + : null; + + // Unbind current VR service and do necessary callbacks. + updateCurrentVrServiceLocked(false, null, 0, null); + } + } + } + + private void setSleepState(boolean isAsleep) { + synchronized(mLock) { + + if (!isAsleep) { + mSystemSleepFlags |= FLAG_AWAKE; + } else { + mSystemSleepFlags &= ~FLAG_AWAKE; + } + + setVrModeAllowedLocked(mSystemSleepFlags == FLAG_ALL); + } + } + + private void setScreenOn(boolean isScreenOn) { + synchronized(mLock) { + if (isScreenOn) { + mSystemSleepFlags |= FLAG_SCREEN_ON; + } else { + mSystemSleepFlags &= ~FLAG_SCREEN_ON; + } + setVrModeAllowedLocked(mSystemSleepFlags == FLAG_ALL); + } + } + private final Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { @@ -148,7 +211,9 @@ public class VrManagerService extends SystemService implements EnabledComponentC } break; case MSG_PENDING_VR_STATE_CHANGE : { synchronized(mLock) { - VrManagerService.this.consumeAndApplyPendingStateLocked(); + if (mVrModeAllowed) { + VrManagerService.this.consumeAndApplyPendingStateLocked(); + } } } break; default : @@ -255,7 +320,6 @@ public class VrManagerService extends SystemService implements EnabledComponentC public void onEnabledComponentChanged() { synchronized (mLock) { int currentUser = ActivityManager.getCurrentUser(); - // Update listeners ArraySet<ComponentName> enabledListeners = mComponentObserver.getEnabled(currentUser); @@ -268,12 +332,12 @@ public class VrManagerService extends SystemService implements EnabledComponentC } mNotifAccessManager.update(enabledPackages); - if (mCurrentVrService == null) { - return; // No active services + if (!mVrModeAllowed) { + return; // Don't do anything, we shouldn't be in VR mode. } // If there is a pending state change, we'd better deal with that first - consumeAndApplyPendingStateLocked(); + consumeAndApplyPendingStateLocked(false); if (mCurrentVrService == null) { return; // No active services @@ -321,6 +385,7 @@ public class VrManagerService extends SystemService implements EnabledComponentC return; } pw.println("********* Dump of VrManagerService *********"); + pw.println("VR mode is currently: " + ((mVrModeAllowed) ? "allowed" : "disallowed")); pw.println("Previous state transitions:\n"); String tab = " "; dumpStateTransitions(pw); @@ -374,13 +439,17 @@ public class VrManagerService extends SystemService implements EnabledComponentC @Override public void setVrMode(boolean enabled, ComponentName packageName, int userId, ComponentName callingPackage) { - VrManagerService.this.setVrMode(enabled, packageName, userId, callingPackage, false); + VrManagerService.this.setVrMode(enabled, packageName, userId, callingPackage); } @Override - public void setVrModeImmediate(boolean enabled, ComponentName packageName, int userId, - ComponentName callingPackage) { - VrManagerService.this.setVrMode(enabled, packageName, userId, callingPackage, true); + public void onSleepStateChanged(boolean isAsleep) { + VrManagerService.this.setSleepState(isAsleep); + } + + @Override + public void onScreenStateChanged(boolean isScreenOn) { + VrManagerService.this.setScreenOn(isScreenOn); } @Override @@ -424,6 +493,10 @@ public class VrManagerService extends SystemService implements EnabledComponentC mComponentObserver.rebuildAll(); } + } else if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) { + synchronized (mLock) { + mVrModeAllowed = true; + } } } @@ -466,12 +539,16 @@ public class VrManagerService extends SystemService implements EnabledComponentC false, mOverlayToken, null, oldUserId); } + if (!mVrModeEnabled) { + return; + } + // Apply the restrictions for the current user based on vr state String[] exemptions = (exemptedPackage == null) ? new String[0] : new String[] { exemptedPackage }; appOpsManager.setUserRestrictionForUser(AppOpsManager.OP_SYSTEM_ALERT_WINDOW, - mVrModeEnabled, mOverlayToken, exemptions, newUserId); + true, mOverlayToken, exemptions, newUserId); } private void updateDependentAppOpsLocked(String newVrServicePackage, int newUserId, @@ -512,7 +589,8 @@ public class VrManagerService extends SystemService implements EnabledComponentC boolean validUserComponent = (mComponentObserver.isValid(component, userId) == EnabledComponentsObserver.NO_ERROR); - if (!mVrModeEnabled && !enabled) { + boolean goingIntoVrMode = validUserComponent && enabled; + if (!mVrModeEnabled && !goingIntoVrMode) { return validUserComponent; // Disabled -> Disabled transition does nothing. } @@ -520,29 +598,39 @@ public class VrManagerService extends SystemService implements EnabledComponentC ? mCurrentVrService.getComponent().getPackageName() : null; final int oldUserId = mCurrentVrModeUser; - // Always send mode change events. - changeVrModeLocked(enabled); + // Notify system services and VR HAL of mode change. + changeVrModeLocked(goingIntoVrMode); - if (!enabled || !validUserComponent) { - // Unbind whatever is running + boolean nothingChanged = false; + if (!goingIntoVrMode) { + // Not going into VR mode, unbind whatever is running if (mCurrentVrService != null) { - Slog.i(TAG, "Disconnecting " + mCurrentVrService.getComponent() + " for user " + - mCurrentVrService.getUserId()); + Slog.i(TAG, "Leaving VR mode, disconnecting " + + mCurrentVrService.getComponent() + " for user " + + mCurrentVrService.getUserId()); mCurrentVrService.disconnect(); mCurrentVrService = null; + } else { + nothingChanged = true; } } else { + // Going into VR mode if (mCurrentVrService != null) { - // Unbind any running service that doesn't match the component/user selection + // Unbind any running service that doesn't match the latest component/user + // selection. if (mCurrentVrService.disconnectIfNotMatching(component, userId)) { - Slog.i(TAG, "Disconnecting " + mCurrentVrService.getComponent() + - " for user " + mCurrentVrService.getUserId()); + Slog.i(TAG, "VR mode component changed to " + component + + ", disconnecting " + mCurrentVrService.getComponent() + + " for user " + mCurrentVrService.getUserId()); createAndConnectService(component, userId); sendUpdatedCaller = true; + } else { + nothingChanged = true; } - // The service with the correct component/user is bound + // The service with the correct component/user is already bound, do nothing. } else { - // Nothing was previously running, bind a new service + // Nothing was previously running, bind a new service for the latest + // component/user selection. createAndConnectService(component, userId); sendUpdatedCaller = true; } @@ -577,7 +665,10 @@ public class VrManagerService extends SystemService implements EnabledComponentC } }); } - logStateLocked(); + + if (!nothingChanged) { + logStateLocked(); + } return validUserComponent; } finally { @@ -663,16 +754,28 @@ public class VrManagerService extends SystemService implements EnabledComponentC private void grantCoarseLocationPermissionIfNeeded(String pkg, int userId) { // Don't clobber the user if permission set in current state explicitly if (!isPermissionUserUpdated(Manifest.permission.ACCESS_COARSE_LOCATION, pkg, userId)) { - mContext.getPackageManager().grantRuntimePermission(pkg, - Manifest.permission.ACCESS_COARSE_LOCATION, new UserHandle(userId)); + try { + mContext.getPackageManager().grantRuntimePermission(pkg, + Manifest.permission.ACCESS_COARSE_LOCATION, new UserHandle(userId)); + } catch (IllegalArgumentException e) { + // Package was removed during update. + Slog.w(TAG, "Could not grant coarse location permission, package " + pkg + + " was removed."); + } } } private void revokeCoarseLocationPermissionIfNeeded(String pkg, int userId) { // Don't clobber the user if permission set in current state explicitly if (!isPermissionUserUpdated(Manifest.permission.ACCESS_COARSE_LOCATION, pkg, userId)) { - mContext.getPackageManager().revokeRuntimePermission(pkg, - Manifest.permission.ACCESS_COARSE_LOCATION, new UserHandle(userId)); + try { + mContext.getPackageManager().revokeRuntimePermission(pkg, + Manifest.permission.ACCESS_COARSE_LOCATION, new UserHandle(userId)); + } catch (IllegalArgumentException e) { + // Package was removed during update. + Slog.w(TAG, "Could not revoke coarse location permission, package " + pkg + + " was removed."); + } } } @@ -766,12 +869,29 @@ public class VrManagerService extends SystemService implements EnabledComponentC sBinderChecker); } + /** + * Apply the pending VR state. If no state is pending, disconnect any currently bound + * VR listener service. + */ private void consumeAndApplyPendingStateLocked() { + consumeAndApplyPendingStateLocked(true); + } + + /** + * Apply the pending VR state. + * + * @param disconnectIfNoPendingState if {@code true}, then any currently bound VR listener + * service will be disconnected if no state is pending. If this is {@code false} then the + * nothing will be changed when there is no pending state. + */ + private void consumeAndApplyPendingStateLocked(boolean disconnectIfNoPendingState) { if (mPendingState != null) { updateCurrentVrServiceLocked(mPendingState.enabled, mPendingState.targetPackageName, mPendingState.userId, mPendingState.callingPackage); mPendingState = null; + } else if (disconnectIfNoPendingState) { + updateCurrentVrServiceLocked(false, null, 0, null); } } @@ -822,13 +942,20 @@ public class VrManagerService extends SystemService implements EnabledComponentC /* * Implementation of VrManagerInternal calls. These are callable from system services. */ - private void setVrMode(boolean enabled, @NonNull ComponentName targetPackageName, - int userId, @NonNull ComponentName callingPackage, boolean immediate) { + int userId, @NonNull ComponentName callingPackage) { synchronized (mLock) { + VrState pending = new VrState(enabled, targetPackageName, userId, callingPackage); + if (!mVrModeAllowed) { + // We're not allowed to be in VR mode. Make this state pending. This will be + // applied the next time we are allowed to enter VR mode unless it is superseded by + // another call. + mPendingState = pending; + return; + } - if (!enabled && mCurrentVrService != null && !immediate) { + if (!enabled && mCurrentVrService != null) { // If we're transitioning out of VR mode, delay briefly to avoid expensive HAL calls // and service bind/unbind in case we are immediately switching to another VR app. if (mPendingState == null) { @@ -836,7 +963,7 @@ public class VrManagerService extends SystemService implements EnabledComponentC PENDING_STATE_DELAY_MS); } - mPendingState = new VrState(enabled, targetPackageName, userId, callingPackage); + mPendingState = pending; return; } else { mHandler.removeMessages(MSG_PENDING_VR_STATE_CHANGE); diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index 2ea4379b1b4a..08b9dcc27270 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -1566,6 +1566,19 @@ public class WallpaperManagerService extends IWallpaperManager.Stub { throw new IllegalStateException("Wallpaper not yet initialized for user " + userId); } final long ident = Binder.clearCallingIdentity(); + + // Live wallpapers can't be specified for keyguard. If we're using a static + // system+lock image currently, migrate the system wallpaper to be a lock-only + // image as part of making a different live component active as the system + // wallpaper. + if (mImageWallpaper.equals(wallpaper.wallpaperComponent)) { + if (mLockWallpaperMap.get(userId) == null) { + // We're using the static imagery and there is no lock-specific image in place, + // therefore it's a shared system+lock image that we need to migrate. + migrateSystemToLockWallpaperLocked(userId); + } + } + try { wallpaper.imageWallpaperPending = false; if (bindWallpaperComponentLocked(name, false, true, wallpaper, null)) { diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java index cd46165b56b3..d8e0b0b84281 100644 --- a/services/core/java/com/android/server/wm/AppTransition.java +++ b/services/core/java/com/android/server/wm/AppTransition.java @@ -57,6 +57,7 @@ import android.os.Debug; import android.os.IBinder; import android.os.IRemoteCallback; import android.os.RemoteException; +import android.os.SystemProperties; import android.util.ArraySet; import android.util.Slog; import android.util.SparseArray; @@ -234,6 +235,8 @@ public class AppTransition implements Dump { private boolean mLastHadClipReveal; private boolean mProlongedAnimationsEnded; + private final boolean mGridLayoutRecentsEnabled; + AppTransition(Context context, WindowManagerService service) { mContext = context; mService = service; @@ -272,6 +275,7 @@ public class AppTransition implements Dump { }; mClipRevealTranslationY = (int) (CLIP_REVEAL_TRANSLATION_Y_DP * mContext.getResources().getDisplayMetrics().density); + mGridLayoutRecentsEnabled = SystemProperties.getBoolean("ro.recents.grid", false); } boolean isTransitionSet() { @@ -921,12 +925,12 @@ public class AppTransition implements Dump { float scaleW = appWidth / thumbWidth; getNextAppTransitionStartRect(taskId, mTmpRect); final float fromX; - final float fromY; + float fromY; final float toX; - final float toY; + float toY; final float pivotX; final float pivotY; - if (isTvUiMode(uiMode) || orientation == Configuration.ORIENTATION_PORTRAIT) { + if (shouldScaleDownThumbnailTransition(uiMode, orientation)) { fromX = mTmpRect.left; fromY = mTmpRect.top; @@ -936,6 +940,12 @@ public class AppTransition implements Dump { toY = appRect.height() / 2 * (1 - 1 / scaleW) + appRect.top; pivotX = mTmpRect.width() / 2; pivotY = appRect.height() / 2 / scaleW; + if (mGridLayoutRecentsEnabled) { + // In the grid layout, the header is displayed above the thumbnail instead of + // overlapping it. + fromY -= thumbHeightI; + toY -= thumbHeightI * scaleW; + } } else { pivotX = 0; pivotY = 0; @@ -984,7 +994,10 @@ public class AppTransition implements Dump { // This AnimationSet uses the Interpolators assigned above. AnimationSet set = new AnimationSet(false); set.addAnimation(scale); - set.addAnimation(alpha); + if (!mGridLayoutRecentsEnabled) { + // In the grid layout, the header should be shown for the whole animation. + set.addAnimation(alpha); + } set.addAnimation(translate); set.addAnimation(clipAnim); a = set; @@ -1003,7 +1016,10 @@ public class AppTransition implements Dump { // This AnimationSet uses the Interpolators assigned above. AnimationSet set = new AnimationSet(false); set.addAnimation(scale); - set.addAnimation(alpha); + if (!mGridLayoutRecentsEnabled) { + // In the grid layout, the header should be shown for the whole animation. + set.addAnimation(alpha); + } set.addAnimation(translate); a = set; @@ -1097,12 +1113,14 @@ public class AppTransition implements Dump { mTmpFromClipRect.inset(contentInsets); mNextAppTransitionInsets.set(contentInsets); - if (isTvUiMode(uiMode) || orientation == Configuration.ORIENTATION_PORTRAIT) { + if (shouldScaleDownThumbnailTransition(uiMode, orientation)) { // We scale the width and clip to the top/left square float scale = thumbWidth / (appWidth - contentInsets.left - contentInsets.right); - int unscaledThumbHeight = (int) (thumbHeight / scale); - mTmpFromClipRect.bottom = mTmpFromClipRect.top + unscaledThumbHeight; + if (!mGridLayoutRecentsEnabled) { + int unscaledThumbHeight = (int) (thumbHeight / scale); + mTmpFromClipRect.bottom = mTmpFromClipRect.top + unscaledThumbHeight; + } mNextAppTransitionInsets.set(contentInsets); @@ -1960,6 +1978,15 @@ public class AppTransition implements Dump { } /** + * @return whether the transition should show the thumbnail being scaled down. + */ + private boolean shouldScaleDownThumbnailTransition(int uiMode, int orientation) { + return isTvUiMode(uiMode) + || mGridLayoutRecentsEnabled + || orientation == Configuration.ORIENTATION_PORTRAIT; + } + + /** * @return whether the specified {@param uiMode} is the TV mode. */ private boolean isTvUiMode(int uiMode) { diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java index 6f0a43a20e8a..d62a861fdb57 100644 --- a/services/core/java/com/android/server/wm/DockedStackDividerController.java +++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java @@ -658,6 +658,14 @@ public class DockedStackDividerController implements DimLayerUser { mDelayedImeWin = imeWin; imeWin.mWinAnimator.startDelayingAnimationStart(); } + + // If we are already waiting for something to be drawn, clear out the old one so it + // still gets executed. + // TODO: Have a real system where we can wait on different windows to be drawn with + // different callbacks. + if (mService.mWaitingForDrawnCallback != null) { + mService.mWaitingForDrawnCallback.run(); + } mService.mWaitingForDrawnCallback = () -> { mAnimationStartDelayed = false; if (mDelayedImeWin != null) { diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index aed8d20b55b7..0d970591cb5c 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -5793,24 +5793,6 @@ public class WindowManagerService extends IWindowManager.Stub } } - // Called by window manager policy. Not exposed externally. - @Override - public boolean isInputMethodWindowVisible() { - synchronized (mWindowMap) { - return mInputMethodWindow != null && mInputMethodWindow.isVisibleLw(); - } - } - - // Called by window manager policy. Not exposed externally. - @Override - public void hideCurrentInputMethod() { - final InputMethodManagerInternal inputMethodManagerInternal = - LocalServices.getService(InputMethodManagerInternal.class); - if (inputMethodManagerInternal != null) { - inputMethodManagerInternal.hideCurrentInputMethod(); - } - } - // Called by window manager policy. Not exposed externally. @Override public void lockDeviceNow() { diff --git a/services/core/jni/com_android_server_SystemServer.cpp b/services/core/jni/com_android_server_SystemServer.cpp index c7d6b95083d2..e46490bb3fac 100644 --- a/services/core/jni/com_android_server_SystemServer.cpp +++ b/services/core/jni/com_android_server_SystemServer.cpp @@ -22,15 +22,22 @@ #include <cutils/properties.h> #include <utils/Log.h> #include <utils/misc.h> +#include <utils/AndroidThreads.h> namespace android { +static int start_sensor_service(void* /*unused*/) { + SensorService::instantiate(); + return 0; +} + static void android_server_SystemServer_startSensorService(JNIEnv* /* env */, jobject /* clazz */) { char propBuf[PROPERTY_VALUE_MAX]; property_get("system_init.startsensorservice", propBuf, "1"); if (strcmp(propBuf, "1") == 0) { - // Start the sensor service - SensorService::instantiate(); + // Start the sensor service in a new thread + createThreadEtc(start_sensor_service, nullptr, + "StartSensorThread", PRIORITY_FOREGROUND); } } diff --git a/services/core/jni/com_android_server_lights_LightsService.cpp b/services/core/jni/com_android_server_lights_LightsService.cpp index c8e3946970e9..bf91fe3fd0b4 100644 --- a/services/core/jni/com_android_server_lights_LightsService.cpp +++ b/services/core/jni/com_android_server_lights_LightsService.cpp @@ -128,12 +128,12 @@ static void setLight_native(JNIEnv* /* env */, jobject /* clazz */, jlong ptr, } } else { // Only set non-brightness settings when not in low-persistence mode - state.color = colorARGB; state.flashMode = flashMode; state.flashOnMS = onMS; state.flashOffMS = offMS; } + state.color = colorARGB; state.brightnessMode = brightnessMode; { diff --git a/services/core/proto/ipconnectivity.proto b/services/core/proto/ipconnectivity.proto index e0d7f0984eb0..cf372bc90c74 100644 --- a/services/core/proto/ipconnectivity.proto +++ b/services/core/proto/ipconnectivity.proto @@ -17,6 +17,22 @@ message NetworkId { optional int32 network_id = 1; }; +// Transport describes a physical technology used by a network. It is a subset +// of the TRANSPORT_* constants defined in android.net.NetworkCapabilities. +enum Transport { + UNKNOWN = 0; + BLUETOOTH = 1; + CELLULAR = 2; + ETHERNET = 3; + WIFI = 4; +}; + +// A pair of (key, value) integers for describing histogram-like statistics. +message Pair { + optional int32 key = 1; + optional int32 value = 2; +}; + // Logs changes in the system default network. Changes can be 1) acquiring a // default network with no previous default, 2) a switch of the system default // network to a new default network, 3) a loss of the system default network. @@ -49,10 +65,12 @@ message DefaultNetworkEvent { // This message is associated to android.net.metrics.IpReachabilityEvent. message IpReachabilityEvent { // The interface name (wlan, rmnet, lo, ...) on which the probe was sent. - optional string if_name = 1; + // Deprecated since version 2, replaced by transport field. + optional string if_name = 1 [deprecated = true]; // The event type code of the probe, represented by constants defined in // android.net.metrics.IpReachabilityEvent. + // NUD_FAILED_ORGANIC and PROVISIONING_LOST_ORGANIC recorded since version 1. optional int32 event_type = 2; }; @@ -92,6 +110,7 @@ message ValidationProbeEvent { // Logs DNS lookup latencies. Repeated fields must have the same length. // This message is associated to android.net.metrics.DnsEvent. +// Deprecated since version 2. message DNSLookupBatch { // The id of the network on which the DNS lookups took place. optional NetworkId network_id = 1; @@ -106,13 +125,62 @@ message DNSLookupBatch { repeated int32 latencies_ms = 4; }; +// Represents a collections of DNS lookup latencies and counters for a +// particular combination of DNS query type and return code. +// Since version 2. +message DNSLatencies { + // The type of the DNS lookups, as defined in android.net.metrics.DnsEvent. + // Acts as a key for a set of DNS query results. + // Possible values are: 0 for getaddrinfo, 1 for gethostbyname. + optional int32 type = 1; + + // The return value of the DNS resolver for the DNS lookups. + // Acts as a key for a set of DNS query results. + // Possible values are: 0 for success, or errno code for failures. + optional int32 return_code = 2; + + // The number of query operations recorded. + optional int32 query_count = 3; + + // The number of query operations returning A IPv4 records. + optional int32 a_count = 4; + + // The number of query operations returning AAAA IPv6 records. + optional int32 aaaa_count = 5; + + // The time it took for each DNS lookup to complete. The number of repeated + // values can be less than query_count in case of event rate-limiting. + repeated int32 latencies_ms = 6; +}; + +// Represents latency and errno statistics of the connect() system call. +// Since version 2. +message ConnectStatistics { + // The number of connect() operations recorded. + optional int32 connect_count = 1; + + // The number of connect() operations with IPv6 socket address. + optional int32 ipv6_addr_count = 2; + + // The time it took for each successful connect() operation to complete. + // The number of repeated values can be less than connect_count in case of + // event rate-limiting. + repeated int32 latencies_ms = 3; + + // Counts of all error values returned by failed connect() operations. + // The Pair key field is the errno code. The Pair value field is the count + // for that errno code. + repeated Pair errnos_counters = 4; +}; + // Represents a DHCP event on a single interface, which can be a DHCPClient // state transition or a response packet parsing error. // This message is associated to android.net.metrics.DhcpClientEvent and // android.net.metrics.DhcpErrorEvent. message DHCPEvent { // The interface name (wlan, rmnet, lo, ...) on which the event happened. - optional string if_name = 1; + // Deprecated since version 2, replaced by transport field. + optional string if_name = 1 [deprecated = true]; oneof value { // The name of a state in the DhcpClient state machine, represented by @@ -126,11 +194,12 @@ message DHCPEvent { // Lifetime duration in milliseconds of a DhcpClient state, or transition // time in milliseconds between specific pairs of DhcpClient's states. - // Only populated when state_transition is populated. + // Only populated since version 1, when state_transition is populated. optional int32 duration_ms = 4; } // Represents the generation of an Android Packet Filter program. +// Since version 1. message ApfProgramEvent { // Lifetime of the program in seconds. optional int64 lifetime = 1; @@ -154,6 +223,7 @@ message ApfProgramEvent { // Represents Router Advertisement listening statistics for an interface with // Android Packet Filter enabled. +// Since version 1. message ApfStatistics { // The time interval in milliseconds these stastistics cover. optional int64 duration_ms = 1; @@ -183,6 +253,7 @@ message ApfStatistics { // Represents the reception of a Router Advertisement packet for an interface // with Android Packet Filter enabled. +// Since version 1. message RaEvent { // All lifetime values are expressed in seconds. The default value for an // option lifetime that was not present in the RA option list is -1. @@ -213,7 +284,8 @@ message RaEvent { // This message is associated to android.net.metrics.IpManagerEvent. message IpProvisioningEvent { // The interface name (wlan, rmnet, lo, ...) on which the probe was sent. - optional string if_name = 1; + // Deprecated since version 2, replaced by transport field. + optional string if_name = 1 [deprecated = true]; // The code of the IP provisioning event, represented by constants defined in // android.net.metrics.IpManagerEvent. @@ -224,11 +296,15 @@ message IpProvisioningEvent { } // Represents one of the IP connectivity event defined in this file. -// Next tag: 12 +// Next tag: 15 message IpConnectivityEvent { // Time in ms when the event was recorded. optional int64 time_ms = 1; + // Physical transport of the network on which the event happened. + // Since version 2. + optional Transport transport = 12; + // Event type. oneof event { @@ -242,7 +318,14 @@ message IpConnectivityEvent { NetworkEvent network_event = 4; // A batch of DNS lookups. - DNSLookupBatch dns_lookup_batch = 5; + // Deprecated in the nyc-mr2 release since version 2, and replaced by dns_latencies. + DNSLookupBatch dns_lookup_batch = 5 [deprecated = true]; + + // DNS lookup latency statistics. + DNSLatencies dns_latencies = 13; + + // Connect latency and errno statistics. + ConnectStatistics connect_statistics = 14; // A DHCP client event or DHCP receive error. DHCPEvent dhcp_event = 6; @@ -271,4 +354,11 @@ message IpConnectivityLog { // The number of events that had to be dropped due to a full buffer. optional int32 dropped_events = 2; + + // The version number of the metrics events being collected. + // nyc-dev: not populated, implicitly 0. + // nyc-dr1: not populated, implicitly 1 (sailfish and marlin only). + // nyc-mr1: not populated, implicitly 1. + // nyc-mr2: 2. + optional int32 version = 3; }; diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index f679e7a158b7..6de92d7ddfca 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -17,6 +17,7 @@ package com.android.server.devicepolicy; import static android.Manifest.permission.MANAGE_CA_CERTIFICATES; +import static android.app.admin.DevicePolicyManager.ACTION_SHOW_DEVICE_MONITORING_DIALOG; import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_COMPLEX; import static android.app.admin.DevicePolicyManager.WIPE_EXTERNAL_STORAGE; import static android.app.admin.DevicePolicyManager.WIPE_RESET_PROTECTION_DATA; @@ -51,6 +52,7 @@ import android.app.admin.DeviceAdminReceiver; import android.app.admin.DevicePolicyManager; import android.app.admin.DevicePolicyManagerInternal; import android.app.admin.IDevicePolicyManager; +import android.app.admin.NetworkEvent; import android.app.admin.SecurityLog; import android.app.admin.SecurityLog.SecurityEvent; import android.app.admin.SystemUpdatePolicy; @@ -79,8 +81,10 @@ import android.graphics.Color; import android.media.AudioManager; import android.media.IAudioService; import android.net.ConnectivityManager; +import android.net.IIpConnectivityMetrics; import android.net.ProxyInfo; import android.net.Uri; +import android.net.metrics.IpConnectivityLog; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; import android.os.AsyncTask; @@ -218,6 +222,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { private static final int MONITORING_CERT_NOTIFICATION_ID = R.plurals.ssl_ca_cert_warning; private static final int PROFILE_WIPED_NOTIFICATION_ID = 1001; + private static final int NETWORK_LOGGING_NOTIFICATION_ID = 1002; private static final String ATTR_PERMISSION_PROVIDER = "permission-provider"; private static final String ATTR_SETUP_COMPLETE = "setup-complete"; @@ -355,6 +360,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { boolean mIsWatch; private final SecurityLogMonitor mSecurityLogMonitor; + private NetworkLogger mNetworkLogger; private final AtomicBoolean mRemoteBugreportServiceIsActive = new AtomicBoolean(); private final AtomicBoolean mRemoteBugreportSharingAccepted = new AtomicBoolean(); @@ -481,6 +487,19 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { final int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, getSendingUserId()); + /* + * Network logging would ideally be started in setDeviceOwnerSystemPropertyLocked(), + * however it's too early in the boot process to register with IIpConnectivityMetrics + * to listen for events. + */ + if (Intent.ACTION_USER_STARTED.equals(action) + && userHandle == mOwners.getDeviceOwnerUserId()) { + synchronized (DevicePolicyManagerService.this) { + if (isNetworkLoggingEnabledInternalLocked()) { + setNetworkLoggingActiveInternal(true); + } + } + } if (Intent.ACTION_BOOT_COMPLETED.equals(action) && userHandle == mOwners.getDeviceOwnerUserId() && getDeviceOwnerRemoteBugreportUri() != null) { @@ -552,6 +571,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { private static final String TAG_DISABLE_ACCOUNT_MANAGEMENT = "disable-account-management"; private static final String TAG_REQUIRE_AUTO_TIME = "require_auto_time"; private static final String TAG_FORCE_EPHEMERAL_USERS = "force_ephemeral_users"; + private static final String TAG_IS_NETWORK_LOGGING_ENABLED = "is_network_logging_enabled"; private static final String TAG_ACCOUNT_TYPE = "account-type"; private static final String TAG_PERMITTED_ACCESSIBILITY_SERVICES = "permitted-accessiblity-services"; @@ -590,6 +610,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { private static final String TAG_PARENT_ADMIN = "parent-admin"; private static final String TAG_ORGANIZATION_COLOR = "organization-color"; private static final String TAG_ORGANIZATION_NAME = "organization-name"; + private static final String ATTR_LAST_NETWORK_LOGGING_NOTIFICATION = "last-notification"; + private static final String ATTR_NUM_NETWORK_LOGGING_NOTIFICATIONS = "num-notifications"; final DeviceAdminInfo info; @@ -646,6 +668,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { boolean disableScreenCapture = false; // Can only be set by a device/profile owner. boolean requireAutoTime = false; // Can only be set by a device owner. boolean forceEphemeralUsers = false; // Can only be set by a device owner. + boolean isNetworkLoggingEnabled = false; // Can only be set by a device owner. + + // one notification after enabling + 3 more after reboots + static final int DEF_MAXIMUM_NETWORK_LOGGING_NOTIFICATIONS_SHOWN = 4; + int numNetworkLoggingNotifications = 0; + long lastNetworkLoggingNotificationTimeMs = 0; // Time in milliseconds since epoch ActiveAdmin parentAdmin; final boolean isParent; @@ -854,6 +882,15 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { out.attribute(null, ATTR_VALUE, Boolean.toString(forceEphemeralUsers)); out.endTag(null, TAG_FORCE_EPHEMERAL_USERS); } + if (isNetworkLoggingEnabled) { + out.startTag(null, TAG_IS_NETWORK_LOGGING_ENABLED); + out.attribute(null, ATTR_VALUE, Boolean.toString(isNetworkLoggingEnabled)); + out.attribute(null, ATTR_NUM_NETWORK_LOGGING_NOTIFICATIONS, + Integer.toString(numNetworkLoggingNotifications)); + out.attribute(null, ATTR_LAST_NETWORK_LOGGING_NOTIFICATION, + Long.toString(lastNetworkLoggingNotificationTimeMs)); + out.endTag(null, TAG_IS_NETWORK_LOGGING_ENABLED); + } if (disabledKeyguardFeatures != DEF_KEYGUARD_FEATURES_DISABLED) { out.startTag(null, TAG_DISABLE_KEYGUARD_FEATURES); out.attribute(null, ATTR_VALUE, Integer.toString(disabledKeyguardFeatures)); @@ -1040,6 +1077,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } else if (TAG_FORCE_EPHEMERAL_USERS.equals(tag)) { forceEphemeralUsers = Boolean.parseBoolean( parser.getAttributeValue(null, ATTR_VALUE)); + } else if (TAG_IS_NETWORK_LOGGING_ENABLED.equals(tag)) { + isNetworkLoggingEnabled = Boolean.parseBoolean( + parser.getAttributeValue(null, ATTR_VALUE)); + lastNetworkLoggingNotificationTimeMs = Long.parseLong( + parser.getAttributeValue(null, ATTR_LAST_NETWORK_LOGGING_NOTIFICATION)); + numNetworkLoggingNotifications = Integer.parseInt( + parser.getAttributeValue(null, ATTR_NUM_NETWORK_LOGGING_NOTIFICATIONS)); } else if (TAG_DISABLE_KEYGUARD_FEATURES.equals(tag)) { disabledKeyguardFeatures = Integer.parseInt( parser.getAttributeValue(null, ATTR_VALUE)); @@ -1280,6 +1324,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { pw.println(requireAutoTime); pw.print(prefix); pw.print("forceEphemeralUsers="); pw.println(forceEphemeralUsers); + pw.print(prefix); pw.print("isNetworkLoggingEnabled="); + pw.println(isNetworkLoggingEnabled); pw.print(prefix); pw.print("disabledKeyguardFeatures="); pw.println(disabledKeyguardFeatures); pw.print(prefix); pw.print("crossProfileWidgetProviders="); @@ -1406,6 +1452,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return mContext.getSystemService(NotificationManager.class); } + IIpConnectivityMetrics getIIpConnectivityMetrics() { + return (IIpConnectivityMetrics) IIpConnectivityMetrics.Stub.asInterface( + ServiceManager.getService(IpConnectivityLog.SERVICE_NAME)); + } + PowerManagerInternal getPowerManagerInternal() { return LocalServices.getService(PowerManagerInternal.class); } @@ -1418,6 +1469,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return (TrustManager) mContext.getSystemService(Context.TRUST_SERVICE); } + AlarmManager getAlarmManager() { + return (AlarmManager) mContext.getSystemService(AlarmManager.class); + } + IWindowManager getIWindowManager() { return IWindowManager.Stub .asInterface(ServiceManager.getService(Context.WINDOW_SERVICE)); @@ -1943,7 +1998,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { long token = mInjector.binderClearCallingIdentity(); try { int affectedUserHandle = parent ? getProfileParentId(userHandle) : userHandle; - AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); + AlarmManager am = mInjector.getAlarmManager(); PendingIntent pi = PendingIntent.getBroadcastAsUser(context, REQUEST_EXPIRE_PASSWORD, new Intent(ACTION_EXPIRED_PASSWORD_NOTIFICATION), PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT, @@ -5896,8 +5951,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { throw new IllegalArgumentException("Invalid component " + admin + " for device owner"); } + final boolean hasIncompatibleAccountsOrNonAdb = + hasIncompatibleAccountsOrNonAdbNoLock(userId, admin); synchronized (this) { - enforceCanSetDeviceOwnerLocked(admin, userId); + enforceCanSetDeviceOwnerLocked(admin, userId, hasIncompatibleAccountsOrNonAdb); if (getActiveAdminUncheckedLocked(admin, userId) == null || getUserData(userId).mRemovingAdmins.contains(admin)) { throw new IllegalArgumentException("Not active admin: " + admin); @@ -6088,8 +6145,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { throw new IllegalArgumentException("Component " + who + " not installed for userId:" + userHandle); } + final boolean hasIncompatibleAccountsOrNonAdb = + hasIncompatibleAccountsOrNonAdbNoLock(userHandle, who); synchronized (this) { - enforceCanSetProfileOwnerLocked(who, userHandle); + enforceCanSetProfileOwnerLocked(who, userHandle, hasIncompatibleAccountsOrNonAdb); if (getActiveAdminUncheckedLocked(who, userHandle) == null || getUserData(userHandle).mRemovingAdmins.contains(who)) { @@ -6410,9 +6469,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { * The profile owner can only be set before the user setup phase has completed, * except for: * - SYSTEM_UID - * - adb if there are no accounts. (But see {@link #hasIncompatibleAccountsLocked}) + * - adb unless hasIncompatibleAccountsOrNonAdb is true. */ - private void enforceCanSetProfileOwnerLocked(@Nullable ComponentName owner, int userHandle) { + private void enforceCanSetProfileOwnerLocked(@Nullable ComponentName owner, int userHandle, + boolean hasIncompatibleAccountsOrNonAdb) { UserInfo info = getUserInfo(userHandle); if (info == null) { // User doesn't exist. @@ -6433,7 +6493,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { int callingUid = mInjector.binderGetCallingUid(); if (callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID) { if ((mIsWatch || hasUserSetupCompleted(userHandle)) - && hasIncompatibleAccountsLocked(userHandle, owner)) { + && hasIncompatibleAccountsOrNonAdb) { throw new IllegalStateException("Not allowed to set the profile owner because " + "there are already some accounts on the profile"); } @@ -6450,14 +6510,16 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { * The Device owner can only be set by adb or an app with the MANAGE_PROFILE_AND_DEVICE_OWNERS * permission. */ - private void enforceCanSetDeviceOwnerLocked(@Nullable ComponentName owner, int userId) { + private void enforceCanSetDeviceOwnerLocked(@Nullable ComponentName owner, int userId, + boolean hasIncompatibleAccountsOrNonAdb) { int callingUid = mInjector.binderGetCallingUid(); boolean isAdb = callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID; if (!isAdb) { enforceCanManageProfileAndDeviceOwners(); } - final int code = checkSetDeviceOwnerPreConditionLocked(owner, userId, isAdb); + final int code = checkSetDeviceOwnerPreConditionLocked(owner, userId, isAdb, + hasIncompatibleAccountsOrNonAdb); switch (code) { case CODE_OK: return; @@ -6546,6 +6608,16 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } + private void enforceDeviceOwnerOrManageUsers() { + synchronized (this) { + if (getActiveAdminWithPolicyForUidLocked(null, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER, + mInjector.binderGetCallingUid()) != null) { + return; + } + } + enforceManageUsers(); + } + private void ensureCallerPackage(@Nullable String packageName) { if (packageName == null) { Preconditions.checkState(isCallerWithSystemUid(), @@ -8717,7 +8789,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { * except for adb command if no accounts or additional users are present on the device. */ private synchronized @DeviceOwnerPreConditionCode int checkSetDeviceOwnerPreConditionLocked( - @Nullable ComponentName owner, int deviceOwnerUserId, boolean isAdb) { + @Nullable ComponentName owner, int deviceOwnerUserId, boolean isAdb, + boolean hasIncompatibleAccountsOrNonAdb) { if (mOwners.hasDeviceOwner()) { return CODE_HAS_DEVICE_OWNER; } @@ -8737,7 +8810,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (mUserManager.getUserCount() > 1) { return CODE_NONSYSTEM_USER_EXISTS; } - if (hasIncompatibleAccountsLocked(UserHandle.USER_SYSTEM, owner)) { + if (hasIncompatibleAccountsOrNonAdb) { return CODE_ACCOUNTS_NOT_EMPTY; } } else { @@ -8765,7 +8838,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { private boolean isDeviceOwnerProvisioningAllowed(int deviceOwnerUserId) { synchronized (this) { return CODE_OK == checkSetDeviceOwnerPreConditionLocked( - /* owner unknown */ null, deviceOwnerUserId, /* isAdb */ false); + /* owner unknown */ null, deviceOwnerUserId, /* isAdb */ false, + /* hasIncompatibleAccountsOrNonAdb=*/ true); } } @@ -9099,12 +9173,33 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } private synchronized void disableDeviceOwnerManagedSingleUserFeaturesIfNeeded() { - if (!isDeviceOwnerManagedSingleUserDevice()) { + final boolean isSingleUserManagedDevice = isDeviceOwnerManagedSingleUserDevice(); + + // disable security logging if needed + if (!isSingleUserManagedDevice) { mInjector.securityLogSetLoggingEnabledProperty(false); - Slog.w(LOG_TAG, "Security logging turned off as it's no longer a single user device."); + Slog.w(LOG_TAG, "Security logging turned off as it's no longer a single user managed" + + " device."); + } + + // disable backup service if needed + // note: when clearing DO, the backup service shouldn't be disabled if it was enabled by + // the device owner + if (mOwners.hasDeviceOwner() && !isSingleUserManagedDevice) { + setBackupServiceEnabledInternal(false); + Slog.w(LOG_TAG, "Backup is off as it's a managed device that has more that one user."); + } + + // disable network logging if needed + if (!isSingleUserManagedDevice) { + setNetworkLoggingActiveInternal(false); + Slog.w(LOG_TAG, "Network logging turned off as it's no longer a single user managed" + + " device."); + // if there still is a device owner, disable logging policy, otherwise the admin + // has been nuked if (mOwners.hasDeviceOwner()) { - setBackupServiceEnabledInternal(false); - Slog.w(LOG_TAG, "Backup is off as it's a managed device that has more that one user."); + getDeviceOwnerAdminLocked().isNetworkLoggingEnabled = false; + saveSettingsLocked(mOwners.getDeviceOwnerUserId()); } } } @@ -9338,8 +9433,25 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { * - Otherwise, if there's any account that does not have ..._ALLOWED, or does have * ..._DISALLOWED, return true. * - Otherwise return false. + * + * If the caller is *not* ADB, it also returns true. The returned value shouldn't be used + * when the caller is not ADB. + * + * DO NOT CALL IT WITH THE DPMS LOCK HELD. */ - private boolean hasIncompatibleAccountsLocked(int userId, @Nullable ComponentName owner) { + private boolean hasIncompatibleAccountsOrNonAdbNoLock( + int userId, @Nullable ComponentName owner) { + final boolean isAdb = (mInjector.binderGetCallingUid() == Process.SHELL_UID) + || (mInjector.binderGetCallingUid() == Process.ROOT_UID); + if (!isAdb) { + return true; + } + + if (Thread.holdsLock(this)) { + Slog.wtf(LOG_TAG, "hasIncompatibleAccountsNoLock() called with the DPMS lock held."); + return true; + } + final long token = mInjector.binderClearCallingIdentity(); try { final AccountManager am = AccountManager.get(mContext); @@ -9347,22 +9459,30 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { if (accounts.length == 0) { return false; } + synchronized (this) { + if (owner == null || !isAdminTestOnlyLocked(owner, userId)) { + Log.w(LOG_TAG, + "Non test-only owner can't be installed with existing accounts."); + return true; + } + } + final String[] feature_allow = { DevicePolicyManager.ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_ALLOWED }; final String[] feature_disallow = { DevicePolicyManager.ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_DISALLOWED }; - // Even if we find incompatible accounts along the way, we still check all accounts - // for logging. boolean compatible = true; for (Account account : accounts) { if (hasAccountFeatures(am, account, feature_disallow)) { Log.e(LOG_TAG, account + " has " + feature_disallow[0]); compatible = false; + break; } if (!hasAccountFeatures(am, account, feature_allow)) { Log.e(LOG_TAG, account + " doesn't have " + feature_allow[0]); compatible = false; + break; } } if (compatible) { @@ -9370,28 +9490,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } else { Log.e(LOG_TAG, "Found incompatible accounts"); } - - // Then check if the owner is test-only. - String log; - if (owner == null) { - // Owner is unknown. Suppose it's not test-only - compatible = false; - log = "Only test-only device/profile owner can be installed with accounts"; - } else if (isAdminTestOnlyLocked(owner, userId)) { - if (compatible) { - log = "Installing test-only owner " + owner; - } else { - log = "Can't install test-only owner " + owner + " with incompatible accounts"; - } - } else { - compatible = false; - log = "Can't install non test-only owner " + owner + " with accounts"; - } - if (compatible) { - Log.w(LOG_TAG, log); - } else { - Log.e(LOG_TAG, log); - } return !compatible; } finally { mInjector.binderRestoreCallingIdentity(token); @@ -9448,4 +9546,128 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } } + + @Override + public synchronized void setNetworkLoggingEnabled(ComponentName admin, boolean enabled) { + if (!mHasFeature) { + return; + } + Preconditions.checkNotNull(admin); + ensureDeviceOwnerManagingSingleUser(admin); + + if (enabled == isNetworkLoggingEnabledInternalLocked()) { + // already in the requested state + return; + } + ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked(); + deviceOwner.isNetworkLoggingEnabled = enabled; + if (!enabled) { + deviceOwner.numNetworkLoggingNotifications = 0; + deviceOwner.lastNetworkLoggingNotificationTimeMs = 0; + } + saveSettingsLocked(mInjector.userHandleGetCallingUserId()); + + setNetworkLoggingActiveInternal(enabled); + } + + private synchronized void setNetworkLoggingActiveInternal(boolean active) { + final long callingIdentity = mInjector.binderClearCallingIdentity(); + try { + if (active) { + mNetworkLogger = new NetworkLogger(this, mInjector.getPackageManagerInternal()); + if (!mNetworkLogger.startNetworkLogging()) { + mNetworkLogger = null; + Slog.wtf(LOG_TAG, "Network logging could not be started due to the logging" + + " service not being available yet."); + } + sendNetworkLoggingNotificationLocked(); + } else { + if (mNetworkLogger != null && !mNetworkLogger.stopNetworkLogging()) { + mNetworkLogger = null; + Slog.wtf(LOG_TAG, "Network logging could not be stopped due to the logging" + + " service not being available yet."); + } + mNetworkLogger = null; + mInjector.getNotificationManager().cancel(NETWORK_LOGGING_NOTIFICATION_ID); + } + } finally { + mInjector.binderRestoreCallingIdentity(callingIdentity); + } + } + + @Override + public boolean isNetworkLoggingEnabled(ComponentName admin) { + if (!mHasFeature) { + return false; + } + synchronized (this) { + enforceDeviceOwnerOrManageUsers(); + return isNetworkLoggingEnabledInternalLocked(); + } + } + + private boolean isNetworkLoggingEnabledInternalLocked() { + ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked(); + return (deviceOwner != null) && deviceOwner.isNetworkLoggingEnabled; + } + + /* + * A maximum of 1200 events are returned, and the total marshalled size is in the order of + * 100kB, so returning a List instead of ParceledListSlice is acceptable. + * Ideally this would be done with ParceledList, however it only supports homogeneous types. + * + * @see NetworkLoggingHandler#MAX_EVENTS_PER_BATCH + */ + @Override + public synchronized List<NetworkEvent> retrieveNetworkLogs(ComponentName admin, + long batchToken) { + if (!mHasFeature) { + return null; + } + Preconditions.checkNotNull(admin); + ensureDeviceOwnerManagingSingleUser(admin); + + if (mNetworkLogger == null) { + return null; + } + return isNetworkLoggingEnabledInternalLocked() + ? mNetworkLogger.retrieveLogs(batchToken) + : null; + } + + private void sendNetworkLoggingNotificationLocked() { + final ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked(); + if (deviceOwner == null || !deviceOwner.isNetworkLoggingEnabled) { + return; + } + if (deviceOwner.numNetworkLoggingNotifications >= + ActiveAdmin.DEF_MAXIMUM_NETWORK_LOGGING_NOTIFICATIONS_SHOWN) { + return; + } + final long now = System.currentTimeMillis(); + if (now - deviceOwner.lastNetworkLoggingNotificationTimeMs < MS_PER_DAY) { + return; + } + deviceOwner.numNetworkLoggingNotifications++; + if (deviceOwner.numNetworkLoggingNotifications + >= ActiveAdmin.DEF_MAXIMUM_NETWORK_LOGGING_NOTIFICATIONS_SHOWN) { + deviceOwner.lastNetworkLoggingNotificationTimeMs = 0; + } else { + deviceOwner.lastNetworkLoggingNotificationTimeMs = now; + } + final Intent intent = new Intent(DevicePolicyManager.ACTION_SHOW_DEVICE_MONITORING_DIALOG); + intent.setPackage("com.android.systemui"); + final PendingIntent pendingIntent = PendingIntent.getBroadcastAsUser(mContext, 0, intent, 0, + UserHandle.CURRENT); + Notification notification = new Notification.Builder(mContext) + .setSmallIcon(R.drawable.ic_qs_network_logging) + .setContentTitle(mContext.getString(R.string.network_logging_notification_title)) + .setContentText(mContext.getString(R.string.network_logging_notification_text)) + .setTicker(mContext.getString(R.string.network_logging_notification_title)) + .setShowWhen(true) + .setContentIntent(pendingIntent) + .build(); + mInjector.getNotificationManager().notify(NETWORK_LOGGING_NOTIFICATION_ID, notification); + saveSettingsLocked(mOwners.getDeviceOwnerUserId()); + } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLogger.java b/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLogger.java new file mode 100644 index 000000000000..b82cb3cfbeaf --- /dev/null +++ b/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLogger.java @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2016 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.devicepolicy; + +import android.app.admin.ConnectEvent; +import android.app.admin.DnsEvent; +import android.app.admin.NetworkEvent; +import android.content.pm.PackageManagerInternal; +import android.net.IIpConnectivityMetrics; +import android.net.INetdEventCallback; +import android.os.Bundle; +import android.os.Message; +import android.os.Process; +import android.os.RemoteException; +import android.util.Log; +import android.util.Slog; + +import com.android.server.ServiceThread; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * A class for managing network logging. + * This class is not thread-safe, callers should synchronize access. + */ +final class NetworkLogger { + + private static final String TAG = NetworkLogger.class.getSimpleName(); + + private final DevicePolicyManagerService mDpm; + private final PackageManagerInternal mPm; + private final AtomicBoolean mIsLoggingEnabled = new AtomicBoolean(false); + + private IIpConnectivityMetrics mIpConnectivityMetrics; + private ServiceThread mHandlerThread; + private NetworkLoggingHandler mNetworkLoggingHandler; + + private final INetdEventCallback mNetdEventCallback = new INetdEventCallback.Stub() { + @Override + public void onDnsEvent(String hostname, String[] ipAddresses, int ipAddressesCount, + long timestamp, int uid) { + if (!mIsLoggingEnabled.get()) { + return; + } + DnsEvent dnsEvent = new DnsEvent(hostname, ipAddresses, ipAddressesCount, + mPm.getNameForUid(uid), timestamp); + sendNetworkEvent(dnsEvent); + } + + @Override + public void onConnectEvent(String ipAddr, int port, long timestamp, int uid) { + if (!mIsLoggingEnabled.get()) { + return; + } + ConnectEvent connectEvent = new ConnectEvent(ipAddr, port, mPm.getNameForUid(uid), + timestamp); + sendNetworkEvent(connectEvent); + } + + private void sendNetworkEvent(NetworkEvent event) { + Message msg = mNetworkLoggingHandler.obtainMessage( + NetworkLoggingHandler.LOG_NETWORK_EVENT_MSG); + Bundle bundle = new Bundle(); + bundle.putParcelable(NetworkLoggingHandler.NETWORK_EVENT_KEY, event); + msg.setData(bundle); + mNetworkLoggingHandler.sendMessage(msg); + } + }; + + NetworkLogger(DevicePolicyManagerService dpm, PackageManagerInternal pm) { + mDpm = dpm; + mPm = pm; + } + + private boolean checkIpConnectivityMetricsService() { + if (mIpConnectivityMetrics != null) { + return true; + } + final IIpConnectivityMetrics service = mDpm.mInjector.getIIpConnectivityMetrics(); + if (service == null) { + return false; + } + mIpConnectivityMetrics = service; + return true; + } + + boolean startNetworkLogging() { + Log.d(TAG, "Starting network logging."); + if (!checkIpConnectivityMetricsService()) { + // the IIpConnectivityMetrics service should have been present at this point + Slog.wtf(TAG, "Failed to register callback with IIpConnectivityMetrics."); + return false; + } + try { + if (mIpConnectivityMetrics.registerNetdEventCallback(mNetdEventCallback)) { + mHandlerThread = new ServiceThread(TAG, Process.THREAD_PRIORITY_BACKGROUND, + /* allowIo */ false); + mHandlerThread.start(); + mNetworkLoggingHandler = new NetworkLoggingHandler(mHandlerThread.getLooper(), + mDpm); + mNetworkLoggingHandler.scheduleBatchFinalization(); + mIsLoggingEnabled.set(true); + return true; + } else { + return false; + } + } catch (RemoteException re) { + Slog.wtf(TAG, "Failed to make remote calls to register the callback", re); + return false; + } + } + + boolean stopNetworkLogging() { + Log.d(TAG, "Stopping network logging"); + // stop the logging regardless of whether we fail to unregister listener + mIsLoggingEnabled.set(false); + try { + if (!checkIpConnectivityMetricsService()) { + // the IIpConnectivityMetrics service should have been present at this point + Slog.wtf(TAG, "Failed to unregister callback with IIpConnectivityMetrics."); + // logging is forcefully disabled even if unregistering fails + return true; + } + return mIpConnectivityMetrics.unregisterNetdEventCallback(); + } catch (RemoteException re) { + Slog.wtf(TAG, "Failed to make remote calls to unregister the callback", re); + } finally { + mHandlerThread.quitSafely(); + return true; + } + } + + List<NetworkEvent> retrieveLogs(long batchToken) { + return mNetworkLoggingHandler.retrieveFullLogBatch(batchToken); + } +} diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java b/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java new file mode 100644 index 000000000000..a56a3709f02a --- /dev/null +++ b/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2016 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.devicepolicy; + +import android.app.AlarmManager; +import android.app.AlarmManager.OnAlarmListener; +import android.app.admin.DeviceAdminReceiver; +import android.app.admin.NetworkEvent; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.SystemClock; +import android.util.Log; + +import com.android.internal.annotations.GuardedBy; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +/** + * A Handler class for managing network logging on a background thread. + */ +final class NetworkLoggingHandler extends Handler { + + private static final String TAG = NetworkLoggingHandler.class.getSimpleName(); + + static final String NETWORK_EVENT_KEY = "network_event"; + + // If this value changes, update DevicePolicyManager#retrieveNetworkLogs() javadoc + private static final int MAX_EVENTS_PER_BATCH = 1200; + private static final long BATCH_FINALIZATION_TIMEOUT_MS = TimeUnit.MINUTES.toMillis(90); + private static final long BATCH_FINALIZATION_TIMEOUT_ALARM_INTERVAL_MS = + TimeUnit.MINUTES.toMillis(30); + + private static final String NETWORK_LOGGING_TIMEOUT_ALARM_TAG = "NetworkLogging.batchTimeout"; + + private final DevicePolicyManagerService mDpm; + private final AlarmManager mAlarmManager; + + private final OnAlarmListener mBatchTimeoutAlarmListener = new OnAlarmListener() { + @Override + public void onAlarm() { + Log.d(TAG, "Received a batch finalization timeout alarm, finalizing " + + mNetworkEvents.size() + " pending events."); + synchronized (NetworkLoggingHandler.this) { + finalizeBatchAndNotifyDeviceOwnerIfNotEmpty(); + } + } + }; + + static final int LOG_NETWORK_EVENT_MSG = 1; + + // threadsafe as it's Handler's thread confined + @GuardedBy("this") + private ArrayList<NetworkEvent> mNetworkEvents = new ArrayList<NetworkEvent>(); + + @GuardedBy("this") + private ArrayList<NetworkEvent> mFullBatch; + + // each full batch is represented by its token, which the DPC has to provide back to revieve it + @GuardedBy("this") + private long mCurrentFullBatchToken; + + NetworkLoggingHandler(Looper looper, DevicePolicyManagerService dpm) { + super(looper); + mDpm = dpm; + mAlarmManager = mDpm.mInjector.getAlarmManager(); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case LOG_NETWORK_EVENT_MSG: { + NetworkEvent networkEvent = msg.getData().getParcelable(NETWORK_EVENT_KEY); + if (networkEvent != null) { + mNetworkEvents.add(networkEvent); + if (mNetworkEvents.size() >= MAX_EVENTS_PER_BATCH) { + finalizeBatchAndNotifyDeviceOwnerIfNotEmpty(); + } + } + break; + } + default: { + Log.d(TAG, "NetworkLoggingHandler received an unknown of message."); + break; + } + } + } + + void scheduleBatchFinalization() { + final long when = SystemClock.elapsedRealtime() + BATCH_FINALIZATION_TIMEOUT_MS; + mAlarmManager.setWindow(AlarmManager.ELAPSED_REALTIME_WAKEUP, when, + BATCH_FINALIZATION_TIMEOUT_ALARM_INTERVAL_MS, NETWORK_LOGGING_TIMEOUT_ALARM_TAG, + mBatchTimeoutAlarmListener, this); + Log.d(TAG, "Scheduled a new batch finalization alarm " + BATCH_FINALIZATION_TIMEOUT_MS + + "ms from now."); + } + + private synchronized void finalizeBatchAndNotifyDeviceOwnerIfNotEmpty() { + if (mNetworkEvents.size() > 0) { + // finalize the batch and start a new one from scratch + mFullBatch = mNetworkEvents; + mCurrentFullBatchToken++; + mNetworkEvents = new ArrayList<NetworkEvent>(); + // notify DO that there's a new non-empty batch waiting + Bundle extras = new Bundle(); + extras.putLong(DeviceAdminReceiver.EXTRA_NETWORK_LOGS_TOKEN, mCurrentFullBatchToken); + extras.putInt(DeviceAdminReceiver.EXTRA_NETWORK_LOGS_COUNT, mFullBatch.size()); + Log.d(TAG, "Sending network logging batch broadcast to device owner, batchToken: " + + mCurrentFullBatchToken); + mDpm.sendDeviceOwnerCommand(DeviceAdminReceiver.ACTION_NETWORK_LOGS_AVAILABLE, extras); + } else { + // don't notify the DO, since there are no events; DPC can still retrieve + // the last full batch + Log.d(TAG, "Was about to finalize the batch, but there were no events to send to" + + " the DPC, the batchToken of last available batch: " + + mCurrentFullBatchToken); + } + // regardless of whether the batch was non-empty schedule a new finalization after timeout + scheduleBatchFinalization(); + } + + synchronized List<NetworkEvent> retrieveFullLogBatch(long batchToken) { + if (batchToken != mCurrentFullBatchToken) { + return null; + } + return mFullBatch; + } +} + diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 76b73d5130d1..2b9ce2c622c5 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -206,6 +206,7 @@ public final class SystemServer { private boolean mOnlyCore; private boolean mFirstBoot; + private final boolean mRuntimeRestart; /** * Start the sensor service. @@ -222,6 +223,8 @@ public final class SystemServer { public SystemServer() { // Check for factory test mode. mFactoryTestMode = FactoryTest.getMode(); + // Remember if it's runtime restart(when sys.boot_completed is already set) or reboot + mRuntimeRestart = "1".equals(SystemProperties.get("sys.boot_completed")); } private void run() { @@ -321,6 +324,7 @@ public final class SystemServer { // Create the system service manager. mSystemServiceManager = new SystemServiceManager(mSystemContext); + mSystemServiceManager.setRuntimeRestarted(mRuntimeRestart); LocalServices.addService(SystemServiceManager.class, mSystemServiceManager); } finally { Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER); diff --git a/services/net/java/android/net/apf/ApfFilter.java b/services/net/java/android/net/apf/ApfFilter.java index 4c7545240c23..0b47079c0d21 100644 --- a/services/net/java/android/net/apf/ApfFilter.java +++ b/services/net/java/android/net/apf/ApfFilter.java @@ -194,8 +194,10 @@ public class ApfFilter { { (byte) 0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 }; private static final int ICMP6_TYPE_OFFSET = ETH_HEADER_LEN + IPV6_HEADER_LEN; - private static final int ICMP6_NEIGHBOR_ANNOUNCEMENT = 136; + private static final int ICMP6_ROUTER_SOLICITATION = 133; private static final int ICMP6_ROUTER_ADVERTISEMENT = 134; + private static final int ICMP6_NEIGHBOR_SOLICITATION = 135; + private static final int ICMP6_NEIGHBOR_ANNOUNCEMENT = 136; // NOTE: this must be added to the IPv4 header length in IPV4_HEADER_SIZE_MEMORY_SLOT private static final int UDP_DESTINATION_PORT_OFFSET = ETH_HEADER_LEN + 2; @@ -795,6 +797,8 @@ public class ApfFilter { // if it's multicast and we're dropping multicast: // drop // pass + // if it's ICMPv6 RS to any: + // drop // if it's ICMPv6 NA to ff02::1: // drop @@ -819,10 +823,12 @@ public class ApfFilter { // Add unsolicited multicast neighbor announcements filter String skipUnsolicitedMulticastNALabel = "skipUnsolicitedMulticastNA"; - // If not neighbor announcements, skip unsolicited multicast NA filter gen.addLoad8(Register.R0, ICMP6_TYPE_OFFSET); + // Drop all router solicitations (b/32833400) + gen.addJumpIfR0Equals(ICMP6_ROUTER_SOLICITATION, gen.DROP_LABEL); + // If not neighbor announcements, skip filter. gen.addJumpIfR0NotEquals(ICMP6_NEIGHBOR_ANNOUNCEMENT, skipUnsolicitedMulticastNALabel); - // If to ff02::1, drop + // If to ff02::1, drop. // TODO: Drop only if they don't contain the address of on-link neighbours. gen.addLoadImmediate(Register.R0, IPV6_DEST_ADDR_OFFSET); gen.addJumpIfBytesNotEqual(Register.R0, IPV6_ALL_NODES_ADDRESS, @@ -842,6 +848,7 @@ public class ApfFilter { * <li>Pass all non-ICMPv6 IPv6 packets, * <li>Pass all non-IPv4 and non-IPv6 packets, * <li>Drop IPv6 ICMPv6 NAs to ff02::1. + * <li>Drop IPv6 ICMPv6 RSs. * <li>Let execution continue off the end of the program for IPv6 ICMPv6 packets. This allows * insertion of RA filters here, or if there aren't any, just passes the packets. * </ul> diff --git a/services/net/java/android/net/dhcp/DhcpPacket.java b/services/net/java/android/net/dhcp/DhcpPacket.java index ef4bc024c153..060ded6b7b1b 100644 --- a/services/net/java/android/net/dhcp/DhcpPacket.java +++ b/services/net/java/android/net/dhcp/DhcpPacket.java @@ -25,8 +25,10 @@ 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. + * + * @hide */ -abstract class DhcpPacket { +public abstract class DhcpPacket { protected static final String TAG = "DhcpPacket"; // dhcpcd has a minimum lease of 20 seconds, but DhcpStateMachine would refuse to wake up the diff --git a/services/net/java/android/net/ip/ConnectivityPacketTracker.java b/services/net/java/android/net/ip/ConnectivityPacketTracker.java new file mode 100644 index 000000000000..884a8a754266 --- /dev/null +++ b/services/net/java/android/net/ip/ConnectivityPacketTracker.java @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2016 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.ip; + +import static android.system.OsConstants.*; + +import android.net.NetworkUtils; +import android.net.util.BlockingSocketReader; +import android.net.util.ConnectivityPacketSummary; +import android.os.Handler; +import android.system.ErrnoException; +import android.system.Os; +import android.system.PacketSocketAddress; +import android.util.Log; +import android.util.LocalLog; + +import libcore.io.IoBridge; +import libcore.util.HexEncoding; + +import java.io.FileDescriptor; +import java.io.InterruptedIOException; +import java.io.IOException; +import java.net.NetworkInterface; +import java.net.SocketException; + + +/** + * Critical connectivity packet tracking daemon. + * + * Tracks ARP, DHCPv4, and IPv6 RS/RA/NS/NA packets. + * + * This class's constructor, start() and stop() methods must only be called + * from the same thread on which the passed in |log| is accessed. + * + * Log lines include a hexdump of the packet, which can be decoded via: + * + * echo -n H3XSTR1NG | sed -e 's/\([0-9A-F][0-9A-F]\)/\1 /g' -e 's/^/000000 /' + * | text2pcap - - + * | tcpdump -n -vv -e -r - + * + * @hide + */ +public class ConnectivityPacketTracker { + private static final String TAG = ConnectivityPacketTracker.class.getSimpleName(); + private static final boolean DBG = false; + private static final String MARK_START = "--- START ---"; + private static final String MARK_STOP = "--- STOP ---"; + + private final String mTag; + private final Handler mHandler; + private final LocalLog mLog; + private final BlockingSocketReader mPacketListener; + + public ConnectivityPacketTracker(NetworkInterface netif, LocalLog log) { + final String ifname; + final int ifindex; + final byte[] hwaddr; + final int mtu; + + try { + ifname = netif.getName(); + ifindex = netif.getIndex(); + hwaddr = netif.getHardwareAddress(); + mtu = netif.getMTU(); + } catch (NullPointerException|SocketException e) { + throw new IllegalArgumentException("bad network interface", e); + } + + mTag = TAG + "." + ifname; + mHandler = new Handler(); + mLog = log; + mPacketListener = new PacketListener(ifindex, hwaddr, mtu); + } + + public void start() { + mLog.log(MARK_START); + mPacketListener.start(); + } + + public void stop() { + mPacketListener.stop(); + mLog.log(MARK_STOP); + } + + private final class PacketListener extends BlockingSocketReader { + private final int mIfIndex; + private final byte mHwAddr[]; + + PacketListener(int ifindex, byte[] hwaddr, int mtu) { + super(mtu); + mIfIndex = ifindex; + mHwAddr = hwaddr; + } + + @Override + protected FileDescriptor createSocket() { + FileDescriptor s = null; + try { + // TODO: Evaluate switching to SOCK_DGRAM and changing the + // BlockingSocketReader's read() to recvfrom(), so that this + // might work on non-ethernet-like links (via SLL). + s = Os.socket(AF_PACKET, SOCK_RAW, 0); + NetworkUtils.attachControlPacketFilter(s, ARPHRD_ETHER); + Os.bind(s, new PacketSocketAddress((short) ETH_P_ALL, mIfIndex)); + } catch (ErrnoException | IOException e) { + logError("Failed to create packet tracking socket: ", e); + closeSocket(s); + return null; + } + return s; + } + + @Override + protected void handlePacket(byte[] recvbuf, int length) { + final String summary = ConnectivityPacketSummary.summarize( + mHwAddr, recvbuf, length); + if (summary == null) return; + + if (DBG) Log.d(mTag, summary); + addLogEntry(summary + + "\n[" + new String(HexEncoding.encode(recvbuf, 0, length)) + "]"); + } + + @Override + protected void logError(String msg, Exception e) { + Log.e(mTag, msg, e); + addLogEntry(msg + e); + } + + private void addLogEntry(String entry) { + mHandler.post(() -> mLog.log(entry)); + } + } +} diff --git a/services/net/java/android/net/ip/IpManager.java b/services/net/java/android/net/ip/IpManager.java index 01d93048bfe2..abdf6831b06c 100644 --- a/services/net/java/android/net/ip/IpManager.java +++ b/services/net/java/android/net/ip/IpManager.java @@ -374,6 +374,7 @@ public class IpManager extends StateMachine { private static final int EVENT_DHCPACTION_TIMEOUT = 10; private static final int MAX_LOG_RECORDS = 500; + private static final int MAX_PACKET_RECORDS = 100; private static final boolean NO_CALLBACKS = false; private static final boolean SEND_CALLBACKS = true; @@ -399,6 +400,7 @@ public class IpManager extends StateMachine { private final WakeupMessage mDhcpActionTimeoutAlarm; private final AvoidBadWifiTracker mAvoidBadWifiTracker; private final LocalLog mLocalLog; + private final LocalLog mConnectivityPacketLog; private final MessageHandlingLogger mMsgStateLogger; private final IpConnectivityLog mMetricsLog = new IpConnectivityLog(); @@ -438,6 +440,10 @@ public class IpManager extends StateMachine { mCallback = new LoggingCallbackWrapper(callback); mNwService = nwService; + mLocalLog = new LocalLog(MAX_LOG_RECORDS); + mConnectivityPacketLog = new LocalLog(MAX_PACKET_RECORDS); + mMsgStateLogger = new MessageHandlingLogger(); + mNetlinkTracker = new NetlinkTracker( mInterfaceName, new NetlinkTracker.Callback() { @@ -451,48 +457,79 @@ public class IpManager extends StateMachine { super.interfaceAdded(iface); if (mClatInterfaceName.equals(iface)) { mCallback.setNeighborDiscoveryOffload(false); + } else if (!mInterfaceName.equals(iface)) { + return; } + + final String msg = "interfaceAdded(" + iface +")"; + logMsg(msg); } @Override public void interfaceRemoved(String iface) { super.interfaceRemoved(iface); + // TODO: Also observe mInterfaceName going down and take some + // kind of appropriate action. if (mClatInterfaceName.equals(iface)) { // TODO: consider sending a message to the IpManager main // StateMachine thread, in case "NDO enabled" state becomes // tied to more things that 464xlat operation. mCallback.setNeighborDiscoveryOffload(true); + } else if (!mInterfaceName.equals(iface)) { + return; } + + final String msg = "interfaceRemoved(" + iface +")"; + logMsg(msg); } - }; - try { - mNwService.registerObserver(mNetlinkTracker); - } catch (RemoteException e) { - Log.e(mTag, "Couldn't register NetlinkTracker: " + e.toString()); - } + private void logMsg(String msg) { + Log.d(mTag, msg); + getHandler().post(() -> { mLocalLog.log("OBSERVED " + msg); }); + } + }; - mAvoidBadWifiTracker = new AvoidBadWifiTracker(mContext, getHandler()); + mLinkProperties = new LinkProperties(); + mLinkProperties.setInterfaceName(mInterfaceName); - resetLinkProperties(); + mAvoidBadWifiTracker = new AvoidBadWifiTracker(mContext, getHandler(), + () -> { mLocalLog.log("OBSERVED AvoidBadWifi changed"); }); mProvisioningTimeoutAlarm = new WakeupMessage(mContext, getHandler(), mTag + ".EVENT_PROVISIONING_TIMEOUT", EVENT_PROVISIONING_TIMEOUT); mDhcpActionTimeoutAlarm = new WakeupMessage(mContext, getHandler(), mTag + ".EVENT_DHCPACTION_TIMEOUT", EVENT_DHCPACTION_TIMEOUT); - // Super simple StateMachine. + // Anything the StateMachine may access must have been instantiated + // before this point. + configureAndStartStateMachine(); + + // Anything that may send messages to the StateMachine must only be + // configured to do so after the StateMachine has started (above). + startStateMachineUpdaters(); + } + + private void configureAndStartStateMachine() { addState(mStoppedState); addState(mStartedState); addState(mRunningState, mStartedState); addState(mStoppingState); setInitialState(mStoppedState); - mLocalLog = new LocalLog(MAX_LOG_RECORDS); - mMsgStateLogger = new MessageHandlingLogger(); + super.start(); } + private void startStateMachineUpdaters() { + try { + mNwService.registerObserver(mNetlinkTracker); + } catch (RemoteException e) { + Log.e(mTag, "Couldn't register NetlinkTracker: " + e.toString()); + } + + mAvoidBadWifiTracker.start(); + } + @Override protected void onQuitting() { mCallback.onQuit(); @@ -501,6 +538,7 @@ public class IpManager extends StateMachine { // Shut down this IpManager instance altogether. public void shutdown() { stop(); + mAvoidBadWifiTracker.shutdown(); quit(); } @@ -574,7 +612,7 @@ public class IpManager extends StateMachine { } IndentingPrintWriter pw = new IndentingPrintWriter(writer, " "); - pw.println("APF dump:"); + pw.println(mTag + " APF dump:"); pw.increaseIndent(); // Thread-unsafe access to mApfFilter but just used for debugging. ApfFilter apfFilter = mApfFilter; @@ -590,6 +628,12 @@ public class IpManager extends StateMachine { pw.increaseIndent(); mLocalLog.readOnlyLocalLog().dump(fd, pw, args); pw.decreaseIndent(); + + pw.println(); + pw.println(mTag + " connectivity packet log:"); + pw.increaseIndent(); + mConnectivityPacketLog.readOnlyLocalLog().dump(fd, pw, args); + pw.decreaseIndent(); } @@ -631,6 +675,11 @@ public class IpManager extends StateMachine { return shouldLog; } + // TODO: Migrate all Log.e(...) to logError(...). + private void logError(String fmt, Object... args) { + mLocalLog.log("ERROR " + String.format(fmt, args)); + } + private void getNetworkInterface() { try { mNetworkInterface = NetworkInterface.getByName(mInterfaceName); @@ -816,13 +865,7 @@ public class IpManager extends StateMachine { for (RouteInfo route : netlinkLinkProperties.getRoutes()) { newLp.addRoute(route); } - for (InetAddress dns : netlinkLinkProperties.getDnsServers()) { - // Only add likely reachable DNS servers. - // TODO: investigate deleting this. - if (newLp.isReachable(dns)) { - newLp.addDnsServer(dns); - } - } + addAllReachableDnsServers(newLp, netlinkLinkProperties.getDnsServers()); // [3] Add in data from DHCPv4, if available. // @@ -832,13 +875,7 @@ public class IpManager extends StateMachine { for (RouteInfo route : mDhcpResults.getRoutes(mInterfaceName)) { newLp.addRoute(route); } - for (InetAddress dns : mDhcpResults.dnsServers) { - // Only add likely reachable DNS servers. - // TODO: investigate deleting this. - if (newLp.isReachable(dns)) { - newLp.addDnsServer(dns); - } - } + addAllReachableDnsServers(newLp, mDhcpResults.dnsServers); newLp.setDomains(mDhcpResults.domains); if (mDhcpResults.mtu != 0) { @@ -860,6 +897,18 @@ public class IpManager extends StateMachine { return newLp; } + private static void addAllReachableDnsServers( + LinkProperties lp, Iterable<InetAddress> dnses) { + // TODO: Investigate deleting this reachability check. We should be + // able to pass everything down to netd and let netd do evaluation + // and RFC6724-style sorting. + for (InetAddress dns : dnses) { + if (!dns.isAnyLocalAddress() && lp.isReachable(dns)) { + lp.addDnsServer(dns); + } + } + } + // Returns false if we have lost provisioning, true otherwise. private boolean handleLinkPropertiesUpdate(boolean sendCallbacks) { final LinkProperties newLp = assembleLinkProperties(); @@ -880,7 +929,7 @@ public class IpManager extends StateMachine { mNwService.setInterfaceConfig(mInterfaceName, ifcg); if (VDBG) Log.d(mTag, "IPv4 configuration succeeded"); } catch (IllegalStateException | RemoteException e) { - Log.e(mTag, "IPv4 configuration failed: ", e); + logError("IPv4 configuration failed: %s", e); return false; } return true; @@ -944,6 +993,12 @@ public class IpManager extends StateMachine { } } + private void doImmediateProvisioningFailure(int failureType) { + if (DBG) { Log.e(mTag, "onProvisioningFailure(): " + failureType); } + recordMetric(failureType); + mCallback.onProvisioningFailure(new LinkProperties(mLinkProperties)); + } + private boolean startIPv4() { // If we have a StaticIpConfiguration attempt to apply it and // handle the result accordingly. @@ -951,9 +1006,6 @@ public class IpManager extends StateMachine { if (setIPv4Address(mConfiguration.mStaticIpConfig.ipAddress)) { handleIPv4Success(new DhcpResults(mConfiguration.mStaticIpConfig)); } else { - if (VDBG) { Log.d(mTag, "onProvisioningFailure()"); } - recordMetric(IpManagerEvent.PROVISIONING_FAIL); - mCallback.onProvisioningFailure(new LinkProperties(mLinkProperties)); return false; } } else { @@ -972,16 +1024,40 @@ public class IpManager extends StateMachine { mNwService.setInterfaceIpv6PrivacyExtensions(mInterfaceName, true); mNwService.enableIpv6(mInterfaceName); } catch (RemoteException re) { - Log.e(mTag, "Unable to change interface settings: " + re); + logError("Unable to change interface settings: %s", re); return false; } catch (IllegalStateException ie) { - Log.e(mTag, "Unable to change interface settings: " + ie); + logError("Unable to change interface settings: %s", ie); return false; } return true; } + private boolean startIpReachabilityMonitor() { + try { + mIpReachabilityMonitor = new IpReachabilityMonitor( + mContext, + mInterfaceName, + new IpReachabilityMonitor.Callback() { + @Override + public void notifyLost(InetAddress ip, String logMsg) { + mCallback.onReachabilityLost(logMsg); + } + }, + mAvoidBadWifiTracker); + } catch (IllegalArgumentException iae) { + // Failed to start IpReachabilityMonitor. Log it and call + // onProvisioningFailure() immediately. + // + // See http://b/31038971. + logError("IpReachabilityMonitor failure: %s", iae); + mIpReachabilityMonitor = null; + } + + return (mIpReachabilityMonitor != null); + } + private void stopAllIP() { // We don't need to worry about routes, just addresses, because: // - disableIpv6() will clear autoconf IPv6 routes as well, and @@ -1153,6 +1229,7 @@ public class IpManager extends StateMachine { } class RunningState extends State { + private ConnectivityPacketTracker mPacketTracker; private boolean mDhcpActionInFlight; @Override @@ -1165,29 +1242,26 @@ public class IpManager extends StateMachine { mCallback.setFallbackMulticastFilter(mMulticastFiltering); } - if (mConfiguration.mEnableIPv6) { - // TODO: Consider transitionTo(mStoppingState) if this fails. - startIPv6(); + mPacketTracker = createPacketTracker(); + if (mPacketTracker != null) mPacketTracker.start(); + + if (mConfiguration.mEnableIPv6 && !startIPv6()) { + doImmediateProvisioningFailure(IpManagerEvent.ERROR_STARTING_IPV6); + transitionTo(mStoppingState); + return; } - if (mConfiguration.mEnableIPv4) { - if (!startIPv4()) { - transitionTo(mStoppingState); - return; - } + if (mConfiguration.mEnableIPv4 && !startIPv4()) { + doImmediateProvisioningFailure(IpManagerEvent.ERROR_STARTING_IPV4); + transitionTo(mStoppingState); + return; } - if (mConfiguration.mUsingIpReachabilityMonitor) { - mIpReachabilityMonitor = new IpReachabilityMonitor( - mContext, - mInterfaceName, - new IpReachabilityMonitor.Callback() { - @Override - public void notifyLost(InetAddress ip, String logMsg) { - mCallback.onReachabilityLost(logMsg); - } - }, - mAvoidBadWifiTracker); + if (mConfiguration.mUsingIpReachabilityMonitor && !startIpReachabilityMonitor()) { + doImmediateProvisioningFailure( + IpManagerEvent.ERROR_STARTING_IPREACHABILITYMONITOR); + transitionTo(mStoppingState); + return; } } @@ -1205,6 +1279,11 @@ public class IpManager extends StateMachine { mDhcpClient.doQuit(); } + if (mPacketTracker != null) { + mPacketTracker.stop(); + mPacketTracker = null; + } + if (mApfFilter != null) { mApfFilter.shutdown(); mApfFilter = null; @@ -1213,6 +1292,14 @@ public class IpManager extends StateMachine { resetLinkProperties(); } + private ConnectivityPacketTracker createPacketTracker() { + try { + return new ConnectivityPacketTracker(mNetworkInterface, mConnectivityPacketLog); + } catch (IllegalArgumentException e) { + return null; + } + } + private void ensureDhcpAction() { if (!mDhcpActionInFlight) { mCallback.onPreDhcpAction(); diff --git a/services/net/java/android/net/util/AvoidBadWifiTracker.java b/services/net/java/android/net/util/AvoidBadWifiTracker.java index c14e811e0584..2abaeb1ae35b 100644 --- a/services/net/java/android/net/util/AvoidBadWifiTracker.java +++ b/services/net/java/android/net/util/AvoidBadWifiTracker.java @@ -57,7 +57,11 @@ public class AvoidBadWifiTracker { private final Context mContext; private final Handler mHandler; private final Runnable mReevaluateRunnable; + private final Uri mUri; + private final ContentResolver mResolver; private final SettingObserver mSettingObserver; + private final BroadcastReceiver mBroadcastReceiver; + private volatile boolean mAvoidBadWifi = true; public AvoidBadWifiTracker(Context ctx, Handler handler) { @@ -68,19 +72,36 @@ public class AvoidBadWifiTracker { mContext = ctx; mHandler = handler; mReevaluateRunnable = () -> { if (update() && cb != null) cb.run(); }; + mUri = Settings.Global.getUriFor(NETWORK_AVOID_BAD_WIFI); + mResolver = mContext.getContentResolver(); mSettingObserver = new SettingObserver(); - - final IntentFilter intentFilter = new IntentFilter(); - intentFilter.addAction(Intent.ACTION_CONFIGURATION_CHANGED); - mContext.registerReceiverAsUser(new BroadcastReceiver() { + mBroadcastReceiver = new BroadcastReceiver() { + @Override public void onReceive(Context context, Intent intent) { reevaluate(); } - }, UserHandle.ALL, intentFilter, null, null); + }; update(); } + public void start() { + mResolver.registerContentObserver(mUri, false, mSettingObserver); + + final IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(Intent.ACTION_CONFIGURATION_CHANGED); + mContext.registerReceiverAsUser( + mBroadcastReceiver, UserHandle.ALL, intentFilter, null, null); + + reevaluate(); + } + + public void shutdown() { + mResolver.unregisterContentObserver(mSettingObserver); + + mContext.unregisterReceiver(mBroadcastReceiver); + } + public boolean currentValue() { return mAvoidBadWifi; } @@ -100,8 +121,7 @@ public class AvoidBadWifiTracker { } public String getSettingsValue() { - final ContentResolver resolver = mContext.getContentResolver(); - return Settings.Global.getString(resolver, NETWORK_AVOID_BAD_WIFI); + return Settings.Global.getString(mResolver, NETWORK_AVOID_BAD_WIFI); } @VisibleForTesting @@ -117,12 +137,8 @@ public class AvoidBadWifiTracker { } private class SettingObserver extends ContentObserver { - private final Uri mUri = Settings.Global.getUriFor(NETWORK_AVOID_BAD_WIFI); - public SettingObserver() { super(null); - final ContentResolver resolver = mContext.getContentResolver(); - resolver.registerContentObserver(mUri, false, this); } @Override diff --git a/services/net/java/android/net/util/BlockingSocketReader.java b/services/net/java/android/net/util/BlockingSocketReader.java new file mode 100644 index 000000000000..12fa1e57653a --- /dev/null +++ b/services/net/java/android/net/util/BlockingSocketReader.java @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2016 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.util; + +import android.annotation.Nullable; +import android.system.ErrnoException; +import android.system.Os; +import android.system.OsConstants; + +import libcore.io.IoBridge; + +import java.io.FileDescriptor; +import java.io.InterruptedIOException; +import java.io.IOException; + + +/** + * A thread that reads from a socket and passes the received packets to a + * subclass's handlePacket() method. The packet receive buffer is recycled + * on every read call, so subclasses should make any copies they would like + * inside their handlePacket() implementation. + * + * All public methods may be called from any thread. + * + * @hide + */ +public abstract class BlockingSocketReader { + public static final int DEFAULT_RECV_BUF_SIZE = 2 * 1024; + + private final byte[] mPacket; + private final Thread mThread; + private volatile FileDescriptor mSocket; + private volatile boolean mRunning; + private volatile long mPacketsReceived; + + // Make it slightly easier for subclasses to properly close a socket + // without having to know this incantation. + public static final void closeSocket(@Nullable FileDescriptor fd) { + try { + IoBridge.closeAndSignalBlockedThreads(fd); + } catch (IOException ignored) {} + } + + protected BlockingSocketReader() { + this(DEFAULT_RECV_BUF_SIZE); + } + + protected BlockingSocketReader(int recvbufsize) { + if (recvbufsize < DEFAULT_RECV_BUF_SIZE) { + recvbufsize = DEFAULT_RECV_BUF_SIZE; + } + mPacket = new byte[recvbufsize]; + mThread = new Thread(() -> { mainLoop(); }); + } + + public final boolean start() { + if (mSocket != null) return false; + + try { + mSocket = createSocket(); + } catch (Exception e) { + logError("Failed to create socket: ", e); + return false; + } + + if (mSocket == null) return false; + + mRunning = true; + mThread.start(); + return true; + } + + public final void stop() { + mRunning = false; + closeSocket(mSocket); + mSocket = null; + } + + public final boolean isRunning() { return mRunning; } + + public final long numPacketsReceived() { return mPacketsReceived; } + + /** + * Subclasses MUST create the listening socket here, including setting + * all desired socket options, interface or address/port binding, etc. + */ + protected abstract FileDescriptor createSocket(); + + /** + * Called by the main loop for every packet. Any desired copies of + * |recvbuf| should be made in here, and the underlying byte array is + * reused across all reads. + */ + protected void handlePacket(byte[] recvbuf, int length) {} + + /** + * Called by the main loop to log errors. In some cases |e| may be null. + */ + protected void logError(String msg, Exception e) {} + + /** + * Called by the main loop just prior to exiting. + */ + protected void onExit() {} + + private final void mainLoop() { + while (isRunning()) { + final int bytesRead; + + try { + // Blocking read. + // TODO: See if this can be converted to recvfrom. + bytesRead = Os.read(mSocket, mPacket, 0, mPacket.length); + if (bytesRead < 1) { + if (isRunning()) logError("Socket closed, exiting", null); + break; + } + mPacketsReceived++; + } catch (ErrnoException e) { + if (e.errno != OsConstants.EINTR) { + if (isRunning()) logError("read error: ", e); + break; + } + continue; + } catch (IOException ioe) { + if (isRunning()) logError("read error: ", ioe); + continue; + } + + try { + handlePacket(mPacket, bytesRead); + } catch (Exception e) { + logError("Unexpected exception: ", e); + break; + } + } + + stop(); + onExit(); + } +} diff --git a/services/net/java/android/net/util/ConnectivityPacketSummary.java b/services/net/java/android/net/util/ConnectivityPacketSummary.java new file mode 100644 index 000000000000..5b068c0b2468 --- /dev/null +++ b/services/net/java/android/net/util/ConnectivityPacketSummary.java @@ -0,0 +1,378 @@ +/* + * Copyright (C) 2016 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.util; + +import android.net.dhcp.DhcpPacket; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Arrays; +import java.util.StringJoiner; + +import static android.system.OsConstants.*; +import static android.net.util.NetworkConstants.*; + + +/** + * Critical connectivity packet summarizing class. + * + * Outputs short descriptions of ARP, DHCPv4, and IPv6 RS/RA/NS/NA packets. + * + * @hide + */ +public class ConnectivityPacketSummary { + private static final String TAG = ConnectivityPacketSummary.class.getSimpleName(); + + private final byte[] mHwAddr; + private final byte[] mBytes; + private final int mLength; + private final ByteBuffer mPacket; + private final String mSummary; + + public static String summarize(byte[] hwaddr, byte[] buffer) { + return summarize(hwaddr, buffer, buffer.length); + } + + // Methods called herein perform some but by no means all error checking. + // They may throw runtime exceptions on malformed packets. + public static String summarize(byte[] hwaddr, byte[] buffer, int length) { + if ((hwaddr == null) || (hwaddr.length != ETHER_ADDR_LEN)) return null; + if (buffer == null) return null; + length = Math.min(length, buffer.length); + return (new ConnectivityPacketSummary(hwaddr, buffer, length)).toString(); + } + + private ConnectivityPacketSummary(byte[] hwaddr, byte[] buffer, int length) { + mHwAddr = hwaddr; + mBytes = buffer; + mLength = Math.min(length, mBytes.length); + mPacket = ByteBuffer.wrap(mBytes, 0, mLength); + mPacket.order(ByteOrder.BIG_ENDIAN); + + final StringJoiner sj = new StringJoiner(" "); + // TODO: support other link-layers, or even no link-layer header. + parseEther(sj); + mSummary = sj.toString(); + } + + public String toString() { + return mSummary; + } + + private void parseEther(StringJoiner sj) { + if (mPacket.remaining() < ETHER_HEADER_LEN) { + sj.add("runt:").add(asString(mPacket.remaining())); + return; + } + + mPacket.position(ETHER_SRC_ADDR_OFFSET); + final ByteBuffer srcMac = (ByteBuffer) mPacket.slice().limit(ETHER_ADDR_LEN); + sj.add(ByteBuffer.wrap(mHwAddr).equals(srcMac) ? "TX" : "RX"); + sj.add(getMacAddressString(srcMac)); + + mPacket.position(ETHER_DST_ADDR_OFFSET); + final ByteBuffer dstMac = (ByteBuffer) mPacket.slice().limit(ETHER_ADDR_LEN); + sj.add(">").add(getMacAddressString(dstMac)); + + mPacket.position(ETHER_TYPE_OFFSET); + final int etherType = asUint(mPacket.getShort()); + switch (etherType) { + case ETHER_TYPE_ARP: + sj.add("arp"); + parseARP(sj); + break; + case ETHER_TYPE_IPV4: + sj.add("ipv4"); + parseIPv4(sj); + break; + case ETHER_TYPE_IPV6: + sj.add("ipv6"); + parseIPv6(sj); + break; + default: + // Unknown ether type. + sj.add("ethtype").add(asString(etherType)); + break; + } + } + + private void parseARP(StringJoiner sj) { + if (mPacket.remaining() < ARP_PAYLOAD_LEN) { + sj.add("runt:").add(asString(mPacket.remaining())); + return; + } + + if (asUint(mPacket.getShort()) != ARP_HWTYPE_ETHER || + asUint(mPacket.getShort()) != ETHER_TYPE_IPV4 || + asUint(mPacket.get()) != ETHER_ADDR_LEN || + asUint(mPacket.get()) != IPV4_ADDR_LEN) { + sj.add("unexpected header"); + return; + } + + final int opCode = asUint(mPacket.getShort()); + + final String senderHwAddr = getMacAddressString(mPacket); + final String senderIPv4 = getIPv4AddressString(mPacket); + getMacAddressString(mPacket); // target hardware address, unused + final String targetIPv4 = getIPv4AddressString(mPacket); + + if (opCode == ARP_REQUEST) { + sj.add("who-has").add(targetIPv4); + } else if (opCode == ARP_REPLY) { + sj.add("reply").add(senderIPv4).add(senderHwAddr); + } else { + sj.add("unknown opcode").add(asString(opCode)); + } + } + + private void parseIPv4(StringJoiner sj) { + if (!mPacket.hasRemaining()) { + sj.add("runt"); + return; + } + + final int startOfIpLayer = mPacket.position(); + final int ipv4HeaderLength = (mPacket.get(startOfIpLayer) & IPV4_IHL_MASK) * 4; + if (mPacket.remaining() < ipv4HeaderLength || + mPacket.remaining() < IPV4_HEADER_MIN_LEN) { + sj.add("runt:").add(asString(mPacket.remaining())); + return; + } + final int startOfTransportLayer = startOfIpLayer + ipv4HeaderLength; + + mPacket.position(startOfIpLayer + IPV4_FLAGS_OFFSET); + final int flagsAndFragment = asUint(mPacket.getShort()); + final boolean isFragment = (flagsAndFragment & IPV4_FRAGMENT_MASK) != 0; + + mPacket.position(startOfIpLayer + IPV4_PROTOCOL_OFFSET); + final int protocol = asUint(mPacket.get()); + + mPacket.position(startOfIpLayer + IPV4_SRC_ADDR_OFFSET); + final String srcAddr = getIPv4AddressString(mPacket); + + mPacket.position(startOfIpLayer + IPV4_DST_ADDR_OFFSET); + final String dstAddr = getIPv4AddressString(mPacket); + + sj.add(srcAddr).add(">").add(dstAddr); + + mPacket.position(startOfTransportLayer); + if (protocol == IPPROTO_UDP) { + sj.add("udp"); + if (isFragment) sj.add("fragment"); + else parseUDP(sj); + } else { + sj.add("proto").add(asString(protocol)); + if (isFragment) sj.add("fragment"); + } + } + + private void parseIPv6(StringJoiner sj) { + if (mPacket.remaining() < IPV6_HEADER_LEN) { + sj.add("runt:").add(asString(mPacket.remaining())); + return; + } + + final int startOfIpLayer = mPacket.position(); + + mPacket.position(startOfIpLayer + IPV6_PROTOCOL_OFFSET); + final int protocol = asUint(mPacket.get()); + + mPacket.position(startOfIpLayer + IPV6_SRC_ADDR_OFFSET); + final String srcAddr = getIPv6AddressString(mPacket); + final String dstAddr = getIPv6AddressString(mPacket); + + sj.add(srcAddr).add(">").add(dstAddr); + + mPacket.position(startOfIpLayer + IPV6_HEADER_LEN); + if (protocol == IPPROTO_ICMPV6) { + sj.add("icmp6"); + parseICMPv6(sj); + } else { + sj.add("proto").add(asString(protocol)); + } + } + + private void parseICMPv6(StringJoiner sj) { + if (mPacket.remaining() < ICMPV6_HEADER_MIN_LEN) { + sj.add("runt:").add(asString(mPacket.remaining())); + return; + } + + final int icmp6Type = asUint(mPacket.get()); + final int icmp6Code = asUint(mPacket.get()); + mPacket.getShort(); // checksum, unused + + switch (icmp6Type) { + case ICMPV6_ROUTER_SOLICITATION: + sj.add("rs"); + parseICMPv6RouterSolicitation(sj); + break; + case ICMPV6_ROUTER_ADVERTISEMENT: + sj.add("ra"); + parseICMPv6RouterAdvertisement(sj); + break; + case ICMPV6_NEIGHBOR_SOLICITATION: + sj.add("ns"); + parseICMPv6NeighborMessage(sj); + break; + case ICMPV6_NEIGHBOR_ADVERTISEMENT: + sj.add("na"); + parseICMPv6NeighborMessage(sj); + break; + default: + sj.add("type").add(asString(icmp6Type)); + sj.add("code").add(asString(icmp6Code)); + break; + } + } + + private void parseICMPv6RouterSolicitation(StringJoiner sj) { + final int RESERVED = 4; + if (mPacket.remaining() < RESERVED) { + sj.add("runt:").add(asString(mPacket.remaining())); + return; + } + + mPacket.position(mPacket.position() + RESERVED); + parseICMPv6NeighborDiscoveryOptions(sj); + } + + private void parseICMPv6RouterAdvertisement(StringJoiner sj) { + final int FLAGS_AND_TIMERS = 3 * 4; + if (mPacket.remaining() < FLAGS_AND_TIMERS) { + sj.add("runt:").add(asString(mPacket.remaining())); + return; + } + + mPacket.position(mPacket.position() + FLAGS_AND_TIMERS); + parseICMPv6NeighborDiscoveryOptions(sj); + } + + private void parseICMPv6NeighborMessage(StringJoiner sj) { + final int RESERVED = 4; + final int minReq = RESERVED + IPV6_ADDR_LEN; + if (mPacket.remaining() < minReq) { + sj.add("runt:").add(asString(mPacket.remaining())); + return; + } + + mPacket.position(mPacket.position() + RESERVED); + sj.add(getIPv6AddressString(mPacket)); + parseICMPv6NeighborDiscoveryOptions(sj); + } + + private void parseICMPv6NeighborDiscoveryOptions(StringJoiner sj) { + // All ND options are TLV, where T is one byte and L is one byte equal + // to the length of T + L + V in units of 8 octets. + while (mPacket.remaining() >= ICMPV6_ND_OPTION_MIN_LENGTH) { + final int ndType = asUint(mPacket.get()); + final int ndLength = asUint(mPacket.get()); + final int ndBytes = ndLength * ICMPV6_ND_OPTION_LENGTH_SCALING_FACTOR - 2; + if (ndBytes < 0 || ndBytes > mPacket.remaining()) { + sj.add("<malformed>"); + break; + } + final int position = mPacket.position(); + + switch (ndType) { + case ICMPV6_ND_OPTION_SLLA: + sj.add("slla"); + sj.add(getMacAddressString(mPacket)); + break; + case ICMPV6_ND_OPTION_TLLA: + sj.add("tlla"); + sj.add(getMacAddressString(mPacket)); + break; + case ICMPV6_ND_OPTION_MTU: + sj.add("mtu"); + final short reserved = mPacket.getShort(); + sj.add(asString(mPacket.getInt())); + break; + default: + // Skip. + break; + } + + mPacket.position(position + ndBytes); + } + } + + private void parseUDP(StringJoiner sj) { + if (mPacket.remaining() < UDP_HEADER_LEN) { + sj.add("runt:").add(asString(mPacket.remaining())); + return; + } + + final int previous = mPacket.position(); + final int srcPort = asUint(mPacket.getShort()); + final int dstPort = asUint(mPacket.getShort()); + sj.add(asString(srcPort)).add(">").add(asString(dstPort)); + + mPacket.position(previous + UDP_HEADER_LEN); + if (srcPort == DHCP4_CLIENT_PORT || dstPort == DHCP4_CLIENT_PORT) { + sj.add("dhcp4"); + parseDHCPv4(sj); + } + } + + private void parseDHCPv4(StringJoiner sj) { + final DhcpPacket dhcpPacket; + try { + dhcpPacket = DhcpPacket.decodeFullPacket(mBytes, mLength, DhcpPacket.ENCAP_L2); + sj.add(dhcpPacket.toString()); + } catch (DhcpPacket.ParseException e) { + sj.add("parse error: " + e); + } + } + + private static String getIPv4AddressString(ByteBuffer ipv4) { + return getIpAddressString(ipv4, IPV4_ADDR_LEN); + } + + private static String getIPv6AddressString(ByteBuffer ipv6) { + return getIpAddressString(ipv6, IPV6_ADDR_LEN); + } + + private static String getIpAddressString(ByteBuffer ip, int byteLength) { + if (ip == null || ip.remaining() < byteLength) return "invalid"; + + byte[] bytes = new byte[byteLength]; + ip.get(bytes, 0, byteLength); + try { + InetAddress addr = InetAddress.getByAddress(bytes); + return addr.getHostAddress(); + } catch (UnknownHostException uhe) { + return "unknown"; + } + } + + private static String getMacAddressString(ByteBuffer mac) { + if (mac == null || mac.remaining() < ETHER_ADDR_LEN) return "invalid"; + + byte[] bytes = new byte[ETHER_ADDR_LEN]; + mac.get(bytes, 0, bytes.length); + Byte[] printableBytes = new Byte[bytes.length]; + int i = 0; + for (byte b : bytes) printableBytes[i++] = b; + + final String MAC48_FORMAT = "%02x:%02x:%02x:%02x:%02x:%02x"; + return String.format(MAC48_FORMAT, printableBytes); + } +} diff --git a/services/net/java/android/net/util/NetworkConstants.java b/services/net/java/android/net/util/NetworkConstants.java new file mode 100644 index 000000000000..362f7570c124 --- /dev/null +++ b/services/net/java/android/net/util/NetworkConstants.java @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2016 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.util; + +import java.nio.ByteBuffer; + + +/** + * Networking protocol constants. + * + * Includes: + * - constants that describe packet layout + * - various helper functions + * + * @hide + */ +public final class NetworkConstants { + private NetworkConstants() { throw new RuntimeException("no instance permitted"); } + + /** + * Ethernet constants. + * + * See also: + * - https://tools.ietf.org/html/rfc894 + * - https://tools.ietf.org/html/rfc7042 + * - http://www.iana.org/assignments/ethernet-numbers/ethernet-numbers.xhtml + * - http://www.iana.org/assignments/ieee-802-numbers/ieee-802-numbers.xhtml + */ + public static final int ETHER_DST_ADDR_OFFSET = 0; + public static final int ETHER_SRC_ADDR_OFFSET = 6; + public static final int ETHER_ADDR_LEN = 6; + + public static final int ETHER_TYPE_OFFSET = 12; + public static final int ETHER_TYPE_LENGTH = 2; + public static final int ETHER_TYPE_ARP = 0x0806; + public static final int ETHER_TYPE_IPV4 = 0x0800; + public static final int ETHER_TYPE_IPV6 = 0x86dd; + + public static final int ETHER_HEADER_LEN = 14; + + private static final byte FF = asByte(0xff); + public static final byte[] ETHER_ADDR_BROADCAST = { + FF, FF, FF, FF, FF, FF + }; + + /** + * ARP constants. + * + * See also: + * - https://tools.ietf.org/html/rfc826 + * - http://www.iana.org/assignments/arp-parameters/arp-parameters.xhtml + */ + public static final int ARP_PAYLOAD_LEN = 28; // For Ethernet+IPv4. + public static final int ARP_REQUEST = 1; + public static final int ARP_REPLY = 2; + public static final int ARP_HWTYPE_RESERVED_LO = 0; + public static final int ARP_HWTYPE_ETHER = 1; + public static final int ARP_HWTYPE_RESERVED_HI = 0xffff; + + /** + * IPv4 constants. + * + * See als: + * - https://tools.ietf.org/html/rfc791 + */ + public static final int IPV4_HEADER_MIN_LEN = 20; + public static final int IPV4_IHL_MASK = 0xf; + public static final int IPV4_FLAGS_OFFSET = 6; + public static final int IPV4_FRAGMENT_MASK = 0x1fff; + public static final int IPV4_PROTOCOL_OFFSET = 9; + public static final int IPV4_SRC_ADDR_OFFSET = 12; + public static final int IPV4_DST_ADDR_OFFSET = 16; + public static final int IPV4_ADDR_LEN = 4; + + /** + * IPv6 constants. + * + * See also: + * - https://tools.ietf.org/html/rfc2460 + */ + public static final int IPV6_HEADER_LEN = 40; + public static final int IPV6_PROTOCOL_OFFSET = 6; + public static final int IPV6_SRC_ADDR_OFFSET = 8; + public static final int IPV6_DST_ADDR_OFFSET = 24; + public static final int IPV6_ADDR_LEN = 16; + + /** + * ICMPv6 constants. + * + * See also: + * - https://tools.ietf.org/html/rfc4443 + * - https://tools.ietf.org/html/rfc4861 + */ + public static final int ICMPV6_HEADER_MIN_LEN = 4; + public static final int ICMPV6_ROUTER_SOLICITATION = 133; + public static final int ICMPV6_ROUTER_ADVERTISEMENT = 134; + public static final int ICMPV6_NEIGHBOR_SOLICITATION = 135; + public static final int ICMPV6_NEIGHBOR_ADVERTISEMENT = 136; + + public static final int ICMPV6_ND_OPTION_MIN_LENGTH = 8; + public static final int ICMPV6_ND_OPTION_LENGTH_SCALING_FACTOR = 8; + public static final int ICMPV6_ND_OPTION_SLLA = 1; + public static final int ICMPV6_ND_OPTION_TLLA = 2; + public static final int ICMPV6_ND_OPTION_MTU = 5; + + /** + * UDP constants. + * + * See also: + * - https://tools.ietf.org/html/rfc768 + */ + public static final int UDP_HEADER_LEN = 8; + + /** + * DHCP(v4) constants. + * + * See also: + * - https://tools.ietf.org/html/rfc2131 + */ + public static final int DHCP4_SERVER_PORT = 67; + public static final int DHCP4_CLIENT_PORT = 68; + + /** + * Utility functions. + */ + public static byte asByte(int i) { return (byte) i; } + + public static String asString(int i) { return Integer.toString(i); } + + public static int asUint(byte b) { return (b & 0xff); } + public static int asUint(short s) { return (s & 0xffff); } +} diff --git a/services/retaildemo/java/com/android/server/retaildemo/RetailDemoModeService.java b/services/retaildemo/java/com/android/server/retaildemo/RetailDemoModeService.java index 6197c42d0146..c6804cdf2a1a 100644 --- a/services/retaildemo/java/com/android/server/retaildemo/RetailDemoModeService.java +++ b/services/retaildemo/java/com/android/server/retaildemo/RetailDemoModeService.java @@ -26,7 +26,6 @@ import android.app.PendingIntent; import android.app.RetailDemoModeServiceInternal; import android.content.BroadcastReceiver; import android.content.ComponentName; -import android.content.ContentProvider; import android.content.ContentResolver; import android.content.Context; import android.content.DialogInterface; @@ -59,6 +58,7 @@ import android.os.UserManager; import android.provider.CallLog; import android.provider.MediaStore; import android.provider.Settings; +import android.text.TextUtils; import android.util.KeyValueListParser; import android.util.Slog; import com.android.internal.os.BackgroundThread; @@ -103,7 +103,8 @@ public class RetailDemoModeService extends SystemService { private static final String DEMO_SESSION_COUNT = "retail_demo_session_count"; private static final String DEMO_SESSION_DURATION = "retail_demo_session_duration"; - boolean mDeviceInDemoMode = false; + boolean mDeviceInDemoMode; + boolean mIsCarrierDemoMode; int mCurrentUserId = UserHandle.USER_SYSTEM; long mUserInactivityTimeout; long mWarningDialogTimeout; @@ -138,7 +139,8 @@ public class RetailDemoModeService extends SystemService { if (!mDeviceInDemoMode) { return; } - switch (intent.getAction()) { + final String action = intent.getAction(); + switch (action) { case Intent.ACTION_SCREEN_OFF: mHandler.removeMessages(MSG_TURN_SCREEN_ON); mHandler.sendEmptyMessageDelayed(MSG_TURN_SCREEN_ON, SCREEN_WAKEUP_DELAY); @@ -166,7 +168,7 @@ public class RetailDemoModeService extends SystemService { mWakeLock.acquire(); break; case MSG_INACTIVITY_TIME_OUT: - if (isDemoLauncherDisabled()) { + if (!mIsCarrierDemoMode && isDemoLauncherDisabled()) { Slog.i(TAG, "User inactivity timeout reached"); showInactivityCountdownDialog(); } @@ -177,12 +179,29 @@ public class RetailDemoModeService extends SystemService { } removeMessages(MSG_START_NEW_SESSION); removeMessages(MSG_INACTIVITY_TIME_OUT); - if (mCurrentUserId != UserHandle.USER_SYSTEM) { + if (!mIsCarrierDemoMode && mCurrentUserId != UserHandle.USER_SYSTEM) { logSessionDuration(); } - final UserInfo demoUser = getUserManager().createUser(DEMO_USER_NAME, - UserInfo.FLAG_DEMO | UserInfo.FLAG_EPHEMERAL); - if (demoUser != null) { + + UserInfo demoUser = null; + if (mIsCarrierDemoMode) { + // Re-use the existing demo user in carrier demo mode. + for (UserInfo user : getUserManager().getUsers()) { + if (user.isDemo()) { + demoUser = user; + break; + } + } + } + + if (demoUser == null) { + // User in carrier demo mode should survive reboots. + final int flags = UserInfo.FLAG_DEMO + | (mIsCarrierDemoMode ? 0 : UserInfo.FLAG_EPHEMERAL); + demoUser = getUserManager().createUser(DEMO_USER_NAME, flags); + } + + if (demoUser != null && mCurrentUserId != demoUser.id) { setupDemoUser(demoUser); getActivityManager().switchUser(demoUser.id); } @@ -196,8 +215,6 @@ public class RetailDemoModeService extends SystemService { private final static String KEY_USER_INACTIVITY_TIMEOUT = "user_inactivity_timeout_ms"; private final static String KEY_WARNING_DIALOG_TIMEOUT = "warning_dialog_timeout_ms"; - private final Uri mDeviceDemoModeUri = Settings.Global - .getUriFor(Settings.Global.DEVICE_DEMO_MODE); private final Uri mDeviceProvisionedUri = Settings.Global .getUriFor(Settings.Global.DEVICE_PROVISIONED); private final Uri mRetailDemoConstantsUri = Settings.Global @@ -211,7 +228,6 @@ public class RetailDemoModeService extends SystemService { public void register() { ContentResolver cr = getContext().getContentResolver(); - cr.registerContentObserver(mDeviceDemoModeUri, false, this, UserHandle.USER_SYSTEM); cr.registerContentObserver(mDeviceProvisionedUri, false, this, UserHandle.USER_SYSTEM); cr.registerContentObserver(mRetailDemoConstantsUri, false, this, UserHandle.USER_SYSTEM); @@ -223,29 +239,28 @@ public class RetailDemoModeService extends SystemService { refreshTimeoutConstants(); return; } - if (mDeviceDemoModeUri.equals(uri)) { - mDeviceInDemoMode = UserManager.isDeviceInDemoMode(getContext()); - if (mDeviceInDemoMode) { + // If device is provisioned and left demo mode - run the cleanup in demo folder + if (isDeviceProvisioned()) { + if (UserManager.isDeviceInDemoMode(getContext())) { putDeviceInDemoMode(); } else { SystemProperties.set(SYSTEM_PROPERTY_RETAIL_DEMO_ENABLED, "0"); + + // Run on the bg thread to not block the fg thread + BackgroundThread.getHandler().post(new Runnable() { + @Override + public void run() { + if (!deletePreloadsFolderContents()) { + Slog.w(TAG, "Failed to delete preloads folder contents"); + } + } + }); + if (mWakeLock.isHeld()) { mWakeLock.release(); } } } - // If device is provisioned and left demo mode - run the cleanup in demo folder - if (!mDeviceInDemoMode && isDeviceProvisioned()) { - // Run on the bg thread to not block the fg thread - BackgroundThread.getHandler().post(new Runnable() { - @Override - public void run() { - if (!deletePreloadsFolderContents()) { - Slog.w(TAG, "Failed to delete preloads folder contents"); - } - } - }); - } } private void refreshTimeoutConstants() { @@ -312,23 +327,22 @@ public class RetailDemoModeService extends SystemService { } boolean isDemoLauncherDisabled() { - IPackageManager pm = AppGlobals.getPackageManager(); int enabledState = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; - String demoLauncherComponent = getContext().getResources() - .getString(R.string.config_demoModeLauncherComponent); try { - enabledState = pm.getComponentEnabledSetting( - ComponentName.unflattenFromString(demoLauncherComponent), - mCurrentUserId); - } catch (RemoteException exc) { - Slog.e(TAG, "Unable to talk to Package Manager", exc); + final IPackageManager iPm = AppGlobals.getPackageManager(); + final String demoLauncherComponent = + getContext().getString(R.string.config_demoModeLauncherComponent); + enabledState = iPm.getComponentEnabledSetting( + ComponentName.unflattenFromString(demoLauncherComponent), mCurrentUserId); + } catch (RemoteException re) { + Slog.e(TAG, "Error retrieving demo launcher enabled setting", re); } return enabledState == PackageManager.COMPONENT_ENABLED_STATE_DISABLED; } private void setupDemoUser(UserInfo userInfo) { - UserManager um = getUserManager(); - UserHandle user = UserHandle.of(userInfo.id); + final UserManager um = getUserManager(); + final UserHandle user = UserHandle.of(userInfo.id); um.setUserRestriction(UserManager.DISALLOW_CONFIG_WIFI, true, user); um.setUserRestriction(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, true, user); um.setUserRestriction(UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS, true, user); @@ -338,14 +352,60 @@ public class RetailDemoModeService extends SystemService { // Set this to false because the default is true on user creation um.setUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS, false, user); // Disallow rebooting in safe mode - controlled by user 0 - getUserManager().setUserRestriction(UserManager.DISALLOW_SAFE_BOOT, true, - UserHandle.SYSTEM); + um.setUserRestriction(UserManager.DISALLOW_SAFE_BOOT, true, UserHandle.SYSTEM); + if (mIsCarrierDemoMode) { + // Enable SMS in carrier demo mode. + um.setUserRestriction(UserManager.DISALLOW_SMS, false, user); + } + Settings.Secure.putIntForUser(getContext().getContentResolver(), Settings.Secure.SKIP_FIRST_USE_HINTS, 1, userInfo.id); Settings.Global.putInt(getContext().getContentResolver(), Settings.Global.PACKAGE_VERIFIER_ENABLE, 0); + grantRuntimePermissionToCamera(user); clearPrimaryCallLog(); + + if (!mIsCarrierDemoMode) { + // Enable demo launcher. + final String demoLauncher = getContext().getString( + R.string.config_demoModeLauncherComponent); + if (!TextUtils.isEmpty(demoLauncher)) { + final ComponentName componentToEnable = + ComponentName.unflattenFromString(demoLauncher); + final String packageName = componentToEnable.getPackageName(); + try { + final IPackageManager iPm = AppGlobals.getPackageManager(); + iPm.setComponentEnabledSetting(componentToEnable, + PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0, userInfo.id); + iPm.setApplicationEnabledSetting(packageName, + PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0, userInfo.id, null); + } catch (RemoteException re) { + // Internal, shouldn't happen + } + } + } else { + // Set the carrier demo mode setting for the demo user. + final String carrierDemoModeSetting = getContext().getString( + R.string.config_carrierDemoModeSetting); + Settings.Secure.putIntForUser(getContext().getContentResolver(), + carrierDemoModeSetting, 1, userInfo.id); + + // Enable packages for carrier demo mode. + final String packageList = getContext().getString( + R.string.config_carrierDemoModePackages); + final String[] packageNames = packageList == null ? new String[0] + : TextUtils.split(packageList, ","); + final IPackageManager iPm = AppGlobals.getPackageManager(); + for (String packageName : packageNames) { + try { + iPm.setApplicationEnabledSetting(packageName, + PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0, userInfo.id, null); + } catch (RemoteException re) { + Slog.e(TAG, "Error enabling application: " + packageName, re); + } + } + } } private void grantRuntimePermissionToCamera(UserHandle user) { @@ -419,7 +479,9 @@ public class RetailDemoModeService extends SystemService { private void registerBroadcastReceiver() { final IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_SCREEN_OFF); + if (!mIsCarrierDemoMode) { + filter.addAction(Intent.ACTION_SCREEN_OFF); + } filter.addAction(ACTION_RESET_DEMO); getContext().registerReceiver(mBroadcastReceiver, filter); } @@ -465,8 +527,18 @@ public class RetailDemoModeService extends SystemService { } private void putDeviceInDemoMode() { + mDeviceInDemoMode = true; + + final String carrierDemoModeSetting = + getContext().getString(R.string.config_carrierDemoModeSetting); + mIsCarrierDemoMode = !TextUtils.isEmpty(carrierDemoModeSetting) + && (Settings.Secure.getInt(getContext().getContentResolver(), + carrierDemoModeSetting, 0) == 1); + SystemProperties.set(SYSTEM_PROPERTY_RETAIL_DEMO_ENABLED, "1"); mHandler.sendEmptyMessage(MSG_START_NEW_SESSION); + + registerBroadcastReceiver(); } @Override @@ -488,10 +560,8 @@ public class RetailDemoModeService extends SystemService { mPreloadAppsInstaller = new PreloadAppsInstaller(getContext()); mPm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE); mAmi = LocalServices.getService(ActivityManagerInternal.class); - mWakeLock = mPm - .newWakeLock( - PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP, - TAG); + mWakeLock = mPm.newWakeLock( + PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP, TAG); mNm = NotificationManager.from(getContext()); mWifiManager = (WifiManager) getContext().getSystemService(Context.WIFI_SERVICE); mCameraManager = (CameraManager) getContext() @@ -500,11 +570,9 @@ public class RetailDemoModeService extends SystemService { SettingsObserver settingsObserver = new SettingsObserver(mHandler); settingsObserver.register(); settingsObserver.refreshTimeoutConstants(); - registerBroadcastReceiver(); break; case PHASE_BOOT_COMPLETED: if (UserManager.isDeviceInDemoMode(getContext())) { - mDeviceInDemoMode = true; putDeviceInDemoMode(); } break; @@ -524,32 +592,38 @@ public class RetailDemoModeService extends SystemService { Slog.wtf(TAG, "Should not allow switch to non-demo user in demo mode"); return; } - if (!mWakeLock.isHeld()) { + if (!mIsCarrierDemoMode && !mWakeLock.isHeld()) { mWakeLock.acquire(); } mCurrentUserId = userId; mAmi.updatePersistentConfigurationForUser(getSystemUsersConfiguration(), userId); + turnOffAllFlashLights(); muteVolumeStreams(); if (!mWifiManager.isWifiEnabled()) { mWifiManager.setWifiEnabled(true); } + // Disable lock screen for demo users. LockPatternUtils lockPatternUtils = new LockPatternUtils(getContext()); lockPatternUtils.setLockScreenDisabled(true, userId); - mNm.notifyAsUser(TAG, 1, createResetNotification(), UserHandle.of(userId)); - synchronized (mActivityLock) { - mUserUntouched = true; - } - MetricsLogger.count(getContext(), DEMO_SESSION_COUNT, 1); - mHandler.removeMessages(MSG_INACTIVITY_TIME_OUT); - mHandler.post(new Runnable() { - @Override - public void run() { - mPreloadAppsInstaller.installApps(userId); + if (!mIsCarrierDemoMode) { + // Show reset notification (except in carrier demo mode). + mNm.notifyAsUser(TAG, 1, createResetNotification(), UserHandle.of(userId)); + + synchronized (mActivityLock) { + mUserUntouched = true; } - }); + MetricsLogger.count(getContext(), DEMO_SESSION_COUNT, 1); + mHandler.removeMessages(MSG_INACTIVITY_TIME_OUT); + mHandler.post(new Runnable() { + @Override + public void run() { + mPreloadAppsInstaller.installApps(userId); + } + }); + } } private RetailDemoModeServiceInternal mLocalService = new RetailDemoModeServiceInternal() { @@ -557,7 +631,7 @@ public class RetailDemoModeService extends SystemService { @Override public void onUserActivity() { - if (!mDeviceInDemoMode) { + if (!mDeviceInDemoMode || mIsCarrierDemoMode) { return; } long timeOfActivity = SystemClock.uptimeMillis(); diff --git a/services/tests/servicestests/Android.mk b/services/tests/servicestests/Android.mk index 50e0662ad4d4..7886b5e9e395 100644 --- a/services/tests/servicestests/Android.mk +++ b/services/tests/servicestests/Android.mk @@ -21,7 +21,8 @@ LOCAL_STATIC_JAVA_LIBRARIES := \ guava \ android-support-test \ mockito-target \ - ShortcutManagerTestUtils + ShortcutManagerTestUtils \ + truth-prebuilt LOCAL_JAVA_LIBRARIES := android.test.runner @@ -46,6 +47,11 @@ LOCAL_JNI_SHARED_LIBRARIES := libservicestestsjni \ LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk +# Code coverage puts us over the dex limit, so enable multi-dex for coverage-enabled builds +ifeq (true,$(EMMA_INSTRUMENT)) +LOCAL_JACK_FLAGS := --multi-dex native +endif # EMMA_INSTRUMENT_STATIC + include $(BUILD_PACKAGE) ######################################################################### diff --git a/services/tests/servicestests/src/android/net/apf/ApfTest.java b/services/tests/servicestests/src/android/net/apf/ApfTest.java index f7c61d15bb5f..6092fddc6be8 100644 --- a/services/tests/servicestests/src/android/net/apf/ApfTest.java +++ b/services/tests/servicestests/src/android/net/apf/ApfTest.java @@ -660,9 +660,13 @@ public class ApfTest extends AndroidTestCase { // The IPv6 all nodes address ff02::1 private static final byte[] IPV6_ALL_NODES_ADDRESS = { (byte) 0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 }; + private static final byte[] IPV6_ALL_ROUTERS_ADDRESS = + { (byte) 0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2 }; private static final int ICMP6_TYPE_OFFSET = ETH_HEADER_LEN + IPV6_HEADER_LEN; + private static final int ICMP6_ROUTER_SOLICITATION = 133; private static final int ICMP6_ROUTER_ADVERTISEMENT = 134; + private static final int ICMP6_NEIGHBOR_SOLICITATION = 135; private static final int ICMP6_NEIGHBOR_ANNOUNCEMENT = 136; private static final int ICMP6_RA_HEADER_LEN = 16; @@ -797,6 +801,12 @@ public class ApfTest extends AndroidTestCase { put(packet, IPV6_DEST_ADDR_OFFSET, IPV6_ALL_NODES_ADDRESS); assertDrop(program, packet.array()); + // Verify ICMPv6 RS to any is dropped + packet.put(ICMP6_TYPE_OFFSET, (byte)ICMP6_ROUTER_SOLICITATION); + assertDrop(program, packet.array()); + put(packet, IPV6_DEST_ADDR_OFFSET, IPV6_ALL_ROUTERS_ADDRESS); + assertDrop(program, packet.array()); + apfFilter.shutdown(); } diff --git a/services/tests/servicestests/src/android/net/util/BlockingSocketReaderTest.java b/services/tests/servicestests/src/android/net/util/BlockingSocketReaderTest.java new file mode 100644 index 000000000000..e03350f29f95 --- /dev/null +++ b/services/tests/servicestests/src/android/net/util/BlockingSocketReaderTest.java @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2016 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.util; + +import static android.system.OsConstants.*; + +import android.system.ErrnoException; +import android.system.Os; +import android.system.StructTimeval; + +import libcore.io.IoBridge; + +import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.IOException; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.SocketException; +import java.util.Arrays; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import junit.framework.TestCase; + + +/** + * Tests for BlockingSocketReader. + * + * @hide + */ +public class BlockingSocketReaderTest extends TestCase { + static final InetAddress LOOPBACK6 = Inet6Address.getLoopbackAddress(); + static final StructTimeval TIMEO = StructTimeval.fromMillis(500); + + protected CountDownLatch mLatch; + protected FileDescriptor mLocalSocket; + protected InetSocketAddress mLocalSockName; + protected byte[] mLastRecvBuf; + protected boolean mExited; + protected BlockingSocketReader mReceiver; + + @Override + public void setUp() { + resetLatch(); + mLocalSocket = null; + mLocalSockName = null; + mLastRecvBuf = null; + mExited = false; + + mReceiver = new BlockingSocketReader() { + @Override + protected FileDescriptor createSocket() { + FileDescriptor s = null; + try { + s = Os.socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP); + Os.bind(s, LOOPBACK6, 0); + mLocalSockName = (InetSocketAddress) Os.getsockname(s); + Os.setsockoptTimeval(s, SOL_SOCKET, SO_SNDTIMEO, TIMEO); + } catch (ErrnoException|SocketException e) { + closeSocket(s); + fail(); + return null; + } + + mLocalSocket = s; + return s; + } + + @Override + protected void handlePacket(byte[] recvbuf, int length) { + mLastRecvBuf = Arrays.copyOf(recvbuf, length); + mLatch.countDown(); + } + + @Override + protected void onExit() { + mExited = true; + mLatch.countDown(); + } + }; + } + + @Override + public void tearDown() { + if (mReceiver != null) mReceiver.stop(); + mReceiver = null; + } + + void resetLatch() { mLatch = new CountDownLatch(1); } + + void waitForActivity() throws Exception { + assertTrue(mLatch.await(500, TimeUnit.MILLISECONDS)); + resetLatch(); + } + + void sendPacket(byte[] contents) throws Exception { + final DatagramSocket sender = new DatagramSocket(); + sender.connect(mLocalSockName); + sender.send(new DatagramPacket(contents, contents.length)); + sender.close(); + } + + public void testBasicWorking() throws Exception { + assertTrue(mReceiver.start()); + assertTrue(mLocalSockName != null); + assertEquals(LOOPBACK6, mLocalSockName.getAddress()); + assertTrue(0 < mLocalSockName.getPort()); + assertTrue(mLocalSocket != null); + assertFalse(mExited); + + final byte[] one = "one 1".getBytes("UTF-8"); + sendPacket(one); + waitForActivity(); + assertEquals(1, mReceiver.numPacketsReceived()); + assertTrue(Arrays.equals(one, mLastRecvBuf)); + assertFalse(mExited); + + final byte[] two = "two 2".getBytes("UTF-8"); + sendPacket(two); + waitForActivity(); + assertEquals(2, mReceiver.numPacketsReceived()); + assertTrue(Arrays.equals(two, mLastRecvBuf)); + assertFalse(mExited); + + mReceiver.stop(); + waitForActivity(); + assertEquals(2, mReceiver.numPacketsReceived()); + assertTrue(Arrays.equals(two, mLastRecvBuf)); + assertTrue(mExited); + } +} diff --git a/services/tests/servicestests/src/android/net/util/ConnectivityPacketSummaryTest.java b/services/tests/servicestests/src/android/net/util/ConnectivityPacketSummaryTest.java new file mode 100644 index 000000000000..dd679bc20090 --- /dev/null +++ b/services/tests/servicestests/src/android/net/util/ConnectivityPacketSummaryTest.java @@ -0,0 +1,401 @@ +/* + * Copyright (C) 2016 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.util; + +import static android.net.util.NetworkConstants.*; + +import libcore.util.HexEncoding; + +import junit.framework.TestCase; + + +/** + * Tests for ConnectivityPacketSummary. + * + * @hide + */ +public class ConnectivityPacketSummaryTest extends TestCase { + private static final byte[] MYHWADDR = { + asByte(0x80), asByte(0x7a), asByte(0xbf), asByte(0x6f), asByte(0x48), asByte(0xf3) + }; + + private String getSummary(String hexBytes) { + hexBytes = hexBytes.replaceAll("\\s+", ""); + final byte[] bytes = HexEncoding.decode(hexBytes.toCharArray(), false); + return ConnectivityPacketSummary.summarize(MYHWADDR, bytes); + } + + public void testParseICMPv6DADProbe() { + final String packet = + // Ethernet + "3333FF6F48F3 807ABF6F48F3 86DD" + + // IPv6 + "600000000018 3A FF" + + "00000000000000000000000000000000" + + "FF0200000000000000000001FF6F48F3" + + // ICMPv6 + "87 00 A8E7" + + "00000000" + + "FE80000000000000827ABFFFFE6F48F3"; + + final String expected = + "TX 80:7a:bf:6f:48:f3 > 33:33:ff:6f:48:f3 ipv6" + + " :: > ff02::1:ff6f:48f3 icmp6" + + " ns fe80::827a:bfff:fe6f:48f3"; + + assertEquals(expected, getSummary(packet)); + } + + public void testParseICMPv6RS() { + final String packet = + // Ethernet + "333300000002 807ABF6F48F3 86DD" + + // IPv6 + "600000000010 3A FF" + + "FE80000000000000827ABFFFFE6F48F3" + + "FF020000000000000000000000000002" + + // ICMPv6 RS + "85 00 6973" + + "00000000" + + "01 01 807ABF6F48F3"; + + final String expected = + "TX 80:7a:bf:6f:48:f3 > 33:33:00:00:00:02 ipv6" + + " fe80::827a:bfff:fe6f:48f3 > ff02::2 icmp6" + + " rs slla 80:7a:bf:6f:48:f3"; + + assertEquals(expected, getSummary(packet)); + } + + public void testParseICMPv6RA() { + final String packet = + // Ethernet + "807ABF6F48F3 100E7E263FC1 86DD" + + // IPv6 + "600000000068 3A FF" + + "FE80000000000000FA000004FD000001" + + "FE80000000000000827ABFFFFE6F48F3" + + // ICMPv6 RA + "86 00 8141" + + "40 00 0E10" + + "00000000" + + "00000000" + + "01 01 00005E000265" + + "05 01 0000000005DC" + + "19 05 000000000E10" + + " 20014860486000000000000000008844" + + " 20014860486000000000000000008888" + + "03 04 40 C0" + + " 00278D00" + + " 00093A80" + + " 00000000" + + " 2401FA000004FD000000000000000000"; + + final String expected = + "RX 10:0e:7e:26:3f:c1 > 80:7a:bf:6f:48:f3 ipv6" + + " fe80::fa00:4:fd00:1 > fe80::827a:bfff:fe6f:48f3 icmp6" + + " ra slla 00:00:5e:00:02:65 mtu 1500"; + + assertEquals(expected, getSummary(packet)); + } + + public void testParseICMPv6NS() { + final String packet = + // Ethernet + "807ABF6F48F3 100E7E263FC1 86DD" + + // IPv6 + "6C0000000020 3A FF" + + "FE80000000000000FA000004FD000001" + + "FF0200000000000000000001FF01C146" + + // ICMPv6 NS + "87 00 8AD4" + + "00000000" + + "2401FA000004FD0015EA6A5C7B01C146" + + "01 01 00005E000265"; + + final String expected = + "RX 10:0e:7e:26:3f:c1 > 80:7a:bf:6f:48:f3 ipv6" + + " fe80::fa00:4:fd00:1 > ff02::1:ff01:c146 icmp6" + + " ns 2401:fa00:4:fd00:15ea:6a5c:7b01:c146 slla 00:00:5e:00:02:65"; + + assertEquals(expected, getSummary(packet)); + } + + public void testInvalidICMPv6NDLength() { + final String packet = + // Ethernet + "807ABF6F48F3 100E7E263FC1 86DD" + + // IPv6 + "600000000068 3A FF" + + "FE80000000000000FA000004FD000001" + + "FE80000000000000827ABFFFFE6F48F3" + + // ICMPv6 RA + "86 00 8141" + + "40 00 0E10" + + "00000000" + + "00000000" + + "01 01 00005E000265" + + "00 00 0102030405D6"; + + final String expected = + "RX 10:0e:7e:26:3f:c1 > 80:7a:bf:6f:48:f3 ipv6" + + " fe80::fa00:4:fd00:1 > fe80::827a:bfff:fe6f:48f3 icmp6" + + " ra slla 00:00:5e:00:02:65 <malformed>"; + + assertEquals(expected, getSummary(packet)); + } + + public void testParseICMPv6NA() { + final String packet = + // Ethernet + "00005E000265 807ABF6F48F3 86DD" + + "600000000020 3A FF" + + "2401FA000004FD0015EA6A5C7B01C146" + + "FE80000000000000FA000004FD000001" + + "88 00 E8126" + + "0000000" + + "2401FA000004FD0015EA6A5C7B01C146" + + "02 01 807ABF6F48F3"; + + final String expected = + "TX 80:7a:bf:6f:48:f3 > 00:00:5e:00:02:65 ipv6" + + " 2401:fa00:4:fd00:15ea:6a5c:7b01:c146 > fe80::fa00:4:fd00:1 icmp6" + + " na 2401:fa00:4:fd00:15ea:6a5c:7b01:c146 tlla 80:7a:bf:6f:48:f3"; + + assertEquals(expected, getSummary(packet)); + } + + public void testParseARPRequest() { + final String packet = + // Ethernet + "FFFFFFFFFFFF 807ABF6F48F3 0806" + + // ARP + "0001 0800 06 04" + + // Request + "0001" + + "807ABF6F48F3 64706ADB" + + "000000000000 64706FFD"; + + final String expected = + "TX 80:7a:bf:6f:48:f3 > ff:ff:ff:ff:ff:ff arp" + + " who-has 100.112.111.253"; + + assertEquals(expected, getSummary(packet)); + } + + public void testParseARPReply() { + final String packet = + // Ethernet + "807ABF6F48F3 288A1CA8DFC1 0806" + + // ARP + "0001 0800 06 04" + + // Reply + "0002" + + "288A1CA8DFC1 64706FFD"+ + "807ABF6F48F3 64706ADB" + + // Ethernet padding to packet min size. + "0000000000000000000000000000"; + + final String expected = + "RX 28:8a:1c:a8:df:c1 > 80:7a:bf:6f:48:f3 arp" + + " reply 100.112.111.253 28:8a:1c:a8:df:c1"; + + assertEquals(expected, getSummary(packet)); + } + + public void testParseDHCPv4Discover() { + final String packet = + // Ethernet + "FFFFFFFFFFFF 807ABF6F48F3 0800" + + // IPv4 + "451001580000400040113986" + + "00000000" + + "FFFFFFFF" + + // UDP + "0044 0043" + + "0144 5559" + + // DHCPv4 + "01 01 06 00" + + "79F7ACA4" + + "0000 0000" + + "00000000" + + "00000000" + + "00000000" + + "00000000" + + "807ABF6F48F300000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "63 82 53 63" + + "35 01 01" + + "3D 07 01807ABF6F48F3" + + "39 02 05DC" + + "3C 12 616E64726F69642D646863702D372E312E32" + + "0C 18 616E64726F69642D36623030366333313333393835343139" + + "37 0A 01 03 06 0F 1A 1C 33 3A 3B 2B" + + "FF" + + "00"; + + final String expectedPrefix = + "TX 80:7a:bf:6f:48:f3 > ff:ff:ff:ff:ff:ff ipv4" + + " 0.0.0.0 > 255.255.255.255 udp" + + " 68 > 67 dhcp4" + + " 80:7a:bf:6f:48:f3 DISCOVER"; + + assertTrue(getSummary(packet).startsWith(expectedPrefix)); + } + + public void testParseDHCPv4Offer() { + final String packet = + // Ethernet + "807ABF6F48F3 288A1CA8DFC1 0800" + + // IPv4 + "4500013D4D2C0000401188CB" + + "64706FFD" + + "64706ADB" + + // UDP + "0043 0044" + + "0129 371D" + + // DHCPv4 + "02 01 06 01" + + "79F7ACA4" + + "0000 0000" + + "00000000" + + "64706ADB" + + "00000000" + + "00000000" + + "807ABF6F48F300000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "63 82 53 63" + + "35 01 02" + + "36 04 AC188A0B" + + "33 04 00000708" + + "01 04 FFFFF000" + + "03 04 64706FFE" + + "06 08 08080808" + + " 08080404" + + "FF0001076165313A363636FF"; + + final String expectedPrefix = + "RX 28:8a:1c:a8:df:c1 > 80:7a:bf:6f:48:f3 ipv4" + + " 100.112.111.253 > 100.112.106.219 udp" + + " 67 > 68 dhcp4" + + " 80:7a:bf:6f:48:f3 OFFER"; + + assertTrue(getSummary(packet).startsWith(expectedPrefix)); + } + + public void testParseDHCPv4Request() { + final String packet = + // Ethernet + "FFFFFFFFFFFF 807ABF6F48F3 0800" + + // IPv4 + "45100164000040004011397A" + + "00000000" + + "FFFFFFFF" + + // UDP + "0044 0043" + + "0150 E5C7" + + // DHCPv4 + "01 01 06 00" + + "79F7ACA4" + + "0001 0000" + + "00000000" + + "00000000" + + "00000000" + + "00000000" + + "807ABF6F48F300000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "63 82 53 63" + + "35 01 03" + + "3D 07 01807ABF6F48F3" + + "32 04 64706ADB" + + "36 04 AC188A0B" + + "39 02 05DC" + + "3C 12 616E64726F69642D646863702D372E312E32" + + "0C 18 616E64726F69642D36623030366333313333393835343139" + + "37 0A 01 03 06 0F 1A 1C 33 3A 3B 2B" + + "FF" + + "00"; + + final String expectedPrefix = + "TX 80:7a:bf:6f:48:f3 > ff:ff:ff:ff:ff:ff ipv4" + + " 0.0.0.0 > 255.255.255.255 udp" + + " 68 > 67 dhcp4" + + " 80:7a:bf:6f:48:f3 REQUEST"; + + assertTrue(getSummary(packet).startsWith(expectedPrefix)); + } + + public void testParseDHCPv4Ack() { + final String packet = + // Ethernet + "807ABF6F48F3 288A1CA8DFC1 0800" + + // IPv4 + "4500013D4D3B0000401188BC" + + "64706FFD" + + "64706ADB" + + // UDP + "0043 0044" + + "0129 341C" + + // DHCPv4 + "02 01 06 01" + + "79F7ACA4" + + "0001 0000" + + "00000000" + + "64706ADB" + + "00000000" + + "00000000" + + "807ABF6F48F300000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "0000000000000000000000000000000000000000000000000000000000000000" + + "63 82 53 63" + + "35 01 05" + + "36 04 AC188A0B" + + "33 04 00000708" + + "01 04 FFFFF000" + + "03 04 64706FFE" + + "06 08 08080808" + + " 08080404" + + "FF0001076165313A363636FF"; + + final String expectedPrefix = + "RX 28:8a:1c:a8:df:c1 > 80:7a:bf:6f:48:f3 ipv4" + + " 100.112.111.253 > 100.112.106.219 udp" + + " 67 > 68 dhcp4" + + " 80:7a:bf:6f:48:f3 ACK"; + + assertTrue(getSummary(packet).startsWith(expectedPrefix)); + } +} diff --git a/services/tests/servicestests/src/com/android/server/connectivity/DnsEventListenerServiceTest.java b/services/tests/servicestests/src/com/android/server/connectivity/DnsEventListenerServiceTest.java deleted file mode 100644 index 033b2c96c8f5..000000000000 --- a/services/tests/servicestests/src/com/android/server/connectivity/DnsEventListenerServiceTest.java +++ /dev/null @@ -1,197 +0,0 @@ -/* - * Copyright (C) 2016, 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.connectivity; - -import android.net.ConnectivityManager.NetworkCallback; -import android.net.ConnectivityManager; -import android.net.Network; -import android.net.metrics.DnsEvent; -import android.net.metrics.IDnsEventListener; -import android.net.metrics.IpConnectivityLog; - -import junit.framework.TestCase; -import org.junit.Before; -import org.junit.Test; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertTrue; - -import org.mockito.ArgumentCaptor; -import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.MockitoAnnotations; -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.anyInt; -import static org.mockito.Mockito.eq; -import static org.mockito.Mockito.timeout; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -import java.io.FileOutputStream; -import java.io.PrintWriter; -import java.util.Arrays; -import java.util.List; -import java.util.OptionalInt; -import java.util.stream.IntStream; - -public class DnsEventListenerServiceTest extends TestCase { - - // TODO: read from DnsEventListenerService after this constant is read from system property - static final int BATCH_SIZE = 100; - static final int EVENT_TYPE = IDnsEventListener.EVENT_GETADDRINFO; - // TODO: read from IDnsEventListener - static final int RETURN_CODE = 1; - - static final byte[] EVENT_TYPES = new byte[BATCH_SIZE]; - static final byte[] RETURN_CODES = new byte[BATCH_SIZE]; - static final int[] LATENCIES = new int[BATCH_SIZE]; - static { - for (int i = 0; i < BATCH_SIZE; i++) { - EVENT_TYPES[i] = EVENT_TYPE; - RETURN_CODES[i] = RETURN_CODE; - LATENCIES[i] = i; - } - } - - DnsEventListenerService mDnsService; - - @Mock ConnectivityManager mCm; - @Mock IpConnectivityLog mLog; - ArgumentCaptor<NetworkCallback> mCallbackCaptor; - ArgumentCaptor<DnsEvent> mEvCaptor; - - public void setUp() { - MockitoAnnotations.initMocks(this); - mCallbackCaptor = ArgumentCaptor.forClass(NetworkCallback.class); - mEvCaptor = ArgumentCaptor.forClass(DnsEvent.class); - mDnsService = new DnsEventListenerService(mCm, mLog); - - verify(mCm, times(1)).registerNetworkCallback(any(), mCallbackCaptor.capture()); - } - - public void testOneBatch() throws Exception { - log(105, LATENCIES); - log(106, Arrays.copyOf(LATENCIES, BATCH_SIZE - 1)); // one lookup short of a batch event - - verifyLoggedEvents(new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES)); - - log(106, Arrays.copyOfRange(LATENCIES, BATCH_SIZE - 1, BATCH_SIZE)); - - mEvCaptor = ArgumentCaptor.forClass(DnsEvent.class); // reset argument captor - verifyLoggedEvents( - new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES), - new DnsEvent(106, EVENT_TYPES, RETURN_CODES, LATENCIES)); - } - - public void testSeveralBatches() throws Exception { - log(105, LATENCIES); - log(106, LATENCIES); - log(105, LATENCIES); - log(107, LATENCIES); - - verifyLoggedEvents( - new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES), - new DnsEvent(106, EVENT_TYPES, RETURN_CODES, LATENCIES), - new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES), - new DnsEvent(107, EVENT_TYPES, RETURN_CODES, LATENCIES)); - } - - public void testBatchAndNetworkLost() throws Exception { - byte[] eventTypes = Arrays.copyOf(EVENT_TYPES, 20); - byte[] returnCodes = Arrays.copyOf(RETURN_CODES, 20); - int[] latencies = Arrays.copyOf(LATENCIES, 20); - - log(105, LATENCIES); - log(105, latencies); - mCallbackCaptor.getValue().onLost(new Network(105)); - log(105, LATENCIES); - - verifyLoggedEvents( - new DnsEvent(105, eventTypes, returnCodes, latencies), - new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES), - new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES)); - } - - public void testConcurrentBatchesAndDumps() throws Exception { - final long stop = System.currentTimeMillis() + 100; - final PrintWriter pw = new PrintWriter(new FileOutputStream("/dev/null")); - new Thread() { - public void run() { - while (System.currentTimeMillis() < stop) { - mDnsService.dump(pw); - } - } - }.start(); - - logAsync(105, LATENCIES); - logAsync(106, LATENCIES); - logAsync(107, LATENCIES); - - verifyLoggedEvents(500, - new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES), - new DnsEvent(106, EVENT_TYPES, RETURN_CODES, LATENCIES), - new DnsEvent(107, EVENT_TYPES, RETURN_CODES, LATENCIES)); - } - - public void testConcurrentBatchesAndNetworkLoss() throws Exception { - logAsync(105, LATENCIES); - Thread.sleep(10L); - // call onLost() asynchronously to logAsync's onDnsEvent() calls. - mCallbackCaptor.getValue().onLost(new Network(105)); - - // do not verify unpredictable batch - verify(mLog, timeout(500).times(1)).log(any()); - } - - void log(int netId, int[] latencies) { - for (int l : latencies) { - mDnsService.onDnsEvent(netId, EVENT_TYPE, RETURN_CODE, l); - } - } - - void logAsync(int netId, int[] latencies) { - new Thread() { - public void run() { - log(netId, latencies); - } - }.start(); - } - - void verifyLoggedEvents(DnsEvent... expected) { - verifyLoggedEvents(0, expected); - } - - void verifyLoggedEvents(int wait, DnsEvent... expectedEvents) { - verify(mLog, timeout(wait).times(expectedEvents.length)).log(mEvCaptor.capture()); - for (DnsEvent got : mEvCaptor.getAllValues()) { - OptionalInt index = IntStream.range(0, expectedEvents.length) - .filter(i -> eventsEqual(expectedEvents[i], got)) - .findFirst(); - // Don't match same expected event more than once. - index.ifPresent(i -> expectedEvents[i] = null); - assertTrue(index.isPresent()); - } - } - - /** equality function for DnsEvent to avoid overriding equals() and hashCode(). */ - static boolean eventsEqual(DnsEvent expected, DnsEvent got) { - return (expected == got) || ((expected != null) && (got != null) - && (expected.netId == got.netId) - && Arrays.equals(expected.eventTypes, got.eventTypes) - && Arrays.equals(expected.returnCodes, got.returnCodes) - && Arrays.equals(expected.latenciesMs, got.latenciesMs)); - } -} diff --git a/services/tests/servicestests/src/com/android/server/connectivity/IpConnectivityEventBuilderTest.java b/services/tests/servicestests/src/com/android/server/connectivity/IpConnectivityEventBuilderTest.java index aed363524560..011e505c46c4 100644 --- a/services/tests/servicestests/src/com/android/server/connectivity/IpConnectivityEventBuilderTest.java +++ b/services/tests/servicestests/src/com/android/server/connectivity/IpConnectivityEventBuilderTest.java @@ -16,6 +16,17 @@ package com.android.server.connectivity; +import static com.android.server.connectivity.MetricsTestUtil.aBool; +import static com.android.server.connectivity.MetricsTestUtil.aByteArray; +import static com.android.server.connectivity.MetricsTestUtil.aLong; +import static com.android.server.connectivity.MetricsTestUtil.aString; +import static com.android.server.connectivity.MetricsTestUtil.aType; +import static com.android.server.connectivity.MetricsTestUtil.anInt; +import static com.android.server.connectivity.MetricsTestUtil.anIntArray; +import static com.android.server.connectivity.MetricsTestUtil.b; +import static com.android.server.connectivity.MetricsTestUtil.describeIpEvent; +import static com.android.server.connectivity.metrics.IpConnectivityLogClass.IpConnectivityLog; + import android.net.ConnectivityMetricsEvent; import android.net.metrics.ApfProgramEvent; import android.net.metrics.ApfStats; @@ -28,24 +39,13 @@ import android.net.metrics.IpReachabilityEvent; import android.net.metrics.NetworkEvent; import android.net.metrics.RaEvent; import android.net.metrics.ValidationProbeEvent; -import com.google.protobuf.nano.MessageNano; +import android.test.suitebuilder.annotation.SmallTest; import java.util.Arrays; import junit.framework.TestCase; -import static com.android.server.connectivity.metrics.IpConnectivityLogClass.IpConnectivityLog; -import static com.android.server.connectivity.MetricsTestUtil.aBool; -import static com.android.server.connectivity.MetricsTestUtil.aByteArray; -import static com.android.server.connectivity.MetricsTestUtil.aLong; -import static com.android.server.connectivity.MetricsTestUtil.aString; -import static com.android.server.connectivity.MetricsTestUtil.aType; -import static com.android.server.connectivity.MetricsTestUtil.anInt; -import static com.android.server.connectivity.MetricsTestUtil.anIntArray; -import static com.android.server.connectivity.MetricsTestUtil.b; -import static com.android.server.connectivity.MetricsTestUtil.describeIpEvent; -import static com.android.server.connectivity.MetricsTestUtil.ipEv; - public class IpConnectivityEventBuilderTest extends TestCase { + @SmallTest public void testDefaultNetworkEventSerialization() { ConnectivityMetricsEvent ev = describeIpEvent( aType(DefaultNetworkEvent.class), @@ -58,6 +58,8 @@ public class IpConnectivityEventBuilderTest extends TestCase { String want = joinLines( "dropped_events: 0", "events <", + " time_ms: 1", + " transport: 0", " default_network_event <", " network_id <", " network_id: 102", @@ -70,12 +72,13 @@ public class IpConnectivityEventBuilderTest extends TestCase { " transport_types: 2", " transport_types: 3", " >", - " time_ms: 1", - ">"); + ">", + "version: 2"); verifySerialization(want, ev); } + @SmallTest public void testDhcpClientEventSerialization() { ConnectivityMetricsEvent ev = describeIpEvent( aType(DhcpClientEvent.class), @@ -86,18 +89,20 @@ public class IpConnectivityEventBuilderTest extends TestCase { String want = joinLines( "dropped_events: 0", "events <", + " time_ms: 1", + " transport: 0", " dhcp_event <", " duration_ms: 192", - " error_code: 0", " if_name: \"wlan0\"", " state_transition: \"SomeState\"", " >", - " time_ms: 1", - ">"); + ">", + "version: 2"); verifySerialization(want, ev); } + @SmallTest public void testDhcpErrorEventSerialization() { ConnectivityMetricsEvent ev = describeIpEvent( aType(DhcpErrorEvent.class), @@ -107,18 +112,20 @@ public class IpConnectivityEventBuilderTest extends TestCase { String want = joinLines( "dropped_events: 0", "events <", + " time_ms: 1", + " transport: 0", " dhcp_event <", " duration_ms: 0", - " error_code: 50397184", " if_name: \"wlan0\"", - " state_transition: \"\"", + " error_code: 50397184", " >", - " time_ms: 1", - ">"); + ">", + "version: 2"); verifySerialization(want, ev); } + @SmallTest public void testDnsEventSerialization() { ConnectivityMetricsEvent ev = describeIpEvent( aType(DnsEvent.class), @@ -130,6 +137,8 @@ public class IpConnectivityEventBuilderTest extends TestCase { String want = joinLines( "dropped_events: 0", "events <", + " time_ms: 1", + " transport: 0", " dns_lookup_batch <", " event_types: 1", " event_types: 1", @@ -159,12 +168,13 @@ public class IpConnectivityEventBuilderTest extends TestCase { " return_codes: 200", " return_codes: 178", " >", - " time_ms: 1", - ">"); + ">", + "version: 2"); verifySerialization(want, ev); } + @SmallTest public void testIpManagerEventSerialization() { ConnectivityMetricsEvent ev = describeIpEvent( aType(IpManagerEvent.class), @@ -175,17 +185,20 @@ public class IpConnectivityEventBuilderTest extends TestCase { String want = joinLines( "dropped_events: 0", "events <", + " time_ms: 1", + " transport: 0", " ip_provisioning_event <", " event_type: 1", " if_name: \"wlan0\"", " latency_ms: 5678", " >", - " time_ms: 1", - ">"); + ">", + "version: 2"); verifySerialization(want, ev); } + @SmallTest public void testIpReachabilityEventSerialization() { ConnectivityMetricsEvent ev = describeIpEvent( aType(IpReachabilityEvent.class), @@ -195,16 +208,19 @@ public class IpConnectivityEventBuilderTest extends TestCase { String want = joinLines( "dropped_events: 0", "events <", + " time_ms: 1", + " transport: 0", " ip_reachability_event <", " event_type: 512", " if_name: \"wlan0\"", " >", - " time_ms: 1", - ">"); + ">", + "version: 2"); verifySerialization(want, ev); } + @SmallTest public void testNetworkEventSerialization() { ConnectivityMetricsEvent ev = describeIpEvent( aType(NetworkEvent.class), @@ -215,6 +231,8 @@ public class IpConnectivityEventBuilderTest extends TestCase { String want = joinLines( "dropped_events: 0", "events <", + " time_ms: 1", + " transport: 0", " network_event <", " event_type: 5", " latency_ms: 20410", @@ -222,12 +240,13 @@ public class IpConnectivityEventBuilderTest extends TestCase { " network_id: 100", " >", " >", - " time_ms: 1", - ">"); + ">", + "version: 2"); verifySerialization(want, ev); } + @SmallTest public void testValidationProbeEventSerialization() { ConnectivityMetricsEvent ev = describeIpEvent( aType(ValidationProbeEvent.class), @@ -240,6 +259,7 @@ public class IpConnectivityEventBuilderTest extends TestCase { "dropped_events: 0", "events <", " time_ms: 1", + " transport: 0", " validation_probe_event <", " latency_ms: 40730", " network_id <", @@ -248,11 +268,13 @@ public class IpConnectivityEventBuilderTest extends TestCase { " probe_result: 204", " probe_type: 1", " >", - ">"); + ">", + "version: 2"); verifySerialization(want, ev); } + @SmallTest public void testApfProgramEventSerialization() { ConnectivityMetricsEvent ev = describeIpEvent( aType(ApfProgramEvent.class), @@ -265,6 +287,8 @@ public class IpConnectivityEventBuilderTest extends TestCase { String want = joinLines( "dropped_events: 0", "events <", + " time_ms: 1", + " transport: 0", " apf_program_event <", " current_ras: 9", " drop_multicast: true", @@ -273,12 +297,13 @@ public class IpConnectivityEventBuilderTest extends TestCase { " lifetime: 200", " program_length: 2048", " >", - " time_ms: 1", - ">"); + ">", + "version: 2"); verifySerialization(want, ev); } + @SmallTest public void testApfStatsSerialization() { ConnectivityMetricsEvent ev = describeIpEvent( aType(ApfStats.class), @@ -294,6 +319,8 @@ public class IpConnectivityEventBuilderTest extends TestCase { String want = joinLines( "dropped_events: 0", "events <", + " time_ms: 1", + " transport: 0", " apf_statistics <", " dropped_ras: 2", " duration_ms: 45000", @@ -304,12 +331,13 @@ public class IpConnectivityEventBuilderTest extends TestCase { " received_ras: 10", " zero_lifetime_ras: 1", " >", - " time_ms: 1", - ">"); + ">", + "version: 2"); verifySerialization(want, ev); } + @SmallTest public void testRaEventSerialization() { ConnectivityMetricsEvent ev = describeIpEvent( aType(RaEvent.class), @@ -323,6 +351,8 @@ public class IpConnectivityEventBuilderTest extends TestCase { String want = joinLines( "dropped_events: 0", "events <", + " time_ms: 1", + " transport: 0", " ra_event <", " dnssl_lifetime: -1", " prefix_preferred_lifetime: 300", @@ -331,17 +361,17 @@ public class IpConnectivityEventBuilderTest extends TestCase { " route_info_lifetime: -1", " router_lifetime: 2000", " >", - " time_ms: 1", - ">"); + ">", + "version: 2"); verifySerialization(want, ev); } static void verifySerialization(String want, ConnectivityMetricsEvent... input) { try { - byte[] got = IpConnectivityEventBuilder.serialize(0, Arrays.asList(input)); - IpConnectivityLog log = new IpConnectivityLog(); - MessageNano.mergeFrom(log, got); + byte[] got = IpConnectivityEventBuilder.serialize(0, + IpConnectivityEventBuilder.toProto(Arrays.asList(input))); + IpConnectivityLog log = IpConnectivityLog.parseFrom(got); assertEquals(want, log.toString()); } catch (Exception e) { fail(e.toString()); diff --git a/services/tests/servicestests/src/com/android/server/connectivity/IpConnectivityMetricsTest.java b/services/tests/servicestests/src/com/android/server/connectivity/IpConnectivityMetricsTest.java index 3fc89b9ff12d..450653cdb01f 100644 --- a/services/tests/servicestests/src/com/android/server/connectivity/IpConnectivityMetricsTest.java +++ b/services/tests/servicestests/src/com/android/server/connectivity/IpConnectivityMetricsTest.java @@ -16,9 +16,13 @@ package com.android.server.connectivity; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.verify; + import android.content.Context; import android.net.ConnectivityMetricsEvent; import android.net.IIpConnectivityMetrics; +import android.net.metrics.ApfProgramEvent; import android.net.metrics.ApfStats; import android.net.metrics.DefaultNetworkEvent; import android.net.metrics.DhcpClientEvent; @@ -28,9 +32,9 @@ import android.net.metrics.IpReachabilityEvent; import android.net.metrics.RaEvent; import android.net.metrics.ValidationProbeEvent; import android.os.Parcelable; +import android.test.suitebuilder.annotation.SmallTest; import android.util.Base64; import com.android.server.connectivity.metrics.IpConnectivityLogClass; -import com.google.protobuf.nano.MessageNano; import java.io.PrintWriter; import java.io.StringWriter; import java.util.Collections; @@ -42,10 +46,6 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import static org.mockito.Mockito.timeout; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - public class IpConnectivityMetricsTest extends TestCase { static final IpReachabilityEvent FAKE_EV = new IpReachabilityEvent("wlan0", IpReachabilityEvent.NUD_FAILED); @@ -57,9 +57,10 @@ public class IpConnectivityMetricsTest extends TestCase { public void setUp() { MockitoAnnotations.initMocks(this); - mService = new IpConnectivityMetrics(mCtx); + mService = new IpConnectivityMetrics(mCtx, (ctx) -> 2000); } + @SmallTest public void testLoggingEvents() throws Exception { IpConnectivityLog logger = new IpConnectivityLog(mMockService); @@ -73,6 +74,7 @@ public class IpConnectivityMetricsTest extends TestCase { assertEventsEqual(expectedEvent(3), got.get(2)); } + @SmallTest public void testLoggingEventsWithMultipleCallers() throws Exception { IpConnectivityLog logger = new IpConnectivityLog(mMockService); @@ -100,6 +102,7 @@ public class IpConnectivityMetricsTest extends TestCase { } } + @SmallTest public void testBufferFlushing() { String output1 = getdump("flush"); assertEquals("", output1); @@ -112,6 +115,29 @@ public class IpConnectivityMetricsTest extends TestCase { assertEquals("", output3); } + @SmallTest + public void testRateLimiting() { + final IpConnectivityLog logger = new IpConnectivityLog(mService.impl); + final ApfProgramEvent ev = new ApfProgramEvent(0, 0, 0, 0, 0); + final long fakeTimestamp = 1; + + int attempt = 100; // More than burst quota, but less than buffer size. + for (int i = 0; i < attempt; i++) { + logger.log(ev); + } + + String output1 = getdump("flush"); + assertFalse("".equals(output1)); + + for (int i = 0; i < attempt; i++) { + assertFalse("expected event to be dropped", logger.log(fakeTimestamp, ev)); + } + + String output2 = getdump("flush"); + assertEquals("", output2); + } + + @SmallTest public void testEndToEndLogging() { IpConnectivityLog logger = new IpConnectivityLog(mService.impl); @@ -132,22 +158,25 @@ public class IpConnectivityMetricsTest extends TestCase { String want = joinLines( "dropped_events: 0", "events <", + " time_ms: 100", + " transport: 0", " ip_reachability_event <", " event_type: 512", " if_name: \"wlan0\"", " >", - " time_ms: 100", ">", "events <", + " time_ms: 200", + " transport: 0", " dhcp_event <", " duration_ms: 192", - " error_code: 0", " if_name: \"wlan0\"", " state_transition: \"SomeState\"", " >", - " time_ms: 200", ">", "events <", + " time_ms: 300", + " transport: 0", " default_network_event <", " network_id <", " network_id: 102", @@ -160,18 +189,19 @@ public class IpConnectivityMetricsTest extends TestCase { " transport_types: 2", " transport_types: 3", " >", - " time_ms: 300", ">", "events <", + " time_ms: 400", + " transport: 0", " ip_provisioning_event <", " event_type: 1", " if_name: \"wlan0\"", " latency_ms: 5678", " >", - " time_ms: 400", ">", "events <", " time_ms: 500", + " transport: 0", " validation_probe_event <", " latency_ms: 40730", " network_id <", @@ -182,6 +212,8 @@ public class IpConnectivityMetricsTest extends TestCase { " >", ">", "events <", + " time_ms: 600", + " transport: 0", " apf_statistics <", " dropped_ras: 2", " duration_ms: 45000", @@ -192,9 +224,10 @@ public class IpConnectivityMetricsTest extends TestCase { " received_ras: 10", " zero_lifetime_ras: 1", " >", - " time_ms: 600", ">", "events <", + " time_ms: 700", + " transport: 0", " ra_event <", " dnssl_lifetime: -1", " prefix_preferred_lifetime: 300", @@ -203,8 +236,8 @@ public class IpConnectivityMetricsTest extends TestCase { " route_info_lifetime: -1", " router_lifetime: 2000", " >", - " time_ms: 700", - ">"); + ">", + "version: 2"); verifySerialization(want, getdump("flush")); } @@ -231,8 +264,7 @@ public class IpConnectivityMetricsTest extends TestCase { try { byte[] got = Base64.decode(output, Base64.DEFAULT); IpConnectivityLogClass.IpConnectivityLog log = - new IpConnectivityLogClass.IpConnectivityLog(); - MessageNano.mergeFrom(log, got); + IpConnectivityLogClass.IpConnectivityLog.parseFrom(got); assertEquals(want, log.toString()); } catch (Exception e) { fail(e.toString()); @@ -260,10 +292,5 @@ public class IpConnectivityMetricsTest extends TestCase { } static final Comparator<ConnectivityMetricsEvent> EVENT_COMPARATOR = - new Comparator<ConnectivityMetricsEvent>() { - @Override - public int compare(ConnectivityMetricsEvent ev1, ConnectivityMetricsEvent ev2) { - return (int) (ev1.timestamp - ev2.timestamp); - } - }; + Comparator.comparingLong((ev) -> ev.timestamp); } diff --git a/services/tests/servicestests/src/com/android/server/connectivity/NetdEventListenerServiceTest.java b/services/tests/servicestests/src/com/android/server/connectivity/NetdEventListenerServiceTest.java new file mode 100644 index 000000000000..97afa60f0c6d --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/connectivity/NetdEventListenerServiceTest.java @@ -0,0 +1,326 @@ +/* + * Copyright (C) 2016, 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.connectivity; + +import android.net.ConnectivityManager; +import android.net.ConnectivityManager.NetworkCallback; +import android.net.Network; +import android.net.metrics.DnsEvent; +import android.net.metrics.INetdEventListener; +import android.net.metrics.IpConnectivityLog; +import android.os.RemoteException; +import android.system.OsConstants; +import android.test.suitebuilder.annotation.SmallTest; +import com.android.server.connectivity.metrics.IpConnectivityLogClass.IpConnectivityEvent; +import java.io.FileOutputStream; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.OptionalInt; +import java.util.stream.IntStream; +import junit.framework.TestCase; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +public class NetdEventListenerServiceTest extends TestCase { + + // TODO: read from NetdEventListenerService after this constant is read from system property + static final int BATCH_SIZE = 100; + static final int EVENT_TYPE = INetdEventListener.EVENT_GETADDRINFO; + // TODO: read from INetdEventListener + static final int RETURN_CODE = 1; + + static final byte[] EVENT_TYPES = new byte[BATCH_SIZE]; + static final byte[] RETURN_CODES = new byte[BATCH_SIZE]; + static final int[] LATENCIES = new int[BATCH_SIZE]; + static { + for (int i = 0; i < BATCH_SIZE; i++) { + EVENT_TYPES[i] = EVENT_TYPE; + RETURN_CODES[i] = RETURN_CODE; + LATENCIES[i] = i; + } + } + + private static final String EXAMPLE_IPV4 = "192.0.2.1"; + private static final String EXAMPLE_IPV6 = "2001:db8:1200::2:1"; + + NetdEventListenerService mNetdEventListenerService; + + @Mock ConnectivityManager mCm; + @Mock IpConnectivityLog mLog; + ArgumentCaptor<NetworkCallback> mCallbackCaptor; + ArgumentCaptor<DnsEvent> mDnsEvCaptor; + + public void setUp() { + MockitoAnnotations.initMocks(this); + mCallbackCaptor = ArgumentCaptor.forClass(NetworkCallback.class); + mDnsEvCaptor = ArgumentCaptor.forClass(DnsEvent.class); + mNetdEventListenerService = new NetdEventListenerService(mCm, mLog); + + verify(mCm, times(1)).registerNetworkCallback(any(), mCallbackCaptor.capture()); + } + + @SmallTest + public void testOneDnsBatch() throws Exception { + log(105, LATENCIES); + log(106, Arrays.copyOf(LATENCIES, BATCH_SIZE - 1)); // one lookup short of a batch event + + verifyLoggedDnsEvents(new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES)); + + log(106, Arrays.copyOfRange(LATENCIES, BATCH_SIZE - 1, BATCH_SIZE)); + + mDnsEvCaptor = ArgumentCaptor.forClass(DnsEvent.class); // reset argument captor + verifyLoggedDnsEvents( + new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES), + new DnsEvent(106, EVENT_TYPES, RETURN_CODES, LATENCIES)); + } + + @SmallTest + public void testSeveralDmsBatches() throws Exception { + log(105, LATENCIES); + log(106, LATENCIES); + log(105, LATENCIES); + log(107, LATENCIES); + + verifyLoggedDnsEvents( + new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES), + new DnsEvent(106, EVENT_TYPES, RETURN_CODES, LATENCIES), + new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES), + new DnsEvent(107, EVENT_TYPES, RETURN_CODES, LATENCIES)); + } + + @SmallTest + public void testDnsBatchAndNetworkLost() throws Exception { + byte[] eventTypes = Arrays.copyOf(EVENT_TYPES, 20); + byte[] returnCodes = Arrays.copyOf(RETURN_CODES, 20); + int[] latencies = Arrays.copyOf(LATENCIES, 20); + + log(105, LATENCIES); + log(105, latencies); + mCallbackCaptor.getValue().onLost(new Network(105)); + log(105, LATENCIES); + + verifyLoggedDnsEvents( + new DnsEvent(105, eventTypes, returnCodes, latencies), + new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES), + new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES)); + } + + @SmallTest + public void testConcurrentDnsBatchesAndDumps() throws Exception { + final long stop = System.currentTimeMillis() + 100; + final PrintWriter pw = new PrintWriter(new FileOutputStream("/dev/null")); + new Thread() { + public void run() { + while (System.currentTimeMillis() < stop) { + mNetdEventListenerService.dump(pw); + } + } + }.start(); + + logDnsAsync(105, LATENCIES); + logDnsAsync(106, LATENCIES); + logDnsAsync(107, LATENCIES); + + verifyLoggedDnsEvents(500, + new DnsEvent(105, EVENT_TYPES, RETURN_CODES, LATENCIES), + new DnsEvent(106, EVENT_TYPES, RETURN_CODES, LATENCIES), + new DnsEvent(107, EVENT_TYPES, RETURN_CODES, LATENCIES)); + } + + @SmallTest + public void testConcurrentDnsBatchesAndNetworkLoss() throws Exception { + logDnsAsync(105, LATENCIES); + Thread.sleep(10L); + // call onLost() asynchronously to logDnsAsync's onDnsEvent() calls. + mCallbackCaptor.getValue().onLost(new Network(105)); + + // do not verify unpredictable batch + verify(mLog, timeout(500).times(1)).log(any()); + } + + @SmallTest + public void testConnectLogging() throws Exception { + final int OK = 0; + Thread[] logActions = { + // ignored + connectEventAction(OsConstants.EALREADY, 0, EXAMPLE_IPV4), + connectEventAction(OsConstants.EALREADY, 0, EXAMPLE_IPV6), + connectEventAction(OsConstants.EINPROGRESS, 0, EXAMPLE_IPV4), + connectEventAction(OsConstants.EINPROGRESS, 0, EXAMPLE_IPV6), + connectEventAction(OsConstants.EINPROGRESS, 0, EXAMPLE_IPV6), + // valid latencies + connectEventAction(OK, 110, EXAMPLE_IPV4), + connectEventAction(OK, 23, EXAMPLE_IPV4), + connectEventAction(OK, 45, EXAMPLE_IPV4), + connectEventAction(OK, 56, EXAMPLE_IPV4), + connectEventAction(OK, 523, EXAMPLE_IPV6), + connectEventAction(OK, 214, EXAMPLE_IPV6), + connectEventAction(OK, 67, EXAMPLE_IPV6), + // errors + connectEventAction(OsConstants.EPERM, 0, EXAMPLE_IPV4), + connectEventAction(OsConstants.EPERM, 0, EXAMPLE_IPV4), + connectEventAction(OsConstants.EAGAIN, 0, EXAMPLE_IPV4), + connectEventAction(OsConstants.EACCES, 0, EXAMPLE_IPV4), + connectEventAction(OsConstants.EACCES, 0, EXAMPLE_IPV4), + connectEventAction(OsConstants.EACCES, 0, EXAMPLE_IPV6), + connectEventAction(OsConstants.EADDRINUSE, 0, EXAMPLE_IPV4), + connectEventAction(OsConstants.ETIMEDOUT, 0, EXAMPLE_IPV4), + connectEventAction(OsConstants.ETIMEDOUT, 0, EXAMPLE_IPV6), + connectEventAction(OsConstants.ETIMEDOUT, 0, EXAMPLE_IPV6), + connectEventAction(OsConstants.ECONNREFUSED, 0, EXAMPLE_IPV4), + }; + + for (Thread t : logActions) { + t.start(); + } + for (Thread t : logActions) { + t.join(); + } + + List<IpConnectivityEvent> events = new ArrayList<>(); + mNetdEventListenerService.flushStatistics(events); + + IpConnectivityEvent got = events.get(0); + String want = joinLines( + "time_ms: 0", + "transport: 0", + "connect_statistics <", + " connect_count: 12", + " errnos_counters <", + " key: 1", + " value: 2", + " >", + " errnos_counters <", + " key: 11", + " value: 1", + " >", + " errnos_counters <", + " key: 13", + " value: 3", + " >", + " errnos_counters <", + " key: 98", + " value: 1", + " >", + " errnos_counters <", + " key: 110", + " value: 3", + " >", + " errnos_counters <", + " key: 111", + " value: 1", + " >", + " ipv6_addr_count: 6", + " latencies_ms: 23", + " latencies_ms: 45", + " latencies_ms: 56", + " latencies_ms: 67", + " latencies_ms: 110", + " latencies_ms: 214", + " latencies_ms: 523"); + verifyConnectEvent(want, got); + } + + Thread connectEventAction(int error, int latencyMs, String ipAddr) { + return new Thread(() -> { + try { + mNetdEventListenerService.onConnectEvent(100, error, latencyMs, ipAddr, 80, 1); + } catch (Exception e) { + fail(e.toString()); + } + }); + } + + void log(int netId, int[] latencies) { + try { + for (int l : latencies) { + mNetdEventListenerService.onDnsEvent(netId, EVENT_TYPE, RETURN_CODE, l, null, null, + 0, 0); + } + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } + + void logDnsAsync(int netId, int[] latencies) { + new Thread() { + public void run() { + log(netId, latencies); + } + }.start(); + } + + void verifyLoggedDnsEvents(DnsEvent... expected) { + verifyLoggedDnsEvents(0, expected); + } + + void verifyLoggedDnsEvents(int wait, DnsEvent... expectedEvents) { + verify(mLog, timeout(wait).times(expectedEvents.length)).log(mDnsEvCaptor.capture()); + for (DnsEvent got : mDnsEvCaptor.getAllValues()) { + OptionalInt index = IntStream.range(0, expectedEvents.length) + .filter(i -> dnsEventsEqual(expectedEvents[i], got)) + .findFirst(); + // Don't match same expected event more than once. + index.ifPresent(i -> expectedEvents[i] = null); + assertTrue(index.isPresent()); + } + } + + /** equality function for DnsEvent to avoid overriding equals() and hashCode(). */ + static boolean dnsEventsEqual(DnsEvent expected, DnsEvent got) { + return (expected == got) || ((expected != null) && (got != null) + && (expected.netId == got.netId) + && Arrays.equals(expected.eventTypes, got.eventTypes) + && Arrays.equals(expected.returnCodes, got.returnCodes) + && Arrays.equals(expected.latenciesMs, got.latenciesMs)); + } + + static String joinLines(String ... elems) { + StringBuilder b = new StringBuilder(); + for (String s : elems) { + b.append(s).append("\n"); + } + return b.toString(); + } + + static void verifyConnectEvent(String expected, IpConnectivityEvent got) { + try { + Arrays.sort(got.connectStatistics.latenciesMs); + Arrays.sort(got.connectStatistics.errnosCounters, + Comparator.comparingInt((p) -> p.key)); + assertEquals(expected, got.toString()); + } catch (Exception e) { + fail(e.toString()); + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/connectivity/NetworkNotificationManagerTest.java b/services/tests/servicestests/src/com/android/server/connectivity/NetworkNotificationManagerTest.java new file mode 100644 index 000000000000..21c2de79d68b --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/connectivity/NetworkNotificationManagerTest.java @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2016 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.connectivity; + +import android.app.Notification; +import android.app.NotificationManager; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.net.NetworkCapabilities; +import android.net.NetworkInfo; +import android.telephony.TelephonyManager; +import android.test.suitebuilder.annotation.SmallTest; +import com.android.server.connectivity.NetworkNotificationManager.NotificationType; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import junit.framework.TestCase; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +import static com.android.server.connectivity.NetworkNotificationManager.NotificationType.*; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class NetworkNotificationManagerTest extends TestCase { + + static final NetworkCapabilities CELL_CAPABILITIES = new NetworkCapabilities(); + static final NetworkCapabilities WIFI_CAPABILITIES = new NetworkCapabilities(); + static { + CELL_CAPABILITIES.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR); + CELL_CAPABILITIES.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); + + WIFI_CAPABILITIES.addTransportType(NetworkCapabilities.TRANSPORT_WIFI); + WIFI_CAPABILITIES.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET); + } + + @Mock Context mCtx; + @Mock Resources mResources; + @Mock PackageManager mPm; + @Mock TelephonyManager mTelephonyManager; + @Mock NotificationManager mNotificationManager; + @Mock NetworkAgentInfo mWifiNai; + @Mock NetworkAgentInfo mCellNai; + @Mock NetworkInfo mNetworkInfo; + ArgumentCaptor<Notification> mCaptor; + + NetworkNotificationManager mManager; + + public void setUp() { + MockitoAnnotations.initMocks(this); + mCaptor = ArgumentCaptor.forClass(Notification.class); + mWifiNai.networkCapabilities = WIFI_CAPABILITIES; + mWifiNai.networkInfo = mNetworkInfo; + mCellNai.networkCapabilities = CELL_CAPABILITIES; + mCellNai.networkInfo = mNetworkInfo; + when(mCtx.getResources()).thenReturn(mResources); + when(mCtx.getPackageManager()).thenReturn(mPm); + when(mCtx.getApplicationInfo()).thenReturn(new ApplicationInfo()); + when(mNetworkInfo.getExtraInfo()).thenReturn("extra"); + when(mResources.getColor(anyInt(), any())).thenReturn(0xFF607D8B); + + mManager = new NetworkNotificationManager(mCtx, mTelephonyManager, mNotificationManager); + } + + @SmallTest + public void testNotificationsShownAndCleared() { + final int NETWORK_ID_BASE = 100; + List<NotificationType> types = Arrays.asList(NotificationType.values()); + List<Integer> ids = new ArrayList<>(types.size()); + for (int i = 0; i < ids.size(); i++) { + ids.add(NETWORK_ID_BASE + i); + } + Collections.shuffle(ids); + Collections.shuffle(types); + + for (int i = 0; i < ids.size(); i++) { + mManager.showNotification(ids.get(i), types.get(i), mWifiNai, mCellNai, null, false); + } + + Collections.shuffle(ids); + for (int i = 0; i < ids.size(); i++) { + mManager.clearNotification(ids.get(i)); + } + + for (int i = 0; i < ids.size(); i++) { + final int id = ids.get(i); + final int eventId = types.get(i).eventId; + final String tag = NetworkNotificationManager.tagFor(id); + verify(mNotificationManager, times(1)).notifyAsUser(eq(tag), eq(eventId), any(), any()); + verify(mNotificationManager, times(1)).cancelAsUser(eq(tag), eq(eventId), any()); + } + } + + @SmallTest + public void testNoInternetNotificationsNotShownForCellular() { + mManager.showNotification(100, NO_INTERNET, mCellNai, mWifiNai, null, false); + mManager.showNotification(101, LOST_INTERNET, mCellNai, mWifiNai, null, false); + + verify(mNotificationManager, never()).notifyAsUser(any(), anyInt(), any(), any()); + + mManager.showNotification(102, NO_INTERNET, mWifiNai, mCellNai, null, false); + + final int eventId = NO_INTERNET.eventId; + final String tag = NetworkNotificationManager.tagFor(102); + verify(mNotificationManager, times(1)).notifyAsUser(eq(tag), eq(eventId), any(), any()); + } + + @SmallTest + public void testNotificationsNotShownIfNoInternetCapability() { + mWifiNai.networkCapabilities = new NetworkCapabilities(); + mWifiNai.networkCapabilities .addTransportType(NetworkCapabilities.TRANSPORT_WIFI); + mManager.showNotification(102, NO_INTERNET, mWifiNai, mCellNai, null, false); + mManager.showNotification(103, LOST_INTERNET, mWifiNai, mCellNai, null, false); + mManager.showNotification(104, NETWORK_SWITCH, mWifiNai, mCellNai, null, false); + + verify(mNotificationManager, never()).notifyAsUser(any(), anyInt(), any(), any()); + } +} diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java index 0783afc0dfd4..956d83a2caf8 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmMockContext.java @@ -16,8 +16,15 @@ package com.android.server.devicepolicy; -import com.android.internal.widget.LockPatternUtils; +import static org.mockito.Matchers.anyBoolean; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; +import android.accounts.Account; +import android.accounts.AccountManager; import android.app.IActivityManager; import android.app.NotificationManager; import android.app.backup.IBackupManager; @@ -44,6 +51,8 @@ import android.test.mock.MockContentResolver; import android.test.mock.MockContext; import android.view.IWindowManager; +import com.android.internal.widget.LockPatternUtils; + import org.junit.Assert; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; @@ -52,13 +61,6 @@ import java.io.File; import java.util.ArrayList; import java.util.List; -import static org.mockito.Matchers.anyBoolean; -import static org.mockito.Matchers.anyInt; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.when; - /** * Context used throughout DPMS tests. */ @@ -264,6 +266,7 @@ public class DpmMockContext extends MockContext { public final SettingsForMock settings; public final MockContentResolver contentResolver; public final TelephonyManager telephonyManager; + public final AccountManager accountManager; /** Note this is a partial mock, not a real mock. */ public final PackageManager packageManager; @@ -298,6 +301,7 @@ public class DpmMockContext extends MockContext { wifiManager = mock(WifiManager.class); settings = mock(SettingsForMock.class); telephonyManager = mock(TelephonyManager.class); + accountManager = mock(AccountManager.class); // Package manager is huge, so we use a partial mock instead. packageManager = spy(context.getPackageManager()); @@ -360,6 +364,7 @@ public class DpmMockContext extends MockContext { } } ); + when(accountManager.getAccountsAsUser(anyInt())).thenReturn(new Account[0]); // Create a data directory. @@ -418,6 +423,8 @@ public class DpmMockContext extends MockContext { return powerManager; case Context.WIFI_SERVICE: return wifiManager; + case Context.ACCOUNT_SERVICE: + return accountManager; } throw new UnsupportedOperationException(); } diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/NetworkEventTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/NetworkEventTest.java new file mode 100644 index 000000000000..315d37cbd662 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/NetworkEventTest.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2016 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.devicepolicy; + +import android.app.admin.ConnectEvent; +import android.app.admin.DnsEvent; +import android.os.Parcel; +import android.test.suitebuilder.annotation.SmallTest; + +import static junit.framework.Assert.assertEquals; + +@SmallTest +public class NetworkEventTest extends DpmTestBase { + + /** + * Test parceling and unparceling of a ConnectEvent. + */ + public void testConnectEventParceling() { + ConnectEvent event = new ConnectEvent("127.0.0.1", 80, "com.android.whateverdude", 100000); + Parcel p = Parcel.obtain(); + p.writeParcelable(event, 0); + p.setDataPosition(0); + ConnectEvent unparceledEvent = p.readParcelable(NetworkEventTest.class.getClassLoader()); + p.recycle(); + assertEquals(event.getIpAddress(), unparceledEvent.getIpAddress()); + assertEquals(event.getPort(), unparceledEvent.getPort()); + assertEquals(event.getPackageName(), unparceledEvent.getPackageName()); + assertEquals(event.getTimestamp(), unparceledEvent.getTimestamp()); + } + + /** + * Test parceling and unparceling of a DnsEvent. + */ + public void testDnsEventParceling() { + DnsEvent event = new DnsEvent("d.android.com", new String[]{"192.168.0.1", "127.0.0.1"}, 2, + "com.android.whateverdude", 100000); + Parcel p = Parcel.obtain(); + p.writeParcelable(event, 0); + p.setDataPosition(0); + DnsEvent unparceledEvent = p.readParcelable(NetworkEventTest.class.getClassLoader()); + p.recycle(); + assertEquals(event.getHostname(), unparceledEvent.getHostname()); + assertEquals(event.getIpAddresses()[0], unparceledEvent.getIpAddresses()[0]); + assertEquals(event.getIpAddresses()[1], unparceledEvent.getIpAddresses()[1]); + assertEquals(event.getIpAddressesCount(), unparceledEvent.getIpAddressesCount()); + assertEquals(event.getPackageName(), unparceledEvent.getPackageName()); + assertEquals(event.getTimestamp(), unparceledEvent.getTimestamp()); + } +} diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java index 3cfdc329a971..cd48f36ade52 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java @@ -4938,6 +4938,9 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { assertEquals(0, mManager.getDynamicShortcuts().size()); assertEquals(0, mManager.getPinnedShortcuts().size()); }); + assertFalse(mService.getPackageShortcutForTest(CALLING_PACKAGE_1, USER_0) + .getPackageInfo().isShadow()); + installPackage(USER_0, CALLING_PACKAGE_2); runWithCaller(CALLING_PACKAGE_2, USER_0, () -> { @@ -4946,6 +4949,8 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { mManager.getPinnedShortcuts()), "s1", "s2", "s3"); }); + assertFalse(mService.getPackageShortcutForTest(CALLING_PACKAGE_2, USER_0) + .getPackageInfo().isShadow()); installPackage(USER_0, LAUNCHER_1); runWithCaller(LAUNCHER_1, USER_0, () -> { @@ -5069,6 +5074,8 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0)) /* empty */); }); + assertFalse(mService.getLauncherShortcutForTest(LAUNCHER_1, USER_0) + .getPackageInfo().isShadow()); runWithCaller(CALLING_PACKAGE_1, USER_0, () -> { assertEquals(0, mManager.getDynamicShortcuts().size()); @@ -5091,6 +5098,8 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { mLauncherApps.getShortcuts(buildAllQuery(CALLING_PACKAGE_3), HANDLE_USER_0)) /* empty */); }); + assertFalse(mService.getLauncherShortcutForTest(LAUNCHER_2, USER_0) + .getPackageInfo().isShadow()); installPackage(USER_0, CALLING_PACKAGE_3); runWithCaller(CALLING_PACKAGE_3, USER_0, () -> { diff --git a/services/tests/servicestests/src/com/android/server/storage/AppCollectorTest.java b/services/tests/servicestests/src/com/android/server/storage/AppCollectorTest.java new file mode 100644 index 000000000000..29185e92d5ed --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/storage/AppCollectorTest.java @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2016 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.storage; + +import android.content.pm.UserInfo; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.IPackageStatsObserver; +import android.content.pm.PackageManager; +import android.content.pm.PackageStats; +import android.os.UserManager; +import android.os.storage.VolumeInfo; +import android.test.AndroidTestCase; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.when; + +@RunWith(JUnit4.class) +public class AppCollectorTest extends AndroidTestCase { + private static final long TIMEOUT = TimeUnit.MINUTES.toMillis(1); + @Mock private Context mContext; + @Mock private PackageManager mPm; + @Mock private UserManager mUm; + private List<ApplicationInfo> mApps; + private List<UserInfo> mUsers; + + @Before + public void setUp() throws Exception { + super.setUp(); + MockitoAnnotations.initMocks(this); + mApps = new ArrayList<>(); + when(mContext.getPackageManager()).thenReturn(mPm); + when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUm); + + // Set up the app list. + when(mPm.getInstalledApplications(anyInt())).thenReturn(mApps); + + // Set up the user list with a single user (0). + mUsers = new ArrayList<>(); + mUsers.add(new UserInfo(0, "", 0)); + when(mUm.getUsers()).thenReturn(mUsers); + } + + @Test + public void testNoApps() throws Exception { + VolumeInfo volume = new VolumeInfo("testuuid", 0, null, null); + volume.fsUuid = "testuuid"; + AppCollector collector = new AppCollector(mContext, volume); + + assertThat(collector.getPackageStats(TIMEOUT)).isEmpty(); + } + + @Test + public void testAppOnExternalVolume() throws Exception { + addApplication("com.test.app", "differentuuid"); + VolumeInfo volume = new VolumeInfo("testuuid", 0, null, null); + volume.fsUuid = "testuuid"; + AppCollector collector = new AppCollector(mContext, volume); + + assertThat(collector.getPackageStats(TIMEOUT)).isEmpty(); + } + + @Test + public void testOneValidApp() throws Exception { + addApplication("com.test.app", "testuuid"); + VolumeInfo volume = new VolumeInfo("testuuid", 0, null, null); + volume.fsUuid = "testuuid"; + AppCollector collector = new AppCollector(mContext, volume); + PackageStats stats = new PackageStats("com.test.app"); + + // Set up this to handle the asynchronous call to the PackageManager. This returns the + // package info for the specified package. + doAnswer(new Answer<Void>() { + @Override + public Void answer(InvocationOnMock invocation) { + try { + ((IPackageStatsObserver.Stub) invocation.getArguments()[2]) + .onGetStatsCompleted(stats, true); + } catch (Exception e) { + // We fail instead of just letting the exception fly because throwing + // out of the callback like this on the background thread causes the test + // runner to crash, rather than reporting the failure. + fail(); + } + return null; + } + }).when(mPm).getPackageSizeInfoAsUser(eq("com.test.app"), eq(0), any()); + + + // Because getPackageStats is a blocking call, we block execution of the test until the + // call finishes. In order to finish the call, we need the above answer to execute. + List<PackageStats> myStats = new ArrayList<>(); + CountDownLatch latch = new CountDownLatch(1); + new Thread(new Runnable() { + @Override + public void run() { + myStats.addAll(collector.getPackageStats(TIMEOUT)); + latch.countDown(); + } + }).start(); + latch.await(); + + assertThat(myStats).containsExactly(stats); + } + + @Test + public void testMultipleUsersOneApp() throws Exception { + addApplication("com.test.app", "testuuid"); + ApplicationInfo otherUsersApp = new ApplicationInfo(); + otherUsersApp.packageName = "com.test.app"; + otherUsersApp.volumeUuid = "testuuid"; + otherUsersApp.uid = 1; + mUsers.add(new UserInfo(1, "", 0)); + + VolumeInfo volume = new VolumeInfo("testuuid", 0, null, null); + volume.fsUuid = "testuuid"; + AppCollector collector = new AppCollector(mContext, volume); + PackageStats stats = new PackageStats("com.test.app"); + PackageStats otherStats = new PackageStats("com.test.app"); + otherStats.userHandle = 1; + + // Set up this to handle the asynchronous call to the PackageManager. This returns the + // package info for our packages. + doAnswer(new Answer<Void>() { + @Override + public Void answer(InvocationOnMock invocation) { + try { + ((IPackageStatsObserver.Stub) invocation.getArguments()[2]) + .onGetStatsCompleted(stats, true); + + // Now callback for the other uid. + ((IPackageStatsObserver.Stub) invocation.getArguments()[2]) + .onGetStatsCompleted(otherStats, true); + } catch (Exception e) { + // We fail instead of just letting the exception fly because throwing + // out of the callback like this on the background thread causes the test + // runner to crash, rather than reporting the failure. + fail(); + } + return null; + } + }).when(mPm).getPackageSizeInfoAsUser(eq("com.test.app"), eq(0), any()); + + + // Because getPackageStats is a blocking call, we block execution of the test until the + // call finishes. In order to finish the call, we need the above answer to execute. + List<PackageStats> myStats = new ArrayList<>(); + CountDownLatch latch = new CountDownLatch(1); + new Thread(new Runnable() { + @Override + public void run() { + myStats.addAll(collector.getPackageStats(TIMEOUT)); + latch.countDown(); + } + }).start(); + latch.await(); + + assertThat(myStats).containsAllOf(stats, otherStats); + } + + @Test(expected=NullPointerException.class) + public void testNullVolumeShouldCauseNPE() throws Exception { + AppCollector collector = new AppCollector(mContext, null); + } + + private void addApplication(String packageName, String uuid) { + ApplicationInfo info = new ApplicationInfo(); + info.packageName = packageName; + info.volumeUuid = uuid; + mApps.add(info); + } + +} diff --git a/services/tests/servicestests/src/com/android/server/storage/DiskStatsFileLoggerTest.java b/services/tests/servicestests/src/com/android/server/storage/DiskStatsFileLoggerTest.java new file mode 100644 index 000000000000..2aca702b809c --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/storage/DiskStatsFileLoggerTest.java @@ -0,0 +1,239 @@ +/* + * Copyright (C) 2016 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.storage; + +import android.content.pm.PackageStats; +import android.test.AndroidTestCase; +import android.util.ArraySet; +import libcore.io.IoUtils; +import org.json.JSONArray; +import org.json.JSONObject; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.io.File; +import java.util.ArrayList; + +import static com.google.common.truth.Truth.assertThat; + +@RunWith(JUnit4.class) +public class DiskStatsFileLoggerTest extends AndroidTestCase { + @Rule public TemporaryFolder temporaryFolder; + public FileCollector.MeasurementResult mMainResult; + public FileCollector.MeasurementResult mDownloadsResult; + private ArrayList<PackageStats> mPackages; + private File mOutputFile; + + @Before + public void setUp() throws Exception { + super.setUp(); + temporaryFolder = new TemporaryFolder(); + temporaryFolder.create(); + mOutputFile = temporaryFolder.newFile(); + mMainResult = new FileCollector.MeasurementResult(); + mDownloadsResult = new FileCollector.MeasurementResult(); + mPackages = new ArrayList<>(); + } + + @Test + public void testEmptyStorage() throws Exception { + DiskStatsFileLogger logger = new DiskStatsFileLogger( + mMainResult, mDownloadsResult,mPackages, 0L); + + logger.dumpToFile(mOutputFile); + + JSONObject output = getOutputFileAsJson(); + assertThat(output.getLong(DiskStatsFileLogger.PHOTOS_KEY)).isEqualTo(0L); + assertThat(output.getLong(DiskStatsFileLogger.VIDEOS_KEY)).isEqualTo(0L); + assertThat(output.getLong(DiskStatsFileLogger.AUDIO_KEY)).isEqualTo(0L); + assertThat(output.getLong(DiskStatsFileLogger.DOWNLOADS_KEY)).isEqualTo(0L); + assertThat(output.getLong(DiskStatsFileLogger.SYSTEM_KEY)).isEqualTo(0L); + assertThat(output.getLong(DiskStatsFileLogger.MISC_KEY)).isEqualTo(0L); + assertThat(output.getLong(DiskStatsFileLogger.APP_SIZE_AGG_KEY)).isEqualTo(0L); + assertThat(output.getLong(DiskStatsFileLogger.APP_CACHE_AGG_KEY)).isEqualTo(0L); + assertThat( + output.getJSONArray(DiskStatsFileLogger.PACKAGE_NAMES_KEY).length()).isEqualTo(0L); + assertThat(output.getJSONArray(DiskStatsFileLogger.APP_SIZES_KEY).length()).isEqualTo(0L); + assertThat(output.getJSONArray(DiskStatsFileLogger.APP_CACHES_KEY).length()).isEqualTo(0L); + } + + @Test + public void testMeasurementResultsReported() throws Exception { + mMainResult.audioSize = 1; + mMainResult.imagesSize = 10; + mMainResult.miscSize = 100; + mDownloadsResult.miscSize = 1000; + DiskStatsFileLogger logger = new DiskStatsFileLogger( + mMainResult, mDownloadsResult,mPackages, 3L); + + logger.dumpToFile(mOutputFile); + + JSONObject output = getOutputFileAsJson(); + assertThat(output.getLong(DiskStatsFileLogger.AUDIO_KEY)).isEqualTo(1L); + assertThat(output.getLong(DiskStatsFileLogger.PHOTOS_KEY)).isEqualTo(10L); + assertThat(output.getLong(DiskStatsFileLogger.MISC_KEY)).isEqualTo(100L); + assertThat(output.getLong(DiskStatsFileLogger.DOWNLOADS_KEY)).isEqualTo(1000L); + assertThat(output.getLong(DiskStatsFileLogger.SYSTEM_KEY)).isEqualTo(3L); + } + + @Test + public void testAppsReported() throws Exception { + PackageStats firstPackage = new PackageStats("com.test.app"); + firstPackage.codeSize = 100; + firstPackage.dataSize = 1000; + firstPackage.cacheSize = 20; + mPackages.add(firstPackage); + + PackageStats secondPackage = new PackageStats("com.test.app2"); + secondPackage.codeSize = 10; + secondPackage.dataSize = 1; + secondPackage.cacheSize = 2; + mPackages.add(secondPackage); + + DiskStatsFileLogger logger = new DiskStatsFileLogger( + mMainResult, mDownloadsResult, mPackages, 0L); + logger.dumpToFile(mOutputFile); + + JSONObject output = getOutputFileAsJson(); + assertThat(output.getLong(DiskStatsFileLogger.APP_SIZE_AGG_KEY)).isEqualTo(1111); + assertThat(output.getLong(DiskStatsFileLogger.APP_CACHE_AGG_KEY)).isEqualTo(22); + + JSONArray packageNames = output.getJSONArray(DiskStatsFileLogger.PACKAGE_NAMES_KEY); + assertThat(packageNames.length()).isEqualTo(2); + JSONArray appSizes = output.getJSONArray(DiskStatsFileLogger.APP_SIZES_KEY); + assertThat(appSizes.length()).isEqualTo(2); + JSONArray cacheSizes = output.getJSONArray(DiskStatsFileLogger.APP_CACHES_KEY); + assertThat(cacheSizes.length()).isEqualTo(2); + + // We need to do this crazy Set over this because the DiskStatsFileLogger provides no + // guarantee of the ordering of the apps in its output. By using a set, we avoid any order + // problems. + ArraySet<AppSizeGrouping> apps = new ArraySet<>(); + for (int i = 0; i < packageNames.length(); i++) { + AppSizeGrouping app = new AppSizeGrouping(packageNames.getString(i), + appSizes.getLong(i), cacheSizes.getLong(i)); + apps.add(app); + } + assertThat(apps).containsAllOf(new AppSizeGrouping("com.test.app", 1100, 20), + new AppSizeGrouping("com.test.app2", 11, 2)); + } + + @Test + public void testEmulatedExternalStorageCounted() throws Exception { + PackageStats app = new PackageStats("com.test.app"); + app.dataSize = 1000; + app.externalDataSize = 1000; + app.cacheSize = 20; + mPackages.add(app); + + DiskStatsFileLogger logger = new DiskStatsFileLogger( + mMainResult, mDownloadsResult, mPackages, 0L); + logger.dumpToFile(mOutputFile); + + JSONObject output = getOutputFileAsJson(); + JSONArray appSizes = output.getJSONArray(DiskStatsFileLogger.APP_SIZES_KEY); + assertThat(appSizes.length()).isEqualTo(1); + assertThat(appSizes.getLong(0)).isEqualTo(2000); + } + + @Test + public void testDuplicatePackageNameIsMergedAcrossMultipleUsers() throws Exception { + PackageStats app = new PackageStats("com.test.app"); + app.dataSize = 1000; + app.externalDataSize = 1000; + app.cacheSize = 20; + app.userHandle = 0; + mPackages.add(app); + + PackageStats secondApp = new PackageStats("com.test.app"); + secondApp.dataSize = 100; + secondApp.externalDataSize = 100; + secondApp.cacheSize = 2; + secondApp.userHandle = 1; + mPackages.add(secondApp); + + DiskStatsFileLogger logger = new DiskStatsFileLogger( + mMainResult, mDownloadsResult, mPackages, 0L); + logger.dumpToFile(mOutputFile); + + JSONObject output = getOutputFileAsJson(); + assertThat(output.getLong(DiskStatsFileLogger.APP_SIZE_AGG_KEY)).isEqualTo(2200); + assertThat(output.getLong(DiskStatsFileLogger.APP_CACHE_AGG_KEY)).isEqualTo(22); + JSONArray packageNames = output.getJSONArray(DiskStatsFileLogger.PACKAGE_NAMES_KEY); + assertThat(packageNames.length()).isEqualTo(1); + assertThat(packageNames.getString(0)).isEqualTo("com.test.app"); + + JSONArray appSizes = output.getJSONArray(DiskStatsFileLogger.APP_SIZES_KEY); + assertThat(appSizes.length()).isEqualTo(1); + assertThat(appSizes.getLong(0)).isEqualTo(2200); + + JSONArray cacheSizes = output.getJSONArray(DiskStatsFileLogger.APP_CACHES_KEY); + assertThat(cacheSizes.length()).isEqualTo(1); + assertThat(cacheSizes.getLong(0)).isEqualTo(22); + } + + private JSONObject getOutputFileAsJson() throws Exception { + return new JSONObject(IoUtils.readFileAsString(mOutputFile.getAbsolutePath())); + } + + /** + * This class exists for putting zipped app size information arrays into a set for comparison + * purposes. + */ + private class AppSizeGrouping { + public String packageName; + public long appSize; + public long cacheSize; + + public AppSizeGrouping(String packageName, long appSize, long cacheSize) { + this.packageName = packageName; + this.appSize = appSize; + this.cacheSize = cacheSize; + } + + @Override + public int hashCode() { + int result = 17; + result = 37 * result + (int)(appSize ^ (appSize >>> 32)); + result = 37 * result + (int)(cacheSize ^ (cacheSize >>> 32)); + result = 37 * result + packageName.hashCode(); + return result; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof AppSizeGrouping)) { + return false; + } + if (this == o) { + return true; + } + AppSizeGrouping grouping = (AppSizeGrouping) o; + return packageName.equals(grouping.packageName) && appSize == grouping.appSize && + cacheSize == grouping.cacheSize; + } + + @Override + public String toString() { + return packageName + " " + appSize + " " + cacheSize; + } + } +}
\ No newline at end of file diff --git a/services/tests/servicestests/src/com/android/server/storage/DiskStatsLoggingServiceTest.java b/services/tests/servicestests/src/com/android/server/storage/DiskStatsLoggingServiceTest.java new file mode 100644 index 000000000000..378908655cb0 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/storage/DiskStatsLoggingServiceTest.java @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2016 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.storage; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.job.JobService; +import android.app.job.JobParameters; +import android.content.pm.PackageStats; +import android.test.AndroidTestCase; + +import com.android.server.storage.DiskStatsLoggingService.LogRunnable; + +import libcore.io.IoUtils; + +import org.json.JSONObject; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.io.File; +import java.io.PrintStream; +import java.util.ArrayList; + +@RunWith(JUnit4.class) +public class DiskStatsLoggingServiceTest extends AndroidTestCase { + @Rule public TemporaryFolder mTemporaryFolder; + @Rule public TemporaryFolder mDownloads; + @Rule public TemporaryFolder mRootDirectory; + @Mock private AppCollector mCollector; + private File mInputFile; + + + @Before + public void setUp() throws Exception { + super.setUp(); + MockitoAnnotations.initMocks(this); + mTemporaryFolder = new TemporaryFolder(); + mTemporaryFolder.create(); + mInputFile = mTemporaryFolder.newFile(); + mDownloads = new TemporaryFolder(); + mDownloads.create(); + mRootDirectory = new TemporaryFolder(); + mRootDirectory.create(); + } + + @Test + public void testEmptyLog() throws Exception { + LogRunnable task = new LogRunnable(); + task.setAppCollector(mCollector); + task.setDownloadsDirectory(mDownloads.getRoot()); + task.setRootDirectory(mRootDirectory.getRoot()); + task.setLogOutputFile(mInputFile); + task.setSystemSize(0L); + task.run(); + + JSONObject json = getJsonOutput(); + assertThat(json.getLong(DiskStatsFileLogger.PHOTOS_KEY)).isEqualTo(0L); + assertThat(json.getLong(DiskStatsFileLogger.VIDEOS_KEY)).isEqualTo(0L); + assertThat(json.getLong(DiskStatsFileLogger.AUDIO_KEY)).isEqualTo(0L); + assertThat(json.getLong(DiskStatsFileLogger.DOWNLOADS_KEY)).isEqualTo(0L); + assertThat(json.getLong(DiskStatsFileLogger.SYSTEM_KEY)).isEqualTo(0L); + assertThat(json.getLong(DiskStatsFileLogger.MISC_KEY)).isEqualTo(0L); + assertThat(json.getLong(DiskStatsFileLogger.APP_SIZE_AGG_KEY)).isEqualTo(0L); + assertThat(json.getLong(DiskStatsFileLogger.APP_CACHE_AGG_KEY)).isEqualTo(0L); + assertThat( + json.getJSONArray(DiskStatsFileLogger.PACKAGE_NAMES_KEY).length()).isEqualTo(0L); + assertThat(json.getJSONArray(DiskStatsFileLogger.APP_SIZES_KEY).length()).isEqualTo(0L); + assertThat(json.getJSONArray(DiskStatsFileLogger.APP_CACHES_KEY).length()).isEqualTo(0L); + } + + @Test + public void testPopulatedLogTask() throws Exception { + // Write data to directories. + writeDataToFile(mDownloads.newFile(), "lol"); + writeDataToFile(mRootDirectory.newFile("test.jpg"), "1234"); + writeDataToFile(mRootDirectory.newFile("test.mp4"), "12345"); + writeDataToFile(mRootDirectory.newFile("test.mp3"), "123456"); + writeDataToFile(mRootDirectory.newFile("test.whatever"), "1234567"); + + // Write apps. + ArrayList<PackageStats> apps = new ArrayList<>(); + PackageStats testApp = new PackageStats("com.test.app"); + testApp.dataSize = 5L; + testApp.cacheSize = 55L; + testApp.codeSize = 10L; + apps.add(testApp); + when(mCollector.getPackageStats(anyInt())).thenReturn(apps); + + LogRunnable task = new LogRunnable(); + task.setAppCollector(mCollector); + task.setDownloadsDirectory(mDownloads.getRoot()); + task.setRootDirectory(mRootDirectory.getRoot()); + task.setLogOutputFile(mInputFile); + task.setSystemSize(10L); + task.run(); + + JSONObject json = getJsonOutput(); + assertThat(json.getLong(DiskStatsFileLogger.PHOTOS_KEY)).isEqualTo(4L); + assertThat(json.getLong(DiskStatsFileLogger.VIDEOS_KEY)).isEqualTo(5L); + assertThat(json.getLong(DiskStatsFileLogger.AUDIO_KEY)).isEqualTo(6L); + assertThat(json.getLong(DiskStatsFileLogger.DOWNLOADS_KEY)).isEqualTo(3L); + assertThat(json.getLong(DiskStatsFileLogger.SYSTEM_KEY)).isEqualTo(10L); + assertThat(json.getLong(DiskStatsFileLogger.MISC_KEY)).isEqualTo(7L); + assertThat(json.getLong(DiskStatsFileLogger.APP_SIZE_AGG_KEY)).isEqualTo(15L); + assertThat(json.getLong(DiskStatsFileLogger.APP_CACHE_AGG_KEY)).isEqualTo(55L); + assertThat( + json.getJSONArray(DiskStatsFileLogger.PACKAGE_NAMES_KEY).length()).isEqualTo(1L); + assertThat(json.getJSONArray(DiskStatsFileLogger.APP_SIZES_KEY).length()).isEqualTo(1L); + assertThat(json.getJSONArray(DiskStatsFileLogger.APP_CACHES_KEY).length()).isEqualTo(1L); + } + + @Test + public void testDontCrashOnPackageStatsTimeout() throws Exception { + when(mCollector.getPackageStats(anyInt())).thenReturn(null); + + LogRunnable task = new LogRunnable(); + task.setAppCollector(mCollector); + task.setDownloadsDirectory(mDownloads.getRoot()); + task.setRootDirectory(mRootDirectory.getRoot()); + task.setLogOutputFile(mInputFile); + task.setSystemSize(10L); + task.run(); + + // No exception should be thrown. + } + + private void writeDataToFile(File f, String data) throws Exception{ + PrintStream out = new PrintStream(f); + out.print(data); + out.close(); + } + + private JSONObject getJsonOutput() throws Exception { + return new JSONObject(IoUtils.readFileAsString(mInputFile.getAbsolutePath())); + } +} diff --git a/services/tests/servicestests/src/com/android/server/storage/FileCollectorTest.java b/services/tests/servicestests/src/com/android/server/storage/FileCollectorTest.java new file mode 100644 index 000000000000..f1b3442f1cf7 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/storage/FileCollectorTest.java @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2016 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.storage; + +import android.test.AndroidTestCase; +import com.android.server.storage.FileCollector.MeasurementResult; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.io.File; +import java.io.PrintStream; + +import static com.google.common.truth.Truth.assertThat; + +@RunWith(JUnit4.class) +public class FileCollectorTest extends AndroidTestCase { + @Rule + public TemporaryFolder temporaryFolder; + + @Before + public void setUp() throws Exception { + temporaryFolder = new TemporaryFolder(); + temporaryFolder.create(); + } + + @Test + public void testEmpty() throws Exception { + MeasurementResult result = FileCollector.getMeasurementResult(temporaryFolder.getRoot()); + assertThat(result.totalAccountedSize()).isEqualTo(0L); + } + + @Test + public void testImageFile() throws Exception { + writeDataToFile(temporaryFolder.newFile("test.jpg"), "1234"); + + MeasurementResult result = FileCollector.getMeasurementResult(temporaryFolder.getRoot()); + + assertThat(result.imagesSize).isEqualTo(4); + } + + @Test + public void testVideoFile() throws Exception { + writeDataToFile(temporaryFolder.newFile("test.mp4"), "1234"); + + MeasurementResult result = FileCollector.getMeasurementResult(temporaryFolder.getRoot()); + + assertThat(result.videosSize).isEqualTo(4); + } + + @Test + public void testAudioFile() throws Exception { + writeDataToFile(temporaryFolder.newFile("test.mp3"), "1234"); + + MeasurementResult result = FileCollector.getMeasurementResult(temporaryFolder.getRoot()); + + assertThat(result.audioSize).isEqualTo(4); + } + + @Test + public void testMiscFile() throws Exception { + writeDataToFile(temporaryFolder.newFile("test"), "1234"); + + MeasurementResult result = FileCollector.getMeasurementResult(temporaryFolder.getRoot()); + + assertThat(result.miscSize).isEqualTo(4); + } + + @Test + public void testNestedFile() throws Exception { + File directory = temporaryFolder.newFolder(); + writeDataToFile(new File(directory, "test"), "1234"); + + MeasurementResult result = FileCollector.getMeasurementResult(temporaryFolder.getRoot()); + + assertThat(result.miscSize).isEqualTo(4); + } + + @Test + public void testMultipleFiles() throws Exception { + writeDataToFile(temporaryFolder.newFile("test"), "1234"); + writeDataToFile(temporaryFolder.newFile("test2"), "12345"); + + MeasurementResult result = FileCollector.getMeasurementResult(temporaryFolder.getRoot()); + + assertThat(result.miscSize).isEqualTo(9); + } + + @Test + public void testTotalSize() throws Exception { + writeDataToFile(temporaryFolder.newFile("test.jpg"), "1"); + writeDataToFile(temporaryFolder.newFile("test.mp3"), "1"); + writeDataToFile(temporaryFolder.newFile("test.mp4"), "1"); + writeDataToFile(temporaryFolder.newFile("test"), "1"); + + MeasurementResult result = FileCollector.getMeasurementResult(temporaryFolder.getRoot()); + + assertThat(result.totalAccountedSize()).isEqualTo(4); + } + + @Test + public void testFileEndsWithPeriod() throws Exception { + writeDataToFile(temporaryFolder.newFile("test."), "1"); + + MeasurementResult result = FileCollector.getMeasurementResult(temporaryFolder.getRoot()); + + assertThat(result.miscSize).isEqualTo(1); + assertThat(result.totalAccountedSize()).isEqualTo(1); + } + + public void testIgnoreFileExtensionCase() throws Exception { + writeDataToFile(temporaryFolder.newFile("test.JpG"), "1234"); + + MeasurementResult result = FileCollector.getMeasurementResult(temporaryFolder.getRoot()); + + assertThat(result.imagesSize).isEqualTo(4); + } + + private void writeDataToFile(File f, String data) throws Exception{ + PrintStream out = new PrintStream(f); + out.print(data); + out.close(); + } +} diff --git a/services/tests/servicestests/src/com/android/server/twilight/TwilightServiceTest.java b/services/tests/servicestests/src/com/android/server/twilight/TwilightServiceTest.java new file mode 100644 index 000000000000..751e4b56be1e --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/twilight/TwilightServiceTest.java @@ -0,0 +1,71 @@ +package com.android.server.twilight; + +/* + * Copyright (C) 2016 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. + */ + +import android.app.AlarmManager; +import android.content.Context; +import android.location.Location; +import android.test.AndroidTestCase; + +public class TwilightServiceTest extends AndroidTestCase { + + private TwilightService mTwilightService; + private Location mInitialLocation; + + @Override + protected void setUp() throws Exception { + final Context context = getContext(); + mTwilightService = new TwilightService(context); + mTwilightService.mAlarmManager = + (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); + + mInitialLocation = createMockLocation(10.0, 10.0); + mTwilightService.onLocationChanged(mInitialLocation); + } + + @Override + protected void tearDown() throws Exception { + mTwilightService = null; + mInitialLocation = null; + } + + public void testValidLocation_updatedLocation() { + final TwilightState priorState = mTwilightService.mLastTwilightState; + final Location validLocation = createMockLocation(35.0, 35.0); + mTwilightService.onLocationChanged(validLocation); + assertEquals(mTwilightService.mLastLocation, validLocation); + assertNotSame(priorState, mTwilightService.mLastTwilightState); + } + + public void testInvalidLocation_ignoreLocationUpdate() { + final TwilightState priorState = mTwilightService.mLastTwilightState; + final Location invalidLocation = createMockLocation(0.0, 0.0); + mTwilightService.onLocationChanged(invalidLocation); + assertEquals(mTwilightService.mLastLocation, mInitialLocation); + assertEquals(priorState, mTwilightService.mLastTwilightState); + } + + private Location createMockLocation(double latitude, double longitude) { + // There's no empty constructor, so we initialize with a string and quickly reset it. + final Location location = new Location(""); + location.reset(); + location.setLatitude(latitude); + location.setLongitude(longitude); + return location; + } + +} diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java index 8560651fa7ec..49084088e75b 100644 --- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java +++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java @@ -32,6 +32,7 @@ import android.hardware.usb.UsbAccessory; import android.hardware.usb.UsbManager; import android.hardware.usb.UsbPort; import android.hardware.usb.UsbPortStatus; +import android.os.BatteryManager; import android.os.FileUtils; import android.os.Handler; import android.os.Looper; @@ -109,9 +110,9 @@ public class UsbDeviceManager { private static final int MSG_SYSTEM_READY = 3; private static final int MSG_BOOT_COMPLETED = 4; private static final int MSG_USER_SWITCHED = 5; - private static final int MSG_SET_USB_DATA_UNLOCKED = 6; - private static final int MSG_UPDATE_USER_RESTRICTIONS = 7; - private static final int MSG_UPDATE_HOST_STATE = 8; + private static final int MSG_UPDATE_USER_RESTRICTIONS = 6; + private static final int MSG_UPDATE_HOST_STATE = 7; + private static final int MSG_UPDATE_CHARGING_STATE = 9; private static final int AUDIO_MODE_SOURCE = 1; @@ -192,6 +193,15 @@ public class UsbDeviceManager { } }; + private final BroadcastReceiver mChargingReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + int chargePlug = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1); + boolean usbCharging = chargePlug == BatteryManager.BATTERY_PLUGGED_USB; + mHandler.sendMessage(MSG_UPDATE_CHARGING_STATE, usbCharging); + } + }; + public UsbDeviceManager(Context context, UsbAlsaManager alsaManager) { mContext = context; mUsbAlsaManager = alsaManager; @@ -216,6 +226,8 @@ public class UsbDeviceManager { } mContext.registerReceiver(mHostReceiver, new IntentFilter(UsbManager.ACTION_USB_PORT_CHANGED)); + mContext.registerReceiver(mChargingReceiver, + new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); } private UsbSettingsManager getCurrentSettings() { @@ -288,7 +300,7 @@ public class UsbDeviceManager { if (functions != null) { mAccessoryModeRequestTime = SystemClock.elapsedRealtime(); - setCurrentFunctions(functions); + setCurrentFunctions(functions, false); } } @@ -330,6 +342,7 @@ public class UsbDeviceManager { private int mUsbNotificationId; private boolean mAdbNotificationShown; private int mCurrentUser = UserHandle.USER_NULL; + private boolean mUsbCharging; public UsbHandler(Looper looper) { super(looper); @@ -337,14 +350,22 @@ public class UsbDeviceManager { // Restore default functions. mCurrentFunctions = SystemProperties.get(USB_CONFIG_PROPERTY, UsbManager.USB_FUNCTION_NONE); - if (UsbManager.USB_FUNCTION_NONE.equals(mCurrentFunctions)) { - mCurrentFunctions = UsbManager.USB_FUNCTION_MTP; - } mCurrentFunctionsApplied = mCurrentFunctions.equals( SystemProperties.get(USB_STATE_PROPERTY)); mAdbEnabled = UsbManager.containsFunction(getDefaultFunctions(), UsbManager.USB_FUNCTION_ADB); - setEnabledFunctions(null, false); + + /** + * Remove MTP from persistent config, to bring usb to a good state + * after fixes to b/31814300. This block can be removed after the update + */ + String persisted = SystemProperties.get(USB_PERSISTENT_CONFIG_PROPERTY); + if (UsbManager.containsFunction(persisted, UsbManager.USB_FUNCTION_MTP)) { + SystemProperties.set(USB_PERSISTENT_CONFIG_PROPERTY, + UsbManager.removeFunction(persisted, UsbManager.USB_FUNCTION_MTP)); + } + + setEnabledFunctions(null, false, false); String state = FileUtils.readTextFile(new File(STATE_PATH), 0, null).trim(); updateState(state); @@ -376,6 +397,14 @@ public class UsbDeviceManager { sendMessage(m); } + public void sendMessage(int what, Object arg, boolean arg1) { + removeMessages(what); + Message m = Message.obtain(this, what); + m.obj = arg; + m.arg1 = (arg1 ? 1 : 0); + sendMessage(m); + } + public void updateState(String state) { int connected, configured; @@ -414,7 +443,10 @@ public class UsbDeviceManager { args.argi2 = sourcePower ? 1 :0; args.argi3 = sinkPower ? 1 :0; - obtainMessage(MSG_UPDATE_HOST_STATE, args).sendToTarget(); + removeMessages(MSG_UPDATE_HOST_STATE); + Message msg = obtainMessage(MSG_UPDATE_HOST_STATE, args); + // debounce rapid transitions of connect/disconnect on type-c ports + sendMessageDelayed(msg, UPDATE_DELAY); } private boolean waitForState(String state) { @@ -440,29 +472,24 @@ public class UsbDeviceManager { return waitForState(config); } - private void setUsbDataUnlocked(boolean enable) { - if (DEBUG) Slog.d(TAG, "setUsbDataUnlocked: " + enable); - mUsbDataUnlocked = enable; - updateUsbNotification(); - updateUsbStateBroadcastIfNeeded(); - setEnabledFunctions(mCurrentFunctions, true); - } - private void setAdbEnabled(boolean enable) { if (DEBUG) Slog.d(TAG, "setAdbEnabled: " + enable); if (enable != mAdbEnabled) { mAdbEnabled = enable; + String oldFunctions = mCurrentFunctions; + + // Persist the adb setting + String newFunction = applyAdbFunction(SystemProperties.get( + USB_PERSISTENT_CONFIG_PROPERTY, UsbManager.USB_FUNCTION_NONE)); + SystemProperties.set(USB_PERSISTENT_CONFIG_PROPERTY, newFunction); - // Due to the persist.sys.usb.config property trigger, changing adb state requires - // persisting default function - String oldFunctions = getDefaultFunctions(); - String newFunctions = applyAdbFunction(oldFunctions); - if (!oldFunctions.equals(newFunctions)) { - SystemProperties.set(USB_PERSISTENT_CONFIG_PROPERTY, newFunctions); + // Remove mtp from the config if file transfer is not enabled + if (oldFunctions.equals(UsbManager.USB_FUNCTION_MTP) && + !mUsbDataUnlocked && enable) { + oldFunctions = UsbManager.USB_FUNCTION_NONE; } - // After persisting them use the lock-down aware function set - setEnabledFunctions(mCurrentFunctions, false); + setEnabledFunctions(oldFunctions, true, mUsbDataUnlocked); updateAdbNotification(); } @@ -474,10 +501,17 @@ public class UsbDeviceManager { /** * Evaluates USB function policies and applies the change accordingly. */ - private void setEnabledFunctions(String functions, boolean forceRestart) { + private void setEnabledFunctions(String functions, boolean forceRestart, + boolean usbDataUnlocked) { if (DEBUG) Slog.d(TAG, "setEnabledFunctions functions=" + functions + ", " + "forceRestart=" + forceRestart); + if (usbDataUnlocked != mUsbDataUnlocked) { + mUsbDataUnlocked = usbDataUnlocked; + updateUsbNotification(); + forceRestart = true; + } + // Try to set the enabled functions. final String oldFunctions = mCurrentFunctions; final boolean oldFunctionsApplied = mCurrentFunctionsApplied; @@ -514,7 +548,8 @@ public class UsbDeviceManager { } private boolean trySetEnabledFunctions(String functions, boolean forceRestart) { - if (functions == null) { + if (functions == null || applyAdbFunction(functions) + .equals(UsbManager.USB_FUNCTION_NONE)) { functions = getDefaultFunctions(); } functions = applyAdbFunction(functions); @@ -579,7 +614,7 @@ public class UsbDeviceManager { // make sure accessory mode is off // and restore default functions Slog.d(TAG, "exited USB accessory mode"); - setEnabledFunctions(null, false); + setEnabledFunctions(null, false, false); if (mCurrentAccessory != null) { if (mBootCompleted) { @@ -596,10 +631,6 @@ public class UsbDeviceManager { if (mBroadcastedIntent == null) { for (String key : keySet) { if (intent.getBooleanExtra(key, false)) { - // MTP function is enabled by default. - if (UsbManager.USB_FUNCTION_MTP.equals(key)) { - continue; - } return true; } } @@ -708,14 +739,12 @@ public class UsbDeviceManager { @Override public void handleMessage(Message msg) { + SomeArgs args; switch (msg.what) { case MSG_UPDATE_STATE: mConnected = (msg.arg1 == 1); mConfigured = (msg.arg2 == 1); - if (!mConnected) { - // When a disconnect occurs, relock access to sensitive user data - mUsbDataUnlocked = false; - } + updateUsbNotification(); updateAdbNotification(); if (UsbManager.containsFunction(mCurrentFunctions, @@ -723,7 +752,7 @@ public class UsbDeviceManager { updateCurrentAccessory(); } else if (!mConnected) { // restore defaults when USB is disconnected - setEnabledFunctions(null, false); + setEnabledFunctions(null, false, false); } if (mBootCompleted) { updateUsbStateBroadcastIfNeeded(); @@ -731,7 +760,7 @@ public class UsbDeviceManager { } break; case MSG_UPDATE_HOST_STATE: - SomeArgs args = (SomeArgs) msg.obj; + args = (SomeArgs) msg.obj; mHostConnected = (args.argi1 == 1); mSourcePower = (args.argi2 == 1); mSinkPower = (args.argi3 == 1); @@ -741,18 +770,19 @@ public class UsbDeviceManager { updateUsbStateBroadcastIfNeeded(); } break; + case MSG_UPDATE_CHARGING_STATE: + mUsbCharging = (msg.arg1 == 1); + updateUsbNotification(); + break; case MSG_ENABLE_ADB: setAdbEnabled(msg.arg1 == 1); break; case MSG_SET_CURRENT_FUNCTIONS: String functions = (String)msg.obj; - setEnabledFunctions(functions, false); + setEnabledFunctions(functions, false, msg.arg1 == 1); break; case MSG_UPDATE_USER_RESTRICTIONS: - setEnabledFunctions(mCurrentFunctions, false); - break; - case MSG_SET_USB_DATA_UNLOCKED: - setUsbDataUnlocked(msg.arg1 == 1); + setEnabledFunctions(mCurrentFunctions, false, mUsbDataUnlocked); break; case MSG_SYSTEM_READY: updateUsbNotification(); @@ -780,8 +810,7 @@ public class UsbDeviceManager { Slog.v(TAG, "Current user switched to " + mCurrentUser + "; resetting USB host stack for MTP or PTP"); // avoid leaking sensitive data from previous user - mUsbDataUnlocked = false; - setEnabledFunctions(mCurrentFunctions, true); + setEnabledFunctions(mCurrentFunctions, true, false); } mCurrentUser = msg.arg1; } @@ -825,7 +854,7 @@ public class UsbDeviceManager { } } else if (mSourcePower) { id = com.android.internal.R.string.usb_supplying_notification_title; - } else if (mHostConnected && mSinkPower) { + } else if (mHostConnected && mSinkPower && mUsbCharging) { id = com.android.internal.R.string.usb_charging_notification_title; } if (id != mUsbNotificationId) { @@ -929,6 +958,7 @@ public class UsbDeviceManager { pw.println(" mHostConnected: " + mHostConnected); pw.println(" mSourcePower: " + mSourcePower); pw.println(" mSinkPower: " + mSinkPower); + pw.println(" mUsbCharging: " + mUsbCharging); try { pw.println(" Kernel state: " + FileUtils.readTextFile(new File(STATE_PATH), 0, null).trim()); @@ -965,14 +995,10 @@ public class UsbDeviceManager { return UsbManager.containsFunction(SystemProperties.get(USB_CONFIG_PROPERTY), function); } - public void setCurrentFunctions(String functions) { - if (DEBUG) Slog.d(TAG, "setCurrentFunctions(" + functions + ")"); - mHandler.sendMessage(MSG_SET_CURRENT_FUNCTIONS, functions); - } - - public void setUsbDataUnlocked(boolean unlocked) { - if (DEBUG) Slog.d(TAG, "setUsbDataUnlocked(" + unlocked + ")"); - mHandler.sendMessage(MSG_SET_USB_DATA_UNLOCKED, unlocked); + public void setCurrentFunctions(String functions, boolean usbDataUnlocked) { + if (DEBUG) Slog.d(TAG, "setCurrentFunctions(" + functions + ", " + + usbDataUnlocked + ")"); + mHandler.sendMessage(MSG_SET_CURRENT_FUNCTIONS, functions, usbDataUnlocked); } private void readOemUsbOverrideConfig() { diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java index d6dbe90584f5..daccc00ad80c 100644 --- a/services/usb/java/com/android/server/usb/UsbService.java +++ b/services/usb/java/com/android/server/usb/UsbService.java @@ -287,7 +287,7 @@ public class UsbService extends IUsbManager.Stub { } @Override - public void setCurrentFunction(String function) { + public void setCurrentFunction(String function, boolean usbDataUnlocked) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); if (!isSupportedCurrentFunction(function)) { @@ -297,7 +297,7 @@ public class UsbService extends IUsbManager.Stub { } if (mDeviceManager != null) { - mDeviceManager.setCurrentFunctions(function); + mDeviceManager.setCurrentFunctions(function, usbDataUnlocked); } else { throw new IllegalStateException("USB device mode not supported"); } @@ -320,12 +320,6 @@ public class UsbService extends IUsbManager.Stub { } @Override - public void setUsbDataUnlocked(boolean unlocked) { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); - mDeviceManager.setUsbDataUnlocked(unlocked); - } - - @Override public void allowUsbDebugging(boolean alwaysAllow, String publicKey) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); mDeviceManager.allowUsbDebugging(alwaysAllow, publicKey); |