Merge "Checkpoint staged session when state changes."
diff --git a/api/current.txt b/api/current.txt
index 584aedf..80fea7b 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -29941,12 +29941,16 @@
method public android.net.NetworkSpecifier build();
method public android.net.wifi.aware.WifiAwareManager.NetworkSpecifierBuilder setDiscoverySession(android.net.wifi.aware.DiscoverySession);
method public android.net.wifi.aware.WifiAwareManager.NetworkSpecifierBuilder setPeerHandle(android.net.wifi.aware.PeerHandle);
+ method public android.net.wifi.aware.WifiAwareManager.NetworkSpecifierBuilder setPort(int);
method public android.net.wifi.aware.WifiAwareManager.NetworkSpecifierBuilder setPskPassphrase(java.lang.String);
+ method public android.net.wifi.aware.WifiAwareManager.NetworkSpecifierBuilder setTransportProtocol(int);
}
public final class WifiAwareNetworkInfo implements android.os.Parcelable android.net.TransportInfo {
method public int describeContents();
method public java.net.Inet6Address getPeerIpv6Addr();
+ method public int getPort();
+ method public int getTransportProtocol();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.net.wifi.aware.WifiAwareNetworkInfo> CREATOR;
}
@@ -44332,6 +44336,7 @@
method public int describeContents();
method public int getCdmaDbm();
method public int getCdmaEcio();
+ method public java.util.List<android.telephony.CellSignalStrength> getCellSignalStrengths();
method public int getEvdoDbm();
method public int getEvdoEcio();
method public int getEvdoSnr();
@@ -44774,6 +44779,9 @@
public static abstract class TelephonyManager.CellInfoCallback {
ctor public TelephonyManager.CellInfoCallback();
method public abstract void onCellInfo(java.util.List<android.telephony.CellInfo>);
+ method public void onError(int, java.lang.Throwable);
+ field public static final int ERROR_MODEM_ERROR = 2; // 0x2
+ field public static final int ERROR_TIMEOUT = 1; // 0x1
}
public static abstract class TelephonyManager.UssdResponseCallback {
diff --git a/api/system-current.txt b/api/system-current.txt
index 9a1c804..2428b90 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -1301,6 +1301,26 @@
}
+package android.content.om {
+
+ public final class OverlayInfo implements android.os.Parcelable {
+ method public int describeContents();
+ method public boolean isEnabled();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.content.om.OverlayInfo> CREATOR;
+ field public final java.lang.String category;
+ field public final java.lang.String packageName;
+ field public final java.lang.String targetPackageName;
+ field public final int userId;
+ }
+
+ public class OverlayManager {
+ method public java.util.List<android.content.om.OverlayInfo> getOverlayInfosForTarget(java.lang.String, int);
+ method public boolean setEnabledExclusiveInCategory(java.lang.String, int);
+ }
+
+}
+
package android.content.pm {
public class ApplicationInfo extends android.content.pm.PackageItemInfo implements android.os.Parcelable {
@@ -4860,6 +4880,14 @@
field public static final android.os.Parcelable.Creator<android.permission.RuntimePermissionPresentationInfo> CREATOR;
}
+ public final class RuntimePermissionUsageInfo implements android.os.Parcelable {
+ ctor public RuntimePermissionUsageInfo(java.lang.CharSequence, int);
+ method public int describeContents();
+ method public java.lang.CharSequence getName();
+ method public int getAppAccessCount();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.permission.RuntimePermissionUsageInfo> CREATOR;
+ }
}
package android.permissionpresenterservice {
@@ -4981,6 +5009,7 @@
method public static void removeOnPropertyChangedListener(android.provider.DeviceConfig.OnPropertyChangedListener);
method public static void resetToDefaults(int, java.lang.String);
method public static boolean setProperty(java.lang.String, java.lang.String, java.lang.String, boolean);
+ field public static final java.lang.String NAMESPACE_INPUT_NATIVE_BOOT = "input_native_boot";
}
public static abstract interface DeviceConfig.OnPropertyChangedListener {
diff --git a/api/test-current.txt b/api/test-current.txt
index 55705b6..6d40e69 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -1252,6 +1252,71 @@
}
+package android.service.autofill.augmented {
+
+ public abstract class AugmentedAutofillService extends android.app.Service {
+ ctor public AugmentedAutofillService();
+ method protected final void dump(java.io.FileDescriptor, java.io.PrintWriter, java.lang.String[]);
+ method protected void dump(java.io.PrintWriter, java.lang.String[]);
+ method public void onFillRequest(android.service.autofill.augmented.FillRequest, android.os.CancellationSignal, android.service.autofill.augmented.FillController, android.service.autofill.augmented.FillCallback);
+ field public static final java.lang.String SERVICE_INTERFACE = "android.service.autofill.augmented.AugmentedAutofillService";
+ }
+
+ public final class FillCallback {
+ method public void onSuccess(android.service.autofill.augmented.FillResponse);
+ }
+
+ public final class FillController {
+ method public void autofill(java.util.List<android.util.Pair<android.view.autofill.AutofillId, android.view.autofill.AutofillValue>>);
+ }
+
+ public final class FillRequest {
+ method public android.content.ComponentName getActivityComponent();
+ method public android.view.autofill.AutofillId getFocusedId();
+ method public android.view.autofill.AutofillValue getFocusedValue();
+ method public android.service.autofill.augmented.PresentationParams getPresentationParams();
+ method public int getTaskId();
+ }
+
+ public final class FillResponse implements android.os.Parcelable {
+ method public int describeContents();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.service.autofill.augmented.FillResponse> CREATOR;
+ }
+
+ public static final class FillResponse.Builder {
+ ctor public FillResponse.Builder();
+ method public android.service.autofill.augmented.FillResponse build();
+ method public android.service.autofill.augmented.FillResponse.Builder setFillWindow(android.service.autofill.augmented.FillWindow);
+ method public android.service.autofill.augmented.FillResponse.Builder setIgnoredIds(java.util.List<android.view.autofill.AutofillId>);
+ }
+
+ public final class FillWindow implements java.lang.AutoCloseable {
+ ctor public FillWindow();
+ method public void destroy();
+ method public boolean update(android.service.autofill.augmented.PresentationParams.Area, android.view.View, long);
+ field public static final long FLAG_METADATA_ADDRESS = 1L; // 0x1L
+ }
+
+ public abstract class PresentationParams {
+ method public int getFlags();
+ method public android.service.autofill.augmented.PresentationParams.Area getFullArea();
+ method public android.service.autofill.augmented.PresentationParams.Area getSuggestionArea();
+ field public static final int FLAG_HINT_GRAVITY_BOTTOM = 2; // 0x2
+ field public static final int FLAG_HINT_GRAVITY_LEFT = 4; // 0x4
+ field public static final int FLAG_HINT_GRAVITY_RIGHT = 8; // 0x8
+ field public static final int FLAG_HINT_GRAVITY_TOP = 1; // 0x1
+ field public static final int FLAG_HOST_IME = 16; // 0x10
+ field public static final int FLAG_HOST_SYSTEM = 32; // 0x20
+ }
+
+ public static abstract class PresentationParams.Area {
+ method public android.graphics.Rect getBounds();
+ method public android.service.autofill.augmented.PresentationParams.Area getSubArea(android.graphics.Rect);
+ }
+
+}
+
package android.service.notification {
public final class Adjustment implements android.os.Parcelable {
@@ -1853,6 +1918,10 @@
ctor public AutofillId(android.view.autofill.AutofillId, int);
}
+ public final class AutofillManager {
+ field public static final int MAX_TEMP_AUGMENTED_SERVICE_DURATION_MS = 120000; // 0x1d4c0
+ }
+
}
package android.view.inputmethod {
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 786d8d1..860e40d 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -174,7 +174,7 @@
WifiEnabledStateChanged wifi_enabled_state_changed = 113;
WifiRunningStateChanged wifi_running_state_changed = 114;
AppCompacted app_compacted = 115;
- NetworkDnsEventReported network_dns_event_Reported = 116;
+ NetworkDnsEventReported network_dns_event_reported = 116;
DocsUIPickerLaunchedFromReported docs_ui_picker_launched_from_reported = 117;
DocsUIPickResultReported docs_ui_pick_result_reported = 118;
DocsUISearchModeReported docs_ui_search_mode_reported = 119;
@@ -3802,7 +3802,7 @@
//bionic/libc/include/netdb.h
//system/netd/resolv/include/netd_resolv/resolv.h
enum ReturnCode {
- EAI_NOERR = 0;
+ EAI_NO_ERR = 0;
EAI_ADDRFAMILY = 1;
EAI_AGAIN = 2;
EAI_BADFLAGS = 3;
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 347670d..98c5a0fb 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -1023,7 +1023,9 @@
*
* @return The content capture manager
*/
- @NonNull private ContentCaptureManager getContentCaptureManager() {
+ @Nullable private ContentCaptureManager getContentCaptureManager() {
+ // ContextCapture disabled for system apps
+ if (getApplicationInfo().isSystemApp()) return null;
if (mContentCaptureManager == null) {
mContentCaptureManager = getSystemService(ContentCaptureManager.class);
}
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index d827f6c..3adafd72 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -45,6 +45,8 @@
import android.content.Context;
import android.content.IRestrictionsManager;
import android.content.RestrictionsManager;
+import android.content.om.IOverlayManager;
+import android.content.om.OverlayManager;
import android.content.pm.CrossProfileApps;
import android.content.pm.ICrossProfileApps;
import android.content.pm.IShortcutService;
@@ -1053,6 +1055,14 @@
return new ShortcutManager(ctx, IShortcutService.Stub.asInterface(b));
}});
+ registerService(Context.OVERLAY_SERVICE, OverlayManager.class,
+ new CachedServiceFetcher<OverlayManager>() {
+ @Override
+ public OverlayManager createService(ContextImpl ctx) throws ServiceNotFoundException {
+ IBinder b = ServiceManager.getServiceOrThrow(Context.OVERLAY_SERVICE);
+ return new OverlayManager(ctx, IOverlayManager.Stub.asInterface(b));
+ }});
+
registerService(Context.NETWORK_WATCHLIST_SERVICE, NetworkWatchlistManager.class,
new CachedServiceFetcher<NetworkWatchlistManager>() {
@Override
diff --git a/core/java/android/content/om/OverlayInfo.java b/core/java/android/content/om/OverlayInfo.java
index dd55003..1989f06 100644
--- a/core/java/android/content/om/OverlayInfo.java
+++ b/core/java/android/content/om/OverlayInfo.java
@@ -18,8 +18,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
-import android.annotation.UnsupportedAppUsage;
-import android.os.Build;
+import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
@@ -32,8 +31,10 @@
*
* @hide
*/
+@SystemApi
public final class OverlayInfo implements Parcelable {
+ /** @hide */
@IntDef(prefix = "STATE_", value = {
STATE_UNKNOWN,
STATE_MISSING_TARGET,
@@ -44,6 +45,7 @@
STATE_TARGET_UPGRADING,
STATE_OVERLAY_UPGRADING,
})
+ /** @hide */
@Retention(RetentionPolicy.SOURCE)
public @interface State {}
@@ -52,17 +54,23 @@
* objects exposed outside the {@link
* com.android.server.om.OverlayManagerService} should never have this
* state.
+ *
+ * @hide
*/
public static final int STATE_UNKNOWN = -1;
/**
* The target package of the overlay is not installed. The overlay cannot be enabled.
+ *
+ * @hide
*/
public static final int STATE_MISSING_TARGET = 0;
/**
* Creation of idmap file failed (e.g. no matching resources). The overlay
* cannot be enabled.
+ *
+ * @hide
*/
public static final int STATE_NO_IDMAP = 1;
@@ -70,6 +78,7 @@
* The overlay is currently disabled. It can be enabled.
*
* @see IOverlayManager#setEnabled
+ * @hide
*/
public static final int STATE_DISABLED = 2;
@@ -77,18 +86,21 @@
* The overlay is currently enabled. It can be disabled.
*
* @see IOverlayManager#setEnabled
+ * @hide
*/
public static final int STATE_ENABLED = 3;
/**
* The target package is currently being upgraded; the state will change
* once the package installation has finished.
+ * @hide
*/
public static final int STATE_TARGET_UPGRADING = 4;
/**
* The overlay package is currently being upgraded; the state will change
* once the package installation has finished.
+ * @hide
*/
public static final int STATE_OVERLAY_UPGRADING = 5;
@@ -96,6 +108,7 @@
* The overlay package is currently enabled because it is marked as
* 'static'. It cannot be disabled but will change state if for instance
* its target is uninstalled.
+ * @hide
*/
public static final int STATE_ENABLED_STATIC = 6;
@@ -103,40 +116,52 @@
* Overlay category: theme.
* <p>
* Change how Android (including the status bar, dialogs, ...) looks.
+ *
+ * @hide
*/
public static final String CATEGORY_THEME = "android.theme";
/**
* Package name of the overlay package
+ *
+ * @hide
*/
- @UnsupportedAppUsage
+ @SystemApi
public final String packageName;
/**
* Package name of the target package
+ *
+ * @hide
*/
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+ @SystemApi
public final String targetPackageName;
/**
* Category of the overlay package
+ *
+ * @hide
*/
+ @SystemApi
public final String category;
/**
* Full path to the base APK for this overlay package
+ * @hide
*/
public final String baseCodePath;
/**
* The state of this OverlayInfo as defined by the STATE_* constants in this class.
+ * @hide
*/
- @UnsupportedAppUsage
public final @State int state;
/**
* User handle for which this overlay applies
+ * @hide
*/
+ @SystemApi
public final int userId;
/**
@@ -161,12 +186,15 @@
*
* @param source the source OverlayInfo to base the new instance on
* @param state the new state for the source OverlayInfo
+ *
+ * @hide
*/
public OverlayInfo(@NonNull OverlayInfo source, @State int state) {
this(source.packageName, source.targetPackageName, source.category, source.baseCodePath,
state, source.userId, source.priority, source.isStatic);
}
+ /** @hide */
public OverlayInfo(@NonNull String packageName, @NonNull String targetPackageName,
@NonNull String category, @NonNull String baseCodePath, int state, int userId,
int priority, boolean isStatic) {
@@ -181,6 +209,7 @@
ensureValidState();
}
+ /** @hide */
public OverlayInfo(Parcel source) {
packageName = source.readString();
targetPackageName = source.readString();
@@ -255,8 +284,9 @@
* Disabled overlay packages are installed but are currently not in use.
*
* @return true if the overlay is enabled, else false.
+ * @hide
*/
- @UnsupportedAppUsage
+ @SystemApi
public boolean isEnabled() {
switch (state) {
case STATE_ENABLED:
@@ -272,6 +302,7 @@
* debugging purposes.
*
* @return a human readable String representing the state.
+ * @hide
*/
public static String stateToString(@State int state) {
switch (state) {
diff --git a/core/java/android/content/om/OverlayManager.java b/core/java/android/content/om/OverlayManager.java
new file mode 100644
index 0000000..7a2220bf
--- /dev/null
+++ b/core/java/android/content/om/OverlayManager.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.om;
+
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.content.Context;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+
+import java.util.List;
+
+/**
+ * Updates OverlayManager state; gets information about installed overlay packages.
+ * @hide
+ */
+@SystemApi
+@SystemService(Context.OVERLAY_SERVICE)
+public class OverlayManager {
+
+ private final IOverlayManager mService;
+ private final Context mContext;
+
+ /**
+ * Creates a new instance.
+ *
+ * @param context The current context in which to operate.
+ * @param service The backing system service.
+ *
+ * @hide
+ */
+ public OverlayManager(Context context, IOverlayManager service) {
+ mContext = context;
+ mService = service;
+ }
+
+ /** @hide */
+ public OverlayManager(Context context) {
+ this(context, IOverlayManager.Stub.asInterface(
+ ServiceManager.getService(Context.OVERLAY_SERVICE)));
+ }
+ /**
+ * Request that an overlay package is enabled and any other overlay packages with the same
+ * target package and category are disabled.
+ *
+ * @param packageName the name of the overlay package to enable.
+ * @param userId The user for which to change the overlay.
+ * @return true if the system successfully registered the request, false otherwise.
+ *
+ * @hide
+ */
+ @SystemApi
+ public boolean setEnabledExclusiveInCategory(@Nullable final String packageName,
+ int userId) {
+ try {
+ return mService.setEnabledExclusiveInCategory(packageName, userId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns information about all overlays for the given target package for
+ * the specified user. The returned list is ordered according to the
+ * overlay priority with the highest priority at the end of the list.
+ *
+ * @param targetPackageName The name of the target package.
+ * @param userId The user to get the OverlayInfos for.
+ * @return A list of OverlayInfo objects; if no overlays exist for the
+ * requested package, an empty list is returned.
+ *
+ * @hide
+ */
+ @SystemApi
+ public List<OverlayInfo> getOverlayInfosForTarget(@Nullable final String targetPackageName,
+ int userId) {
+ try {
+ return mService.getOverlayInfosForTarget(targetPackageName, userId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+}
diff --git a/core/java/android/hardware/hdmi/HdmiControlManager.java b/core/java/android/hardware/hdmi/HdmiControlManager.java
index b520d2c..f5d288e 100644
--- a/core/java/android/hardware/hdmi/HdmiControlManager.java
+++ b/core/java/android/hardware/hdmi/HdmiControlManager.java
@@ -16,6 +16,8 @@
package android.hardware.hdmi;
+import static com.android.internal.os.RoSystemProperties.PROPERTY_HDMI_IS_DEVICE_HDMI_CEC_SWITCH;
+
import android.annotation.Nullable;
import android.annotation.RequiresFeature;
import android.annotation.RequiresPermission;
@@ -27,6 +29,7 @@
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.RemoteException;
+import android.os.SystemProperties;
import android.util.ArrayMap;
import android.util.Log;
@@ -264,6 +267,10 @@
private final boolean mHasTvDevice;
// True if we have a logical device of type audio system hosted in the system.
private final boolean mHasAudioSystemDevice;
+ // True if we have a logical device of type audio system hosted in the system.
+ private final boolean mHasSwitchDevice;
+ // True if it's a switch device.
+ private final boolean mIsSwitchDevice;
/**
* {@hide} - hide this constructor because it has a parameter of type IHdmiControlService,
@@ -283,6 +290,9 @@
mHasTvDevice = hasDeviceType(types, HdmiDeviceInfo.DEVICE_TV);
mHasPlaybackDevice = hasDeviceType(types, HdmiDeviceInfo.DEVICE_PLAYBACK);
mHasAudioSystemDevice = hasDeviceType(types, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
+ mHasSwitchDevice = hasDeviceType(types, HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH);
+ mIsSwitchDevice = SystemProperties.getBoolean(
+ PROPERTY_HDMI_IS_DEVICE_HDMI_CEC_SWITCH, false);
}
private static boolean hasDeviceType(int[] types, int type) {
@@ -319,6 +329,9 @@
return mHasPlaybackDevice ? new HdmiPlaybackClient(mService) : null;
case HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM:
return mHasAudioSystemDevice ? new HdmiAudioSystemClient(mService) : null;
+ case HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH:
+ return (mHasSwitchDevice || mIsSwitchDevice)
+ ? new HdmiSwitchClient(mService) : null;
default:
return null;
}
@@ -373,6 +386,24 @@
}
/**
+ * Gets an object that represents an HDMI-CEC logical device of type switch on the system.
+ *
+ * <p>Used to send HDMI control messages to other devices like TV through HDMI bus. It is also
+ * possible to communicate with other logical devices hosted in the same system if the system is
+ * configured to host more than one type of HDMI-CEC logical devices.
+ *
+ * @return {@link HdmiSwitchClient} instance. {@code null} on failure.
+ *
+ * TODO(b/110094868): unhide for Q
+ * @hide
+ */
+ @Nullable
+ @SuppressLint("Doclava125")
+ public HdmiSwitchClient getSwitchClient() {
+ return (HdmiSwitchClient) getClient(HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH);
+ }
+
+ /**
* Controls standby mode of the system. It will also try to turn on/off the connected devices if
* necessary.
*
diff --git a/core/java/android/hardware/hdmi/HdmiSwitchClient.java b/core/java/android/hardware/hdmi/HdmiSwitchClient.java
new file mode 100644
index 0000000..1ac2973
--- /dev/null
+++ b/core/java/android/hardware/hdmi/HdmiSwitchClient.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2018 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.hardware.hdmi;
+
+import android.annotation.NonNull;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * HdmiSwitchClient represents HDMI-CEC logical device of type Switch in the Android system which
+ * acts as switch.
+ *
+ * <p>HdmiSwitchClient has a CEC device type of HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH,
+ * but it is used by all Android TV devices that have multiple HDMI inputs,
+ * even if it is not a "pure" swicth and has another device type like TV or Player.
+ *
+ * @hide
+ * TODO(b/110094868): unhide and add @SystemApi for Q
+ */
+public class HdmiSwitchClient extends HdmiClient {
+
+ private static final String TAG = "HdmiSwitchClient";
+
+ /* package */ HdmiSwitchClient(IHdmiControlService service) {
+ super(service);
+ }
+
+ private static IHdmiControlCallback getCallbackWrapper(final SelectCallback callback) {
+ return new IHdmiControlCallback.Stub() {
+ @Override
+ public void onComplete(int result) {
+ callback.onComplete(result);
+ }
+ };
+ }
+
+ /** @hide */
+ // TODO(b/110094868): unhide for Q
+ @Override
+ public int getDeviceType() {
+ return HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH;
+ }
+
+ /**
+ * Selects a CEC logical device to be a new active source.
+ *
+ * @param logicalAddress logical address of the device to select
+ * @param callback callback to get the result with
+ * @throws {@link IllegalArgumentException} if the {@code callback} is null
+ *
+ * @hide
+ * TODO(b/110094868): unhide and add @SystemApi for Q
+ */
+ public void deviceSelect(int logicalAddress, @NonNull SelectCallback callback) {
+ if (callback == null) {
+ throw new IllegalArgumentException("callback must not be null.");
+ }
+ try {
+ mService.deviceSelect(logicalAddress, getCallbackWrapper(callback));
+ } catch (RemoteException e) {
+ Log.e(TAG, "failed to select device: ", e);
+ }
+ }
+
+ /**
+ * Selects a HDMI port to be a new route path.
+ *
+ * @param portId HDMI port to select
+ * @param callback callback to get the result with
+ * @throws {@link IllegalArgumentException} if the {@code callback} is null
+ *
+ * @hide
+ * TODO(b/110094868): unhide and add @SystemApi for Q
+ */
+ public void portSelect(int portId, @NonNull SelectCallback callback) {
+ if (callback == null) {
+ throw new IllegalArgumentException("Callback must not be null");
+ }
+ try {
+ mService.portSelect(portId, getCallbackWrapper(callback));
+ } catch (RemoteException e) {
+ Log.e(TAG, "failed to select port: ", e);
+ }
+ }
+
+ /**
+ * Returns all the CEC devices connected to the device.
+ *
+ * <p>This only applies to device with multiple HDMI inputs
+ *
+ * @return list of {@link HdmiDeviceInfo} for connected CEC devices. Empty list is returned if
+ * there is none.
+ *
+ * @hide
+ * TODO(b/110094868): unhide and add @SystemApi for Q
+ */
+ public List<HdmiDeviceInfo> getDeviceList() {
+ try {
+ return mService.getDeviceList();
+ } catch (RemoteException e) {
+ Log.e("TAG", "Failed to call getDeviceList():", e);
+ return Collections.<HdmiDeviceInfo>emptyList();
+ }
+ }
+
+ /**
+ * Callback interface used to get the result of {@link #deviceSelect} or {@link #portSelect}.
+ *
+ * @hide
+ * TODO(b/110094868): unhide and add @SystemApi for Q
+ */
+ public interface SelectCallback {
+
+ /**
+ * Called when the operation is finished.
+ *
+ * @param result the result value of {@link #deviceSelect} or {@link #portSelect}.
+ */
+ void onComplete(int result);
+ }
+}
diff --git a/core/java/android/os/ParcelableException.aidl b/core/java/android/os/ParcelableException.aidl
new file mode 100644
index 0000000..d214922
--- /dev/null
+++ b/core/java/android/os/ParcelableException.aidl
@@ -0,0 +1,18 @@
+/* Copyright 2018, 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.os;
+
+parcelable ParcelableException;
diff --git a/core/java/android/permission/IPermissionController.aidl b/core/java/android/permission/IPermissionController.aidl
index 0e18b44..7a7bd83 100644
--- a/core/java/android/permission/IPermissionController.aidl
+++ b/core/java/android/permission/IPermissionController.aidl
@@ -31,4 +31,5 @@
void revokeRuntimePermission(String packageName, String permissionName);
void countPermissionApps(in List<String> permissionNames, boolean countOnlyGranted,
boolean countSystem, in RemoteCallback callback);
+ void getPermissionUsages(boolean countSystem, long numMillis, in RemoteCallback callback);
}
diff --git a/core/java/android/permission/PermissionControllerManager.java b/core/java/android/permission/PermissionControllerManager.java
index d7332ae..0865b62 100644
--- a/core/java/android/permission/PermissionControllerManager.java
+++ b/core/java/android/permission/PermissionControllerManager.java
@@ -18,6 +18,7 @@
import static android.permission.PermissionControllerService.SERVICE_INTERFACE;
+import static com.android.internal.util.Preconditions.checkArgumentNonnegative;
import static com.android.internal.util.Preconditions.checkCollectionElementsNotNull;
import static com.android.internal.util.Preconditions.checkNotNull;
@@ -146,6 +147,20 @@
void onCountPermissionApps(int numApps);
}
+ /**
+ * Callback for delivering the result of {@link #getPermissionUsages}.
+ *
+ * @hide
+ */
+ public interface OnPermissionUsageResultCallback {
+ /**
+ * The result for {@link #getPermissionUsages}.
+ *
+ * @param users The users list.
+ */
+ void onPermissionUsageResult(@NonNull List<RuntimePermissionUsageInfo> users);
+ }
+
private final @NonNull Context mContext;
/**
@@ -264,6 +279,28 @@
}
/**
+ * Count how many apps have used permissions.
+ *
+ * @param countSystem Also count system apps
+ * @param numMillis The number of milliseconds in the past to check for uses
+ * @param executor Executor on which to invoke the callback
+ * @param callback Callback to receive the result
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.GET_RUNTIME_PERMISSIONS)
+ public void getPermissionUsages(boolean countSystem, long numMillis,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OnPermissionUsageResultCallback callback) {
+ checkArgumentNonnegative(numMillis);
+ checkNotNull(executor);
+ checkNotNull(callback);
+
+ sRemoteService.scheduleRequest(new PendingGetPermissionUsagesRequest(sRemoteService,
+ countSystem, numMillis, executor, callback));
+ }
+
+ /**
* A connection to the remote service
*/
static final class RemoteService extends
@@ -326,6 +363,7 @@
private final boolean mDoDryRun;
private final int mReason;
private final @NonNull String mCallingPackage;
+ private final @NonNull Executor mExecutor;
private final @NonNull OnRevokeRuntimePermissionsCallback mCallback;
private final @NonNull RemoteCallback mRemoteCallback;
@@ -341,6 +379,7 @@
mDoDryRun = doDryRun;
mReason = reason;
mCallingPackage = callingPackage;
+ mExecutor = executor;
mCallback = callback;
mRemoteCallback = new RemoteCallback(result -> executor.execute(() -> {
@@ -375,7 +414,13 @@
@Override
protected void onTimeout(RemoteService remoteService) {
- mCallback.onRevokeRuntimePermissions(Collections.emptyMap());
+ long token = Binder.clearCallingIdentity();
+ try {
+ mExecutor.execute(
+ () -> mCallback.onRevokeRuntimePermissions(Collections.emptyMap()));
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
@Override
@@ -521,4 +566,61 @@
}
}
}
+
+ /**
+ * Request for {@link #getPermissionUsages}
+ */
+ private static final class PendingGetPermissionUsagesRequest extends
+ AbstractRemoteService.PendingRequest<RemoteService, IPermissionController> {
+ private final @NonNull OnPermissionUsageResultCallback mCallback;
+ private final boolean mCountSystem;
+ private final long mNumMillis;
+
+ private final @NonNull RemoteCallback mRemoteCallback;
+
+ private PendingGetPermissionUsagesRequest(@NonNull RemoteService service,
+ boolean countSystem, long numMillis, @NonNull @CallbackExecutor Executor executor,
+ @NonNull OnPermissionUsageResultCallback callback) {
+ super(service);
+
+ mCountSystem = countSystem;
+ mNumMillis = numMillis;
+ mCallback = callback;
+
+ mRemoteCallback = new RemoteCallback(result -> executor.execute(() -> {
+ long token = Binder.clearCallingIdentity();
+ try {
+ final List<RuntimePermissionUsageInfo> reportedUsers;
+ List<RuntimePermissionUsageInfo> users = null;
+ if (result != null) {
+ users = result.getParcelableArrayList(KEY_RESULT);
+ } else {
+ users = Collections.emptyList();
+ }
+ reportedUsers = users;
+
+ callback.onPermissionUsageResult(reportedUsers);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+
+ finish();
+ }
+ }), null);
+ }
+
+ @Override
+ protected void onTimeout(RemoteService remoteService) {
+ mCallback.onPermissionUsageResult(Collections.emptyList());
+ }
+
+ @Override
+ public void run() {
+ try {
+ getService().getServiceInterface().getPermissionUsages(mCountSystem, mNumMillis,
+ mRemoteCallback);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error counting permission users", e);
+ }
+ }
+ }
}
diff --git a/core/java/android/permission/PermissionControllerService.java b/core/java/android/permission/PermissionControllerService.java
index f621737..75d61e6 100644
--- a/core/java/android/permission/PermissionControllerService.java
+++ b/core/java/android/permission/PermissionControllerService.java
@@ -17,6 +17,7 @@
package android.permission;
import static com.android.internal.util.Preconditions.checkArgument;
+import static com.android.internal.util.Preconditions.checkArgumentNonnegative;
import static com.android.internal.util.Preconditions.checkCollectionElementsNotNull;
import static com.android.internal.util.Preconditions.checkNotNull;
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
@@ -112,6 +113,17 @@
public abstract int onCountPermissionApps(@NonNull List<String> permissionNames,
boolean countOnlyGranted, boolean countSystem);
+ /**
+ * Count how many apps have used permissions.
+ *
+ * @param countSystem Also count system apps
+ * @param numMillis The number of milliseconds in the past to check for uses
+ *
+ * @return descriptions of the users of permissions
+ */
+ public abstract @NonNull List<RuntimePermissionUsageInfo>
+ onPermissionUsageResult(boolean countSystem, long numMillis);
+
@Override
public final IBinder onBind(Intent intent) {
return new IPermissionController.Stub() {
@@ -187,6 +199,20 @@
PermissionControllerService.this, permissionNames, countOnlyGranted,
countSystem, callback));
}
+
+ @Override
+ public void getPermissionUsages(boolean countSystem, long numMillis,
+ RemoteCallback callback) {
+ checkArgumentNonnegative(numMillis);
+ checkNotNull(callback, "callback");
+
+ enforceCallingPermission(Manifest.permission.GET_RUNTIME_PERMISSIONS, null);
+
+ mHandler.sendMessage(
+ obtainMessage(PermissionControllerService::getPermissionUsages,
+ PermissionControllerService.this, countSystem, numMillis,
+ callback));
+ }
};
}
@@ -230,4 +256,17 @@
result.putInt(PermissionControllerManager.KEY_RESULT, numApps);
callback.sendResult(result);
}
+
+ private void getPermissionUsages(boolean countSystem, long numMillis,
+ @NonNull RemoteCallback callback) {
+ List<RuntimePermissionUsageInfo> users =
+ onPermissionUsageResult(countSystem, numMillis);
+ if (users != null && !users.isEmpty()) {
+ Bundle result = new Bundle();
+ result.putParcelableList(PermissionControllerManager.KEY_RESULT, users);
+ callback.sendResult(result);
+ } else {
+ callback.sendResult(null);
+ }
+ }
}
diff --git a/core/java/android/permission/RuntimePermissionUsageInfo.aidl b/core/java/android/permission/RuntimePermissionUsageInfo.aidl
new file mode 100644
index 0000000..88820dd
--- /dev/null
+++ b/core/java/android/permission/RuntimePermissionUsageInfo.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2019, 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.permission;
+
+parcelable RuntimePermissionUsageInfo;
\ No newline at end of file
diff --git a/core/java/android/permission/RuntimePermissionUsageInfo.java b/core/java/android/permission/RuntimePermissionUsageInfo.java
new file mode 100644
index 0000000..af1a1be
--- /dev/null
+++ b/core/java/android/permission/RuntimePermissionUsageInfo.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2019 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.permission;
+
+import static com.android.internal.util.Preconditions.checkArgumentNonnegative;
+import static com.android.internal.util.Preconditions.checkNotNull;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * This class contains information about how a runtime permission
+ * is used. A single runtime permission presented to the user may
+ * correspond to multiple platform defined permissions, e.g. the
+ * location permission may control both the coarse and fine platform
+ * permissions.
+ *
+ * @hide
+ */
+@SystemApi
+public final class RuntimePermissionUsageInfo implements Parcelable {
+ private final @NonNull CharSequence mName;
+ private final int mNumUsers;
+
+ /**
+ * Creates a new instance.
+ *
+ * @param name The permission group name.
+ * @param numUsers The number of apps that have used this permission.
+ */
+ public RuntimePermissionUsageInfo(@NonNull CharSequence name, int numUsers) {
+ checkNotNull(name);
+ checkArgumentNonnegative(numUsers);
+
+ mName = name;
+ mNumUsers = numUsers;
+ }
+
+ private RuntimePermissionUsageInfo(Parcel parcel) {
+ this(parcel.readCharSequence(), parcel.readInt());
+ }
+
+ /**
+ * @return The number of apps that accessed this permission
+ */
+ public int getAppAccessCount() {
+ return mNumUsers;
+ }
+
+ /**
+ * Gets the permission group name.
+ *
+ * @return The name.
+ */
+ public @NonNull CharSequence getName() {
+ return mName;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel parcel, int flags) {
+ parcel.writeCharSequence(mName);
+ parcel.writeInt(mNumUsers);
+ }
+
+ public static final Creator<RuntimePermissionUsageInfo> CREATOR =
+ new Creator<RuntimePermissionUsageInfo>() {
+ public RuntimePermissionUsageInfo createFromParcel(Parcel source) {
+ return new RuntimePermissionUsageInfo(source);
+ }
+
+ public RuntimePermissionUsageInfo[] newArray(int size) {
+ return new RuntimePermissionUsageInfo[size];
+ }
+ };
+}
diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java
index 4e207ed..c795895 100644
--- a/core/java/android/provider/DeviceConfig.java
+++ b/core/java/android/provider/DeviceConfig.java
@@ -48,6 +48,15 @@
*/
public static final Uri CONTENT_URI = Uri.parse("content://" + Settings.AUTHORITY + "/config");
+ /**
+ * Namespace for all input-related features that are used at the native level.
+ * These features are applied at reboot.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String NAMESPACE_INPUT_NATIVE_BOOT = "input_native_boot";
+
private static final Object sLock = new Object();
@GuardedBy("sLock")
private static Map<OnPropertyChangedListener, Pair<String, Executor>> sListeners =
diff --git a/core/java/android/service/autofill/augmented/AugmentedAutofillService.java b/core/java/android/service/autofill/augmented/AugmentedAutofillService.java
index a9f4034..0116961 100644
--- a/core/java/android/service/autofill/augmented/AugmentedAutofillService.java
+++ b/core/java/android/service/autofill/augmented/AugmentedAutofillService.java
@@ -22,6 +22,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.app.Service;
import android.content.ComponentName;
import android.content.Intent;
@@ -58,6 +59,9 @@
* @hide
*/
@SystemApi
+@TestApi
+// TODO(b/122654591): @TestApi is needed because CtsAutoFillServiceTestCases hosts the service
+// in the same package as the test, and that module is compiled with SDK=test_current
public abstract class AugmentedAutofillService extends Service {
private static final String TAG = AugmentedAutofillService.class.getSimpleName();
@@ -268,7 +272,6 @@
*
* <p>Used to make sure the SmartSuggestionsParams is updated when a new fields is focused.
*/
- // TODO(b/111330312): might not be needed when using IME
@GuardedBy("mLock")
private AutofillId mLastShownId;
diff --git a/core/java/android/service/autofill/augmented/FillCallback.java b/core/java/android/service/autofill/augmented/FillCallback.java
index 620ec59..bfb4aad 100644
--- a/core/java/android/service/autofill/augmented/FillCallback.java
+++ b/core/java/android/service/autofill/augmented/FillCallback.java
@@ -20,6 +20,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.service.autofill.augmented.AugmentedAutofillService.AutofillProxy;
import android.util.Log;
@@ -29,6 +30,9 @@
* @hide
*/
@SystemApi
+@TestApi
+//TODO(b/122654591): @TestApi is needed because CtsAutoFillServiceTestCases hosts the service
+//in the same package as the test, and that module is compiled with SDK=test_current
public final class FillCallback {
private static final String TAG = FillCallback.class.getSimpleName();
diff --git a/core/java/android/service/autofill/augmented/FillController.java b/core/java/android/service/autofill/augmented/FillController.java
index e65cf47..d7bc893 100644
--- a/core/java/android/service/autofill/augmented/FillController.java
+++ b/core/java/android/service/autofill/augmented/FillController.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.os.RemoteException;
import android.service.autofill.augmented.AugmentedAutofillService.AutofillProxy;
import android.util.Log;
@@ -36,6 +37,9 @@
* @hide
*/
@SystemApi
+@TestApi
+//TODO(b/122654591): @TestApi is needed because CtsAutoFillServiceTestCases hosts the service
+//in the same package as the test, and that module is compiled with SDK=test_current
public final class FillController {
private static final String TAG = "FillController";
diff --git a/core/java/android/service/autofill/augmented/FillRequest.java b/core/java/android/service/autofill/augmented/FillRequest.java
index 57d2cc8..dad5067 100644
--- a/core/java/android/service/autofill/augmented/FillRequest.java
+++ b/core/java/android/service/autofill/augmented/FillRequest.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.content.ComponentName;
import android.service.autofill.augmented.AugmentedAutofillService.AutofillProxy;
import android.view.autofill.AutofillId;
@@ -29,6 +30,9 @@
*/
@SystemApi
// TODO(b/111330312): pass a requestId and/or sessionId
+@TestApi
+// TODO(b/122654591): @TestApi is needed because CtsAutoFillServiceTestCases hosts the service
+// in the same package as the test, and that module is compiled with SDK=test_current
public final class FillRequest {
final AutofillProxy mProxy;
diff --git a/core/java/android/service/autofill/augmented/FillResponse.java b/core/java/android/service/autofill/augmented/FillResponse.java
index 1ecfab4..5285132 100644
--- a/core/java/android/service/autofill/augmented/FillResponse.java
+++ b/core/java/android/service/autofill/augmented/FillResponse.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.os.Parcel;
import android.os.Parcelable;
import android.view.autofill.AutofillId;
@@ -30,6 +31,9 @@
* @hide
*/
@SystemApi
+@TestApi
+//TODO(b/122654591): @TestApi is needed because CtsAutoFillServiceTestCases hosts the service
+//in the same package as the test, and that module is compiled with SDK=test_current
public final class FillResponse implements Parcelable {
private final FillWindow mFillWindow;
@@ -50,6 +54,9 @@
* @hide
*/
@SystemApi
+ @TestApi
+ //TODO(b/122654591): @TestApi is needed because CtsAutoFillServiceTestCases hosts the service
+ //in the same package as the test, and that module is compiled with SDK=test_current
public static final class Builder {
private FillWindow mFillWindow;
diff --git a/core/java/android/service/autofill/augmented/FillWindow.java b/core/java/android/service/autofill/augmented/FillWindow.java
index bad7ddd..33b88e42 100644
--- a/core/java/android/service/autofill/augmented/FillWindow.java
+++ b/core/java/android/service/autofill/augmented/FillWindow.java
@@ -20,6 +20,7 @@
import android.annotation.LongDef;
import android.annotation.NonNull;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.app.Dialog;
import android.graphics.Rect;
import android.service.autofill.augmented.AugmentedAutofillService.AutofillProxy;
@@ -61,6 +62,9 @@
* @hide
*/
@SystemApi
+@TestApi
+//TODO(b/122654591): @TestApi is needed because CtsAutoFillServiceTestCases hosts the service
+//in the same package as the test, and that module is compiled with SDK=test_current
public final class FillWindow implements AutoCloseable {
private static final String TAG = "FillWindow";
diff --git a/core/java/android/service/autofill/augmented/PresentationParams.java b/core/java/android/service/autofill/augmented/PresentationParams.java
index 0124ecc..b60064e 100644
--- a/core/java/android/service/autofill/augmented/PresentationParams.java
+++ b/core/java/android/service/autofill/augmented/PresentationParams.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.graphics.Rect;
import android.service.autofill.augmented.AugmentedAutofillService.AutofillProxy;
import android.util.DebugUtils;
@@ -48,6 +49,9 @@
* @hide
*/
@SystemApi
+@TestApi
+//TODO(b/122654591): @TestApi is needed because CtsAutoFillServiceTestCases hosts the service
+//in the same package as the test, and that module is compiled with SDK=test_current
public abstract class PresentationParams {
/**
@@ -147,8 +151,11 @@
* Area associated with a {@link PresentationParams Smart Suggestions} provider.
*
* @hide
- * */
+ */
@SystemApi
+ @TestApi
+ //TODO(b/122654591): @TestApi is needed because CtsAutoFillServiceTestCases hosts the service
+ //in the same package as the test, and that module is compiled with SDK=test_current
public abstract static class Area {
/** @hide */
diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java
index 96ef8ba..ccd0fc1 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -22,6 +22,7 @@
import android.annotation.TestApi;
import android.annotation.UnsupportedAppUsage;
import android.graphics.FrameInfo;
+import android.graphics.Insets;
import android.hardware.display.DisplayManagerGlobal;
import android.os.Build;
import android.os.Handler;
@@ -199,7 +200,7 @@
* @hide
*/
private static final String[] CALLBACK_TRACE_TITLES = {
- "input", "animation", "traversal", "commit"
+ "input", "animation", "insets_animation", "traversal", "commit"
};
/**
@@ -209,18 +210,33 @@
public static final int CALLBACK_INPUT = 0;
/**
- * Callback type: Animation callback. Runs before traversals.
+ * Callback type: Animation callback. Runs before {@link #CALLBACK_INSETS_ANIMATION}.
* @hide
*/
@TestApi
public static final int CALLBACK_ANIMATION = 1;
/**
+ * Callback type: Animation callback to handle inset updates. This is separate from
+ * {@link #CALLBACK_ANIMATION} as we need to "gather" all inset animation updates via
+ * {@link WindowInsetsAnimationController#changeInsets} for multiple ongoing animations but then
+ * update the whole view system with a single callback to {@link View#dispatchWindowInsetsAnimationProgress}
+ * that contains all the combined updated insets.
+ * <p>
+ * Both input and animation may change insets, so we need to run this after these callbacks, but
+ * before traversals.
+ * <p>
+ * Runs before traversals.
+ * @hide
+ */
+ public static final int CALLBACK_INSETS_ANIMATION = 2;
+
+ /**
* Callback type: Traversal callback. Handles layout and draw. Runs
* after all other asynchronous messages have been handled.
* @hide
*/
- public static final int CALLBACK_TRAVERSAL = 2;
+ public static final int CALLBACK_TRAVERSAL = 3;
/**
* Callback type: Commit callback. Handles post-draw operations for the frame.
@@ -232,7 +248,7 @@
* to the view hierarchy state) actually took effect.
* @hide
*/
- public static final int CALLBACK_COMMIT = 3;
+ public static final int CALLBACK_COMMIT = 4;
private static final int CALLBACK_LAST = CALLBACK_COMMIT;
@@ -704,6 +720,7 @@
mFrameInfo.markAnimationsStart();
doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
+ doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos);
mFrameInfo.markPerformTraversalsStart();
doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java
index 7b9f78e..ce71b07 100644
--- a/core/java/android/view/InsetsAnimationControlImpl.java
+++ b/core/java/android/view/InsetsAnimationControlImpl.java
@@ -45,7 +45,9 @@
* @hide
*/
@VisibleForTesting
-public class InsetsAnimationControlImpl implements WindowInsetsAnimationController {
+public class InsetsAnimationControlImpl implements WindowInsetsAnimationController {
+
+ private final Rect mTmpFrame = new Rect();
private final WindowInsetsAnimationControlListener mListener;
private final SparseArray<InsetsSourceConsumer> mConsumers;
@@ -61,19 +63,23 @@
private final InsetsState mInitialInsetsState;
private final @InsetType int mTypes;
private final Supplier<SyncRtSurfaceTransactionApplier> mTransactionApplierSupplier;
-
+ private final InsetsController mController;
+ private final WindowInsetsAnimationListener.InsetsAnimation mAnimation;
private Insets mCurrentInsets;
+ private Insets mPendingInsets;
@VisibleForTesting
public InsetsAnimationControlImpl(SparseArray<InsetsSourceConsumer> consumers, Rect frame,
InsetsState state, WindowInsetsAnimationControlListener listener,
@InsetType int types,
- Supplier<SyncRtSurfaceTransactionApplier> transactionApplierSupplier) {
+ Supplier<SyncRtSurfaceTransactionApplier> transactionApplierSupplier,
+ InsetsController controller) {
mConsumers = consumers;
mListener = listener;
mTypes = types;
mTransactionApplierSupplier = transactionApplierSupplier;
- mInitialInsetsState = new InsetsState(state);
+ mController = controller;
+ mInitialInsetsState = new InsetsState(state, true /* copySources */);
mCurrentInsets = getInsetsFromState(mInitialInsetsState, frame, null /* typeSideMap */);
mHiddenInsets = calculateInsets(mInitialInsetsState, frame, consumers, false /* shown */,
null /* typeSideMap */);
@@ -83,6 +89,10 @@
// TODO: Check for controllability first and wait for IME if needed.
listener.onReady(this, types);
+
+ mAnimation = new WindowInsetsAnimationListener.InsetsAnimation(mTypes, mHiddenInsets,
+ mShownInsets);
+ mController.dispatchAnimationStarted(mAnimation);
}
@Override
@@ -108,29 +118,35 @@
@Override
public void changeInsets(Insets insets) {
- insets = sanitize(insets);
- final Insets offset = Insets.subtract(mShownInsets, insets);
+ mPendingInsets = sanitize(insets);
+ mController.scheduleApplyChangeInsets();
+ }
+
+ void applyChangeInsets(InsetsState state) {
+ final Insets offset = Insets.subtract(mShownInsets, mPendingInsets);
ArrayList<SurfaceParams> params = new ArrayList<>();
if (offset.left != 0) {
- updateLeashesForSide(INSET_SIDE_LEFT, offset.left, params);
+ updateLeashesForSide(INSET_SIDE_LEFT, offset.left, params, state);
}
if (offset.top != 0) {
- updateLeashesForSide(INSET_SIDE_TOP, offset.top, params);
+ updateLeashesForSide(INSET_SIDE_TOP, offset.top, params, state);
}
if (offset.right != 0) {
- updateLeashesForSide(INSET_SIDE_RIGHT, offset.right, params);
+ updateLeashesForSide(INSET_SIDE_RIGHT, offset.right, params, state);
}
if (offset.bottom != 0) {
- updateLeashesForSide(INSET_SIDE_BOTTOM, offset.bottom, params);
+ updateLeashesForSide(INSET_SIDE_BOTTOM, offset.bottom, params, state);
}
SyncRtSurfaceTransactionApplier applier = mTransactionApplierSupplier.get();
applier.scheduleApply(params.toArray(new SurfaceParams[params.size()]));
- mCurrentInsets = insets;
+ mCurrentInsets = mPendingInsets;
}
@Override
public void finish(int shownTypes) {
// TODO
+
+ mController.dispatchAnimationFinished(mAnimation);
}
private Insets calculateInsets(InsetsState state, Rect frame,
@@ -146,7 +162,7 @@
@Nullable @InsetSide SparseIntArray typeSideMap) {
return state.calculateInsets(frame, false /* isScreenRound */,
false /* alwaysConsumerNavBar */, null /* displayCutout */, typeSideMap)
- .getSystemWindowInsets();
+ .getInsets(mTypes);
}
private Insets sanitize(Insets insets) {
@@ -154,7 +170,7 @@
}
private void updateLeashesForSide(@InsetSide int side, int inset,
- ArrayList<SurfaceParams> surfaceParams) {
+ ArrayList<SurfaceParams> surfaceParams, InsetsState state) {
ArraySet<InsetsSourceConsumer> items = mSideSourceMap.get(side);
// TODO: Implement behavior when inset spans over multiple types
for (int i = items.size() - 1; i >= 0; i--) {
@@ -162,24 +178,32 @@
final InsetsSource source = mInitialInsetsState.getSource(consumer.getType());
final SurfaceControl leash = consumer.getControl().getLeash();
mTmpMatrix.setTranslate(source.getFrame().left, source.getFrame().top);
- addTranslationToMatrix(side, inset, mTmpMatrix);
+
+ mTmpFrame.set(source.getFrame());
+ addTranslationToMatrix(side, inset, mTmpMatrix, mTmpFrame);
+
+ state.getSource(source.getType()).setFrame(mTmpFrame);
surfaceParams.add(new SurfaceParams(leash, 1f, mTmpMatrix, null, 0, 0f));
}
}
- private void addTranslationToMatrix(@InsetSide int side, int inset, Matrix m) {
+ private void addTranslationToMatrix(@InsetSide int side, int inset, Matrix m, Rect frame) {
switch (side) {
case INSET_SIDE_LEFT:
m.postTranslate(-inset, 0);
+ frame.offset(-inset, 0);
break;
case INSET_SIDE_TOP:
m.postTranslate(0, -inset);
+ frame.offset(0, -inset);
break;
case INSET_SIDE_RIGHT:
m.postTranslate(inset, 0);
+ frame.offset(inset, 0);
break;
case INSET_SIDE_BOTTOM:
m.postTranslate(0, inset);
+ frame.offset(0, inset);
break;
}
}
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 01af37e..c2ade76 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.graphics.Insets;
import android.graphics.Rect;
import android.os.RemoteException;
import android.util.ArraySet;
@@ -49,9 +50,29 @@
private final SparseArray<InsetsSourceControl> mTmpControlArray = new SparseArray<>();
private final ArrayList<InsetsAnimationControlImpl> mAnimationControls = new ArrayList<>();
+ private WindowInsets mLastInsets;
+
+ private boolean mAnimCallbackScheduled;
+
+ private final Runnable mAnimCallback;
public InsetsController(ViewRootImpl viewRoot) {
mViewRoot = viewRoot;
+ mAnimCallback = () -> {
+ mAnimCallbackScheduled = false;
+ if (mAnimationControls.isEmpty()) {
+ return;
+ }
+
+ InsetsState state = new InsetsState(mState, true /* copySources */);
+ for (int i = mAnimationControls.size() - 1; i >= 0; i--) {
+ mAnimationControls.get(i).applyChangeInsets(state);
+ }
+ WindowInsets insets = state.calculateInsets(mFrame, mLastInsets.isRound(),
+ mLastInsets.shouldAlwaysConsumeNavBar(), mLastInsets.getDisplayCutout(),
+ null /* typeSideMap */);
+ mViewRoot.mView.dispatchWindowInsetsAnimationProgress(insets);
+ };
}
void onFrameChanged(Rect frame) {
@@ -82,8 +103,9 @@
@VisibleForTesting
public WindowInsets calculateInsets(boolean isScreenRound,
boolean alwaysConsumeNavBar, DisplayCutout cutout) {
- return mState.calculateInsets(mFrame, isScreenRound, alwaysConsumeNavBar, cutout,
+ mLastInsets = mState.calculateInsets(mFrame, isScreenRound, alwaysConsumeNavBar, cutout,
null /* typeSideMap */);
+ return mLastInsets;
}
/**
@@ -148,7 +170,7 @@
}
final InsetsAnimationControlImpl controller = new InsetsAnimationControlImpl(consumers,
mFrame, mState, listener, types,
- () -> new SyncRtSurfaceTransactionApplier(mViewRoot.mView));
+ () -> new SyncRtSurfaceTransactionApplier(mViewRoot.mView), this);
mAnimationControls.add(controller);
}
@@ -200,4 +222,20 @@
pw.println(prefix); pw.println("InsetsController:");
mState.dump(prefix + " ", pw);
}
+
+ void dispatchAnimationStarted(WindowInsetsAnimationListener.InsetsAnimation animation) {
+ mViewRoot.mView.dispatchWindowInsetsAnimationStarted(animation);
+ }
+
+ void dispatchAnimationFinished(WindowInsetsAnimationListener.InsetsAnimation animation) {
+ mViewRoot.mView.dispatchWindowInsetsAnimationFinished(animation);
+ }
+
+ void scheduleApplyChangeInsets() {
+ if (!mAnimCallbackScheduled) {
+ mViewRoot.mChoreographer.postCallback(Choreographer.CALLBACK_INSETS_ANIMATION,
+ mAnimCallback, null /* token*/);
+ mAnimCallbackScheduled = true;
+ }
+ }
}
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index 0931914..cf8c0707 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -107,6 +107,10 @@
set(copy);
}
+ public InsetsState(InsetsState copy, boolean copySources) {
+ set(copy, copySources);
+ }
+
/**
* Calculates {@link WindowInsets} based on the current source configuration.
*
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index cd0e579..abefd55 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -96,6 +96,7 @@
import android.view.AccessibilityIterators.TextSegmentIterator;
import android.view.AccessibilityIterators.WordTextSegmentIterator;
import android.view.ContextMenu.ContextMenuInfo;
+import android.view.WindowInsetsAnimationListener.InsetsAnimation;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityEventSource;
import android.view.accessibility.AccessibilityManager;
@@ -4547,6 +4548,8 @@
OnCapturedPointerListener mOnCapturedPointerListener;
private ArrayList<OnUnhandledKeyEventListener> mUnhandledKeyListeners;
+
+ private WindowInsetsAnimationListener mWindowInsetsAnimationListener;
}
@UnsupportedAppUsage
@@ -10488,6 +10491,37 @@
}
/**
+ * Sets a {@link WindowInsetsAnimationListener} to be notified about animations of windows that
+ * cause insets.
+ *
+ * @param listener The listener to set.
+ * @hide pending unhide
+ */
+ public void setWindowInsetsAnimationListener(WindowInsetsAnimationListener listener) {
+ getListenerInfo().mWindowInsetsAnimationListener = listener;
+ }
+
+ void dispatchWindowInsetsAnimationStarted(InsetsAnimation animation) {
+ if (mListenerInfo != null && mListenerInfo.mWindowInsetsAnimationListener != null) {
+ mListenerInfo.mWindowInsetsAnimationListener.onStarted(animation);
+ }
+ }
+
+ WindowInsets dispatchWindowInsetsAnimationProgress(WindowInsets insets) {
+ if (mListenerInfo != null && mListenerInfo.mWindowInsetsAnimationListener != null) {
+ return mListenerInfo.mWindowInsetsAnimationListener.onProgress(insets);
+ } else {
+ return insets;
+ }
+ }
+
+ void dispatchWindowInsetsAnimationFinished(InsetsAnimation animation) {
+ if (mListenerInfo != null && mListenerInfo.mOnApplyWindowInsetsListener != null) {
+ mListenerInfo.mWindowInsetsAnimationListener.onFinished(animation);
+ }
+ }
+
+ /**
* Compute the view's coordinate within the surface.
*
* <p>Computes the coordinates of this view in its surface. The argument
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 9d11397..0986cfa 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -51,6 +51,7 @@
import android.util.Pools.SynchronizedPool;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
+import android.view.WindowInsetsAnimationListener.InsetsAnimation;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
@@ -7139,6 +7140,34 @@
return insets;
}
+ @Override
+ void dispatchWindowInsetsAnimationStarted(InsetsAnimation animation) {
+ super.dispatchWindowInsetsAnimationStarted(animation);
+ final int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ getChildAt(i).dispatchWindowInsetsAnimationStarted(animation);
+ }
+ }
+
+ @Override
+ WindowInsets dispatchWindowInsetsAnimationProgress(WindowInsets insets) {
+ insets = super.dispatchWindowInsetsAnimationProgress(insets);
+ final int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ getChildAt(i).dispatchWindowInsetsAnimationProgress(insets);
+ }
+ return insets;
+ }
+
+ @Override
+ void dispatchWindowInsetsAnimationFinished(InsetsAnimation animation) {
+ super.dispatchWindowInsetsAnimationFinished(animation);
+ final int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ getChildAt(i).dispatchWindowInsetsAnimationFinished(animation);
+ }
+ }
+
/**
* Returns the animation listener to which layout animation events are
* sent.
diff --git a/core/java/android/view/WindowInsetsAnimationController.java b/core/java/android/view/WindowInsetsAnimationController.java
index 9de517d..cf4415d 100644
--- a/core/java/android/view/WindowInsetsAnimationController.java
+++ b/core/java/android/view/WindowInsetsAnimationController.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.graphics.Insets;
import android.view.WindowInsets.Type.InsetType;
+import android.view.WindowInsetsAnimationListener.InsetsAnimation;
/**
* Interface to control a window inset animation frame-by-frame.
@@ -28,8 +29,13 @@
/**
* Retrieves the {@link Insets} when the windows this animation is controlling are fully hidden.
+ * <p>
+ * If there are any animation listeners registered, this value is the same as
+ * {@link InsetsAnimation#getLowerBound()} that will be passed into the callbacks.
*
* @return Insets when the windows this animation is controlling are fully hidden.
+ *
+ * @see InsetsAnimation#getLowerBound()
*/
@NonNull Insets getHiddenStateInsets();
@@ -38,8 +44,13 @@
* <p>
* In case the size of a window causing insets is changing in the middle of the animation, we
* execute that height change after this animation has finished.
+ * <p>
+ * If there are any animation listeners registered, this value is the same as
+ * {@link InsetsAnimation#getUpperBound()} that will be passed into the callbacks.
*
* @return Insets when the windows this animation is controlling are fully shown.
+ *
+ * @see InsetsAnimation#getUpperBound()
*/
@NonNull Insets getShownStateInsets();
@@ -59,8 +70,11 @@
* <p>
* Note that this will <b>not</b> inform the view system of a full inset change via
* {@link View#dispatchApplyWindowInsets} in order to avoid a full layout pass during the
- * animation. If you'd like to animate views during a window inset animation, use
- * TODO add link to animation listeners.
+ * animation. If you'd like to animate views during a window inset animation, register a
+ * {@link WindowInsetsAnimationListener} by calling
+ * {@link View#setWindowInsetsAnimationListener(WindowInsetsAnimationListener)} that will be
+ * notified about any insets change via {@link WindowInsetsAnimationListener#onProgress} during
+ * the animation.
* <p>
* {@link View#dispatchApplyWindowInsets} will instead be called once the animation has
* finished, i.e. once {@link #finish} has been called.
@@ -70,6 +84,9 @@
* the resulting insets of that configuration will match the passed in parameter.
* Note that these insets are being clamped to the range from
* {@link #getHiddenStateInsets} to {@link #getShownStateInsets}
+ *
+ * @see WindowInsetsAnimationListener
+ * @see View#setWindowInsetsAnimationListener(WindowInsetsAnimationListener)
*/
void changeInsets(@NonNull Insets insets);
diff --git a/core/java/android/view/WindowInsetsAnimationListener.java b/core/java/android/view/WindowInsetsAnimationListener.java
new file mode 100644
index 0000000..682ab5b
--- /dev/null
+++ b/core/java/android/view/WindowInsetsAnimationListener.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2018 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.view;
+
+import android.graphics.Insets;
+
+/**
+ * Interface that allows the application to listen to animation events for windows that cause
+ * insets.
+ * @hide pending unhide
+ */
+public interface WindowInsetsAnimationListener {
+
+ /**
+ * Called when an inset animation gets started.
+ *
+ * @param animation The animation that is about to start.
+ */
+ void onStarted(InsetsAnimation animation);
+
+ /**
+ * Called when the insets change as part of running an animation. Note that even if multiple
+ * animations for different types are running, there will only be one progress callback per
+ * frame. The {@code insets} passed as an argument represents the overall state and will include
+ * all types, regardless of whether they are animating or not.
+ * <p>
+ * Note that insets dispatch is hierarchical: It will start at the root of the view hierarchy,
+ * and then traverse it and invoke the callback of the specific {@link View} being traversed.
+ * The callback may return a modified instance by calling {@link WindowInsets#inset(int, int, int, int)}
+ * to indicate that a part of the insets have been used to offset or clip its children, and the
+ * children shouldn't worry about that part anymore.
+ *
+ * @param insets The current insets.
+ * @return The insets to dispatch to the subtree of the hierarchy.
+ */
+ WindowInsets onProgress(WindowInsets insets);
+
+ /**
+ * Called when an inset animation has finished.
+ *
+ * @param animation The animation that has finished running.
+ */
+ void onFinished(InsetsAnimation animation);
+
+ /**
+ * Class representing an animation of a set of windows that cause insets.
+ */
+ class InsetsAnimation {
+
+ private final @WindowInsets.Type.InsetType int mTypeMask;
+ private final Insets mLowerBound;
+ private final Insets mUpperBound;
+
+ /**
+ * @hide
+ */
+ InsetsAnimation(int typeMask, Insets lowerBound, Insets upperBound) {
+ mTypeMask = typeMask;
+ mLowerBound = lowerBound;
+ mUpperBound = upperBound;
+ }
+
+ /**
+ * @return The bitmask of {@link WindowInsets.Type.InsetType}s that are animating.
+ */
+ public @WindowInsets.Type.InsetType int getTypeMask() {
+ return mTypeMask;
+ }
+
+ /**
+ * Queries the lower inset bound of the animation. If the animation is about showing or
+ * hiding a window that cause insets, the lower bound is {@link Insets#NONE} and the upper
+ * bound is the same as {@link WindowInsets#getInsets(int)} for the fully shown state. This
+ * is the same as {@link WindowInsetsAnimationController#getHiddenStateInsets} and
+ * {@link WindowInsetsAnimationController#getShownStateInsets} in case the listener gets
+ * invoked because of an animation that originates from
+ * {@link WindowInsetsAnimationController}.
+ * <p>
+ * However, if the size of a window that causes insets is changing, these are the
+ * lower/upper bounds of that size animation.
+ * <p>
+ * There are no overlapping animations for a specific type, but there may be two animations
+ * running at the same time for different inset types.
+ *
+ * @see #getUpperBound()
+ * @see WindowInsetsAnimationController#getHiddenStateInsets
+ * TODO: It's a bit weird that these are global per window but onProgress is hierarchical.
+ * TODO: If multiple types are animating, querying the bound per type isn't possible. Should
+ * we:
+ * 1. Offer bounds by type here?
+ * 2. Restrict one animation to one single type only?
+ * Returning WindowInsets here isn't feasible in case of overlapping animations: We can't
+ * fill in the insets for the types from the other animation into the WindowInsets object
+ * as it's changing as well.
+ */
+ public Insets getLowerBound() {
+ return mLowerBound;
+ }
+
+ /**
+ * @see #getLowerBound()
+ * @see WindowInsetsAnimationController#getShownStateInsets
+ */
+ public Insets getUpperBound() {
+ return mUpperBound;
+ }
+ }
+}
diff --git a/core/java/android/view/autofill/AutofillId.java b/core/java/android/view/autofill/AutofillId.java
index cb1d89c..9c935af 100644
--- a/core/java/android/view/autofill/AutofillId.java
+++ b/core/java/android/view/autofill/AutofillId.java
@@ -15,6 +15,7 @@
*/
package android.view.autofill;
+import android.annotation.NonNull;
import android.annotation.TestApi;
import android.os.Parcel;
import android.os.Parcelable;
@@ -25,33 +26,47 @@
*/
public final class AutofillId implements Parcelable {
+ /** @hide */
+ public static final int NO_SESSION = 0;
+
+ private static final int FLAG_IS_VIRTUAL = 0x1;
+ private static final int FLAG_HAS_SESSION = 0x2;
+
private final int mViewId;
- private final boolean mVirtual;
+ private final int mFlags;
private final int mVirtualId;
+ private final int mSessionId;
/** @hide */
@TestApi
public AutofillId(int id) {
- mVirtual = false;
- mViewId = id;
- mVirtualId = View.NO_ID;
+ this(/* flags= */ 0, id, View.NO_ID, NO_SESSION);
}
/** @hide */
@TestApi
- public AutofillId(AutofillId parent, int virtualChildId) {
- mVirtual = true;
- mViewId = parent.mViewId;
- mVirtualId = virtualChildId;
+ public AutofillId(@NonNull AutofillId parent, int virtualChildId) {
+ this(FLAG_IS_VIRTUAL, parent.mViewId, virtualChildId, NO_SESSION);
}
/** @hide */
public AutofillId(int parentId, int virtualChildId) {
- mVirtual = true;
+ this(FLAG_IS_VIRTUAL, parentId, virtualChildId, NO_SESSION);
+ }
+
+ /** @hide */
+ public AutofillId(@NonNull AutofillId parent, int virtualChildId, int sessionId) {
+ this(FLAG_IS_VIRTUAL | FLAG_HAS_SESSION, parent.mViewId, virtualChildId, sessionId);
+ }
+
+ private AutofillId(int flags, int parentId, int virtualChildId, int sessionId) {
+ mFlags = flags;
mViewId = parentId;
mVirtualId = virtualChildId;
+ mSessionId = sessionId;
}
+
/** @hide */
public int getViewId() {
return mViewId;
@@ -64,7 +79,16 @@
/** @hide */
public boolean isVirtual() {
- return mVirtual;
+ return (mFlags & FLAG_IS_VIRTUAL) != 0;
+ }
+
+ private boolean hasSession() {
+ return (mFlags & FLAG_HAS_SESSION) != 0;
+ }
+
+ /** @hide */
+ public int getSessionId() {
+ return mSessionId;
}
/////////////////////////////////
@@ -77,6 +101,7 @@
int result = 1;
result = prime * result + mViewId;
result = prime * result + mVirtualId;
+ result = prime * result + mSessionId;
return result;
}
@@ -88,15 +113,19 @@
final AutofillId other = (AutofillId) obj;
if (mViewId != other.mViewId) return false;
if (mVirtualId != other.mVirtualId) return false;
+ if (mSessionId != other.mSessionId) return false;
return true;
}
@Override
public String toString() {
final StringBuilder builder = new StringBuilder().append(mViewId);
- if (mVirtual) {
+ if (isVirtual()) {
builder.append(':').append(mVirtualId);
}
+ if (hasSession()) {
+ builder.append('@').append(mSessionId);
+ }
return builder.toString();
}
@@ -108,21 +137,24 @@
@Override
public void writeToParcel(Parcel parcel, int flags) {
parcel.writeInt(mViewId);
- parcel.writeInt(mVirtual ? 1 : 0);
- parcel.writeInt(mVirtualId);
- }
-
- private AutofillId(Parcel parcel) {
- mViewId = parcel.readInt();
- mVirtual = parcel.readInt() == 1;
- mVirtualId = parcel.readInt();
+ parcel.writeInt(mFlags);
+ if (isVirtual()) {
+ parcel.writeInt(mVirtualId);
+ }
+ if (hasSession()) {
+ parcel.writeInt(mSessionId);
+ }
}
public static final Parcelable.Creator<AutofillId> CREATOR =
new Parcelable.Creator<AutofillId>() {
@Override
public AutofillId createFromParcel(Parcel source) {
- return new AutofillId(source);
+ final int viewId = source.readInt();
+ final int flags = source.readInt();
+ final int virtualId = (flags & FLAG_IS_VIRTUAL) != 0 ? source.readInt() : View.NO_ID;
+ final int sessionId = (flags & FLAG_HAS_SESSION) != 0 ? source.readInt() : NO_SESSION;
+ return new AutofillId(flags, viewId, virtualId, sessionId);
}
@Override
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 90ccc25..93941d0 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -26,6 +26,7 @@
import android.annotation.Nullable;
import android.annotation.RequiresFeature;
import android.annotation.SystemService;
+import android.annotation.TestApi;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -329,6 +330,12 @@
private static final int SYNC_CALLS_TIMEOUT_MS = 5000;
/**
+ * @hide
+ */
+ @TestApi
+ public static final int MAX_TEMP_AUGMENTED_SERVICE_DURATION_MS = 1_000 * 60 * 2; // 2 minutes
+
+ /**
* Makes an authentication id from a request id and a dataset id.
*
* @param requestId The request id.
diff --git a/core/java/android/view/contentcapture/ContentCaptureSession.java b/core/java/android/view/contentcapture/ContentCaptureSession.java
index 6890beaf..d9a8416 100644
--- a/core/java/android/view/contentcapture/ContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/ContentCaptureSession.java
@@ -28,6 +28,7 @@
import android.view.contentcapture.ViewNode.ViewStructureImpl;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
import dalvik.system.CloseGuard;
@@ -107,7 +108,7 @@
/** @hide */
@Nullable
- protected final String mId = UUID.randomUUID().toString();
+ protected final String mId;
private int mState = STATE_UNKNOWN;
@@ -123,6 +124,13 @@
/** @hide */
protected ContentCaptureSession() {
+ this(UUID.randomUUID().toString());
+ }
+
+ /** @hide */
+ @VisibleForTesting
+ public ContentCaptureSession(@NonNull String id) {
+ mId = Preconditions.checkNotNull(id);
mCloseGuard.open("destroy");
}
@@ -140,6 +148,13 @@
return mContentCaptureSessionId;
}
+ /** @hide */
+ @VisibleForTesting
+ public int getIdAsInt() {
+ // TODO(b/121197119): use sessionId instead of hashcode once it's changed to int
+ return mId.hashCode();
+ }
+
/**
* Creates a new {@link ContentCaptureSession}.
*
@@ -315,9 +330,7 @@
public @NonNull AutofillId newAutofillId(@NonNull AutofillId parentId, int virtualChildId) {
Preconditions.checkNotNull(parentId);
Preconditions.checkArgument(!parentId.isVirtual(), "virtual ids cannot have children");
- // TODO(b/121197119): we need to add the session id to the AutofillId to make them unique
- // per session
- return new AutofillId(parentId, virtualChildId);
+ return new AutofillId(parentId, virtualChildId, getIdAsInt());
}
/**
@@ -333,8 +346,7 @@
@NonNull
public final ViewStructure newVirtualViewStructure(@NonNull AutofillId parentId,
int virtualId) {
- // TODO(b/121197119): use the constructor that takes a session id / assert on unit test.
- return new ViewNode.ViewStructureImpl(parentId, virtualId);
+ return new ViewNode.ViewStructureImpl(parentId, virtualId, getIdAsInt());
}
boolean isContentCaptureEnabled() {
diff --git a/core/java/android/view/contentcapture/ViewNode.java b/core/java/android/view/contentcapture/ViewNode.java
index b7a486a..ddfecb0 100644
--- a/core/java/android/view/contentcapture/ViewNode.java
+++ b/core/java/android/view/contentcapture/ViewNode.java
@@ -672,9 +672,9 @@
}
@VisibleForTesting // Must be public to be accessed from FrameworkCoreTests' apk.
- public ViewStructureImpl(@NonNull AutofillId parentId, int virtualId) {
+ public ViewStructureImpl(@NonNull AutofillId parentId, int virtualId, int sessionId) {
mNode.mParentAutofillId = Preconditions.checkNotNull(parentId);
- mNode.mAutofillId = new AutofillId(parentId, virtualId);
+ mNode.mAutofillId = new AutofillId(parentId, virtualId, sessionId);
}
@VisibleForTesting // Must be public to be accessed from FrameworkCoreTests' apk.
diff --git a/core/java/com/android/internal/infra/AbstractRemoteService.java b/core/java/com/android/internal/infra/AbstractRemoteService.java
index e8ac223..5945958 100644
--- a/core/java/com/android/internal/infra/AbstractRemoteService.java
+++ b/core/java/com/android/internal/infra/AbstractRemoteService.java
@@ -213,6 +213,7 @@
}
mService = null;
mServiceDied = true;
+ cancelScheduledUnbind();
@SuppressWarnings("unchecked") // TODO(b/117779333): fix this warning
final S castService = (S) this;
mVultureCallback.onServiceDied(castService);
diff --git a/core/java/com/android/internal/os/RoSystemProperties.java b/core/java/com/android/internal/os/RoSystemProperties.java
index a319d83..b529bbe 100644
--- a/core/java/com/android/internal/os/RoSystemProperties.java
+++ b/core/java/com/android/internal/os/RoSystemProperties.java
@@ -30,6 +30,7 @@
public static final String CONTROL_PRIVAPP_PERMISSIONS =
SystemProperties.get("ro.control_privapp_permissions");
+ // ------ ro.hdmi.* -------- //
/**
* Property to indicate if a CEC audio device should forward volume keys when system audio
* mode is off.
@@ -38,6 +39,14 @@
SystemProperties.getBoolean(
"ro.hdmi.cec_audio_device_forward_volume_keys_system_audio_mode_off", false);
+ /**
+ * Property to indicate if the current device is a cec switch device.
+ *
+ * <p> Default is false.
+ */
+ public static final String PROPERTY_HDMI_IS_DEVICE_HDMI_CEC_SWITCH =
+ "ro.hdmi.property_is_device_hdmi_cec_switch";
+
// ------ ro.config.* -------- //
public static final boolean CONFIG_AVOID_GFX_ACCEL =
SystemProperties.getBoolean("ro.config.avoid_gfx_accel", false);
diff --git a/core/java/com/android/internal/util/ContrastColorUtil.java b/core/java/com/android/internal/util/ContrastColorUtil.java
index a403c06..e0ba317f 100644
--- a/core/java/com/android/internal/util/ContrastColorUtil.java
+++ b/core/java/com/android/internal/util/ContrastColorUtil.java
@@ -586,7 +586,7 @@
*
* @param color the base color to use
* @param amount the amount from 1 to 100 how much to modify the color
- * @return the now color that was modified
+ * @return the new color that was modified
*/
public static int getShiftedColor(int color, int amount) {
final double[] result = ColorUtilsFromCompat.getTempDouble3Array();
@@ -599,6 +599,19 @@
return ColorUtilsFromCompat.LABToColor(result[0], result[1], result[2]);
}
+ /**
+ * Blends the provided color with white to create a muted version.
+ *
+ * @param color the color to mute
+ * @param alpha the amount from 0 to 1 to set the alpha component of the white scrim
+ * @return the new color that was modified
+ */
+ public static int getMutedColor(int color, float alpha) {
+ int whiteScrim = ColorUtilsFromCompat.setAlphaComponent(
+ Color.WHITE, (int) (255 * alpha));
+ return compositeColors(whiteScrim, color);
+ }
+
private static boolean shouldUseDark(int backgroundColor, boolean defaultBackgroundIsDark) {
if (backgroundColor == Notification.COLOR_DEFAULT) {
return !defaultBackgroundIsDark;
@@ -675,6 +688,18 @@
}
/**
+ * Set the alpha component of {@code color} to be {@code alpha}.
+ */
+ @ColorInt
+ public static int setAlphaComponent(@ColorInt int color,
+ @IntRange(from = 0x0, to = 0xFF) int alpha) {
+ if (alpha < 0 || alpha > 255) {
+ throw new IllegalArgumentException("alpha must be between 0 and 255.");
+ }
+ return (color & 0x00ffffff) | (alpha << 24);
+ }
+
+ /**
* Returns the luminance of a color as a float between {@code 0.0} and {@code 1.0}.
* <p>Defined as the Y component in the XYZ representation of {@code color}.</p>
*/
diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp
index 888dab1..7d63ec9 100644
--- a/core/jni/android_os_Debug.cpp
+++ b/core/jni/android_os_Debug.cpp
@@ -29,7 +29,6 @@
#include <time.h>
#include <unistd.h>
-#include <atomic>
#include <iomanip>
#include <string>
#include <vector>
@@ -42,6 +41,7 @@
#include <nativehelper/JNIHelp.h>
#include <nativehelper/ScopedUtfChars.h>
#include "jni.h"
+#include <meminfo/procmeminfo.h>
#include <meminfo/sysmeminfo.h>
#include <memtrack/memtrack.h>
#include <memunreachable/memunreachable.h>
@@ -150,14 +150,6 @@
int swappedOutPss;
};
-enum pss_rollup_support {
- PSS_ROLLUP_UNTRIED,
- PSS_ROLLUP_SUPPORTED,
- PSS_ROLLUP_UNSUPPORTED
-};
-
-static std::atomic<pss_rollup_support> g_pss_rollup_support;
-
#define BINDER_STATS "/proc/binder/stats"
static jlong android_os_Debug_getNativeHeapSize(JNIEnv *env, jobject clazz)
@@ -555,37 +547,9 @@
android_os_Debug_getDirtyPagesPid(env, clazz, getpid(), object);
}
-UniqueFile OpenSmapsOrRollup(int pid)
-{
- enum pss_rollup_support rollup_support =
- g_pss_rollup_support.load(std::memory_order_relaxed);
- if (rollup_support != PSS_ROLLUP_UNSUPPORTED) {
- std::string smaps_rollup_path =
- base::StringPrintf("/proc/%d/smaps_rollup", pid);
- UniqueFile fp_rollup = MakeUniqueFile(smaps_rollup_path.c_str(), "re");
- if (fp_rollup == nullptr && errno != ENOENT) {
- return fp_rollup; // Actual error, not just old kernel.
- }
- if (fp_rollup != nullptr) {
- if (rollup_support == PSS_ROLLUP_UNTRIED) {
- ALOGI("using rollup pss collection");
- g_pss_rollup_support.store(PSS_ROLLUP_SUPPORTED,
- std::memory_order_relaxed);
- }
- return fp_rollup;
- }
- g_pss_rollup_support.store(PSS_ROLLUP_UNSUPPORTED,
- std::memory_order_relaxed);
- }
-
- std::string smaps_path = base::StringPrintf("/proc/%d/smaps", pid);
- return MakeUniqueFile(smaps_path.c_str(), "re");
-}
-
static jlong android_os_Debug_getPssPid(JNIEnv *env, jobject clazz, jint pid,
jlongArray outUssSwapPssRss, jlongArray outMemtrack)
{
- char lineBuffer[1024];
jlong pss = 0;
jlong rss = 0;
jlong swapPss = 0;
@@ -597,59 +561,14 @@
pss = uss = rss = memtrack = graphics_mem.graphics + graphics_mem.gl + graphics_mem.other;
}
- {
- UniqueFile fp = OpenSmapsOrRollup(pid);
-
- if (fp != nullptr) {
- char* line;
-
- while (true) {
- if (fgets(lineBuffer, sizeof (lineBuffer), fp.get()) == NULL) {
- break;
- }
- line = lineBuffer;
-
- switch (line[0]) {
- case 'P':
- if (strncmp(line, "Pss:", 4) == 0) {
- char* c = line + 4;
- while (*c != 0 && (*c < '0' || *c > '9')) {
- c++;
- }
- pss += atoi(c);
- } else if (strncmp(line, "Private_Clean:", 14) == 0
- || strncmp(line, "Private_Dirty:", 14) == 0) {
- char* c = line + 14;
- while (*c != 0 && (*c < '0' || *c > '9')) {
- c++;
- }
- uss += atoi(c);
- }
- break;
- case 'R':
- if (strncmp(line, "Rss:", 4) == 0) {
- char* c = line + 4;
- while (*c != 0 && (*c < '0' || *c > '9')) {
- c++;
- }
- rss += atoi(c);
- }
- break;
- case 'S':
- if (strncmp(line, "SwapPss:", 8) == 0) {
- char* c = line + 8;
- jlong lSwapPss;
- while (*c != 0 && (*c < '0' || *c > '9')) {
- c++;
- }
- lSwapPss = atoi(c);
- swapPss += lSwapPss;
- pss += lSwapPss; // Also in swap, those pages would be accounted as Pss without SWAP
- }
- break;
- }
- }
- }
+ ::android::meminfo::ProcMemInfo proc_mem(pid);
+ ::android::meminfo::MemUsage stats;
+ if (proc_mem.SmapsOrRollup(&stats)) {
+ pss += stats.pss;
+ uss += stats.uss;
+ rss += stats.rss;
+ swapPss = stats.swap_pss;
+ pss += swapPss; // Also in swap, those pages would be accounted as Pss without SWAP
}
if (outUssSwapPssRss != NULL) {
@@ -686,34 +605,6 @@
return android_os_Debug_getPssPid(env, clazz, getpid(), NULL, NULL);
}
-static long get_allocated_vmalloc_memory() {
- char line[1024];
-
- long vmalloc_allocated_size = 0;
-
- UniqueFile fp = MakeUniqueFile("/proc/vmallocinfo", "re");
- if (fp == nullptr) {
- return 0;
- }
-
- while (true) {
- if (fgets(line, 1024, fp.get()) == NULL) {
- break;
- }
-
- // check to see if there are pages mapped in vmalloc area
- if (!strstr(line, "pages=")) {
- continue;
- }
-
- long nr_pages;
- if (sscanf(line, "%*x-%*x %*ld %*s pages=%ld", &nr_pages) == 1) {
- vmalloc_allocated_size += (nr_pages * getpagesize());
- }
- }
- return vmalloc_allocated_size;
-}
-
// The 1:1 mapping of MEMINFO_* enums here must match with the constants from
// Debug.java.
enum {
@@ -763,9 +654,8 @@
if (outArray != NULL) {
outLen = MEMINFO_COUNT;
for (int i = 0; i < outLen; i++) {
- // TODO: move get_allocated_vmalloc_memory() to libmeminfo
if (i == MEMINFO_VMALLOC_USED) {
- outArray[i] = get_allocated_vmalloc_memory() / 1024;
+ outArray[i] = smi.ReadVmallocInfo() / 1024;
continue;
}
outArray[i] = mem[i];
@@ -775,7 +665,6 @@
env->ReleaseLongArrayElements(out, outArray, 0);
}
-
static jint read_binder_stat(const char* stat)
{
UniqueFile fp = MakeUniqueFile(BINDER_STATS, "re");
diff --git a/core/jni/android_os_Debug.h b/core/jni/android_os_Debug.h
index c7b731b..747776a 100644
--- a/core/jni/android_os_Debug.h
+++ b/core/jni/android_os_Debug.h
@@ -19,6 +19,7 @@
#include <memory>
#include <stdio.h>
+#include <meminfo/meminfo.h>
#include <android-base/stringprintf.h>
#include <android-base/unique_fd.h>
@@ -34,8 +35,6 @@
return UniqueFile(fopen(path, mode), safeFclose);
}
-UniqueFile OpenSmapsOrRollup(int pid);
-
} // namespace android
#endif // ANDROID_OS_HW_BLOB_H
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index 0c1a8aa..798825f 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -25,6 +25,7 @@
#include <cutils/sched_policy.h>
#include <utils/String8.h>
#include <utils/Vector.h>
+#include <meminfo/procmeminfo.h>
#include <meminfo/sysmeminfo.h>
#include <processgroup/processgroup.h>
@@ -1083,21 +1084,12 @@
static jlong android_os_Process_getPss(JNIEnv* env, jobject clazz, jint pid)
{
- UniqueFile file = OpenSmapsOrRollup(pid);
- if (file == nullptr) {
+ ::android::meminfo::ProcMemInfo proc_mem(pid);
+ uint64_t pss;
+ if (!proc_mem.SmapsOrRollupPss(&pss)) {
return (jlong) -1;
}
- // Tally up all of the Pss from the various maps
- char line[256];
- jlong pss = 0;
- while (fgets(line, sizeof(line), file.get())) {
- jlong v;
- if (sscanf(line, "Pss: %" SCNd64 " kB", &v) == 1) {
- pss += v;
- }
- }
-
// Return the Pss value in bytes, not kilobytes
return pss * 1024;
}
diff --git a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
index d520f15..81ca910 100644
--- a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
+++ b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
@@ -19,6 +19,7 @@
import static android.view.InsetsState.TYPE_NAVIGATION_BAR;
import static android.view.InsetsState.TYPE_TOP_BAR;
import static junit.framework.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import android.graphics.Insets;
@@ -83,7 +84,7 @@
consumers.put(TYPE_NAVIGATION_BAR, navConsumer);
mController = new InsetsAnimationControlImpl(consumers,
new Rect(0, 0, 500, 500), state, mMockListener, WindowInsets.Type.systemBars(),
- () -> mMockTransactionApplier);
+ () -> mMockTransactionApplier, mock(InsetsController.class));
}
@Test
diff --git a/core/tests/coretests/src/android/view/autofill/AutofillIdTest.java b/core/tests/coretests/src/android/view/autofill/AutofillIdTest.java
new file mode 100644
index 0000000..7619af2
--- /dev/null
+++ b/core/tests/coretests/src/android/view/autofill/AutofillIdTest.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2019 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.view.autofill;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.os.Parcel;
+import android.view.View;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class AutofillIdTest {
+
+ @Test
+ public void testNonVirtual() {
+ final AutofillId id = new AutofillId(42);
+ assertThat(id.getViewId()).isEqualTo(42);
+ assertThat(id.isVirtual()).isFalse();
+ assertThat(id.getVirtualChildId()).isEqualTo(View.NO_ID);
+
+ final AutofillId clone = cloneThroughParcel(id);
+ assertThat(clone.getViewId()).isEqualTo(42);
+ assertThat(clone.isVirtual()).isFalse();
+ assertThat(clone.getVirtualChildId()).isEqualTo(View.NO_ID);
+ }
+
+ @Test
+ public void testVirtual() {
+ final AutofillId id = new AutofillId(42, 108);
+ assertThat(id.getViewId()).isEqualTo(42);
+ assertThat(id.isVirtual()).isTrue();
+ assertThat(id.getVirtualChildId()).isEqualTo(108);
+
+ final AutofillId clone = cloneThroughParcel(id);
+ assertThat(clone.getViewId()).isEqualTo(42);
+ assertThat(clone.isVirtual()).isTrue();
+ assertThat(clone.getVirtualChildId()).isEqualTo(108);
+ }
+
+ @Test
+ public void testVirtual_parentObjectConstructor() {
+ assertThrows(NullPointerException.class, () -> new AutofillId(null, 108));
+
+ final AutofillId id = new AutofillId(new AutofillId(42), 108);
+ assertThat(id.getViewId()).isEqualTo(42);
+ assertThat(id.isVirtual()).isTrue();
+ assertThat(id.getVirtualChildId()).isEqualTo(108);
+
+ final AutofillId clone = cloneThroughParcel(id);
+ assertThat(clone.getViewId()).isEqualTo(42);
+ assertThat(clone.isVirtual()).isTrue();
+ assertThat(clone.getVirtualChildId()).isEqualTo(108);
+ }
+
+ @Test
+ public void testVirtual_withSession() {
+ final AutofillId id = new AutofillId(new AutofillId(42), 108, 666);
+ assertThat(id.getViewId()).isEqualTo(42);
+ assertThat(id.isVirtual()).isTrue();
+ assertThat(id.getVirtualChildId()).isEqualTo(108);
+ assertThat(id.getSessionId()).isEqualTo(666);
+
+ final AutofillId clone = cloneThroughParcel(id);
+ assertThat(clone.getViewId()).isEqualTo(42);
+ assertThat(clone.isVirtual()).isTrue();
+ assertThat(clone.getVirtualChildId()).isEqualTo(108);
+ assertThat(clone.getSessionId()).isEqualTo(666);
+ }
+
+ @Test
+ public void testEqualsHashCode() {
+ final AutofillId realId = new AutofillId(42);
+ final AutofillId realIdSame = new AutofillId(42);
+ assertThat(realId).isEqualTo(realIdSame);
+ assertThat(realIdSame).isEqualTo(realId);
+ assertThat(realId.hashCode()).isEqualTo(realIdSame.hashCode());
+
+ final AutofillId realIdDifferent = new AutofillId(108);
+ assertThat(realId).isNotEqualTo(realIdDifferent);
+ assertThat(realIdDifferent).isNotEqualTo(realId);
+
+ final AutofillId virtualId = new AutofillId(42, 1);
+ final AutofillId virtualIdSame = new AutofillId(42, 1);
+ assertThat(virtualId).isEqualTo(virtualIdSame);
+ assertThat(virtualIdSame).isEqualTo(virtualId);
+ assertThat(virtualId.hashCode()).isEqualTo(virtualIdSame.hashCode());
+ assertThat(virtualId).isNotEqualTo(realId);
+ assertThat(realId).isNotEqualTo(virtualId);
+
+ final AutofillId virtualIdDifferentChild = new AutofillId(42, 2);
+ assertThat(virtualIdDifferentChild).isNotEqualTo(virtualId);
+ assertThat(virtualId).isNotEqualTo(virtualIdDifferentChild);
+ assertThat(virtualIdDifferentChild).isNotEqualTo(realId);
+ assertThat(realId).isNotEqualTo(virtualIdDifferentChild);
+
+ final AutofillId virtualIdDifferentParent = new AutofillId(108, 1);
+ assertThat(virtualIdDifferentParent).isNotEqualTo(virtualId);
+ assertThat(virtualId).isNotEqualTo(virtualIdDifferentParent);
+ assertThat(virtualIdDifferentParent).isNotEqualTo(virtualIdDifferentChild);
+ assertThat(virtualIdDifferentChild).isNotEqualTo(virtualIdDifferentParent);
+
+ final AutofillId virtualIdDifferentSession = new AutofillId(new AutofillId(42), 1, 108);
+ assertThat(virtualIdDifferentSession).isNotEqualTo(virtualId);
+ assertThat(virtualId).isNotEqualTo(virtualIdDifferentSession);
+ assertThat(virtualIdDifferentSession).isNotEqualTo(realId);
+ assertThat(realId).isNotEqualTo(virtualIdDifferentSession);
+
+ final AutofillId sameVirtualIdDifferentSession = new AutofillId(new AutofillId(42), 1, 108);
+ assertThat(sameVirtualIdDifferentSession).isEqualTo(virtualIdDifferentSession);
+ assertThat(virtualIdDifferentSession).isEqualTo(sameVirtualIdDifferentSession);
+ assertThat(sameVirtualIdDifferentSession.hashCode())
+ .isEqualTo(virtualIdDifferentSession.hashCode());
+ }
+
+ private AutofillId cloneThroughParcel(AutofillId id) {
+ Parcel parcel = Parcel.obtain();
+
+ try {
+ // Write to parcel
+ parcel.setDataPosition(0); // Sanity / paranoid check
+ id.writeToParcel(parcel, 0);
+
+ // Read from parcel
+ parcel.setDataPosition(0);
+ AutofillId clone = AutofillId.CREATOR.createFromParcel(parcel);
+ assertThat(clone).isNotNull();
+ return clone;
+ } finally {
+ parcel.recycle();
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java
index 59f3a4c..73cceae 100644
--- a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java
+++ b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureSessionTest.java
@@ -19,11 +19,14 @@
import static org.testng.Assert.assertThrows;
+import android.view.View;
+import android.view.ViewStructure;
import android.view.autofill.AutofillId;
+import android.view.contentcapture.ViewNode.ViewStructureImpl;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.Spy;
+import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
/**
@@ -35,34 +38,104 @@
@RunWith(MockitoJUnitRunner.class)
public class ContentCaptureSessionTest {
- /**
- * Uses a spy as ContentCaptureSession is abstract but (so far) we're testing its concrete
- * methods.
- */
- @Spy
- private ContentCaptureSession mMockSession;
+ private ContentCaptureSession mSession1 = new MyContentCaptureSession("111");
+
+ private ContentCaptureSession mSession2 = new MyContentCaptureSession("2222");
+
+ @Mock
+ private View mMockView;
@Test
public void testNewAutofillId_invalid() {
- assertThrows(NullPointerException.class, () -> mMockSession.newAutofillId(null, 42));
+ assertThrows(NullPointerException.class, () -> mSession1.newAutofillId(null, 42));
assertThrows(IllegalArgumentException.class,
- () -> mMockSession.newAutofillId(new AutofillId(42, 42), 42));
+ () -> mSession1.newAutofillId(new AutofillId(42, 42), 42));
}
@Test
public void testNewAutofillId_valid() {
final AutofillId parentId = new AutofillId(42);
- final AutofillId childId = mMockSession.newAutofillId(parentId, 108);
+ final AutofillId childId = mSession1.newAutofillId(parentId, 108);
assertThat(childId.getViewId()).isEqualTo(42);
assertThat(childId.getVirtualChildId()).isEqualTo(108);
- // TODO(b/121197119): assert session id
+ assertThat(childId.getSessionId()).isEqualTo(mSession1.getIdAsInt());
+ }
+
+ @Test
+ public void testNewAutofillId_differentSessions() {
+ assertThat(mSession1.getIdAsInt()).isNotSameAs(mSession2.getIdAsInt()); //sanity check
+ final AutofillId parentId = new AutofillId(42);
+ final AutofillId childId1 = mSession1.newAutofillId(parentId, 108);
+ final AutofillId childId2 = mSession2.newAutofillId(parentId, 108);
+ assertThat(childId1).isNotEqualTo(childId2);
+ assertThat(childId2).isNotEqualTo(childId1);
}
@Test
public void testNotifyXXX_null() {
- assertThrows(NullPointerException.class, () -> mMockSession.notifyViewAppeared(null));
- assertThrows(NullPointerException.class, () -> mMockSession.notifyViewDisappeared(null));
+ assertThrows(NullPointerException.class, () -> mSession1.notifyViewAppeared(null));
+ assertThrows(NullPointerException.class, () -> mSession1.notifyViewDisappeared(null));
assertThrows(NullPointerException.class,
- () -> mMockSession.notifyViewTextChanged(null, "whatever", 0));
+ () -> mSession1.notifyViewTextChanged(null, "whatever", 0));
+ }
+
+ @Test
+ public void testNewViewStructure() {
+ assertThat(mMockView.getAutofillId()).isNotNull(); // sanity check
+ final ViewStructure structure = mSession1.newViewStructure(mMockView);
+ assertThat(structure).isNotNull();
+ assertThat(structure.getAutofillId()).isEqualTo(mMockView.getAutofillId());
+ }
+
+ @Test
+ public void testNewVirtualViewStructure() {
+ final AutofillId parentId = new AutofillId(42);
+ final ViewStructure structure = mSession1.newVirtualViewStructure(parentId, 108);
+ assertThat(structure).isNotNull();
+ final AutofillId childId = mSession1.newAutofillId(parentId, 108);
+ assertThat(structure.getAutofillId()).isEqualTo(childId);
+ }
+
+ // Cannot use @Spy because we need to pass the session id on constructor
+ private class MyContentCaptureSession extends ContentCaptureSession {
+
+ private MyContentCaptureSession(String id) {
+ super(id);
+ }
+
+ @Override
+ MainContentCaptureSession getMainCaptureSession() {
+ throw new UnsupportedOperationException("should not have been called");
+ }
+
+ @Override
+ ContentCaptureSession newChild(ContentCaptureContext context) {
+ throw new UnsupportedOperationException("should not have been called");
+ }
+
+ @Override
+ void flush() {
+ throw new UnsupportedOperationException("should not have been called");
+ }
+
+ @Override
+ void onDestroy() {
+ throw new UnsupportedOperationException("should not have been called");
+ }
+
+ @Override
+ void internalNotifyViewAppeared(ViewStructureImpl node) {
+ throw new UnsupportedOperationException("should not have been called");
+ }
+
+ @Override
+ void internalNotifyViewDisappeared(AutofillId id) {
+ throw new UnsupportedOperationException("should not have been called");
+ }
+
+ @Override
+ void internalNotifyViewTextChanged(AutofillId id, CharSequence text, int flags) {
+ throw new UnsupportedOperationException("should not have been called");
+ }
}
}
diff --git a/core/tests/coretests/src/android/view/contentcapture/ViewNodeTest.java b/core/tests/coretests/src/android/view/contentcapture/ViewNodeTest.java
index 995946b..bbfe01c 100644
--- a/core/tests/coretests/src/android/view/contentcapture/ViewNodeTest.java
+++ b/core/tests/coretests/src/android/view/contentcapture/ViewNodeTest.java
@@ -110,10 +110,10 @@
@Test
public void testAutofillIdMethods_explicitIdsConstructor() {
AutofillId initialParentId = new AutofillId(42);
- ViewStructureImpl structure = new ViewStructureImpl(initialParentId, 108);
+ ViewStructureImpl structure = new ViewStructureImpl(initialParentId, 108, 666);
ViewNode node = structure.getNode();
- assertThat(node.getAutofillId()).isEqualTo(new AutofillId(initialParentId, 108));
+ assertThat(node.getAutofillId()).isEqualTo(new AutofillId(initialParentId, 108, 666));
assertThat(node.getParentAutofillId()).isEqualTo(initialParentId);
AutofillId newChildId = new AutofillId(108);
diff --git a/core/tests/overlaytests/device/Android.mk b/core/tests/overlaytests/device/Android.mk
index 680ebeb..5630749 100644
--- a/core/tests/overlaytests/device/Android.mk
+++ b/core/tests/overlaytests/device/Android.mk
@@ -19,7 +19,7 @@
LOCAL_MODULE_TAGS := tests
LOCAL_PACKAGE_NAME := OverlayDeviceTests
LOCAL_PRIVATE_PLATFORM_APIS := true
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+LOCAL_STATIC_JAVA_LIBRARIES := androidx.test.rules
LOCAL_COMPATIBILITY_SUITE := device-tests
LOCAL_TARGET_REQUIRED_MODULES := \
OverlayDeviceTests_AppOverlayOne \
diff --git a/core/tests/overlaytests/device/AndroidManifest.xml b/core/tests/overlaytests/device/AndroidManifest.xml
index d14fdf5..4881636 100644
--- a/core/tests/overlaytests/device/AndroidManifest.xml
+++ b/core/tests/overlaytests/device/AndroidManifest.xml
@@ -23,7 +23,7 @@
<uses-library android:name="android.test.runner"/>
</application>
- <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.overlaytest"
android:label="Runtime resource overlay tests" />
</manifest>
diff --git a/core/tests/overlaytests/device/src/com/android/overlaytest/OverlayBaseTest.java b/core/tests/overlaytests/device/src/com/android/overlaytest/OverlayBaseTest.java
index 5a86885..91fcdbb 100644
--- a/core/tests/overlaytests/device/src/com/android/overlaytest/OverlayBaseTest.java
+++ b/core/tests/overlaytests/device/src/com/android/overlaytest/OverlayBaseTest.java
@@ -25,10 +25,11 @@
import android.content.res.XmlResourceParser;
import android.os.LocaleList;
import android.os.ParcelFileDescriptor;
-import android.support.test.InstrumentationRegistry;
import android.util.AttributeSet;
import android.util.Xml;
+import androidx.test.InstrumentationRegistry;
+
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
diff --git a/core/tests/overlaytests/device/src/com/android/overlaytest/WithMultipleOverlaysTest.java b/core/tests/overlaytests/device/src/com/android/overlaytest/WithMultipleOverlaysTest.java
index f35e511..cd3ed9d 100644
--- a/core/tests/overlaytests/device/src/com/android/overlaytest/WithMultipleOverlaysTest.java
+++ b/core/tests/overlaytests/device/src/com/android/overlaytest/WithMultipleOverlaysTest.java
@@ -16,7 +16,7 @@
package com.android.overlaytest;
-import android.support.test.filters.MediumTest;
+import androidx.test.filters.MediumTest;
import org.junit.BeforeClass;
import org.junit.runner.RunWith;
diff --git a/core/tests/overlaytests/device/src/com/android/overlaytest/WithOverlayTest.java b/core/tests/overlaytests/device/src/com/android/overlaytest/WithOverlayTest.java
index 037449f..c0d4281 100644
--- a/core/tests/overlaytests/device/src/com/android/overlaytest/WithOverlayTest.java
+++ b/core/tests/overlaytests/device/src/com/android/overlaytest/WithOverlayTest.java
@@ -16,7 +16,7 @@
package com.android.overlaytest;
-import android.support.test.filters.MediumTest;
+import androidx.test.filters.MediumTest;
import org.junit.BeforeClass;
import org.junit.runner.RunWith;
diff --git a/core/tests/overlaytests/device/src/com/android/overlaytest/WithoutOverlayTest.java b/core/tests/overlaytests/device/src/com/android/overlaytest/WithoutOverlayTest.java
index f657b5c..33c7b25 100644
--- a/core/tests/overlaytests/device/src/com/android/overlaytest/WithoutOverlayTest.java
+++ b/core/tests/overlaytests/device/src/com/android/overlaytest/WithoutOverlayTest.java
@@ -16,7 +16,7 @@
package com.android.overlaytest;
-import android.support.test.filters.MediumTest;
+import androidx.test.filters.MediumTest;
import org.junit.BeforeClass;
import org.junit.runner.RunWith;
diff --git a/data/etc/Android.bp b/data/etc/Android.bp
index 25dabad..0bffa38 100644
--- a/data/etc/Android.bp
+++ b/data/etc/Android.bp
@@ -39,6 +39,9 @@
name: "privapp-permissions-platform.xml",
sub_dir: "permissions",
src: "privapp-permissions-platform.xml",
+ required: [
+ "privapp_whitelist_com.android.settings.intelligence",
+ ]
}
prebuilt_etc {
@@ -50,6 +53,13 @@
}
prebuilt_etc {
+ name: "privapp_whitelist_com.android.settings.intelligence",
+ sub_dir: "permissions",
+ src: "com.android.settings.intelligence.xml",
+ filename_from_src: true,
+}
+
+prebuilt_etc {
name: "privapp_whitelist_com.android.systemui",
product_specific: true,
sub_dir: "permissions",
diff --git a/data/etc/com.android.settings.intelligence.xml b/data/etc/com.android.settings.intelligence.xml
new file mode 100644
index 0000000..6ca30c1
--- /dev/null
+++ b/data/etc/com.android.settings.intelligence.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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
+ -->
+<permissions>
+ <privapp-permissions package="com.android.settings.intelligence">
+ <permission name="android.permission.MANAGE_FINGERPRINT"/>
+ <permission name="android.permission.MODIFY_PHONE_STATE"/>
+ <permission name="android.permission.READ_SEARCH_INDEXABLES"/>
+ <permission name="android.permission.WRITE_SETTINGS_HOMEPAGE_DATA"/>
+ </privapp-permissions>
+</permissions>
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index fbe613d..393a2a6 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -280,13 +280,6 @@
<permission name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME"/>
</privapp-permissions>
- <privapp-permissions package="com.android.settings.intelligence">
- <permission name="android.permission.MANAGE_FINGERPRINT"/>
- <permission name="android.permission.MODIFY_PHONE_STATE"/>
- <permission name="android.permission.READ_SEARCH_INDEXABLES"/>
- <permission name="android.permission.WRITE_SETTINGS_HOMEPAGE_DATA"/>
- </privapp-permissions>
-
<privapp-permissions package="com.android.sharedstoragebackup">
<permission name="android.permission.WRITE_MEDIA_STORAGE"/>
</privapp-permissions>
diff --git a/graphics/java/android/graphics/ColorSpace.java b/graphics/java/android/graphics/ColorSpace.java
index f0efb58..95317a4 100644
--- a/graphics/java/android/graphics/ColorSpace.java
+++ b/graphics/java/android/graphics/ColorSpace.java
@@ -984,11 +984,12 @@
* {@link Named#SRGB sRGB} primaries.
* </li>
* <li>
- * Its white point is withing 1e-3 of the CIE standard
+ * Its white point is within 1e-3 of the CIE standard
* illuminant {@link #ILLUMINANT_D65 D65}.
* </li>
* <li>Its opto-electronic transfer function is not linear.</li>
* <li>Its electro-optical transfer function is not linear.</li>
+ * <li>Its transfer functions yield values within 1e-3 of {@link Named#SRGB}.</li>
* <li>Its range is \([0..1]\).</li>
* </ul>
* <p>This method always returns true for {@link Named#SRGB}.</p>
@@ -3145,19 +3146,35 @@
float max,
@IntRange(from = MIN_ID, to = MAX_ID) int id) {
if (id == 0) return true;
- if (!compare(primaries, SRGB_PRIMARIES)) {
+ if (!ColorSpace.compare(primaries, SRGB_PRIMARIES)) {
return false;
}
- if (!compare(whitePoint, ILLUMINANT_D65)) {
+ if (!ColorSpace.compare(whitePoint, ILLUMINANT_D65)) {
return false;
}
- if (OETF.applyAsDouble(0.5) < 0.5001) return false;
- if (EOTF.applyAsDouble(0.5) > 0.5001) return false;
+
if (min != 0.0f) return false;
if (max != 1.0f) return false;
+
+ // We would have already returned true if this was SRGB itself, so
+ // it is safe to reference it here.
+ ColorSpace.Rgb srgb = (ColorSpace.Rgb) get(Named.SRGB);
+
+ for (double x = 0.0; x <= 1.0; x += 1 / 255.0) {
+ if (!compare(x, OETF, srgb.mOetf)) return false;
+ if (!compare(x, EOTF, srgb.mEotf)) return false;
+ }
+
return true;
}
+ private static boolean compare(double point, @NonNull DoubleUnaryOperator a,
+ @NonNull DoubleUnaryOperator b) {
+ double rA = a.applyAsDouble(point);
+ double rB = b.applyAsDouble(point);
+ return Math.abs(rA - rB) <= 1e-3;
+ }
+
/**
* Computes whether the specified CIE xyY or XYZ primaries (with Y set to 1) form
* a wide color gamut. A color gamut is considered wide if its area is > 90%
diff --git a/packages/ExtServices/src/android/ext/services/notification/Assistant.java b/packages/ExtServices/src/android/ext/services/notification/Assistant.java
index 838b88b..499d493 100644
--- a/packages/ExtServices/src/android/ext/services/notification/Assistant.java
+++ b/packages/ExtServices/src/android/ext/services/notification/Assistant.java
@@ -286,6 +286,9 @@
if (!isForCurrentUser(sbn)) {
return;
}
+
+ mAgingHelper.onNotificationRemoved(sbn.getKey());
+
boolean updatedImpressions = false;
String channelId = mLiveNotifications.remove(sbn.getKey()).getChannel().getId();
String key = getKey(sbn.getPackageName(), sbn.getUserId(), channelId);
diff --git a/packages/SystemUI/res/layout/bubble_view.xml b/packages/SystemUI/res/layout/bubble_view.xml
new file mode 100644
index 0000000..204408cd
--- /dev/null
+++ b/packages/SystemUI/res/layout/bubble_view.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2018 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
+ -->
+<com.android.systemui.bubbles.BubbleView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:id="@+id/bubble_view">
+
+ <com.android.systemui.bubbles.BadgedImageView
+ android:id="@+id/bubble_image"
+ android:layout_width="@dimen/bubble_size"
+ android:layout_height="@dimen/bubble_size"
+ android:padding="@dimen/bubble_view_padding"
+ android:clipToPadding="false"/>
+
+ <TextView
+ android:id="@+id/message_view"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:minWidth="@dimen/bubble_message_min_width"
+ android:maxWidth="@dimen/bubble_message_max_width"
+ android:padding="@dimen/bubble_message_padding"/>
+
+</com.android.systemui.bubbles.BubbleView>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 10e5f74..ef16bca 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -981,14 +981,16 @@
<!-- How much a bubble is elevated -->
<dimen name="bubble_elevation">8dp</dimen>
+ <!-- Padding around a collapsed bubble -->
+ <dimen name="bubble_view_padding">0dp</dimen>
<!-- Padding between bubbles when displayed in expanded state -->
<dimen name="bubble_padding">8dp</dimen>
- <!-- Padding around the view displayed when the bubble is expanded -->
- <dimen name="bubble_expanded_view_padding">8dp</dimen>
<!-- Size of the collapsed bubble -->
<dimen name="bubble_size">56dp</dimen>
- <!-- Size of an icon displayed within the bubble -->
- <dimen name="bubble_icon_size">24dp</dimen>
+ <!-- How much to inset the icon in the circle -->
+ <dimen name="bubble_icon_inset">16dp</dimen>
+ <!-- Padding around the view displayed when the bubble is expanded -->
+ <dimen name="bubble_expanded_view_padding">8dp</dimen>
<!-- Default height of the expanded view shown when the bubble is expanded -->
<dimen name="bubble_expanded_default_height">400dp</dimen>
<!-- Height of the triangle that points to the expanded bubble -->
@@ -1001,4 +1003,10 @@
<dimen name="bubble_expanded_header_height">48dp</dimen>
<!-- Left and right padding applied to the header. -->
<dimen name="bubble_expanded_header_horizontal_padding">24dp</dimen>
+ <!-- Max width of the message bubble-->
+ <dimen name="bubble_message_max_width">144dp</dimen>
+ <!-- Min width of the message bubble -->
+ <dimen name="bubble_message_min_width">32dp</dimen>
+ <!-- Interior padding of the message bubble -->
+ <dimen name="bubble_message_padding">4dp</dimen>
</resources>
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BadgeRenderer.java b/packages/SystemUI/src/com/android/systemui/bubbles/BadgeRenderer.java
new file mode 100644
index 0000000..845b084
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BadgeRenderer.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2018 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.systemui.bubbles;
+
+import static android.graphics.Paint.ANTI_ALIAS_FLAG;
+import static android.graphics.Paint.FILTER_BITMAP_FLAG;
+
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.util.Log;
+
+// XXX: Mostly opied from launcher code / can we share?
+/**
+ * Contains parameters necessary to draw a badge for an icon (e.g. the size of the badge).
+ */
+public class BadgeRenderer {
+
+ private static final String TAG = "BadgeRenderer";
+
+ // The badge sizes are defined as percentages of the app icon size.
+ private static final float SIZE_PERCENTAGE = 0.38f;
+
+ // Extra scale down of the dot
+ private static final float DOT_SCALE = 0.6f;
+
+ private final float mDotCenterOffset;
+ private final float mCircleRadius;
+ private final Paint mCirclePaint = new Paint(ANTI_ALIAS_FLAG | FILTER_BITMAP_FLAG);
+
+ public BadgeRenderer(int iconSizePx) {
+ mDotCenterOffset = SIZE_PERCENTAGE * iconSizePx;
+ int size = (int) (DOT_SCALE * mDotCenterOffset);
+ mCircleRadius = size / 2f;
+ }
+
+ /**
+ * Draw a circle in the top right corner of the given bounds.
+ *
+ * @param color The color (based on the icon) to use for the badge.
+ * @param iconBounds The bounds of the icon being badged.
+ * @param badgeScale The progress of the animation, from 0 to 1.
+ * @param spaceForOffset How much space to offset the badge up and to the left or right.
+ * @param onLeft Whether the badge should be draw on left or right side.
+ */
+ public void draw(Canvas canvas, int color, Rect iconBounds, float badgeScale,
+ Point spaceForOffset, boolean onLeft) {
+ if (iconBounds == null) {
+ Log.e(TAG, "Invalid null argument(s) passed in call to draw.");
+ return;
+ }
+ canvas.save();
+ // We draw the badge relative to its center.
+ int x = onLeft ? iconBounds.left : iconBounds.right;
+ float offset = onLeft ? (mDotCenterOffset / 2) : -(mDotCenterOffset / 2);
+ float badgeCenterX = x + offset;
+ float badgeCenterY = iconBounds.top + mDotCenterOffset / 2;
+
+ canvas.translate(badgeCenterX + spaceForOffset.x, badgeCenterY - spaceForOffset.y);
+
+ canvas.scale(badgeScale, badgeScale);
+ mCirclePaint.setColor(color);
+ canvas.drawCircle(0, 0, mCircleRadius, mCirclePaint);
+ canvas.restore();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java
new file mode 100644
index 0000000..92d3cc1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BadgedImageView.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2018 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.systemui.bubbles;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Path;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.widget.ImageView;
+
+import com.android.systemui.R;
+
+/**
+ * View that circle crops its contents and supports displaying a coloured dot on a top corner.
+ */
+public class BadgedImageView extends ImageView {
+
+ private BadgeRenderer mDotRenderer;
+ private int mIconSize;
+ private Rect mTempBounds = new Rect();
+ private Point mTempPoint = new Point();
+ private Path mClipPath = new Path();
+
+ private float mDotScale = 0f;
+ private int mUpdateDotColor;
+ private boolean mShowUpdateDot;
+ private boolean mOnLeft;
+
+ public BadgedImageView(Context context) {
+ this(context, null);
+ }
+
+ public BadgedImageView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public BadgedImageView(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public BadgedImageView(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ setScaleType(ScaleType.CENTER_CROP);
+ mIconSize = getResources().getDimensionPixelSize(R.dimen.bubble_size);
+ mDotRenderer = new BadgeRenderer(mIconSize);
+ }
+
+ // TODO: Clipping oval path isn't great: rerender image into a separate, rounded bitmap and
+ // then draw would be better
+ @Override
+ public void onDraw(Canvas canvas) {
+ canvas.save();
+ // Circle crop
+ mClipPath.addOval(getPaddingStart(), getPaddingTop(),
+ getWidth() - getPaddingEnd(), getHeight() - getPaddingBottom(), Path.Direction.CW);
+ canvas.clipPath(mClipPath);
+ super.onDraw(canvas);
+
+ // After we've circle cropped what we're showing, restore so we don't clip the badge
+ canvas.restore();
+
+ // Draw the badge
+ if (mShowUpdateDot) {
+ getDrawingRect(mTempBounds);
+ mTempPoint.set((getWidth() - mIconSize) / 2, getPaddingTop());
+ mDotRenderer.draw(canvas, mUpdateDotColor, mTempBounds, mDotScale, mTempPoint,
+ mOnLeft);
+ }
+ }
+
+ /**
+ * Set whether the dot should appear on left or right side of the view.
+ */
+ public void setDotPosition(boolean onLeft) {
+ mOnLeft = onLeft;
+ invalidate();
+ }
+
+ /**
+ * Set whether the dot should show or not.
+ */
+ public void setShowDot(boolean showBadge) {
+ mShowUpdateDot = showBadge;
+ invalidate();
+ }
+
+ /**
+ * @return whether the dot is being displayed.
+ */
+ public boolean isShowingDot() {
+ return mShowUpdateDot;
+ }
+
+ /**
+ * The colour to use for the dot.
+ */
+ public void setDotColor(int color) {
+ mUpdateDotColor = color;
+ invalidate();
+ }
+
+ /**
+ * How big the dot should be, fraction from 0 to 1.
+ */
+ public void setDotScale(float fraction) {
+ mDotScale = fraction;
+ invalidate();
+ }
+
+ public float getDotScale() {
+ return mDotScale;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index 9f3ff78..d7bf77d 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -21,33 +21,42 @@
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import static com.android.systemui.bubbles.BubbleMovementHelper.EDGE_OVERLAP;
+import static com.android.systemui.statusbar.StatusBarState.SHADE;
+import static com.android.systemui.statusbar.notification.NotificationAlertingManager.alertAgain;
+import android.annotation.Nullable;
+import android.app.INotificationManager;
import android.app.Notification;
import android.app.PendingIntent;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.graphics.Point;
import android.graphics.Rect;
+import android.os.RemoteException;
+import android.os.ServiceManager;
import android.provider.Settings;
import android.service.notification.StatusBarNotification;
import android.util.Log;
+import android.view.LayoutInflater;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.FrameLayout;
-import androidx.annotation.Nullable;
-
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.statusbar.NotificationVisibility;
import com.android.systemui.Dependency;
import com.android.systemui.R;
+import com.android.systemui.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.row.NotificationInflater;
import com.android.systemui.statusbar.phone.StatusBarWindowController;
-import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
+import java.util.Set;
import javax.inject.Inject;
import javax.inject.Singleton;
@@ -66,8 +75,6 @@
// Enables some subset of notifs to automatically become bubbles
private static final boolean DEBUG_ENABLE_AUTO_BUBBLE = false;
- // When a bubble is dismissed, recreate it as a notification
- private static final boolean DEBUG_DEMOTE_TO_NOTIF = false;
// Secure settings
private static final String ENABLE_AUTO_BUBBLE_MESSAGES = "experiment_autobubble_messaging";
@@ -80,6 +87,7 @@
private final NotificationEntryManager mNotificationEntryManager;
private BubbleStateChangeListener mStateChangeListener;
private BubbleExpandListener mExpandListener;
+ private LayoutInflater mInflater;
private final Map<String, BubbleView> mBubbles = new HashMap<>();
private BubbleStackView mStackView;
@@ -87,6 +95,12 @@
// Bubbles get added to the status bar view
private final StatusBarWindowController mStatusBarWindowController;
+ private StatusBarStateListener mStatusBarStateListener;
+
+ private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider =
+ Dependency.get(NotificationInterruptionStateProvider.class);
+
+ private INotificationManager mNotificationManagerService;
// Used for determining view rect for touch interaction
private Rect mTempRect = new Rect();
@@ -107,23 +121,53 @@
public interface BubbleExpandListener {
/**
* Called when the expansion state of the bubble stack changes.
- *
* @param isExpanding whether it's expanding or collapsing
- * @param amount fraction of how expanded or collapsed it is, 1 being fully, 0 at the start
+ * @param key the notification key associated with bubble being expanded
*/
- void onBubbleExpandChanged(boolean isExpanding, float amount);
+ void onBubbleExpandChanged(boolean isExpanding, String key);
+ }
+
+ /**
+ * Listens for the current state of the status bar and updates the visibility state
+ * of bubbles as needed.
+ */
+ private class StatusBarStateListener implements StatusBarStateController.StateListener {
+ private int mState;
+ /**
+ * Returns the current status bar state.
+ */
+ public int getCurrentState() {
+ return mState;
+ }
+
+ @Override
+ public void onStateChanged(int newState) {
+ mState = newState;
+ updateVisibility();
+ }
}
@Inject
public BubbleController(Context context, StatusBarWindowController statusBarWindowController) {
mContext = context;
- mNotificationEntryManager = Dependency.get(NotificationEntryManager.class);
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
mDisplaySize = new Point();
wm.getDefaultDisplay().getSize(mDisplaySize);
- mStatusBarWindowController = statusBarWindowController;
+ mInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ mNotificationEntryManager = Dependency.get(NotificationEntryManager.class);
mNotificationEntryManager.addNotificationEntryListener(mEntryListener);
+
+ try {
+ mNotificationManagerService = INotificationManager.Stub.asInterface(
+ ServiceManager.getServiceOrThrow(Context.NOTIFICATION_SERVICE));
+ } catch (ServiceManager.ServiceNotFoundException e) {
+ e.printStackTrace();
+ }
+
+ mStatusBarWindowController = statusBarWindowController;
+ mStatusBarStateListener = new StatusBarStateListener();
+ Dependency.get(StatusBarStateController.class).addCallback(mStatusBarStateListener);
}
/**
@@ -148,7 +192,12 @@
* screen (e.g. if on AOD).
*/
public boolean hasBubbles() {
- return mBubbles.size() > 0;
+ for (BubbleView bv : mBubbles.values()) {
+ if (!bv.getEntry().isBubbleDismissed()) {
+ return true;
+ }
+ }
+ return false;
}
/**
@@ -163,7 +212,7 @@
*/
public void collapseStack() {
if (mStackView != null) {
- mStackView.animateExpansion(false);
+ mStackView.collapseStack();
}
}
@@ -174,33 +223,32 @@
if (mStackView == null) {
return;
}
- Point startPoint = getStartPoint(mStackView.getStackWidth(), mDisplaySize);
- // Reset the position of the stack (TODO - or should we save / respect last user position?)
- mStackView.setPosition(startPoint.x, startPoint.y);
- for (String key: mBubbles.keySet()) {
- removeBubble(key);
+ Set<String> keys = mBubbles.keySet();
+ for (String key: keys) {
+ mBubbles.get(key).getEntry().setBubbleDismissed(true);
}
+ mStackView.stackDismissed();
+
+ // Reset the position of the stack (TODO - or should we save / respect last user position?)
+ Point startPoint = getStartPoint(mStackView.getStackWidth(), mDisplaySize);
+ mStackView.setPosition(startPoint.x, startPoint.y);
+
+ updateVisibility();
mNotificationEntryManager.updateNotifications();
- updateBubblesShowing();
}
/**
- * Adds a bubble associated with the provided notification entry or updates it if it exists.
+ * Adds or updates a bubble associated with the provided notification entry.
+ *
+ * @param notif the notification associated with this bubble.
+ * @param updatePosition whether this update should promote the bubble to the top of the stack.
*/
- public void addBubble(NotificationEntry notif) {
+ public void updateBubble(NotificationEntry notif, boolean updatePosition) {
if (mBubbles.containsKey(notif.key)) {
// It's an update
BubbleView bubble = mBubbles.get(notif.key);
- mStackView.updateBubble(bubble, notif);
+ mStackView.updateBubble(bubble, notif, updatePosition);
} else {
- // It's new
- BubbleView bubble = new BubbleView(mContext);
- bubble.setNotif(notif);
- if (shouldUseActivityView(mContext)) {
- bubble.setAppOverlayIntent(getAppOverlayIntent(notif));
- }
- mBubbles.put(bubble.getKey(), bubble);
-
boolean setPosition = mStackView != null && mStackView.getVisibility() != VISIBLE;
if (mStackView == null) {
setPosition = true;
@@ -215,15 +263,22 @@
mStackView.setExpandListener(mExpandListener);
}
}
+ // It's new
+ BubbleView bubble = (BubbleView) mInflater.inflate(
+ R.layout.bubble_view, mStackView, false /* attachToRoot */);
+ bubble.setNotif(notif);
+ if (shouldUseActivityView(mContext)) {
+ bubble.setAppOverlayIntent(getAppOverlayIntent(notif));
+ }
+ mBubbles.put(bubble.getKey(), bubble);
mStackView.addBubble(bubble);
if (setPosition) {
// Need to add the bubble to the stack before we can know the width
Point startPoint = getStartPoint(mStackView.getStackWidth(), mDisplaySize);
mStackView.setPosition(startPoint.x, startPoint.y);
- mStackView.setVisibility(VISIBLE);
}
- updateBubblesShowing();
}
+ updateVisibility();
}
@Nullable
@@ -245,79 +300,96 @@
* Removes the bubble associated with the {@param uri}.
*/
void removeBubble(String key) {
- BubbleView bv = mBubbles.get(key);
+ BubbleView bv = mBubbles.remove(key);
if (mStackView != null && bv != null) {
mStackView.removeBubble(bv);
bv.destroyActivityView(mStackView);
- bv.getEntry().setBubbleDismissed(true);
}
- NotificationEntry entry = mNotificationEntryManager.getNotificationData().get(key);
+ NotificationEntry entry = bv != null ? bv.getEntry() : null;
if (entry != null) {
entry.setBubbleDismissed(true);
- if (!DEBUG_DEMOTE_TO_NOTIF) {
- mNotificationEntryManager.performRemoveNotification(entry.notification);
- }
+ mNotificationEntryManager.updateNotifications();
}
- mNotificationEntryManager.updateNotifications();
-
- updateBubblesShowing();
+ updateVisibility();
}
@SuppressWarnings("FieldCanBeLocal")
private final NotificationEntryListener mEntryListener = new NotificationEntryListener() {
@Override
public void onPendingEntryAdded(NotificationEntry entry) {
- if (shouldAutoBubble(mContext, entry)) {
+ if (shouldAutoBubble(mContext, entry) || shouldBubble(entry)) {
+ // TODO: handle group summaries
+ // It's a new notif, it shows in the shade and as a bubble
entry.setIsBubble(true);
+ entry.setShowInShadeWhenBubble(true);
+ }
+ }
+
+ @Override
+ public void onEntryInflated(NotificationEntry entry,
+ @NotificationInflater.InflationFlag int inflatedFlags) {
+ if (entry.isBubble() && mNotificationInterruptionStateProvider.shouldBubbleUp(entry)) {
+ updateBubble(entry, true /* updatePosition */);
+ }
+ }
+
+ @Override
+ public void onPreEntryUpdated(NotificationEntry entry) {
+ if (mNotificationInterruptionStateProvider.shouldBubbleUp(entry)
+ && alertAgain(entry, entry.notification.getNotification())) {
+ entry.setShowInShadeWhenBubble(true);
+ entry.setBubbleDismissed(false); // updates come back as bubbles even if dismissed
+ if (mBubbles.containsKey(entry.key)) {
+ mBubbles.get(entry.key).updateDotVisibility();
+ }
+ updateBubble(entry, true /* updatePosition */);
+ }
+ }
+
+ @Override
+ public void onEntryRemoved(NotificationEntry entry,
+ @Nullable NotificationVisibility visibility,
+ boolean removedByUser) {
+ entry.setShowInShadeWhenBubble(false);
+ if (mBubbles.containsKey(entry.key)) {
+ mBubbles.get(entry.key).updateDotVisibility();
+ }
+ if (!removedByUser) {
+ // This was a cancel so we should remove the bubble
+ removeBubble(entry.key);
}
}
};
+ /**
+ * Lets any listeners know if bubble state has changed.
+ */
private void updateBubblesShowing() {
- boolean hasBubblesShowing = false;
- for (BubbleView bv : mBubbles.values()) {
- if (!bv.getEntry().isBubbleDismissed()) {
- hasBubblesShowing = true;
- break;
- }
+ if (mStackView == null) {
+ return;
}
+
boolean hadBubbles = mStatusBarWindowController.getBubblesShowing();
+ boolean hasBubblesShowing = hasBubbles() && mStackView.getVisibility() == VISIBLE;
mStatusBarWindowController.setBubblesShowing(hasBubblesShowing);
- if (mStackView != null && !hasBubblesShowing) {
- mStackView.setVisibility(INVISIBLE);
- }
if (mStateChangeListener != null && hadBubbles != hasBubblesShowing) {
mStateChangeListener.onHasBubblesChanged(hasBubblesShowing);
}
}
/**
- * Sets the visibility of the bubbles, doesn't un-bubble them, just changes visibility.
+ * Updates the visibility of the bubbles based on current state.
+ * Does not un-bubble, just hides or un-hides. Will notify any
+ * {@link BubbleStateChangeListener}s if visibility changes.
*/
- public void updateVisibility(boolean visible) {
- if (mStackView == null) {
- return;
- }
- ArrayList<BubbleView> viewsToRemove = new ArrayList<>();
- for (BubbleView bv : mBubbles.values()) {
- NotificationEntry entry = bv.getEntry();
- if (entry != null) {
- if (entry.isRowRemoved() || entry.isBubbleDismissed() || entry.isRowDismissed()) {
- viewsToRemove.add(bv);
- }
- }
- }
- for (BubbleView bubbleView : viewsToRemove) {
- mBubbles.remove(bubbleView.getKey());
- mStackView.removeBubble(bubbleView);
- bubbleView.destroyActivityView(mStackView);
- }
- if (mStackView != null) {
- mStackView.setVisibility(visible ? VISIBLE : INVISIBLE);
- if (!visible) {
- collapseStack();
- }
+ public void updateVisibility() {
+ if (mStatusBarStateListener.getCurrentState() == SHADE && hasBubbles()) {
+ // Bubbles only appear in unlocked shade
+ mStackView.setVisibility(hasBubbles() ? VISIBLE : INVISIBLE);
+ } else if (mStackView != null) {
+ mStackView.setVisibility(INVISIBLE);
+ collapseStack();
}
updateBubblesShowing();
}
@@ -368,18 +440,41 @@
}
/**
- * Whether the notification should bubble or not.
+ * Whether the notification has been developer configured to bubble and is allowed by the user.
*/
- private static boolean shouldAutoBubble(Context context, NotificationEntry entry) {
+ private boolean shouldBubble(NotificationEntry entry) {
+ StatusBarNotification n = entry.notification;
+ boolean canAppOverlay = false;
+ try {
+ canAppOverlay = mNotificationManagerService.areAppOverlaysAllowedForPackage(
+ n.getPackageName(), n.getUid());
+ } catch (RemoteException e) {
+ Log.w(TAG, "Error calling NoMan to determine if app can overlay", e);
+ }
+
+ boolean canChannelOverlay = mNotificationEntryManager.getNotificationData().getChannel(
+ entry.key).canOverlayApps();
+ boolean hasOverlayIntent = n.getNotification().getAppOverlayIntent() != null;
+ return hasOverlayIntent && canChannelOverlay && canAppOverlay;
+ }
+
+ /**
+ * Whether the notification should bubble or not. Gated by debug flag.
+ * <p>
+ * If a notification has been set to bubble via proper bubble APIs or if it is an important
+ * message-like notification.
+ * </p>
+ */
+ private boolean shouldAutoBubble(Context context, NotificationEntry entry) {
if (entry.isBubbleDismissed()) {
return false;
}
+ StatusBarNotification n = entry.notification;
boolean autoBubbleMessages = shouldAutoBubbleMessages(context) || DEBUG_ENABLE_AUTO_BUBBLE;
boolean autoBubbleOngoing = shouldAutoBubbleOngoing(context) || DEBUG_ENABLE_AUTO_BUBBLE;
boolean autoBubbleAll = shouldAutoBubbleAll(context) || DEBUG_ENABLE_AUTO_BUBBLE;
- StatusBarNotification n = entry.notification;
boolean hasRemoteInput = false;
if (n.getNotification().actions != null) {
for (Notification.Action action : n.getNotification().actions) {
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedViewContainer.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedViewContainer.java
index badefe1..71ae1f8 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedViewContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedViewContainer.java
@@ -21,6 +21,7 @@
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.drawable.ShapeDrawable;
+import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
import android.widget.LinearLayout;
@@ -88,6 +89,7 @@
*/
public void setHeaderText(CharSequence text) {
mHeaderView.setText(text);
+ mHeaderView.setVisibility(TextUtils.isEmpty(text) ? GONE : VISIBLE);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index 3280a33..9a11b96 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -64,9 +64,9 @@
private boolean mIsExpanded;
private int mExpandedBubbleHeight;
+ private BubbleTouchHandler mTouchHandler;
private BubbleView mExpandedBubble;
private Point mCollapsedPosition;
- private BubbleTouchHandler mTouchHandler;
private BubbleController.BubbleExpandListener mExpandListener;
private boolean mViewUpdatedRequested = false;
@@ -211,13 +211,24 @@
*/
public void setExpandedBubble(BubbleView bubbleToExpand) {
mExpandedBubble = bubbleToExpand;
+ boolean prevExpanded = mIsExpanded;
mIsExpanded = true;
- updateExpandedBubble();
- requestUpdate();
+ if (!prevExpanded) {
+ // If we weren't previously expanded we should animate open.
+ animateExpansion(true /* expand */);
+ } else {
+ // If we were expanded just update the views
+ updateExpandedBubble();
+ requestUpdate();
+ }
+ mExpandedBubble.getEntry().setShowInShadeWhenBubble(false);
+ notifyExpansionChanged(mExpandedBubble, true /* expanded */);
}
/**
- * Adds a bubble to the stack.
+ * Adds a bubble to the top of the stack.
+ *
+ * @param bubbleView the view to add to the stack.
*/
public void addBubble(BubbleView bubbleView) {
mBubbleContainer.addView(bubbleView, 0,
@@ -234,17 +245,26 @@
mBubbleContainer.removeView(bubbleView);
boolean wasExpanded = mIsExpanded;
int bubbleCount = mBubbleContainer.getChildCount();
- if (bubbleView.equals(mExpandedBubble) && bubbleCount > 0) {
+ if (mIsExpanded && bubbleView.equals(mExpandedBubble) && bubbleCount > 0) {
// If we have other bubbles and are expanded go to the next one or previous
// if the bubble removed was last
int nextIndex = bubbleCount > removedIndex ? removedIndex : bubbleCount - 1;
- mExpandedBubble = (BubbleView) mBubbleContainer.getChildAt(nextIndex);
+ BubbleView expandedBubble = (BubbleView) mBubbleContainer.getChildAt(nextIndex);
+ setExpandedBubble(expandedBubble);
}
mIsExpanded = wasExpanded && mBubbleContainer.getChildCount() > 0;
- requestUpdate();
- if (wasExpanded && !mIsExpanded && mExpandListener != null) {
- mExpandListener.onBubbleExpandChanged(mIsExpanded, 1 /* amount */);
+ if (wasExpanded != mIsExpanded) {
+ notifyExpansionChanged(mExpandedBubble, mIsExpanded);
}
+ requestUpdate();
+ }
+
+ /**
+ * Dismiss the stack of bubbles.
+ */
+ public void stackDismissed() {
+ collapseStack();
+ mBubbleContainer.removeAllViews();
}
/**
@@ -252,11 +272,19 @@
*
* @param bubbleView the view to update in the stack.
* @param entry the entry to update it with.
+ * @param updatePosition whether this bubble should be moved to top of the stack.
*/
- public void updateBubble(BubbleView bubbleView, NotificationEntry entry) {
- // TODO - move to top of bubble stack, make it show its update if it makes sense
+ public void updateBubble(BubbleView bubbleView, NotificationEntry entry,
+ boolean updatePosition) {
bubbleView.update(entry);
- if (bubbleView.equals(mExpandedBubble)) {
+ if (updatePosition && !mIsExpanded) {
+ // If alerting it gets promoted to top of the stack
+ mBubbleContainer.removeView(bubbleView);
+ mBubbleContainer.addView(bubbleView, 0);
+ requestUpdate();
+ }
+ if (mIsExpanded && bubbleView.equals(mExpandedBubble)) {
+ entry.setShowInShadeWhenBubble(false);
requestUpdate();
}
}
@@ -287,17 +315,36 @@
}
/**
+ * Collapses the stack of bubbles.
+ */
+ public void collapseStack() {
+ if (mIsExpanded) {
+ // TODO: Save opened bubble & move it to top of stack
+ animateExpansion(false /* shouldExpand */);
+ notifyExpansionChanged(mExpandedBubble, mIsExpanded);
+ }
+ }
+
+ /**
+ * Expands the stack fo bubbles.
+ */
+ public void expandStack() {
+ if (!mIsExpanded) {
+ mExpandedBubble = getTopBubble();
+ mExpandedBubble.getEntry().setShowInShadeWhenBubble(false);
+ animateExpansion(true /* shouldExpand */);
+ notifyExpansionChanged(mExpandedBubble, true /* expanded */);
+ }
+ }
+
+ /**
* Tell the stack to animate to collapsed or expanded state.
*/
- public void animateExpansion(boolean shouldExpand) {
+ private void animateExpansion(boolean shouldExpand) {
if (mIsExpanded != shouldExpand) {
mIsExpanded = shouldExpand;
- mExpandedBubble = shouldExpand ? getTopBubble() : null;
updateExpandedBubble();
- if (mExpandListener != null) {
- mExpandListener.onBubbleExpandChanged(mIsExpanded, 1 /* amount */);
- }
if (shouldExpand) {
// Save current position so that we might return there
savePosition();
@@ -347,6 +394,13 @@
mCollapsedPosition = getPosition();
}
+ private void notifyExpansionChanged(BubbleView bubbleView, boolean expanded) {
+ if (mExpandListener != null) {
+ NotificationEntry entry = bubbleView != null ? bubbleView.getEntry() : null;
+ mExpandListener.onBubbleExpandChanged(expanded, entry != null ? entry.key : null);
+ }
+ }
+
private BubbleView getTopBubble() {
return getBubbleAt(0);
}
@@ -400,6 +454,7 @@
}
if (mExpandedBubble.hasAppOverlayIntent()) {
+ // Bubble with activity view expanded state
ActivityView expandedView = mExpandedBubble.getActivityView();
expandedView.setLayoutParams(new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, mExpandedBubbleHeight));
@@ -417,19 +472,27 @@
@Override
public void onActivityViewDestroyed(ActivityView view) {
- NotificationEntry entry = mExpandedBubble.getEntry();
+ NotificationEntry entry = mExpandedBubble != null
+ ? mExpandedBubble.getEntry() : null;
Log.d(TAG, "onActivityViewDestroyed(key="
+ ((entry != null) ? entry.key : "(none)") + "): " + view);
}
});
} else {
+ // Bubble with notification view expanded state
ExpandableNotificationRow row = mExpandedBubble.getRowView();
- if (!row.equals(mExpandedViewContainer.getExpandedView())) {
- // Different expanded view than what we have
+ if (row.getParent() != null) {
+ // Row might still be in the shade when we expand
+ ((ViewGroup) row.getParent()).removeView(row);
+ }
+ if (mIsExpanded) {
+ mExpandedViewContainer.setExpandedView(row);
+ } else {
mExpandedViewContainer.setExpandedView(null);
}
- mExpandedViewContainer.setExpandedView(row);
+ // Bubble with notification as expanded state doesn't need a header / title
mExpandedViewContainer.setHeaderText(null);
+
}
int pointerPosition = mExpandedBubble.getPosition().x
+ (mExpandedBubble.getWidth() / 2);
@@ -456,7 +519,8 @@
int bubbsCount = mBubbleContainer.getChildCount();
for (int i = 0; i < bubbsCount; i++) {
BubbleView bv = (BubbleView) mBubbleContainer.getChildAt(i);
- bv.setZ(bubbsCount - 1);
+ bv.updateDotVisibility();
+ bv.setZ(bubbsCount - i);
int transX = mIsExpanded ? (bv.getWidth() + mBubblePadding) * i : mBubblePadding * i;
ViewState viewState = new ViewState();
@@ -510,6 +574,7 @@
private void applyRowState(ExpandableNotificationRow view) {
view.reset();
view.setHeadsUp(false);
+ view.resetTranslation();
view.setOnKeyguard(false);
view.setOnAmbient(false);
view.setClipBottomAmount(0);
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
index 96b2dba..97784b0 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTouchHandler.java
@@ -110,7 +110,7 @@
: stack.getTargetView(event);
boolean isFloating = targetView instanceof FloatingView;
if (!isFloating || targetView == null || action == MotionEvent.ACTION_OUTSIDE) {
- stack.animateExpansion(false /* shouldExpand */);
+ stack.collapseStack();
cleanUpDismissTarget();
resetTouches();
return false;
@@ -196,9 +196,13 @@
mMovementHelper.getTranslateAnim(floatingView, toGoTo, 100, 0).start();
}
} else if (floatingView.equals(stack.getExpandedBubble())) {
- stack.animateExpansion(false /* shouldExpand */);
+ stack.collapseStack();
} else if (isBubbleStack) {
- stack.animateExpansion(!stack.isExpanded() /* shouldExpand */);
+ if (stack.isExpanded()) {
+ stack.collapseStack();
+ } else {
+ stack.expandStack();
+ }
} else {
stack.setExpandedBubble((BubbleView) floatingView);
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java
index c1bbb93..91893ef 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleView.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2012 The Android Open Source Project
+ * Copyright (C) 2018 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.
@@ -16,40 +16,47 @@
package com.android.systemui.bubbles;
+import android.annotation.Nullable;
import android.app.ActivityView;
import android.app.Notification;
import android.app.PendingIntent;
import android.content.Context;
import android.graphics.Color;
import android.graphics.Point;
+import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
-import android.graphics.drawable.ShapeDrawable;
-import android.graphics.drawable.shapes.OvalShape;
+import android.graphics.drawable.InsetDrawable;
+import android.graphics.drawable.LayerDrawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
+import android.widget.FrameLayout;
+import android.widget.TextView;
-import com.android.internal.util.ContrastColorUtil;
+import com.android.internal.graphics.ColorUtils;
+import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
/**
- * A floating object on the screen that has a collapsed and expanded state.
+ * A floating object on the screen that can post message updates.
*/
-class BubbleView extends LinearLayout implements BubbleTouchHandler.FloatingView {
+public class BubbleView extends FrameLayout implements BubbleTouchHandler.FloatingView {
private static final String TAG = "BubbleView";
+ // Same value as Launcher3 badge code
+ private static final float WHITE_SCRIM_ALPHA = 0.54f;
private Context mContext;
- private View mIconView;
+
+ private BadgedImageView mBadgedImageView;
+ private TextView mMessageView;
+ private int mPadding;
+ private int mIconInset;
private NotificationEntry mEntry;
- private int mBubbleSize;
- private int mIconSize;
private PendingIntent mAppOverlayIntent;
private ActivityView mActivityView;
@@ -67,66 +74,156 @@
public BubbleView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
- setOrientation(LinearLayout.VERTICAL);
mContext = context;
- mBubbleSize = getResources().getDimensionPixelSize(R.dimen.bubble_size);
- mIconSize = getResources().getDimensionPixelSize(R.dimen.bubble_icon_size);
+ // XXX: can this padding just be on the view and we look it up?
+ mPadding = getResources().getDimensionPixelSize(R.dimen.bubble_view_padding);
+ mIconInset = getResources().getDimensionPixelSize(R.dimen.bubble_icon_inset);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mBadgedImageView = (BadgedImageView) findViewById(R.id.bubble_image);
+ mMessageView = (TextView) findViewById(R.id.message_view);
+ mMessageView.setVisibility(GONE);
+ mMessageView.setPivotX(0);
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ updateViews();
+ }
+
+ @Override
+ protected void onMeasure(int widthSpec, int heightSpec) {
+ measureChild(mBadgedImageView, widthSpec, heightSpec);
+ measureChild(mMessageView, widthSpec, heightSpec);
+ boolean messageGone = mMessageView.getVisibility() == GONE;
+ int imageHeight = mBadgedImageView.getMeasuredHeight();
+ int imageWidth = mBadgedImageView.getMeasuredWidth();
+ int messageHeight = messageGone ? 0 : mMessageView.getMeasuredHeight();
+ int messageWidth = messageGone ? 0 : mMessageView.getMeasuredWidth();
+ setMeasuredDimension(
+ getPaddingStart() + imageWidth + mPadding + messageWidth + getPaddingEnd(),
+ getPaddingTop() + Math.max(imageHeight, messageHeight) + getPaddingBottom());
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ left = getPaddingStart();
+ top = getPaddingTop();
+ int imageWidth = mBadgedImageView.getMeasuredWidth();
+ int imageHeight = mBadgedImageView.getMeasuredHeight();
+ int messageWidth = mMessageView.getMeasuredWidth();
+ int messageHeight = mMessageView.getMeasuredHeight();
+ mBadgedImageView.layout(left, top, left + imageWidth, top + imageHeight);
+ mMessageView.layout(left + imageWidth + mPadding, top,
+ left + imageWidth + mPadding + messageWidth, top + messageHeight);
}
/**
* Populates this view with a notification.
+ * <p>
+ * This should only be called when a new notification is being set on the view, updates to the
+ * current notification should use {@link #update(NotificationEntry)}.
*
* @param entry the notification to display as a bubble.
*/
public void setNotif(NotificationEntry entry) {
- removeAllViews();
- // TODO: migrate to inflater
- mIconView = new ImageView(mContext);
- addView(mIconView);
-
- LinearLayout.LayoutParams iconLp = (LinearLayout.LayoutParams) mIconView.getLayoutParams();
- iconLp.width = mBubbleSize;
- iconLp.height = mBubbleSize;
- mIconView.setLayoutParams(iconLp);
-
- update(entry);
- }
-
- /**
- * Updates the UI based on the entry.
- */
- public void update(NotificationEntry entry) {
mEntry = entry;
- Notification n = entry.notification.getNotification();
- Icon ic = n.getLargeIcon() != null ? n.getLargeIcon() : n.getSmallIcon();
-
- if (n.getLargeIcon() == null) {
- createCircledIcon(n.color, ic, ((ImageView) mIconView));
- } else {
- ((ImageView) mIconView).setImageIcon(ic);
- }
+ updateViews();
}
/**
- * @return the key identifying this bubble / notification entry associated with this
- * bubble, if it exists.
+ * The {@link NotificationEntry} associated with this view, if one exists.
*/
- public String getKey() {
- return mEntry == null ? null : mEntry.key;
- }
-
- /**
- * @return the notification entry associated with this bubble.
- */
+ @Nullable
public NotificationEntry getEntry() {
return mEntry;
}
/**
- * @return the view to display notification content when the bubble is expanded.
+ * The key for the {@link NotificationEntry} associated with this view, if one exists.
*/
+ @Nullable
+ public String getKey() {
+ return (mEntry != null) ? mEntry.key : null;
+ }
+
+ /**
+ * Updates the UI based on the entry, updates badge and animates messages as needed.
+ */
+ public void update(NotificationEntry entry) {
+ mEntry = entry;
+ updateViews();
+ }
+
+
+ /**
+ * @return the {@link ExpandableNotificationRow} view to display notification content when the
+ * bubble is expanded.
+ */
+ @Nullable
public ExpandableNotificationRow getRowView() {
- return mEntry.getRow();
+ return (mEntry != null) ? mEntry.getRow() : null;
+ }
+
+ /**
+ * Marks this bubble as "read", i.e. no badge should show.
+ */
+ public void updateDotVisibility() {
+ boolean showDot = getEntry().showInShadeWhenBubble();
+ animateDot(showDot);
+ }
+
+ /**
+ * Animates the badge to show or hide.
+ */
+ private void animateDot(boolean showDot) {
+ if (mBadgedImageView.isShowingDot() != showDot) {
+ mBadgedImageView.setShowDot(showDot);
+ mBadgedImageView.clearAnimation();
+ mBadgedImageView.animate().setDuration(200)
+ .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
+ .setUpdateListener((valueAnimator) -> {
+ float fraction = valueAnimator.getAnimatedFraction();
+ fraction = showDot ? fraction : 1 - fraction;
+ mBadgedImageView.setDotScale(fraction);
+ }).withEndAction(() -> {
+ if (!showDot) {
+ mBadgedImageView.setShowDot(false);
+ }
+ }).start();
+ }
+ }
+
+ private void updateViews() {
+ if (mEntry == null) {
+ return;
+ }
+ Notification n = mEntry.notification.getNotification();
+ boolean isLarge = n.getLargeIcon() != null;
+ Icon ic = isLarge ? n.getLargeIcon() : n.getSmallIcon();
+ Drawable iconDrawable = ic.loadDrawable(mContext);
+ if (!isLarge) {
+ // Center icon on coloured background
+ iconDrawable.setTint(Color.WHITE); // TODO: dark mode
+ Drawable bg = new ColorDrawable(n.color);
+ InsetDrawable d = new InsetDrawable(iconDrawable, mIconInset);
+ Drawable[] layers = {bg, d};
+ mBadgedImageView.setImageDrawable(new LayerDrawable(layers));
+ } else {
+ mBadgedImageView.setImageDrawable(iconDrawable);
+ }
+ int badgeColor = determineDominateColor(iconDrawable, n.color);
+ mBadgedImageView.setDotColor(badgeColor);
+ animateDot(mEntry.showInShadeWhenBubble() /* showDot */);
+ }
+
+ private int determineDominateColor(Drawable d, int defaultTint) {
+ // XXX: should we pull from the drawable, app icon, notif tint?
+ return ColorUtils.blendARGB(defaultTint, Color.WHITE, WHITE_SCRIM_ALPHA);
}
/**
@@ -170,8 +267,8 @@
@Override
public void setPosition(int x, int y) {
- setTranslationX(x);
- setTranslationY(y);
+ setPositionX(x);
+ setPositionY(y);
}
@Override
@@ -189,25 +286,6 @@
return new Point((int) getTranslationX(), (int) getTranslationY());
}
- // Seems sub optimal
- private void createCircledIcon(int tint, Icon icon, ImageView v) {
- // TODO: dark mode
- icon.setTint(Color.WHITE);
- icon.scaleDownIfNecessary(mIconSize, mIconSize);
- v.setImageDrawable(icon.loadDrawable(mContext));
- v.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
- LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) v.getLayoutParams();
- int color = ContrastColorUtil.ensureContrast(tint, Color.WHITE,
- false /* isBgDarker */, 3);
- Drawable d = new ShapeDrawable(new OvalShape());
- d.setTint(color);
- v.setBackgroundDrawable(d);
-
- lp.width = mBubbleSize;
- lp.height = mBubbleSize;
- v.setLayoutParams(lp);
- }
-
/**
* @return whether an ActivityView should be used to display the content of this Bubble
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
index bf6caa0..f2ff85b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java
@@ -16,8 +16,6 @@
package com.android.systemui.statusbar;
-import static com.android.systemui.statusbar.StatusBarState.SHADE;
-
import android.content.Context;
import android.content.res.Resources;
import android.os.Trace;
@@ -26,7 +24,6 @@
import android.view.ViewGroup;
import com.android.systemui.R;
-import com.android.systemui.bubbles.BubbleController;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -66,7 +63,6 @@
protected final VisualStabilityManager mVisualStabilityManager;
private final StatusBarStateController mStatusBarStateController;
private final NotificationEntryManager mEntryManager;
- private final BubbleController mBubbleController;
// Lazy
private final Lazy<ShadeController> mShadeController;
@@ -80,41 +76,6 @@
private NotificationPresenter mPresenter;
private NotificationListContainer mListContainer;
- private StatusBarStateListener mStatusBarStateListener;
-
- /**
- * Listens for the current state of the status bar and updates the visibility state
- * of bubbles as needed.
- */
- public class StatusBarStateListener implements StatusBarStateController.StateListener {
- private int mState;
- private BubbleController mController;
-
- public StatusBarStateListener(BubbleController controller) {
- mController = controller;
- }
-
- /**
- * Returns the current status bar state.
- */
- public int getCurrentState() {
- return mState;
- }
-
- @Override
- public void onStateChanged(int newState) {
- mState = newState;
- // Order here matters because we need to remove the expandable notification row
- // from it's current parent (NSSL or bubble) before it can be added to the new parent
- if (mState == SHADE) {
- updateNotificationViews();
- mController.updateVisibility(true);
- } else {
- mController.updateVisibility(false);
- updateNotificationViews();
- }
- }
- }
@Inject
public NotificationViewHierarchyManager(Context context,
@@ -123,20 +84,16 @@
VisualStabilityManager visualStabilityManager,
StatusBarStateController statusBarStateController,
NotificationEntryManager notificationEntryManager,
- BubbleController bubbleController,
Lazy<ShadeController> shadeController) {
mLockscreenUserManager = notificationLockscreenUserManager;
mGroupManager = groupManager;
mVisualStabilityManager = visualStabilityManager;
mStatusBarStateController = statusBarStateController;
mEntryManager = notificationEntryManager;
- mBubbleController = bubbleController;
mShadeController = shadeController;
Resources res = context.getResources();
mAlwaysExpandNonGroupedNotification =
res.getBoolean(R.bool.config_alwaysExpandNonGroupedNotifications);
- mStatusBarStateListener = new StatusBarStateListener(mBubbleController);
- mStatusBarStateController.addCallback(mStatusBarStateListener);
}
public void setUpWithPresenter(NotificationPresenter presenter,
@@ -153,7 +110,6 @@
ArrayList<NotificationEntry> activeNotifications = mEntryManager.getNotificationData()
.getActiveNotifications();
ArrayList<ExpandableNotificationRow> toShow = new ArrayList<>(activeNotifications.size());
- ArrayList<NotificationEntry> toBubble = new ArrayList<>();
final int N = activeNotifications.size();
for (int i = 0; i < N; i++) {
NotificationEntry ent = activeNotifications.get(i);
@@ -162,13 +118,6 @@
// temporarily become children if they were isolated before.
continue;
}
- ent.getRow().setStatusBarState(mStatusBarStateListener.getCurrentState());
- boolean showAsBubble = ent.isBubble() && !ent.isBubbleDismissed()
- && mStatusBarStateListener.getCurrentState() == SHADE;
- if (showAsBubble) {
- toBubble.add(ent);
- continue;
- }
int userId = ent.notification.getUserId();
@@ -269,12 +218,6 @@
}
- for (int i = 0; i < toBubble.size(); i++) {
- // TODO: might make sense to leave them in the shade and just reposition them
- NotificationEntry ent = toBubble.get(i);
- mBubbleController.addBubble(ent);
- }
-
mVisualStabilityManager.onReorderingFinished();
// clear the map again for the next usage
mTmpChildOrderMap.clear();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationAlertingManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationAlertingManager.java
index 60d8cf4..5605f3d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationAlertingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationAlertingManager.java
@@ -150,7 +150,14 @@
}
}
- private static boolean alertAgain(
+ /**
+ * Checks whether an update for a notification warrants an alert for the user.
+ *
+ * @param oldEntry the entry for this notification.
+ * @param newNotification the new notification for this entry.
+ * @return whether this notification should alert the user.
+ */
+ public static boolean alertAgain(
NotificationEntry oldEntry, Notification newNotification) {
return oldEntry == null || !oldEntry.hasInterrupted()
|| (newNotification.flags & Notification.FLAG_ONLY_ALERT_ONCE) == 0;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java
index e199ead..154d7b35 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java
@@ -134,6 +134,10 @@
}
}
+ if (entry.isBubble() && !entry.showInShadeWhenBubble()) {
+ return true;
+ }
+
return false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java
index fc7a2b3..c50f10b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationInterruptionStateProvider.java
@@ -135,6 +135,29 @@
}
/**
+ * Whether the notification should appear as a bubble with a fly-out on top of the screen.
+ *
+ * @param entry the entry to check
+ * @return true if the entry should bubble up, false otherwise
+ */
+ public boolean shouldBubbleUp(NotificationEntry entry) {
+ StatusBarNotification sbn = entry.notification;
+ if (!entry.isBubble()) {
+ if (DEBUG) {
+ Log.d(TAG, "No bubble up: notification " + sbn.getKey()
+ + " is bubble? " + entry.isBubble());
+ }
+ return false;
+ }
+
+ if (!canHeadsUpCommon(entry)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
* Whether the notification should peek in from the top and alert the user.
*
* @param entry the entry to check
@@ -150,10 +173,12 @@
return false;
}
- // TODO: need to changes this, e.g. should still heads up in expanded shade, might want
- // message bubble from the bubble to go through heads up path
boolean inShade = mStatusBarStateController.getState() == SHADE;
- if (entry.isBubble() && !entry.isBubbleDismissed() && inShade) {
+ if (entry.isBubble() && inShade) {
+ if (DEBUG) {
+ Log.d(TAG, "No heads up: in unlocked shade where notification is shown as a "
+ + "bubble: " + sbn.getKey());
+ }
return false;
}
@@ -164,9 +189,13 @@
return false;
}
- if (!mUseHeadsUp || mPresenter.isDeviceInVrMode()) {
+ if (!canHeadsUpCommon(entry)) {
+ return false;
+ }
+
+ if (entry.importance < NotificationManager.IMPORTANCE_HIGH) {
if (DEBUG) {
- Log.d(TAG, "No heads up: no huns or vr mode");
+ Log.d(TAG, "No heads up: unimportant notification: " + sbn.getKey());
}
return false;
}
@@ -186,34 +215,6 @@
return false;
}
- if (entry.shouldSuppressPeek()) {
- if (DEBUG) {
- Log.d(TAG, "No heads up: suppressed by DND: " + sbn.getKey());
- }
- return false;
- }
-
- if (isSnoozedPackage(sbn)) {
- if (DEBUG) {
- Log.d(TAG, "No heads up: snoozed package: " + sbn.getKey());
- }
- return false;
- }
-
- if (entry.hasJustLaunchedFullScreenIntent()) {
- if (DEBUG) {
- Log.d(TAG, "No heads up: recent fullscreen: " + sbn.getKey());
- }
- return false;
- }
-
- if (entry.importance < NotificationManager.IMPORTANCE_HIGH) {
- if (DEBUG) {
- Log.d(TAG, "No heads up: unimportant notification: " + sbn.getKey());
- }
- return false;
- }
-
if (!mHeadsUpSuppressor.canHeadsUp(entry, sbn)) {
return false;
}
@@ -302,6 +303,49 @@
return true;
}
+ /**
+ * Common checks between heads up alerting and bubble fly out alerting. See
+ * {@link #shouldHeadsUp(NotificationEntry)} and
+ * {@link #shouldBubbleUp(NotificationEntry)}. Notifications that fail any of these
+ * checks should not interrupt the user on screen.
+ *
+ * @param entry the entry to check
+ * @return true if these checks pass, false if the notification should not interrupt on screen
+ */
+ public boolean canHeadsUpCommon(NotificationEntry entry) {
+ StatusBarNotification sbn = entry.notification;
+
+ if (!mUseHeadsUp || mPresenter.isDeviceInVrMode()) {
+ if (DEBUG) {
+ Log.d(TAG, "No heads up: no huns or vr mode");
+ }
+ return false;
+ }
+
+ if (entry.shouldSuppressPeek()) {
+ if (DEBUG) {
+ Log.d(TAG, "No heads up: suppressed by DND: " + sbn.getKey());
+ }
+ return false;
+ }
+
+ if (isSnoozedPackage(sbn)) {
+ if (DEBUG) {
+ Log.d(TAG, "No heads up: snoozed package: " + sbn.getKey());
+ }
+ return false;
+ }
+
+ if (entry.hasJustLaunchedFullScreenIntent()) {
+ if (DEBUG) {
+ Log.d(TAG, "No heads up: recent fullscreen: " + sbn.getKey());
+ }
+ return false;
+ }
+
+ return true;
+ }
+
private boolean isSnoozedPackage(StatusBarNotification sbn) {
return mHeadsUpManager.isSnoozed(sbn.getPackageName());
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index 58aa02c..ee551ee 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -141,6 +141,14 @@
private boolean mIsBubble;
/**
+ * Whether this notification should be shown in the shade when it is also displayed as a bubble.
+ *
+ * <p>When a notification is a bubble we don't show it in the shade once the bubble has been
+ * expanded</p>
+ */
+ private boolean mShowInShadeWhenBubble;
+
+ /**
* Whether the user has dismissed this notification when it was in bubble form.
*/
private boolean mUserDismissedBubble;
@@ -200,6 +208,23 @@
}
/**
+ * Sets whether this notification should be shown in the shade when it is also displayed as a
+ * bubble.
+ */
+ public void setShowInShadeWhenBubble(boolean showInShade) {
+ mShowInShadeWhenBubble = showInShade;
+ }
+
+ /**
+ * Whether this notification should be shown in the shade when it is also displayed as a
+ * bubble.
+ */
+ public boolean showInShadeWhenBubble() {
+ // We always show it in the shade if non-clearable
+ return !isClearable() || mShowInShadeWhenBubble;
+ }
+
+ /**
* Resets the notification entry to be re-used.
*/
public void reset() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 95bd1ce..df0189f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.notification.row;
-import static com.android.systemui.statusbar.StatusBarState.SHADE;
import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters;
import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_AMBIENT;
import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_CONTRACTED;
@@ -2322,7 +2321,7 @@
}
private boolean isShownAsBubble() {
- return mEntry.isBubble() && (mStatusBarState == SHADE || mStatusBarState == -1);
+ return mEntry.isBubble() && !mEntry.showInShadeWhenBubble() && !mEntry.isBubbleDismissed();
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 3f93192..514bb22 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -461,13 +461,6 @@
private NotificationMediaManager mMediaManager;
protected NotificationLockscreenUserManager mLockscreenUserManager;
protected NotificationRemoteInputManager mRemoteInputManager;
- protected BubbleController mBubbleController;
- private final BubbleController.BubbleExpandListener mBubbleExpandListener =
- (isExpanding, amount) -> {
- if (amount == 1) {
- updateScrimController();
- }
- };
private final BroadcastReceiver mWallpaperChangedReceiver = new BroadcastReceiver() {
@Override
@@ -589,6 +582,12 @@
private NotificationActivityStarter mNotificationActivityStarter;
private boolean mPulsing;
private ContentObserver mFeatureFlagObserver;
+ protected BubbleController mBubbleController;
+ private final BubbleController.BubbleExpandListener mBubbleExpandListener =
+ (isExpanding, key) -> {
+ mEntryManager.updateNotifications();
+ updateScrimController();
+ };
@Override
public void onActiveStateChanged(int code, int uid, String packageName, boolean active) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index 4f61009..04d24dc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -346,7 +346,7 @@
}
private void handleFullScreenIntent(NotificationEntry entry) {
- boolean isHeadsUped = mNotificationInterruptionStateProvider.shouldHeadsUp(entry);
+ boolean isHeadsUped = mNotificationInterruptionStateProvider.canHeadsUpCommon(entry);
if (!isHeadsUped && entry.notification.getNotification().fullScreenIntent != null) {
if (shouldSuppressFullScreenIntent(entry)) {
if (DEBUG) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
index 21d3652..fa5cf04 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
@@ -19,7 +19,6 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -74,7 +73,8 @@
private ExpandableNotificationRow mRow;
private ExpandableNotificationRow mRow2;
- private final NotificationData mNotificationData = new NotificationData();
+ @Mock
+ private NotificationData mNotificationData;
@Before
public void setUp() throws Exception {
@@ -93,6 +93,7 @@
// Return non-null notification data from the NEM
when(mNotificationEntryManager.getNotificationData()).thenReturn(mNotificationData);
+ when(mNotificationData.getChannel(mRow.getEntry().key)).thenReturn(mRow.getEntry().channel);
mBubbleController = new TestableBubbleController(mContext, mStatusBarWindowController);
@@ -103,26 +104,21 @@
}
@Test
- public void testIsBubble() {
- assertTrue(mRow.getEntry().isBubble());
- }
-
- @Test
public void testAddBubble() {
- mBubbleController.addBubble(mRow.getEntry());
+ mBubbleController.updateBubble(mRow.getEntry(), true /* updatePosition */);
assertTrue(mBubbleController.hasBubbles());
}
@Test
public void testHasBubbles() {
assertFalse(mBubbleController.hasBubbles());
- mBubbleController.addBubble(mRow.getEntry());
+ mBubbleController.updateBubble(mRow.getEntry(), true /* updatePosition */);
assertTrue(mBubbleController.hasBubbles());
}
@Test
public void testRemoveBubble() {
- mBubbleController.addBubble(mRow.getEntry());
+ mBubbleController.updateBubble(mRow.getEntry(), true /* updatePosition */);
assertTrue(mBubbleController.hasBubbles());
mBubbleController.removeBubble(mRow.getEntry().key);
@@ -133,35 +129,35 @@
@Test
public void testDismissStack() {
- mBubbleController.addBubble(mRow.getEntry());
- mBubbleController.addBubble(mRow2.getEntry());
+ mBubbleController.updateBubble(mRow.getEntry(), true /* updatePosition */);
+ mBubbleController.updateBubble(mRow2.getEntry(), true /* updatePosition */);
assertTrue(mBubbleController.hasBubbles());
mBubbleController.dismissStack();
assertFalse(mStatusBarWindowController.getBubblesShowing());
- verify(mNotificationEntryManager, times(3)).updateNotifications();
+ verify(mNotificationEntryManager).updateNotifications();
}
@Test
public void testIsStackExpanded() {
assertFalse(mBubbleController.isStackExpanded());
- mBubbleController.addBubble(mRow.getEntry());
+ mBubbleController.updateBubble(mRow.getEntry(), true /* updatePosition */);
BubbleStackView stackView = mBubbleController.getStackView();
- stackView.animateExpansion(true /* expanded */);
+ stackView.expandStack();
assertTrue(mBubbleController.isStackExpanded());
- stackView.animateExpansion(false /* expanded */);
+ stackView.collapseStack();
assertFalse(mBubbleController.isStackExpanded());
}
@Test
public void testCollapseStack() {
- mBubbleController.addBubble(mRow.getEntry());
- mBubbleController.addBubble(mRow2.getEntry());
+ mBubbleController.updateBubble(mRow.getEntry(), true /* updatePosition */);
+ mBubbleController.updateBubble(mRow2.getEntry(), true /* updatePosition */);
BubbleStackView stackView = mBubbleController.getStackView();
- stackView.animateExpansion(true /* expanded */);
+ stackView.expandStack();
assertTrue(mBubbleController.isStackExpanded());
mBubbleController.collapseStack();
@@ -174,6 +170,12 @@
assertTrue(mRow.getEntry().isBubble());
}
+ @Test
+ public void testMarkNewNotificationAsShowInShade() {
+ mEntryListener.onPendingEntryAdded(mRow.getEntry());
+ assertTrue(mRow.getEntry().showInShadeWhenBubble());
+ }
+
static class TestableBubbleController extends BubbleController {
TestableBubbleController(Context context,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
index 529da82..2b13f86 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
@@ -17,13 +17,16 @@
package com.android.systemui.statusbar;
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
+import static android.app.NotificationManager.IMPORTANCE_HIGH;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.Instrumentation;
import android.app.Notification;
import android.app.NotificationChannel;
+import android.app.PendingIntent;
import android.content.Context;
+import android.content.Intent;
import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
import android.support.test.InstrumentationRegistry;
@@ -86,8 +89,7 @@
* @throws Exception
*/
public ExpandableNotificationRow createRow(String pkg, int uid) throws Exception {
- return createRow(pkg, uid, false /* isGroupSummary */, null /* groupKey */,
- false /* isBubble */);
+ return createRow(pkg, uid, false /* isGroupSummary */, null /* groupKey */);
}
/**
@@ -98,8 +100,7 @@
* @throws Exception
*/
public ExpandableNotificationRow createRow(Notification notification) throws Exception {
- return generateRow(notification, PKG, UID, 0 /* extraInflationFlags */,
- false /* isBubble */);
+ return generateRow(notification, PKG, UID, 0 /* extraInflationFlags */);
}
/**
@@ -112,8 +113,7 @@
*/
public ExpandableNotificationRow createRow(@InflationFlag int extraInflationFlags)
throws Exception {
- return generateRow(createNotification(), PKG, UID, extraInflationFlags,
- false /* isBubble */);
+ return generateRow(createNotification(), PKG, UID, extraInflationFlags);
}
/**
@@ -134,20 +134,21 @@
return createGroup(2);
}
- /**
- * Retursn an {@link ExpandableNotificationRow} that should be a bubble.
- */
- public ExpandableNotificationRow createBubble() throws Exception {
- return createRow(PKG, UID, false /* isGroupSummary */, null /* groupKey */,
- true /* isBubble */);
- }
-
private ExpandableNotificationRow createGroupSummary(String groupkey) throws Exception {
- return createRow(PKG, UID, true /* isGroupSummary */, groupkey, false);
+ return createRow(PKG, UID, true /* isGroupSummary */, groupkey);
}
private ExpandableNotificationRow createGroupChild(String groupkey) throws Exception {
- return createRow(PKG, UID, false /* isGroupSummary */, groupkey, false);
+ return createRow(PKG, UID, false /* isGroupSummary */, groupkey);
+ }
+
+ /**
+ * Returns an {@link ExpandableNotificationRow} that should be shown as a bubble.
+ */
+ public ExpandableNotificationRow createBubble() throws Exception {
+ Notification n = createNotification(false /* isGroupSummary */,
+ null /* groupKey */, true /* isBubble */);
+ return generateRow(n, PKG, UID, 0 /* extraInflationFlags */, IMPORTANCE_HIGH);
}
/**
@@ -157,7 +158,6 @@
* @param uid uid used for creating a {@link StatusBarNotification}
* @param isGroupSummary whether the notification row is a group summary
* @param groupKey the group key for the notification group used across notifications
- * @param isBubble
* @return a row with that's either a standalone notification or a group notification if the
* groupKey is non-null
* @throws Exception
@@ -166,10 +166,10 @@
String pkg,
int uid,
boolean isGroupSummary,
- @Nullable String groupKey, boolean isBubble)
+ @Nullable String groupKey)
throws Exception {
Notification notif = createNotification(isGroupSummary, groupKey);
- return generateRow(notif, pkg, uid, 0 /* inflationFlags */, isBubble);
+ return generateRow(notif, pkg, uid, 0 /* inflationFlags */);
}
/**
@@ -188,8 +188,20 @@
* @param groupKey the group key for the notification group used across notifications
* @return a notification that is in the group specified or standalone if unspecified
*/
+ private Notification createNotification(boolean isGroupSummary, @Nullable String groupKey) {
+ return createNotification(isGroupSummary, groupKey, false /* isBubble */);
+ }
+
+ /**
+ * Creates a notification with the given parameters.
+ *
+ * @param isGroupSummary whether the notification is a group summary
+ * @param groupKey the group key for the notification group used across notifications
+ * @param isBubble whether this notification should bubble
+ * @return a notification that is in the group specified or standalone if unspecified
+ */
private Notification createNotification(boolean isGroupSummary,
- @Nullable String groupKey) {
+ @Nullable String groupKey, boolean isBubble) {
Notification publicVersion = new Notification.Builder(mContext).setSmallIcon(
R.drawable.ic_person)
.setCustomContentView(new RemoteViews(mContext.getPackageName(),
@@ -207,6 +219,10 @@
if (!TextUtils.isEmpty(groupKey)) {
notificationBuilder.setGroup(groupKey);
}
+ if (isBubble) {
+ PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+ notificationBuilder.setAppOverlayIntent(bubbleIntent);
+ }
return notificationBuilder.build();
}
@@ -214,7 +230,17 @@
Notification notification,
String pkg,
int uid,
- @InflationFlag int extraInflationFlags, boolean isBubble)
+ @InflationFlag int extraInflationFlags)
+ throws Exception {
+ return generateRow(notification, pkg, uid, extraInflationFlags, IMPORTANCE_DEFAULT);
+ }
+
+ private ExpandableNotificationRow generateRow(
+ Notification notification,
+ String pkg,
+ int uid,
+ @InflationFlag int extraInflationFlags,
+ int importance)
throws Exception {
LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
mContext.LAYOUT_INFLATER_SERVICE);
@@ -242,9 +268,8 @@
entry.setRow(row);
entry.createIcons(mContext, sbn);
entry.channel = new NotificationChannel(
- notification.getChannelId(), notification.getChannelId(), IMPORTANCE_DEFAULT);
+ notification.getChannelId(), notification.getChannelId(), importance);
entry.channel.setBlockableSystem(true);
- entry.setIsBubble(isBubble);
row.setEntry(entry);
row.getNotificationInflater().addInflationFlags(extraInflationFlags);
NotificationInflaterTest.runThenWaitForInflation(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
index bf91305..56e1fc6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
@@ -36,7 +36,6 @@
import com.android.systemui.Dependency;
import com.android.systemui.InitController;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.bubbles.BubbleController;
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
@@ -96,7 +95,7 @@
mViewHierarchyManager = new NotificationViewHierarchyManager(mContext,
mLockscreenUserManager, mGroupManager, mVisualStabilityManager,
- mock(StatusBarStateController.class), mEntryManager, mock(BubbleController.class),
+ mock(StatusBarStateController.class), mEntryManager,
() -> mShadeController);
Dependency.get(InitController.class).executePostInitTasks();
mViewHierarchyManager.setUpWithPresenter(mPresenter, mListContainer);
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index 6cccd62..24fd7b9 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -19,6 +19,7 @@
import static android.Manifest.permission.MANAGE_AUTO_FILL;
import static android.content.Context.AUTOFILL_MANAGER_SERVICE;
import static android.util.DebugUtils.flagsToString;
+import static android.view.autofill.AutofillManager.MAX_TEMP_AUGMENTED_SERVICE_DURATION_MS;
import static com.android.server.autofill.Helper.sDebug;
import static com.android.server.autofill.Helper.sFullScreenMode;
@@ -101,8 +102,6 @@
private static final Object sLock = AutofillManagerService.class;
- private static final int MAX_TEMP_AUGMENTED_SERVICE_DURATION_MS = 1_000 * 60 * 2; // 2 minutes
-
/**
* IME supports Smart Suggestions.
*/
diff --git a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
index 239a386..a8ff9b0 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
@@ -168,7 +168,7 @@
}
};
- // TODO(b/111330312): set cancellation signal, timeout (from both mClient and service),
+ // TODO(b/122728762): set cancellation signal, timeout (from both mClient and service),
// cache IAugmentedAutofillManagerClient reference, etc...
try {
mClient.getAugmentedAutofillClient(receiver);
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 0348f2b..cf4963c 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -2581,7 +2581,10 @@
final RemoteAugmentedAutofillService remoteService = mService
.getRemoteAugmentedAutofillServiceLocked();
- if (remoteService == null) return null;
+ if (remoteService == null) {
+ if (sVerbose) Slog.v(TAG, "triggerAugmentedAutofillLocked(): no service for user");
+ return null;
+ }
// Define which mode will be used
final int mode;
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 12db4f3..ff378b3 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -149,6 +149,9 @@
if (userBackupManagerService != null) {
userBackupManagerService.tearDownService();
+
+ KeyValueBackupJob.cancel(userId, mContext);
+ FullBackupJob.cancel(userId, mContext);
}
}
@@ -577,9 +580,9 @@
* @return Whether ongoing work will continue. The return value here will be passed along as the
* return value to the callback {@link JobService#onStartJob(JobParameters)}.
*/
- public boolean beginFullBackup(FullBackupJob scheduledJob) {
+ public boolean beginFullBackup(@UserIdInt int userId, FullBackupJob scheduledJob) {
UserBackupManagerService userBackupManagerService =
- getServiceForUserIfCallerHasPermission(UserHandle.USER_SYSTEM, "beginFullBackup()");
+ getServiceForUserIfCallerHasPermission(userId, "beginFullBackup()");
return userBackupManagerService != null
&& userBackupManagerService.beginFullBackup(scheduledJob);
@@ -589,9 +592,9 @@
* Used by the {@link JobScheduler} to end the current full backup task when conditions are no
* longer met for running the full backup job.
*/
- public void endFullBackup() {
+ public void endFullBackup(@UserIdInt int userId) {
UserBackupManagerService userBackupManagerService =
- getServiceForUserIfCallerHasPermission(UserHandle.USER_SYSTEM, "endFullBackup()");
+ getServiceForUserIfCallerHasPermission(userId, "endFullBackup()");
if (userBackupManagerService != null) {
userBackupManagerService.endFullBackup();
diff --git a/services/backup/java/com/android/server/backup/FullBackupJob.java b/services/backup/java/com/android/server/backup/FullBackupJob.java
index 5708b1c..33d21cb0 100644
--- a/services/backup/java/com/android/server/backup/FullBackupJob.java
+++ b/services/backup/java/com/android/server/backup/FullBackupJob.java
@@ -22,18 +22,30 @@
import android.app.job.JobService;
import android.content.ComponentName;
import android.content.Context;
+import android.os.Bundle;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
public class FullBackupJob extends JobService {
+ private static final String USER_ID_EXTRA_KEY = "userId";
+
+ @VisibleForTesting
+ static final int MIN_JOB_ID = 52418896;
+ @VisibleForTesting
+ static final int MAX_JOB_ID = 52419896;
+
private static ComponentName sIdleService =
new ComponentName("android", FullBackupJob.class.getName());
- private static final int JOB_ID = 0x5038;
+ @GuardedBy("mParamsForUser")
+ private final SparseArray<JobParameters> mParamsForUser = new SparseArray<>();
- private JobParameters mParams;
-
- public static void schedule(Context ctx, long minDelay, BackupManagerConstants constants) {
+ public static void schedule(int userId, Context ctx, long minDelay,
+ BackupManagerConstants constants) {
JobScheduler js = (JobScheduler) ctx.getSystemService(Context.JOB_SCHEDULER_SERVICE);
- JobInfo.Builder builder = new JobInfo.Builder(JOB_ID, sIdleService);
+ JobInfo.Builder builder = new JobInfo.Builder(getJobIdForUserId(userId), sIdleService);
synchronized (constants) {
builder.setRequiresDeviceIdle(true)
.setRequiredNetworkType(constants.getFullBackupRequiredNetworkType())
@@ -42,14 +54,28 @@
if (minDelay > 0) {
builder.setMinimumLatency(minDelay);
}
+
+ Bundle extraInfo = new Bundle();
+ extraInfo.putInt(USER_ID_EXTRA_KEY, userId);
+ builder.setTransientExtras(extraInfo);
+
js.schedule(builder.build());
}
+ public static void cancel(int userId, Context ctx) {
+ JobScheduler js = (JobScheduler) ctx.getSystemService(
+ Context.JOB_SCHEDULER_SERVICE);
+ js.cancel(getJobIdForUserId(userId));
+ }
+
// callback from the Backup Manager Service: it's finished its work for this pass
- public void finishBackupPass() {
- if (mParams != null) {
- jobFinished(mParams, false);
- mParams = null;
+ public void finishBackupPass(int userId) {
+ synchronized (mParamsForUser) {
+ JobParameters jobParameters = mParamsForUser.get(userId);
+ if (jobParameters != null) {
+ jobFinished(jobParameters, false);
+ mParamsForUser.remove(userId);
+ }
}
}
@@ -57,19 +83,33 @@
@Override
public boolean onStartJob(JobParameters params) {
- mParams = params;
+ int userId = params.getTransientExtras().getInt(USER_ID_EXTRA_KEY);
+
+ synchronized (mParamsForUser) {
+ mParamsForUser.put(userId, params);
+ }
+
Trampoline service = BackupManagerService.getInstance();
- return service.beginFullBackup(this);
+ return service.beginFullBackup(userId, this);
}
@Override
public boolean onStopJob(JobParameters params) {
- if (mParams != null) {
- mParams = null;
- Trampoline service = BackupManagerService.getInstance();
- service.endFullBackup();
+ int userId = params.getTransientExtras().getInt(USER_ID_EXTRA_KEY);
+
+ synchronized (mParamsForUser) {
+ if (mParamsForUser.removeReturnOld(userId) == null) {
+ return false;
+ }
}
+
+ Trampoline service = BackupManagerService.getInstance();
+ service.endFullBackup(userId);
+
return false;
}
+ private static int getJobIdForUserId(int userId) {
+ return JobIdManager.getJobIdForUserId(MIN_JOB_ID, MAX_JOB_ID, userId);
+ }
}
diff --git a/services/backup/java/com/android/server/backup/JobIdManager.java b/services/backup/java/com/android/server/backup/JobIdManager.java
new file mode 100644
index 0000000..2e834dbf
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/JobIdManager.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2019 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.backup;
+
+/**
+ * Allocates job IDs for {@link FullBackupJob} and {@link KeyValueBackupJob}
+ */
+public class JobIdManager {
+ public static int getJobIdForUserId(int minJobId, int maxJobId, int userId) {
+ if (minJobId + userId > maxJobId) {
+ throw new RuntimeException("No job IDs available in the given range");
+ }
+
+ return minJobId + userId;
+ }
+}
diff --git a/services/backup/java/com/android/server/backup/KeyValueBackupJob.java b/services/backup/java/com/android/server/backup/KeyValueBackupJob.java
index f2e7435..d43859e 100644
--- a/services/backup/java/com/android/server/backup/KeyValueBackupJob.java
+++ b/services/backup/java/com/android/server/backup/KeyValueBackupJob.java
@@ -25,8 +25,14 @@
import android.app.job.JobService;
import android.content.ComponentName;
import android.content.Context;
+import android.os.Bundle;
import android.os.RemoteException;
import android.util.Slog;
+import android.util.SparseBooleanArray;
+import android.util.SparseLongArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import java.util.Random;
@@ -38,7 +44,8 @@
private static final String TAG = "KeyValueBackupJob";
private static ComponentName sKeyValueJobService =
new ComponentName("android", KeyValueBackupJob.class.getName());
- private static final int JOB_ID = 0x5039;
+
+ private static final String USER_ID_EXTRA_KEY = "userId";
// Once someone asks for a backup, this is how long we hold off until we find
// an on-charging opportunity. If we hit this max latency we will run the operation
@@ -46,16 +53,22 @@
// BackupManager.backupNow().
private static final long MAX_DEFERRAL = AlarmManager.INTERVAL_DAY;
- private static boolean sScheduled = false;
- private static long sNextScheduled = 0;
+ @GuardedBy("KeyValueBackupJob.class")
+ private static final SparseBooleanArray sScheduledForUserId = new SparseBooleanArray();
+ @GuardedBy("KeyValueBackupJob.class")
+ private static final SparseLongArray sNextScheduledForUserId = new SparseLongArray();
- public static void schedule(Context ctx, BackupManagerConstants constants) {
- schedule(ctx, 0, constants);
+ private static final int MIN_JOB_ID = 52417896;
+ private static final int MAX_JOB_ID = 52418896;
+
+ public static void schedule(int userId, Context ctx, BackupManagerConstants constants) {
+ schedule(userId, ctx, 0, constants);
}
- public static void schedule(Context ctx, long delay, BackupManagerConstants constants) {
+ public static void schedule(int userId, Context ctx, long delay,
+ BackupManagerConstants constants) {
synchronized (KeyValueBackupJob.class) {
- if (sScheduled) {
+ if (sScheduledForUserId.get(userId)) {
return;
}
@@ -76,51 +89,61 @@
if (DEBUG_SCHEDULING) {
Slog.v(TAG, "Scheduling k/v pass in " + (delay / 1000 / 60) + " minutes");
}
- JobInfo.Builder builder = new JobInfo.Builder(JOB_ID, sKeyValueJobService)
+
+ JobInfo.Builder builder = new JobInfo.Builder(getJobIdForUserId(userId),
+ sKeyValueJobService)
.setMinimumLatency(delay)
.setRequiredNetworkType(networkType)
.setRequiresCharging(needsCharging)
.setOverrideDeadline(MAX_DEFERRAL);
+
+ Bundle extraInfo = new Bundle();
+ extraInfo.putInt(USER_ID_EXTRA_KEY, userId);
+ builder.setTransientExtras(extraInfo);
+
JobScheduler js = (JobScheduler) ctx.getSystemService(Context.JOB_SCHEDULER_SERVICE);
js.schedule(builder.build());
- sNextScheduled = System.currentTimeMillis() + delay;
- sScheduled = true;
+ sScheduledForUserId.put(userId, true);
+ sNextScheduledForUserId.put(userId, System.currentTimeMillis() + delay);
}
}
- public static void cancel(Context ctx) {
+ public static void cancel(int userId, Context ctx) {
synchronized (KeyValueBackupJob.class) {
- JobScheduler js = (JobScheduler) ctx.getSystemService(Context.JOB_SCHEDULER_SERVICE);
- js.cancel(JOB_ID);
- sNextScheduled = 0;
- sScheduled = false;
+ JobScheduler js = (JobScheduler) ctx.getSystemService(
+ Context.JOB_SCHEDULER_SERVICE);
+ js.cancel(getJobIdForUserId(userId));
+
+ clearScheduledForUserId(userId);
}
}
- public static long nextScheduled() {
+ public static long nextScheduled(int userId) {
synchronized (KeyValueBackupJob.class) {
- return sNextScheduled;
+ return sNextScheduledForUserId.get(userId);
}
}
- public static boolean isScheduled() {
+ @VisibleForTesting
+ public static boolean isScheduled(int userId) {
synchronized (KeyValueBackupJob.class) {
- return sScheduled;
+ return sScheduledForUserId.get(userId);
}
}
@Override
public boolean onStartJob(JobParameters params) {
+ int userId = params.getTransientExtras().getInt(USER_ID_EXTRA_KEY);
+
synchronized (KeyValueBackupJob.class) {
- sNextScheduled = 0;
- sScheduled = false;
+ clearScheduledForUserId(userId);
}
// Time to run a key/value backup!
Trampoline service = BackupManagerService.getInstance();
try {
- service.backupNow();
+ service.backupNowForUser(userId);
} catch (RemoteException e) {}
// This was just a trigger; ongoing wakelock management is done by the
@@ -134,4 +157,13 @@
return false;
}
+ @GuardedBy("KeyValueBackupJob.class")
+ private static void clearScheduledForUserId(int userId) {
+ sScheduledForUserId.delete(userId);
+ sNextScheduledForUserId.delete(userId);
+ }
+
+ private static int getJobIdForUserId(int userId) {
+ return JobIdManager.getJobIdForUserId(MIN_JOB_ID, MAX_JOB_ID, userId);
+ }
}
diff --git a/services/backup/java/com/android/server/backup/Trampoline.java b/services/backup/java/com/android/server/backup/Trampoline.java
index 79d4a2c..4ca2545 100644
--- a/services/backup/java/com/android/server/backup/Trampoline.java
+++ b/services/backup/java/com/android/server/backup/Trampoline.java
@@ -698,15 +698,15 @@
// Full backup/restore entry points - non-Binder; called directly
// by the full-backup scheduled job
- /* package */ boolean beginFullBackup(FullBackupJob scheduledJob) {
+ /* package */ boolean beginFullBackup(@UserIdInt int userId, FullBackupJob scheduledJob) {
BackupManagerService svc = mService;
- return (svc != null) ? svc.beginFullBackup(scheduledJob) : false;
+ return (svc != null) ? svc.beginFullBackup(userId, scheduledJob) : false;
}
- /* package */ void endFullBackup() {
+ /* package */ void endFullBackup(@UserIdInt int userId) {
BackupManagerService svc = mService;
if (svc != null) {
- svc.endFullBackup();
+ svc.endFullBackup(userId);
}
}
}
diff --git a/services/backup/java/com/android/server/backup/TransportManager.java b/services/backup/java/com/android/server/backup/TransportManager.java
index ddce6bb..529430c 100644
--- a/services/backup/java/com/android/server/backup/TransportManager.java
+++ b/services/backup/java/com/android/server/backup/TransportManager.java
@@ -17,6 +17,7 @@
package com.android.server.backup;
import android.annotation.Nullable;
+import android.annotation.UserIdInt;
import android.annotation.WorkerThread;
import android.app.backup.BackupManager;
import android.app.backup.BackupTransport;
@@ -29,7 +30,6 @@
import android.content.pm.ResolveInfo;
import android.os.Bundle;
import android.os.RemoteException;
-import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Slog;
@@ -61,7 +61,7 @@
public static final String SERVICE_ACTION_TRANSPORT_HOST = "android.backup.TRANSPORT_HOST";
private final Intent mTransportServiceIntent = new Intent(SERVICE_ACTION_TRANSPORT_HOST);
- private final Context mContext;
+ private final @UserIdInt int mUserId;
private final PackageManager mPackageManager;
private final Set<ComponentName> mTransportWhitelist;
private final TransportClientManager mTransportClientManager;
@@ -86,22 +86,24 @@
@Nullable
private volatile String mCurrentTransportName;
- TransportManager(Context context, Set<ComponentName> whitelist, String selectedTransport) {
- mContext = context;
+ TransportManager(@UserIdInt int userId, Context context, Set<ComponentName> whitelist,
+ String selectedTransport) {
+ mUserId = userId;
mPackageManager = context.getPackageManager();
mTransportWhitelist = Preconditions.checkNotNull(whitelist);
mCurrentTransportName = selectedTransport;
mTransportStats = new TransportStats();
- mTransportClientManager = new TransportClientManager(context, mTransportStats);
+ mTransportClientManager = new TransportClientManager(mUserId, context, mTransportStats);
}
@VisibleForTesting
TransportManager(
+ @UserIdInt int userId,
Context context,
Set<ComponentName> whitelist,
String selectedTransport,
TransportClientManager transportClientManager) {
- mContext = context;
+ mUserId = userId;
mPackageManager = context.getPackageManager();
mTransportWhitelist = Preconditions.checkNotNull(whitelist);
mCurrentTransportName = selectedTransport;
@@ -575,7 +577,7 @@
private void registerTransportsForIntent(
Intent intent, Predicate<ComponentName> transportComponentFilter) {
List<ResolveInfo> hosts =
- mPackageManager.queryIntentServicesAsUser(intent, 0, UserHandle.USER_SYSTEM);
+ mPackageManager.queryIntentServicesAsUser(intent, 0, mUserId);
if (hosts == null) {
return;
}
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index 6b0adfb..ed6a46c 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -384,7 +384,7 @@
Slog.v(TAG, "Starting with transport " + currentTransport);
}
TransportManager transportManager =
- new TransportManager(context, transportWhitelist, currentTransport);
+ new TransportManager(userId, context, transportWhitelist, currentTransport);
File baseStateDir = UserBackupManagerFiles.getBaseStateDir(userId);
File dataDir = UserBackupManagerFiles.getDataDir(userId);
@@ -1674,8 +1674,8 @@
}
// We don't want the backup jobs to kick in any time soon.
// Reschedules them to run in the distant future.
- KeyValueBackupJob.schedule(mContext, BUSY_BACKOFF_MIN_MILLIS, mConstants);
- FullBackupJob.schedule(mContext, 2 * BUSY_BACKOFF_MIN_MILLIS, mConstants);
+ KeyValueBackupJob.schedule(mUserId, mContext, BUSY_BACKOFF_MIN_MILLIS, mConstants);
+ FullBackupJob.schedule(mUserId, mContext, 2 * BUSY_BACKOFF_MIN_MILLIS, mConstants);
} finally {
Binder.restoreCallingIdentity(oldToken);
}
@@ -1910,7 +1910,7 @@
Runnable r = new Runnable() {
@Override
public void run() {
- FullBackupJob.schedule(mContext, latency, mConstants);
+ FullBackupJob.schedule(mUserId, mContext, latency, mConstants);
}
};
mBackupHandler.postDelayed(r, 2500);
@@ -2033,7 +2033,7 @@
mPowerManager.getPowerSaveState(ServiceType.FULL_BACKUP);
if (result.batterySaverEnabled) {
if (DEBUG) Slog.i(TAG, "Deferring scheduled full backups in battery saver mode");
- FullBackupJob.schedule(mContext, keyValueBackupInterval, mConstants);
+ FullBackupJob.schedule(mUserId, mContext, keyValueBackupInterval, mConstants);
return false;
}
@@ -2147,7 +2147,7 @@
mBackupHandler.post(new Runnable() {
@Override
public void run() {
- FullBackupJob.schedule(mContext, deferTime, mConstants);
+ FullBackupJob.schedule(mUserId, mContext, deferTime, mConstants);
}
});
return false;
@@ -2251,7 +2251,7 @@
}
// ...and schedule a backup pass if necessary
- KeyValueBackupJob.schedule(mContext, mConstants);
+ KeyValueBackupJob.schedule(mUserId, mContext, mConstants);
}
// Note: packageName is currently unused, but may be in the future
@@ -2401,7 +2401,8 @@
mPowerManager.getPowerSaveState(ServiceType.KEYVALUE_BACKUP);
if (result.batterySaverEnabled) {
if (DEBUG) Slog.v(TAG, "Not running backup while in battery save mode");
- KeyValueBackupJob.schedule(mContext, mConstants); // try again in several hours
+ // Try again in several hours.
+ KeyValueBackupJob.schedule(mUserId, mContext, mConstants);
} else {
if (DEBUG) Slog.v(TAG, "Scheduling immediate backup pass");
synchronized (mQueueLock) {
@@ -2414,7 +2415,7 @@
}
// ...and cancel any pending scheduled job, because we've just superseded it
- KeyValueBackupJob.cancel(mContext);
+ KeyValueBackupJob.cancel(mUserId, mContext);
}
}
} finally {
@@ -2737,13 +2738,13 @@
synchronized (mQueueLock) {
if (enable && !wasEnabled && mSetupComplete) {
// if we've just been enabled, start scheduling backup passes
- KeyValueBackupJob.schedule(mContext, mConstants);
+ KeyValueBackupJob.schedule(mUserId, mContext, mConstants);
scheduleNextFullBackupJob(0);
} else if (!enable) {
// No longer enabled, so stop running backups
if (MORE_DEBUG) Slog.i(TAG, "Opting out of backup");
- KeyValueBackupJob.cancel(mContext);
+ KeyValueBackupJob.cancel(mUserId, mContext);
// This also constitutes an opt-out, so we wipe any data for
// this device from the backend. We start that process with
@@ -3451,7 +3452,7 @@
pw.println(isBackupOperationInProgress() ? "Backup in progress" : "No backups running");
pw.println("Last backup pass started: " + mLastBackupPass
+ " (now = " + System.currentTimeMillis() + ')');
- pw.println(" next scheduled: " + KeyValueBackupJob.nextScheduled());
+ pw.println(" next scheduled: " + KeyValueBackupJob.nextScheduled(mUserId));
pw.println("Transport whitelist:");
for (ComponentName transport : mTransportManager.getTransportWhitelist()) {
diff --git a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
index 2ee96d1..0fb4f93 100644
--- a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
+++ b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
@@ -633,7 +633,7 @@
unregisterTask();
if (mJob != null) {
- mJob.finishBackupPass();
+ mJob.finishBackupPass(backupManagerService.getUserId());
}
synchronized (backupManagerService.getQueueLock()) {
diff --git a/services/backup/java/com/android/server/backup/internal/SetupObserver.java b/services/backup/java/com/android/server/backup/internal/SetupObserver.java
index 62ae3eb..c5e912e 100644
--- a/services/backup/java/com/android/server/backup/internal/SetupObserver.java
+++ b/services/backup/java/com/android/server/backup/internal/SetupObserver.java
@@ -77,7 +77,8 @@
if (MORE_DEBUG) {
Slog.d(TAG, "Setup complete so starting backups");
}
- KeyValueBackupJob.schedule(mContext, mUserBackupManagerService.getConstants());
+ KeyValueBackupJob.schedule(mUserBackupManagerService.getUserId(), mContext,
+ mUserBackupManagerService.getConstants());
mUserBackupManagerService.scheduleNextFullBackupJob(0);
}
}
diff --git a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
index f39d795..ef7ff92 100644
--- a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
+++ b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java
@@ -1003,7 +1003,7 @@
// Use the scheduler's default.
delay = 0;
}
- KeyValueBackupJob.schedule(
+ KeyValueBackupJob.schedule(mBackupManagerService.getUserId(),
mBackupManagerService.getContext(), delay, mBackupManagerService.getConstants());
for (String packageName : mOriginalQueue) {
diff --git a/services/backup/java/com/android/server/backup/transport/TransportClient.java b/services/backup/java/com/android/server/backup/transport/TransportClient.java
index e4dcb25..7c5a57c 100644
--- a/services/backup/java/com/android/server/backup/transport/TransportClient.java
+++ b/services/backup/java/com/android/server/backup/transport/TransportClient.java
@@ -20,6 +20,7 @@
import android.annotation.IntDef;
import android.annotation.Nullable;
+import android.annotation.UserIdInt;
import android.annotation.WorkerThread;
import android.content.ComponentName;
import android.content.Context;
@@ -34,7 +35,6 @@
import android.text.format.DateFormat;
import android.util.ArrayMap;
import android.util.EventLog;
-import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -79,6 +79,7 @@
@VisibleForTesting static final String TAG = "TransportClient";
private static final int LOG_BUFFER_SIZE = 5;
+ private final @UserIdInt int mUserId;
private final Context mContext;
private final TransportStats mTransportStats;
private final Intent mBindIntent;
@@ -106,6 +107,7 @@
private volatile IBackupTransport mTransport;
TransportClient(
+ @UserIdInt int userId,
Context context,
TransportStats transportStats,
Intent bindIntent,
@@ -113,6 +115,7 @@
String identifier,
String caller) {
this(
+ userId,
context,
transportStats,
bindIntent,
@@ -124,6 +127,7 @@
@VisibleForTesting
TransportClient(
+ @UserIdInt int userId,
Context context,
TransportStats transportStats,
Intent bindIntent,
@@ -131,6 +135,7 @@
String identifier,
String caller,
Handler listenerHandler) {
+ mUserId = userId;
mContext = context;
mTransportStats = transportStats;
mTransportComponent = transportComponent;
@@ -213,7 +218,7 @@
mBindIntent,
mConnection,
Context.BIND_AUTO_CREATE,
- UserHandle.SYSTEM);
+ UserHandle.of(mUserId));
if (hasBound) {
// We don't need to set a time-out because we are guaranteed to get a call
// back in ServiceConnection, either an onServiceConnected() or
diff --git a/services/backup/java/com/android/server/backup/transport/TransportClientManager.java b/services/backup/java/com/android/server/backup/transport/TransportClientManager.java
index f4e3928..a4e9b10 100644
--- a/services/backup/java/com/android/server/backup/transport/TransportClientManager.java
+++ b/services/backup/java/com/android/server/backup/transport/TransportClientManager.java
@@ -19,12 +19,15 @@
import static com.android.server.backup.TransportManager.SERVICE_ACTION_TRANSPORT_HOST;
import static com.android.server.backup.transport.TransportUtils.formatMessage;
+import android.annotation.UserIdInt;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
+
import com.android.server.backup.TransportManager;
import com.android.server.backup.transport.TransportUtils.Priority;
+
import java.io.PrintWriter;
import java.util.Map;
import java.util.WeakHashMap;
@@ -36,13 +39,16 @@
public class TransportClientManager {
private static final String TAG = "TransportClientManager";
+ private final @UserIdInt int mUserId;
private final Context mContext;
private final TransportStats mTransportStats;
private final Object mTransportClientsLock = new Object();
private int mTransportClientsCreated = 0;
private Map<TransportClient, String> mTransportClientsCallerMap = new WeakHashMap<>();
- public TransportClientManager(Context context, TransportStats transportStats) {
+ public TransportClientManager(@UserIdInt int userId, Context context,
+ TransportStats transportStats) {
+ mUserId = userId;
mContext = context;
mTransportStats = transportStats;
}
@@ -89,6 +95,7 @@
synchronized (mTransportClientsLock) {
TransportClient transportClient =
new TransportClient(
+ mUserId,
mContext,
mTransportStats,
bindIntent,
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 0ff2a09..a96676e 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -516,7 +516,7 @@
}
// This app knows it is in the new model where this operation is not
// allowed, so tell it what has happened.
- UidRecord uidRec = mAm.mActiveUids.get(r.appInfo.uid);
+ UidRecord uidRec = mAm.mProcessList.getUidRecordLocked(r.appInfo.uid);
return new ComponentName("?", "app is in background uid " + uidRec);
}
}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 26141f7..3b08a00 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -24,8 +24,6 @@
import static android.Manifest.permission.REMOVE_TASKS;
import static android.app.ActivityManager.INSTR_FLAG_DISABLE_HIDDEN_API_CHECKS;
import static android.app.ActivityManager.INSTR_FLAG_MOUNT_EXTERNAL_STORAGE_FULL;
-import static android.app.ActivityManager.PROCESS_STATE_CACHED_ACTIVITY;
-import static android.app.ActivityManager.PROCESS_STATE_CACHED_EMPTY;
import static android.app.ActivityManager.PROCESS_STATE_LAST_ACTIVITY;
import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY;
@@ -59,16 +57,11 @@
import static android.os.Process.PROC_SPACE_TERM;
import static android.os.Process.ROOT_UID;
import static android.os.Process.SCHED_FIFO;
-import static android.os.Process.SCHED_OTHER;
import static android.os.Process.SCHED_RESET_ON_FORK;
import static android.os.Process.SE_UID;
import static android.os.Process.SHELL_UID;
import static android.os.Process.SIGNAL_USR1;
import static android.os.Process.SYSTEM_UID;
-import static android.os.Process.THREAD_GROUP_BG_NONINTERACTIVE;
-import static android.os.Process.THREAD_GROUP_DEFAULT;
-import static android.os.Process.THREAD_GROUP_RESTRICTED;
-import static android.os.Process.THREAD_GROUP_TOP_APP;
import static android.os.Process.THREAD_PRIORITY_FOREGROUND;
import static android.os.Process.getTotalMemory;
import static android.os.Process.isThreadInProcess;
@@ -79,7 +72,6 @@
import static android.os.Process.readProcFile;
import static android.os.Process.removeAllProcessGroups;
import static android.os.Process.sendSignal;
-import static android.os.Process.setProcessGroup;
import static android.os.Process.setThreadPriority;
import static android.os.Process.setThreadScheduler;
import static android.os.Process.zygoteProcess;
@@ -96,11 +88,9 @@
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_BACKGROUND;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_LIGHT;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LRU;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_NETWORK;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ_REASON;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PERMISSIONS_REVIEW;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_POWER;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROCESSES;
@@ -109,7 +99,6 @@
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PSS;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_SERVICE;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_UID_OBSERVERS;
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_USAGE_STATS;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_WHITELISTS;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BACKUP;
import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_BROADCAST;
@@ -404,7 +393,7 @@
public static final int TOP_APP_PRIORITY_BOOST = -10;
static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityManagerService" : TAG_AM;
- private static final String TAG_BACKUP = TAG + POSTFIX_BACKUP;
+ static final String TAG_BACKUP = TAG + POSTFIX_BACKUP;
private static final String TAG_BROADCAST = TAG + POSTFIX_BROADCAST;
private static final String TAG_CLEANUP = TAG + POSTFIX_CLEANUP;
private static final String TAG_CONFIGURATION = TAG + POSTFIX_CONFIGURATION;
@@ -412,9 +401,9 @@
static final String TAG_LRU = TAG + POSTFIX_LRU;
private static final String TAG_MU = TAG + POSTFIX_MU;
private static final String TAG_NETWORK = TAG + POSTFIX_NETWORK;
- private static final String TAG_OOM_ADJ = TAG + POSTFIX_OOM_ADJ;
+ static final String TAG_OOM_ADJ = TAG + POSTFIX_OOM_ADJ;
private static final String TAG_POWER = TAG + POSTFIX_POWER;
- private static final String TAG_PROCESS_OBSERVERS = TAG + POSTFIX_PROCESS_OBSERVERS;
+ static final String TAG_PROCESS_OBSERVERS = TAG + POSTFIX_PROCESS_OBSERVERS;
static final String TAG_PROCESSES = TAG + POSTFIX_PROCESSES;
private static final String TAG_PROVIDER = TAG + POSTFIX_PROVIDER;
static final String TAG_PSS = TAG + POSTFIX_PSS;
@@ -541,6 +530,8 @@
private static final int NATIVE_DUMP_TIMEOUT_MS = 2000; // 2 seconds;
+ final OomAdjuster mOomAdjuster;
+
/** All system services */
SystemServiceManager mSystemServiceManager;
@@ -555,7 +546,7 @@
public OomAdjProfiler mOomAdjProfiler = new OomAdjProfiler();
// Whether we should use SCHED_FIFO for UI and RenderThreads.
- private boolean mUseFifoUiScheduling = false;
+ boolean mUseFifoUiScheduling = false;
BroadcastQueue mFgBroadcastQueue;
BroadcastQueue mBgBroadcastQueue;
@@ -654,11 +645,6 @@
final ProcessStatsService mProcessStats;
/**
- * Service for compacting background apps.
- */
- final AppCompactor mAppCompact;
-
- /**
* Non-persistent appId whitelist for background restrictions
*/
int[] mBackgroundAppIdWhitelist = new int[] {
@@ -821,8 +807,6 @@
*/
boolean mFullPssPending = false;
- /** Track all uids that have actively running processes. */
- final ActiveUids mActiveUids;
/**
* This is for verifying the UID report flow.
@@ -1104,32 +1088,7 @@
/**
* State of external calls telling us if the device is awake or asleep.
*/
- private int mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
-
- /**
- * Current sequence id for oom_adj computation traversal.
- */
- int mAdjSeq = 0;
-
- /**
- * Keep track of the non-cached/empty process we last found, to help
- * determine how to distribute cached/empty processes next time.
- */
- int mNumNonCachedProcs = 0;
-
- /**
- * Keep track of the number of cached hidden procs, to balance oom adj
- * distribution between those and empty procs.
- */
- int mNumCachedHiddenProcs = 0;
-
- /**
- * Keep track of the number of service processes we last found, to
- * determine on the next iteration which should be B services.
- */
- int mNumServiceProcs = 0;
- int mNewNumAServiceProcs = 0;
- int mNewNumServiceProcs = 0;
+ int mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
/**
* Allow the current computed overall memory level of the system to go down?
@@ -1256,10 +1215,6 @@
String mTrackAllocationApp = null;
String mNativeDebuggingApp = null;
- final long[] mTmpLong = new long[3];
-
- private final ArraySet<BroadcastQueue> mTmpBroadcastQueue = new ArraySet();
-
private final Injector mInjector;
static final class ProcessChangeItem {
@@ -1334,6 +1289,7 @@
}
}
+ // TODO: Move below 4 members and code to ProcessList
final RemoteCallbackList<IProcessObserver> mProcessObservers = new RemoteCallbackList<>();
ProcessChangeItem[] mActiveProcessChanges = new ProcessChangeItem[5];
@@ -2225,12 +2181,15 @@
mUiContext = null;
mAppErrors = null;
mPackageWatchdog = null;
- mActiveUids = new ActiveUids(this, false /* postChangesToAtm */);
mAppOpsService = mInjector.getAppOpsService(null /* file */, null /* handler */);
mBatteryStatsService = null;
mHandler = hasHandlerThread ? new MainHandler(handlerThread.getLooper()) : null;
mHandlerThread = handlerThread;
mConstants = hasHandlerThread ? new ActivityManagerConstants(this, mHandler) : null;
+ final ActiveUids activeUids = new ActiveUids(this, false /* postChangesToAtm */);
+ mProcessList.init(this, activeUids);
+ mOomAdjuster = new OomAdjuster(this, mProcessList, activeUids);
+
mIntentFirewall = hasHandlerThread
? new IntentFirewall(new IntentFirewallInterface(), mHandler) : null;
mProcessCpuThread = null;
@@ -2246,7 +2205,6 @@
? new PendingIntentController(handlerThread.getLooper(), mUserController) : null;
mProcStartHandlerThread = null;
mProcStartHandler = null;
- mAppCompact = null;
mHiddenApiBlacklist = null;
mFactoryTest = FACTORY_TEST_OFF;
}
@@ -2276,8 +2234,9 @@
mProcStartHandler = new Handler(mProcStartHandlerThread.getLooper());
mConstants = new ActivityManagerConstants(this, mHandler);
-
- mProcessList.init(this);
+ final ActiveUids activeUids = new ActiveUids(this, true /* postChangesToAtm */);
+ mProcessList.init(this, activeUids);
+ mOomAdjuster = new OomAdjuster(this, mProcessList, activeUids);
mFgBroadcastQueue = new BroadcastQueue(this, mHandler,
"foreground", BROADCAST_FG_TIMEOUT, false);
@@ -2293,7 +2252,6 @@
mProviderMap = new ProviderMap(this);
mPackageWatchdog = PackageWatchdog.getInstance(mUiContext);
mAppErrors = new AppErrors(mUiContext, this, mPackageWatchdog);
- mActiveUids = new ActiveUids(this, true /* postChangesToAtm */);
final File systemDir = SystemServiceManager.ensureSystemDir();
@@ -2330,8 +2288,6 @@
DisplayThread.get().getLooper());
mAtmInternal = LocalServices.getService(ActivityTaskManagerInternal.class);
- mAppCompact = new AppCompactor(this);
-
mProcessCpuThread = new Thread("CpuTracker") {
@Override
public void run() {
@@ -2378,7 +2334,8 @@
try {
Process.setThreadGroupAndCpuset(BackgroundThread.get().getThreadId(),
Process.THREAD_GROUP_SYSTEM);
- Process.setThreadGroupAndCpuset(mAppCompact.mCompactionThread.getThreadId(),
+ Process.setThreadGroupAndCpuset(
+ mOomAdjuster.mAppCompact.mCompactionThread.getThreadId(),
Process.THREAD_GROUP_SYSTEM);
} catch (Exception e) {
Slog.w(TAG, "Setting background thread cpuset failed");
@@ -5347,7 +5304,7 @@
private boolean isAppForeground(int uid) {
synchronized (this) {
- UidRecord uidRec = mActiveUids.get(uid);
+ UidRecord uidRec = mProcessList.mActiveUids.get(uid);
if (uidRec == null || uidRec.idle) {
return false;
}
@@ -5359,15 +5316,10 @@
// be guarded by permission checking.
int getUidState(int uid) {
synchronized (this) {
- return getUidStateLocked(uid);
+ return mProcessList.getUidProcStateLocked(uid);
}
}
- int getUidStateLocked(int uid) {
- UidRecord uidRec = mActiveUids.get(uid);
- return uidRec == null ? PROCESS_STATE_NONEXISTENT : uidRec.getCurProcState();
- }
-
// =========================================================
// PROCESS INFO
// =========================================================
@@ -5659,7 +5611,7 @@
int getAppStartModeLocked(int uid, String packageName, int packageTargetSdk,
int callingPid, boolean alwaysRestrict, boolean disabledOnly, boolean forcedStandby) {
- UidRecord uidRec = mActiveUids.get(uid);
+ UidRecord uidRec = mProcessList.getUidRecordLocked(uid);
if (DEBUG_BACKGROUND_CHECK) Slog.d(TAG, "checkAllowBackground: uid=" + uid + " pkg="
+ packageName + " rec=" + uidRec + " always=" + alwaysRestrict + " idle="
+ (uidRec != null ? uidRec.idle : false));
@@ -7880,8 +7832,7 @@
}
synchronized (this) {
- UidRecord uidRec = mActiveUids.get(uid);
- return uidRec != null ? uidRec.getCurProcState() : PROCESS_STATE_NONEXISTENT;
+ return mProcessList.getUidProcStateLocked(uid);
}
}
@@ -7917,7 +7868,7 @@
}
boolean isUidActiveLocked(int uid) {
- final UidRecord uidRecord = mActiveUids.get(uid);
+ final UidRecord uidRecord = mProcessList.getUidRecordLocked(uid);
return uidRecord != null && !uidRecord.setIdle;
}
@@ -8709,7 +8660,7 @@
if (mForceBackgroundCheck) {
// Stop background services for idle UIDs.
- doStopUidForIdleUidsLocked();
+ mProcessList.doStopUidForIdleUidsLocked();
}
}
}
@@ -9558,7 +9509,8 @@
proto.end(broadcastToken);
long serviceToken = proto.start(ActivityManagerServiceProto.SERVICES);
- mServices.writeToProto(proto, ActivityManagerServiceDumpServicesProto.ACTIVE_SERVICES);
+ mServices.writeToProto(proto,
+ ActivityManagerServiceDumpServicesProto.ACTIVE_SERVICES);
proto.end(serviceToken);
long processToken = proto.start(ActivityManagerServiceProto.PROCESSES);
@@ -10110,11 +10062,13 @@
}
}
- if (mActiveUids.size() > 0) {
- if (dumpUids(pw, dumpPackage, dumpAppId, mActiveUids, "UID states:", needSep)) {
+ if (mProcessList.mActiveUids.size() > 0) {
+ if (dumpUids(pw, dumpPackage, dumpAppId, mProcessList.mActiveUids,
+ "UID states:", needSep)) {
needSep = true;
}
}
+
if (dumpAll) {
if (mValidateUids.size() > 0) {
if (dumpUids(pw, dumpPackage, dumpAppId, mValidateUids, "UID validation:",
@@ -10377,12 +10331,8 @@
pw.print(" mLastPowerCheckUptime=");
TimeUtils.formatDuration(mLastPowerCheckUptime, pw);
pw.println("");
- pw.println(" mAdjSeq=" + mAdjSeq + " mLruSeq=" + mProcessList.mLruSeq);
- pw.println(" mNumNonCachedProcs=" + mNumNonCachedProcs
- + " (" + mProcessList.getLruSizeLocked() + " total)"
- + " mNumCachedHiddenProcs=" + mNumCachedHiddenProcs
- + " mNumServiceProcs=" + mNumServiceProcs
- + " mNewNumServiceProcs=" + mNewNumServiceProcs);
+ mOomAdjuster.dumpSequenceNumbersLocked(pw);
+ mOomAdjuster.dumpProcCountsLocked(pw);
pw.println(" mAllowLowerMemLevel=" + mAllowLowerMemLevel
+ " mLastMemoryLevel=" + mLastMemoryLevel
+ " mLastNumProcesses=" + mLastNumProcesses);
@@ -10434,7 +10384,8 @@
if (dumpPackage != null && !r.pkgList.containsKey(dumpPackage)) {
continue;
}
- r.writeToProto(proto, ActivityManagerServiceDumpProcessesProto.PROCS, mProcessList.mLruProcesses.indexOf(r)
+ r.writeToProto(proto, ActivityManagerServiceDumpProcessesProto.PROCS,
+ mProcessList.mLruProcesses.indexOf(r)
);
if (r.isPersistent()) {
numPers++;
@@ -10458,19 +10409,20 @@
&& !ai.mTargetInfo.packageName.equals(dumpPackage)) {
continue;
}
- ai.writeToProto(proto, ActivityManagerServiceDumpProcessesProto.ACTIVE_INSTRUMENTATIONS);
+ ai.writeToProto(proto,
+ ActivityManagerServiceDumpProcessesProto.ACTIVE_INSTRUMENTATIONS);
}
int whichAppId = getAppId(dumpPackage);
- for (int i=0; i<mActiveUids.size(); i++) {
- UidRecord uidRec = mActiveUids.valueAt(i);
+ for (int i = 0; i < mProcessList.mActiveUids.size(); i++) {
+ UidRecord uidRec = mProcessList.mActiveUids.valueAt(i);
if (dumpPackage != null && UserHandle.getAppId(uidRec.uid) != whichAppId) {
continue;
}
uidRec.writeToProto(proto, ActivityManagerServiceDumpProcessesProto.ACTIVE_UIDS);
}
- for (int i=0; i<mValidateUids.size(); i++) {
+ for (int i = 0; i < mValidateUids.size(); i++) {
UidRecord uidRec = mValidateUids.valueAt(i);
if (dumpPackage != null && UserHandle.getAppId(uidRec.uid) != whichAppId) {
continue;
@@ -10545,12 +10497,15 @@
r.writeToProto(proto, ActivityManagerServiceDumpProcessesProto.ON_HOLD_PROCS);
}
- writeProcessesToGcToProto(proto, ActivityManagerServiceDumpProcessesProto.GC_PROCS, dumpPackage);
- mAppErrors.writeToProto(proto, ActivityManagerServiceDumpProcessesProto.APP_ERRORS, dumpPackage);
+ writeProcessesToGcToProto(proto, ActivityManagerServiceDumpProcessesProto.GC_PROCS,
+ dumpPackage);
+ mAppErrors.writeToProto(proto, ActivityManagerServiceDumpProcessesProto.APP_ERRORS,
+ dumpPackage);
mAtmInternal.writeProcessesToProto(proto, dumpPackage, mWakefulness, mTestPssMode);
if (dumpPackage == null) {
- mUserController.writeToProto(proto, ActivityManagerServiceDumpProcessesProto.USER_CONTROLLER);
+ mUserController.writeToProto(proto,
+ ActivityManagerServiceDumpProcessesProto.USER_CONTROLLER);
}
final int NI = mUidObservers.getRegisteredCallbackCount();
@@ -10662,11 +10617,7 @@
proto.write(ActivityManagerServiceDumpProcessesProto.CALL_FINISH_BOOTING, mCallFinishBooting);
proto.write(ActivityManagerServiceDumpProcessesProto.BOOT_ANIMATION_COMPLETE, mBootAnimationComplete);
proto.write(ActivityManagerServiceDumpProcessesProto.LAST_POWER_CHECK_UPTIME_MS, mLastPowerCheckUptime);
- proto.write(ActivityManagerServiceDumpProcessesProto.ADJ_SEQ, mAdjSeq);
- proto.write(ActivityManagerServiceDumpProcessesProto.LRU_SEQ, mProcessList.mLruSeq);
- proto.write(ActivityManagerServiceDumpProcessesProto.NUM_NON_CACHED_PROCS, mNumNonCachedProcs);
- proto.write(ActivityManagerServiceDumpProcessesProto.NUM_SERVICE_PROCS, mNumServiceProcs);
- proto.write(ActivityManagerServiceDumpProcessesProto.NEW_NUM_SERVICE_PROCS, mNewNumServiceProcs);
+ mOomAdjuster.dumpProcessListVariablesLocked(proto);
proto.write(ActivityManagerServiceDumpProcessesProto.ALLOW_LOWER_MEM_LEVEL, mAllowLowerMemLevel);
proto.write(ActivityManagerServiceDumpProcessesProto.LAST_MEMORY_LEVEL, mLastMemoryLevel);
proto.write(ActivityManagerServiceDumpProcessesProto.LAST_NUM_PROCESSES, mLastNumProcesses);
@@ -14640,7 +14591,7 @@
Intent.ACTION_PACKAGE_REPLACED.equals(action)) {
final int uid = getUidFromIntent(intent);
if (uid != -1) {
- final UidRecord uidRec = mActiveUids.get(uid);
+ final UidRecord uidRec = mProcessList.getUidRecordLocked(uid);
if (uidRec != null) {
uidRec.updateHasInternetPermission();
}
@@ -15418,7 +15369,7 @@
// Returns whether the app is receiving broadcast.
// If receiving, fetch all broadcast queues which the app is
// the current [or imminent] receiver on.
- private boolean isReceivingBroadcastLocked(ProcessRecord app,
+ boolean isReceivingBroadcastLocked(ProcessRecord app,
ArraySet<BroadcastQueue> receivingQueues) {
final int N = app.curReceivers.size();
if (N > 0) {
@@ -15538,1087 +15489,6 @@
}
}
- private final ComputeOomAdjWindowCallback mTmpComputeOomAdjWindowCallback =
- new ComputeOomAdjWindowCallback();
-
- /** These methods are called inline during computeOomAdjLocked(), on the same thread */
- private final class ComputeOomAdjWindowCallback
- implements WindowProcessController.ComputeOomAdjCallback {
-
- ProcessRecord app;
- int adj;
- boolean foregroundActivities;
- int procState;
- int schedGroup;
- int appUid;
- int logUid;
- int processStateCurTop;
-
- void initialize(ProcessRecord app, int adj, boolean foregroundActivities,
- int procState, int schedGroup, int appUid, int logUid, int processStateCurTop) {
- this.app = app;
- this.adj = adj;
- this.foregroundActivities = foregroundActivities;
- this.procState = procState;
- this.schedGroup = schedGroup;
- this.appUid = appUid;
- this.logUid = logUid;
- this.processStateCurTop = processStateCurTop;
- }
-
- @Override
- public void onVisibleActivity() {
- // App has a visible activity; only upgrade adjustment.
- if (adj > ProcessList.VISIBLE_APP_ADJ) {
- adj = ProcessList.VISIBLE_APP_ADJ;
- app.adjType = "vis-activity";
- if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
- reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to vis-activity: " + app);
- }
- }
- if (procState > processStateCurTop) {
- procState = processStateCurTop;
- app.adjType = "vis-activity";
- if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
- reportOomAdjMessageLocked(TAG_OOM_ADJ,
- "Raise procstate to vis-activity (top): " + app);
- }
- }
- if (schedGroup < ProcessList.SCHED_GROUP_DEFAULT) {
- schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
- }
- app.cached = false;
- app.empty = false;
- foregroundActivities = true;
- }
-
- @Override
- public void onPausedActivity() {
- if (adj > ProcessList.PERCEPTIBLE_APP_ADJ) {
- adj = ProcessList.PERCEPTIBLE_APP_ADJ;
- app.adjType = "pause-activity";
- if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
- reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to pause-activity: " + app);
- }
- }
- if (procState > processStateCurTop) {
- procState = processStateCurTop;
- app.adjType = "pause-activity";
- if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
- reportOomAdjMessageLocked(TAG_OOM_ADJ,
- "Raise procstate to pause-activity (top): " + app);
- }
- }
- if (schedGroup < ProcessList.SCHED_GROUP_DEFAULT) {
- schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
- }
- app.cached = false;
- app.empty = false;
- foregroundActivities = true;
- }
-
- @Override
- public void onStoppingActivity(boolean finishing) {
- if (adj > ProcessList.PERCEPTIBLE_APP_ADJ) {
- adj = ProcessList.PERCEPTIBLE_APP_ADJ;
- app.adjType = "stop-activity";
- if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
- reportOomAdjMessageLocked(TAG_OOM_ADJ,
- "Raise adj to stop-activity: " + app);
- }
- }
-
- // For the process state, we will at this point consider the process to be cached. It
- // will be cached either as an activity or empty depending on whether the activity is
- // finishing. We do this so that we can treat the process as cached for purposes of
- // memory trimming (determining current memory level, trim command to send to process)
- // since there can be an arbitrary number of stopping processes and they should soon all
- // go into the cached state.
- if (!finishing) {
- if (procState > PROCESS_STATE_LAST_ACTIVITY) {
- procState = PROCESS_STATE_LAST_ACTIVITY;
- app.adjType = "stop-activity";
- if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
- reportOomAdjMessageLocked(TAG_OOM_ADJ,
- "Raise procstate to stop-activity: " + app);
- }
- }
- }
- app.cached = false;
- app.empty = false;
- foregroundActivities = true;
- }
-
- @Override
- public void onOtherActivity() {
- if (procState > PROCESS_STATE_CACHED_ACTIVITY) {
- procState = PROCESS_STATE_CACHED_ACTIVITY;
- app.adjType = "cch-act";
- if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
- reportOomAdjMessageLocked(TAG_OOM_ADJ,
- "Raise procstate to cached activity: " + app);
- }
- }
- }
- }
-
- private final boolean computeOomAdjLocked(ProcessRecord app, int cachedAdj, ProcessRecord TOP_APP,
- boolean doingAll, long now, boolean cycleReEval) {
- if (mAdjSeq == app.adjSeq) {
- if (app.adjSeq == app.completedAdjSeq) {
- // This adjustment has already been computed successfully.
- return false;
- } else {
- // The process is being computed, so there is a cycle. We cannot
- // rely on this process's state.
- app.containsCycle = true;
-
- return false;
- }
- }
-
- if (app.thread == null) {
- app.adjSeq = mAdjSeq;
- app.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_BACKGROUND);
- app.setCurProcState(ActivityManager.PROCESS_STATE_CACHED_EMPTY);
- app.curAdj = ProcessList.CACHED_APP_MAX_ADJ;
- app.setCurRawAdj(ProcessList.CACHED_APP_MAX_ADJ);
- app.completedAdjSeq = app.adjSeq;
- return false;
- }
-
- app.adjTypeCode = ActivityManager.RunningAppProcessInfo.REASON_UNKNOWN;
- app.adjSource = null;
- app.adjTarget = null;
- app.empty = false;
- app.cached = false;
-
- final WindowProcessController wpc = app.getWindowProcessController();
- final int appUid = app.info.uid;
- final int logUid = mCurOomAdjUid;
-
- int prevAppAdj = app.curAdj;
- int prevProcState = app.getCurProcState();
-
- if (app.maxAdj <= ProcessList.FOREGROUND_APP_ADJ) {
- // The max adjustment doesn't allow this app to be anything
- // below foreground, so it is not worth doing work for it.
- if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
- reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making fixed: " + app);
- }
- app.adjType = "fixed";
- app.adjSeq = mAdjSeq;
- app.setCurRawAdj(app.maxAdj);
- app.setHasForegroundActivities(false);
- app.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_DEFAULT);
- app.setCurProcState(ActivityManager.PROCESS_STATE_PERSISTENT);
- // System processes can do UI, and when they do we want to have
- // them trim their memory after the user leaves the UI. To
- // facilitate this, here we need to determine whether or not it
- // is currently showing UI.
- app.systemNoUi = true;
- if (app == TOP_APP) {
- app.systemNoUi = false;
- app.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_TOP_APP);
- app.adjType = "pers-top-activity";
- } else if (app.hasTopUi()) {
- // sched group/proc state adjustment is below
- app.systemNoUi = false;
- app.adjType = "pers-top-ui";
- } else if (wpc.hasVisibleActivities()) {
- app.systemNoUi = false;
- }
- if (!app.systemNoUi) {
- if (mWakefulness == PowerManagerInternal.WAKEFULNESS_AWAKE) {
- // screen on, promote UI
- app.setCurProcState(ActivityManager.PROCESS_STATE_PERSISTENT_UI);
- app.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_TOP_APP);
- } else {
- // screen off, restrict UI scheduling
- app.setCurProcState(ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE);
- app.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_RESTRICTED);
- }
- }
- app.setCurRawProcState(app.getCurProcState());
- app.curAdj = app.maxAdj;
- app.completedAdjSeq = app.adjSeq;
- // if curAdj is less than prevAppAdj, then this process was promoted
- return app.curAdj < prevAppAdj || app.getCurProcState() < prevProcState;
- }
-
- app.systemNoUi = false;
-
- final int PROCESS_STATE_CUR_TOP = mAtmInternal.getTopProcessState();
-
- // Determine the importance of the process, starting with most
- // important to least, and assign an appropriate OOM adjustment.
- int adj;
- int schedGroup;
- int procState;
- int cachedAdjSeq;
-
- boolean foregroundActivities = false;
- mTmpBroadcastQueue.clear();
- if (PROCESS_STATE_CUR_TOP == ActivityManager.PROCESS_STATE_TOP && app == TOP_APP) {
- // The last app on the list is the foreground app.
- adj = ProcessList.FOREGROUND_APP_ADJ;
- schedGroup = ProcessList.SCHED_GROUP_TOP_APP;
- app.adjType = "top-activity";
- foregroundActivities = true;
- procState = PROCESS_STATE_CUR_TOP;
- if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
- reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making top: " + app);
- }
- } else if (app.runningRemoteAnimation) {
- adj = ProcessList.VISIBLE_APP_ADJ;
- schedGroup = ProcessList.SCHED_GROUP_TOP_APP;
- app.adjType = "running-remote-anim";
- procState = PROCESS_STATE_CUR_TOP;
- if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
- reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making running remote anim: " + app);
- }
- } else if (app.getActiveInstrumentation() != null) {
- // Don't want to kill running instrumentation.
- adj = ProcessList.FOREGROUND_APP_ADJ;
- schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
- app.adjType = "instrumentation";
- procState = ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
- if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
- reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making instrumentation: " + app);
- }
- } else if (isReceivingBroadcastLocked(app, mTmpBroadcastQueue)) {
- // An app that is currently receiving a broadcast also
- // counts as being in the foreground for OOM killer purposes.
- // It's placed in a sched group based on the nature of the
- // broadcast as reflected by which queue it's active in.
- adj = ProcessList.FOREGROUND_APP_ADJ;
- schedGroup = (mTmpBroadcastQueue.contains(mFgBroadcastQueue))
- ? ProcessList.SCHED_GROUP_DEFAULT : ProcessList.SCHED_GROUP_BACKGROUND;
- app.adjType = "broadcast";
- procState = ActivityManager.PROCESS_STATE_RECEIVER;
- if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
- reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making broadcast: " + app);
- }
- } else if (app.executingServices.size() > 0) {
- // An app that is currently executing a service callback also
- // counts as being in the foreground.
- adj = ProcessList.FOREGROUND_APP_ADJ;
- schedGroup = app.execServicesFg ?
- ProcessList.SCHED_GROUP_DEFAULT : ProcessList.SCHED_GROUP_BACKGROUND;
- app.adjType = "exec-service";
- procState = ActivityManager.PROCESS_STATE_SERVICE;
- if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
- reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making exec-service: " + app);
- }
- //Slog.i(TAG, "EXEC " + (app.execServicesFg ? "FG" : "BG") + ": " + app);
- } else if (app == TOP_APP) {
- adj = ProcessList.FOREGROUND_APP_ADJ;
- schedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
- app.adjType = "top-sleeping";
- foregroundActivities = true;
- procState = PROCESS_STATE_CUR_TOP;
- if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
- reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making top (sleeping): " + app);
- }
- } else {
- // As far as we know the process is empty. We may change our mind later.
- schedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
- // At this point we don't actually know the adjustment. Use the cached adj
- // value that the caller wants us to.
- adj = cachedAdj;
- procState = ActivityManager.PROCESS_STATE_CACHED_EMPTY;
- app.cached = true;
- app.empty = true;
- app.adjType = "cch-empty";
- if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
- reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making empty: " + app);
- }
- }
-
- // Examine all activities if not already foreground.
- if (!foregroundActivities && wpc.hasActivities()) {
- mTmpComputeOomAdjWindowCallback.initialize(app, adj, foregroundActivities, procState,
- schedGroup, appUid, logUid, PROCESS_STATE_CUR_TOP);
- final int minLayer = wpc.computeOomAdjFromActivities(
- ProcessList.VISIBLE_APP_LAYER_MAX, mTmpComputeOomAdjWindowCallback);
-
- adj = mTmpComputeOomAdjWindowCallback.adj;
- foregroundActivities = mTmpComputeOomAdjWindowCallback.foregroundActivities;
- procState = mTmpComputeOomAdjWindowCallback.procState;
- schedGroup = mTmpComputeOomAdjWindowCallback.schedGroup;
-
- if (adj == ProcessList.VISIBLE_APP_ADJ) {
- adj += minLayer;
- }
- }
-
- if (procState > ActivityManager.PROCESS_STATE_CACHED_RECENT && app.hasRecentTasks()) {
- procState = ActivityManager.PROCESS_STATE_CACHED_RECENT;
- app.adjType = "cch-rec";
- if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
- reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise procstate to cached recent: " + app);
- }
- }
-
- if (adj > ProcessList.PERCEPTIBLE_APP_ADJ
- || procState > ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
- if (app.hasForegroundServices()) {
- // The user is aware of this app, so make it visible.
- adj = ProcessList.PERCEPTIBLE_APP_ADJ;
- procState = ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
- app.cached = false;
- app.adjType = "fg-service";
- schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
- if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
- reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to fg service: " + app);
- }
- } else if (app.hasOverlayUi()) {
- // The process is display an overlay UI.
- adj = ProcessList.PERCEPTIBLE_APP_ADJ;
- procState = ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
- app.cached = false;
- app.adjType = "has-overlay-ui";
- schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
- if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
- reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to overlay ui: " + app);
- }
- }
- }
-
- // If the app was recently in the foreground and moved to a foreground service status,
- // allow it to get a higher rank in memory for some time, compared to other foreground
- // services so that it can finish performing any persistence/processing of in-memory state.
- if (app.hasForegroundServices() && adj > ProcessList.PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ
- && (app.lastTopTime + mConstants.TOP_TO_FGS_GRACE_DURATION > now
- || app.setProcState <= ActivityManager.PROCESS_STATE_TOP)) {
- adj = ProcessList.PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ;
- app.adjType = "fg-service-act";
- if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
- reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to recent fg: " + app);
- }
- }
-
- if (adj > ProcessList.PERCEPTIBLE_APP_ADJ
- || procState > ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND) {
- if (app.forcingToImportant != null) {
- // This is currently used for toasts... they are not interactive, and
- // we don't want them to cause the app to become fully foreground (and
- // thus out of background check), so we yes the best background level we can.
- adj = ProcessList.PERCEPTIBLE_APP_ADJ;
- procState = ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND;
- app.cached = false;
- app.adjType = "force-imp";
- app.adjSource = app.forcingToImportant;
- schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
- if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
- reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to force imp: " + app);
- }
- }
- }
-
- if (mAtmInternal.isHeavyWeightProcess(app.getWindowProcessController())) {
- if (adj > ProcessList.HEAVY_WEIGHT_APP_ADJ) {
- // We don't want to kill the current heavy-weight process.
- adj = ProcessList.HEAVY_WEIGHT_APP_ADJ;
- schedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
- app.cached = false;
- app.adjType = "heavy";
- if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
- reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to heavy: " + app);
- }
- }
- if (procState > ActivityManager.PROCESS_STATE_HEAVY_WEIGHT) {
- procState = ActivityManager.PROCESS_STATE_HEAVY_WEIGHT;
- app.adjType = "heavy";
- if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
- reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise procstate to heavy: " + app);
- }
- }
- }
-
- if (wpc.isHomeProcess()) {
- if (adj > ProcessList.HOME_APP_ADJ) {
- // This process is hosting what we currently consider to be the
- // home app, so we don't want to let it go into the background.
- adj = ProcessList.HOME_APP_ADJ;
- schedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
- app.cached = false;
- app.adjType = "home";
- if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
- reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to home: " + app);
- }
- }
- if (procState > ActivityManager.PROCESS_STATE_HOME) {
- procState = ActivityManager.PROCESS_STATE_HOME;
- app.adjType = "home";
- if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
- reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise procstate to home: " + app);
- }
- }
- }
-
- if (wpc.isPreviousProcess() && app.hasActivities()) {
- if (adj > ProcessList.PREVIOUS_APP_ADJ) {
- // This was the previous process that showed UI to the user.
- // We want to try to keep it around more aggressively, to give
- // a good experience around switching between two apps.
- adj = ProcessList.PREVIOUS_APP_ADJ;
- schedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
- app.cached = false;
- app.adjType = "previous";
- if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
- reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to prev: " + app);
- }
- }
- if (procState > PROCESS_STATE_LAST_ACTIVITY) {
- procState = PROCESS_STATE_LAST_ACTIVITY;
- app.adjType = "previous";
- if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
- reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise procstate to prev: " + app);
- }
- }
- }
-
- if (false) Slog.i(TAG, "OOM " + app + ": initial adj=" + adj
- + " reason=" + app.adjType);
-
- // By default, we use the computed adjustment. It may be changed if
- // there are applications dependent on our services or providers, but
- // this gives us a baseline and makes sure we don't get into an
- // infinite recursion. If we're re-evaluating due to cycles, use the previously computed
- // values.
- app.setCurRawAdj(!cycleReEval ? adj : Math.min(adj, app.getCurRawAdj()));
- app.setCurRawProcState(!cycleReEval
- ? procState
- : Math.min(procState, app.getCurRawProcState()));
-
- app.hasStartedServices = false;
- app.adjSeq = mAdjSeq;
-
- if (mBackupTarget != null && app == mBackupTarget.app) {
- // If possible we want to avoid killing apps while they're being backed up
- if (adj > ProcessList.BACKUP_APP_ADJ) {
- if (DEBUG_BACKUP) Slog.v(TAG_BACKUP, "oom BACKUP_APP_ADJ for " + app);
- adj = ProcessList.BACKUP_APP_ADJ;
- if (procState > ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND) {
- procState = ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND;
- }
- app.adjType = "backup";
- if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
- reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to backup: " + app);
- }
- app.cached = false;
- }
- if (procState > ActivityManager.PROCESS_STATE_BACKUP) {
- procState = ActivityManager.PROCESS_STATE_BACKUP;
- app.adjType = "backup";
- if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
- reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise procstate to backup: " + app);
- }
- }
- }
-
- boolean mayBeTop = false;
- String mayBeTopType = null;
- Object mayBeTopSource = null;
- Object mayBeTopTarget = null;
-
- for (int is = app.services.size()-1;
- is >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ
- || schedGroup == ProcessList.SCHED_GROUP_BACKGROUND
- || procState > ActivityManager.PROCESS_STATE_TOP);
- is--) {
- ServiceRecord s = app.services.valueAt(is);
- if (s.startRequested) {
- app.hasStartedServices = true;
- if (procState > ActivityManager.PROCESS_STATE_SERVICE) {
- procState = ActivityManager.PROCESS_STATE_SERVICE;
- app.adjType = "started-services";
- if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
- reportOomAdjMessageLocked(TAG_OOM_ADJ,
- "Raise procstate to started service: " + app);
- }
- }
- if (app.hasShownUi && !wpc.isHomeProcess()) {
- // If this process has shown some UI, let it immediately
- // go to the LRU list because it may be pretty heavy with
- // UI stuff. We'll tag it with a label just to help
- // debug and understand what is going on.
- if (adj > ProcessList.SERVICE_ADJ) {
- app.adjType = "cch-started-ui-services";
- }
- } else {
- if (now < (s.lastActivity + mConstants.MAX_SERVICE_INACTIVITY)) {
- // This service has seen some activity within
- // recent memory, so we will keep its process ahead
- // of the background processes.
- if (adj > ProcessList.SERVICE_ADJ) {
- adj = ProcessList.SERVICE_ADJ;
- app.adjType = "started-services";
- if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
- reportOomAdjMessageLocked(TAG_OOM_ADJ,
- "Raise adj to started service: " + app);
- }
- app.cached = false;
- }
- }
- // If we have let the service slide into the background
- // state, still have some text describing what it is doing
- // even though the service no longer has an impact.
- if (adj > ProcessList.SERVICE_ADJ) {
- app.adjType = "cch-started-services";
- }
- }
- }
-
- for (int conni = s.connections.size()-1;
- conni >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ
- || schedGroup == ProcessList.SCHED_GROUP_BACKGROUND
- || procState > ActivityManager.PROCESS_STATE_TOP);
- conni--) {
- ArrayList<ConnectionRecord> clist = s.connections.valueAt(conni);
- for (int i = 0;
- i < clist.size() && (adj > ProcessList.FOREGROUND_APP_ADJ
- || schedGroup == ProcessList.SCHED_GROUP_BACKGROUND
- || procState > ActivityManager.PROCESS_STATE_TOP);
- i++) {
- // XXX should compute this based on the max of
- // all connected clients.
- ConnectionRecord cr = clist.get(i);
- if (cr.binding.client == app) {
- // Binding to oneself is not interesting.
- continue;
- }
-
- boolean trackedProcState = false;
- if ((cr.flags&Context.BIND_WAIVE_PRIORITY) == 0) {
- ProcessRecord client = cr.binding.client;
- computeOomAdjLocked(client, cachedAdj, TOP_APP, doingAll, now, cycleReEval);
-
- if (shouldSkipDueToCycle(app, client, procState, adj, cycleReEval)) {
- continue;
- }
-
- int clientAdj = client.getCurRawAdj();
- int clientProcState = client.getCurRawProcState();
-
- if (clientProcState >= PROCESS_STATE_CACHED_ACTIVITY) {
- // If the other app is cached for any reason, for purposes here
- // we are going to consider it empty. The specific cached state
- // doesn't propagate except under certain conditions.
- clientProcState = ActivityManager.PROCESS_STATE_CACHED_EMPTY;
- }
- String adjType = null;
- if ((cr.flags&Context.BIND_ALLOW_OOM_MANAGEMENT) != 0) {
- // Not doing bind OOM management, so treat
- // this guy more like a started service.
- if (app.hasShownUi && !wpc.isHomeProcess()) {
- // If this process has shown some UI, let it immediately
- // go to the LRU list because it may be pretty heavy with
- // UI stuff. We'll tag it with a label just to help
- // debug and understand what is going on.
- if (adj > clientAdj) {
- adjType = "cch-bound-ui-services";
- }
- app.cached = false;
- clientAdj = adj;
- clientProcState = procState;
- } else {
- if (now >= (s.lastActivity + mConstants.MAX_SERVICE_INACTIVITY)) {
- // This service has not seen activity within
- // recent memory, so allow it to drop to the
- // LRU list if there is no other reason to keep
- // it around. We'll also tag it with a label just
- // to help debug and undertand what is going on.
- if (adj > clientAdj) {
- adjType = "cch-bound-services";
- }
- clientAdj = adj;
- }
- }
- }
- if (adj > clientAdj) {
- // If this process has recently shown UI, and
- // the process that is binding to it is less
- // important than being visible, then we don't
- // care about the binding as much as we care
- // about letting this process get into the LRU
- // list to be killed and restarted if needed for
- // memory.
- if (app.hasShownUi && !wpc.isHomeProcess()
- && clientAdj > ProcessList.PERCEPTIBLE_APP_ADJ) {
- if (adj >= ProcessList.CACHED_APP_MIN_ADJ) {
- adjType = "cch-bound-ui-services";
- }
- } else {
- int newAdj;
- if ((cr.flags&(Context.BIND_ABOVE_CLIENT
- |Context.BIND_IMPORTANT)) != 0) {
- if (clientAdj >= ProcessList.PERSISTENT_SERVICE_ADJ) {
- newAdj = clientAdj;
- } else {
- // make this service persistent
- newAdj = ProcessList.PERSISTENT_SERVICE_ADJ;
- schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
- procState = ActivityManager.PROCESS_STATE_PERSISTENT;
- cr.trackProcState(procState, mAdjSeq, now);
- trackedProcState = true;
- }
- } else if ((cr.flags & Context.BIND_ADJUST_BELOW_PERCEPTIBLE) != 0
- && clientAdj < ProcessList.PERCEPTIBLE_APP_ADJ
- && adj > ProcessList.PERCEPTIBLE_APP_ADJ + 1) {
- newAdj = ProcessList.PERCEPTIBLE_APP_ADJ + 1;
- } else if ((cr.flags&Context.BIND_NOT_VISIBLE) != 0
- && clientAdj < ProcessList.PERCEPTIBLE_APP_ADJ
- && adj > ProcessList.PERCEPTIBLE_APP_ADJ) {
- newAdj = ProcessList.PERCEPTIBLE_APP_ADJ;
- } else if (clientAdj >= ProcessList.PERCEPTIBLE_APP_ADJ) {
- newAdj = clientAdj;
- } else {
- if (adj > ProcessList.VISIBLE_APP_ADJ) {
- newAdj = Math.max(clientAdj, ProcessList.VISIBLE_APP_ADJ);
- } else {
- newAdj = adj;
- }
- }
- if (!client.cached) {
- app.cached = false;
- }
- if (adj > newAdj) {
- adj = newAdj;
- app.setCurRawAdj(adj);
- adjType = "service";
- }
- }
- }
- if ((cr.flags & (Context.BIND_NOT_FOREGROUND
- | Context.BIND_IMPORTANT_BACKGROUND)) == 0) {
- // This will treat important bound services identically to
- // the top app, which may behave differently than generic
- // foreground work.
- final int curSchedGroup = client.getCurrentSchedulingGroup();
- if (curSchedGroup > schedGroup) {
- if ((cr.flags&Context.BIND_IMPORTANT) != 0) {
- schedGroup = curSchedGroup;
- } else {
- schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
- }
- }
- if (clientProcState <= ActivityManager.PROCESS_STATE_TOP) {
- if (clientProcState == ActivityManager.PROCESS_STATE_TOP) {
- // Special handling of clients who are in the top state.
- // We *may* want to consider this process to be in the
- // top state as well, but only if there is not another
- // reason for it to be running. Being on the top is a
- // special state, meaning you are specifically running
- // for the current top app. If the process is already
- // running in the background for some other reason, it
- // is more important to continue considering it to be
- // in the background state.
- mayBeTop = true;
- mayBeTopType = "service";
- mayBeTopSource = cr.binding.client;
- mayBeTopTarget = s.instanceName;
- clientProcState = ActivityManager.PROCESS_STATE_CACHED_EMPTY;
- } else {
- // Special handling for above-top states (persistent
- // processes). These should not bring the current process
- // into the top state, since they are not on top. Instead
- // give them the best state after that.
- if ((cr.flags&Context.BIND_FOREGROUND_SERVICE) != 0) {
- clientProcState =
- ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
- } else if (mWakefulness
- == PowerManagerInternal.WAKEFULNESS_AWAKE &&
- (cr.flags&Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE)
- != 0) {
- clientProcState =
- ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
- } else {
- clientProcState =
- ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
- }
- }
- }
- } else if ((cr.flags & Context.BIND_IMPORTANT_BACKGROUND) == 0) {
- if (clientProcState <
- ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND) {
- clientProcState =
- ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND;
- }
- } else {
- if (clientProcState <
- ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND) {
- clientProcState =
- ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND;
- }
- }
- if (!trackedProcState) {
- cr.trackProcState(clientProcState, mAdjSeq, now);
- }
- if (procState > clientProcState) {
- procState = clientProcState;
- app.setCurRawProcState(procState);
- if (adjType == null) {
- adjType = "service";
- }
- }
- if (procState < ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
- && (cr.flags & Context.BIND_SHOWING_UI) != 0) {
- app.setPendingUiClean(true);
- }
- if (adjType != null) {
- app.adjType = adjType;
- app.adjTypeCode = ActivityManager.RunningAppProcessInfo
- .REASON_SERVICE_IN_USE;
- app.adjSource = cr.binding.client;
- app.adjSourceProcState = clientProcState;
- app.adjTarget = s.instanceName;
- if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
- reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to " + adjType
- + ": " + app + ", due to " + cr.binding.client
- + " adj=" + adj + " procState="
- + ProcessList.makeProcStateString(procState));
- }
- }
- }
- if ((cr.flags&Context.BIND_TREAT_LIKE_ACTIVITY) != 0) {
- app.treatLikeActivity = true;
- }
- final ActivityServiceConnectionsHolder a = cr.activity;
- if ((cr.flags&Context.BIND_ADJUST_WITH_ACTIVITY) != 0) {
- if (a != null && adj > ProcessList.FOREGROUND_APP_ADJ
- && a.isActivityVisible()) {
- adj = ProcessList.FOREGROUND_APP_ADJ;
- app.setCurRawAdj(adj);
- if ((cr.flags&Context.BIND_NOT_FOREGROUND) == 0) {
- if ((cr.flags&Context.BIND_IMPORTANT) != 0) {
- schedGroup = ProcessList.SCHED_GROUP_TOP_APP_BOUND;
- } else {
- schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
- }
- }
- app.cached = false;
- app.adjType = "service";
- app.adjTypeCode = ActivityManager.RunningAppProcessInfo
- .REASON_SERVICE_IN_USE;
- app.adjSource = a;
- app.adjSourceProcState = procState;
- app.adjTarget = s.instanceName;
- if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
- reportOomAdjMessageLocked(TAG_OOM_ADJ,
- "Raise to service w/activity: " + app);
- }
- }
- }
- }
- }
- }
-
- for (int provi = app.pubProviders.size()-1;
- provi >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ
- || schedGroup == ProcessList.SCHED_GROUP_BACKGROUND
- || procState > ActivityManager.PROCESS_STATE_TOP);
- provi--) {
- ContentProviderRecord cpr = app.pubProviders.valueAt(provi);
- for (int i = cpr.connections.size()-1;
- i >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ
- || schedGroup == ProcessList.SCHED_GROUP_BACKGROUND
- || procState > ActivityManager.PROCESS_STATE_TOP);
- i--) {
- ContentProviderConnection conn = cpr.connections.get(i);
- ProcessRecord client = conn.client;
- if (client == app) {
- // Being our own client is not interesting.
- continue;
- }
- computeOomAdjLocked(client, cachedAdj, TOP_APP, doingAll, now, cycleReEval);
-
- if (shouldSkipDueToCycle(app, client, procState, adj, cycleReEval)) {
- continue;
- }
-
- int clientAdj = client.getCurRawAdj();
- int clientProcState = client.getCurRawProcState();
-
- if (clientProcState >= PROCESS_STATE_CACHED_ACTIVITY) {
- // If the other app is cached for any reason, for purposes here
- // we are going to consider it empty.
- clientProcState = ActivityManager.PROCESS_STATE_CACHED_EMPTY;
- }
- String adjType = null;
- if (adj > clientAdj) {
- if (app.hasShownUi && !wpc.isHomeProcess()
- && clientAdj > ProcessList.PERCEPTIBLE_APP_ADJ) {
- adjType = "cch-ui-provider";
- } else {
- adj = clientAdj > ProcessList.FOREGROUND_APP_ADJ
- ? clientAdj : ProcessList.FOREGROUND_APP_ADJ;
- app.setCurRawAdj(adj);
- adjType = "provider";
- }
- app.cached &= client.cached;
- }
- if (clientProcState <= ActivityManager.PROCESS_STATE_TOP) {
- if (clientProcState == ActivityManager.PROCESS_STATE_TOP) {
- // Special handling of clients who are in the top state.
- // We *may* want to consider this process to be in the
- // top state as well, but only if there is not another
- // reason for it to be running. Being on the top is a
- // special state, meaning you are specifically running
- // for the current top app. If the process is already
- // running in the background for some other reason, it
- // is more important to continue considering it to be
- // in the background state.
- mayBeTop = true;
- clientProcState = ActivityManager.PROCESS_STATE_CACHED_EMPTY;
- mayBeTopType = adjType = "provider-top";
- mayBeTopSource = client;
- mayBeTopTarget = cpr.name;
- } else {
- // Special handling for above-top states (persistent
- // processes). These should not bring the current process
- // into the top state, since they are not on top. Instead
- // give them the best state after that.
- clientProcState =
- ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
- if (adjType == null) {
- adjType = "provider";
- }
- }
- }
- conn.trackProcState(clientProcState, mAdjSeq, now);
- if (procState > clientProcState) {
- procState = clientProcState;
- app.setCurRawProcState(procState);
- }
- if (client.getCurrentSchedulingGroup() > schedGroup) {
- schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
- }
- if (adjType != null) {
- app.adjType = adjType;
- app.adjTypeCode = ActivityManager.RunningAppProcessInfo
- .REASON_PROVIDER_IN_USE;
- app.adjSource = client;
- app.adjSourceProcState = clientProcState;
- app.adjTarget = cpr.name;
- if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
- reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to " + adjType
- + ": " + app + ", due to " + client
- + " adj=" + adj + " procState="
- + ProcessList.makeProcStateString(procState));
- }
- }
- }
- // If the provider has external (non-framework) process
- // dependencies, ensure that its adjustment is at least
- // FOREGROUND_APP_ADJ.
- if (cpr.hasExternalProcessHandles()) {
- if (adj > ProcessList.FOREGROUND_APP_ADJ) {
- adj = ProcessList.FOREGROUND_APP_ADJ;
- app.setCurRawAdj(adj);
- schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
- app.cached = false;
- app.adjType = "ext-provider";
- app.adjTarget = cpr.name;
- if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
- reportOomAdjMessageLocked(TAG_OOM_ADJ,
- "Raise adj to external provider: " + app);
- }
- }
- if (procState > ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
- procState = ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
- app.setCurRawProcState(procState);
- if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
- reportOomAdjMessageLocked(TAG_OOM_ADJ,
- "Raise procstate to external provider: " + app);
- }
- }
- }
- }
-
- if (app.lastProviderTime > 0 &&
- (app.lastProviderTime+mConstants.CONTENT_PROVIDER_RETAIN_TIME) > now) {
- if (adj > ProcessList.PREVIOUS_APP_ADJ) {
- adj = ProcessList.PREVIOUS_APP_ADJ;
- schedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
- app.cached = false;
- app.adjType = "recent-provider";
- if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
- reportOomAdjMessageLocked(TAG_OOM_ADJ,
- "Raise adj to recent provider: " + app);
- }
- }
- if (procState > PROCESS_STATE_LAST_ACTIVITY) {
- procState = PROCESS_STATE_LAST_ACTIVITY;
- app.adjType = "recent-provider";
- if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
- reportOomAdjMessageLocked(TAG_OOM_ADJ,
- "Raise procstate to recent provider: " + app);
- }
- }
- }
-
- if (mayBeTop && procState > ActivityManager.PROCESS_STATE_TOP) {
- // A client of one of our services or providers is in the top state. We
- // *may* want to be in the top state, but not if we are already running in
- // the background for some other reason. For the decision here, we are going
- // to pick out a few specific states that we want to remain in when a client
- // is top (states that tend to be longer-term) and otherwise allow it to go
- // to the top state.
- switch (procState) {
- case ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE:
- case ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE:
- // Something else is keeping it at this level, just leave it.
- break;
- case ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND:
- case ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND:
- case ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND:
- case ActivityManager.PROCESS_STATE_SERVICE:
- // These all are longer-term states, so pull them up to the top
- // of the background states, but not all the way to the top state.
- procState = ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
- app.adjType = mayBeTopType;
- app.adjSource = mayBeTopSource;
- app.adjTarget = mayBeTopTarget;
- if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
- reportOomAdjMessageLocked(TAG_OOM_ADJ, "May be top raise to " + mayBeTopType
- + ": " + app + ", due to " + mayBeTopSource
- + " adj=" + adj + " procState="
- + ProcessList.makeProcStateString(procState));
- }
- break;
- default:
- // Otherwise, top is a better choice, so take it.
- procState = ActivityManager.PROCESS_STATE_TOP;
- app.adjType = mayBeTopType;
- app.adjSource = mayBeTopSource;
- app.adjTarget = mayBeTopTarget;
- if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
- reportOomAdjMessageLocked(TAG_OOM_ADJ, "May be top raise to " + mayBeTopType
- + ": " + app + ", due to " + mayBeTopSource
- + " adj=" + adj + " procState="
- + ProcessList.makeProcStateString(procState));
- }
- break;
- }
- }
-
- if (procState >= ActivityManager.PROCESS_STATE_CACHED_EMPTY) {
- if (app.hasClientActivities()) {
- // This is a cached process, but with client activities. Mark it so.
- procState = ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT;
- app.adjType = "cch-client-act";
- } else if (app.treatLikeActivity) {
- // This is a cached process, but somebody wants us to treat it like it has
- // an activity, okay!
- procState = PROCESS_STATE_CACHED_ACTIVITY;
- app.adjType = "cch-as-act";
- }
- }
-
- if (adj == ProcessList.SERVICE_ADJ) {
- if (doingAll) {
- app.serviceb = mNewNumAServiceProcs > (mNumServiceProcs/3);
- mNewNumServiceProcs++;
- //Slog.i(TAG, "ADJ " + app + " serviceb=" + app.serviceb);
- if (!app.serviceb) {
- // This service isn't far enough down on the LRU list to
- // normally be a B service, but if we are low on RAM and it
- // is large we want to force it down since we would prefer to
- // keep launcher over it.
- if (mLastMemoryLevel > ProcessStats.ADJ_MEM_FACTOR_NORMAL
- && app.lastPss >= mProcessList.getCachedRestoreThresholdKb()) {
- app.serviceHighRam = true;
- app.serviceb = true;
- //Slog.i(TAG, "ADJ " + app + " high ram!");
- } else {
- mNewNumAServiceProcs++;
- //Slog.i(TAG, "ADJ " + app + " not high ram!");
- }
- } else {
- app.serviceHighRam = false;
- }
- }
- if (app.serviceb) {
- adj = ProcessList.SERVICE_B_ADJ;
- }
- }
-
- app.setCurRawAdj(adj);
-
- //Slog.i(TAG, "OOM ADJ " + app + ": pid=" + app.pid +
- // " adj=" + adj + " curAdj=" + app.curAdj + " maxAdj=" + app.maxAdj);
- if (adj > app.maxAdj) {
- adj = app.maxAdj;
- if (app.maxAdj <= ProcessList.PERCEPTIBLE_APP_ADJ) {
- schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
- }
- }
-
- // Put bound foreground services in a special sched group for additional
- // restrictions on screen off
- if (procState >= ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE &&
- mWakefulness != PowerManagerInternal.WAKEFULNESS_AWAKE) {
- if (schedGroup > ProcessList.SCHED_GROUP_RESTRICTED) {
- schedGroup = ProcessList.SCHED_GROUP_RESTRICTED;
- }
- }
-
- // Do final modification to adj. Everything we do between here and applying
- // the final setAdj must be done in this function, because we will also use
- // it when computing the final cached adj later. Note that we don't need to
- // worry about this for max adj above, since max adj will always be used to
- // keep it out of the cached vaues.
- app.curAdj = app.modifyRawOomAdj(adj);
- app.setCurrentSchedulingGroup(schedGroup);
- app.setCurProcState(procState);
- app.setCurRawProcState(procState);
- app.setHasForegroundActivities(foregroundActivities);
- app.completedAdjSeq = mAdjSeq;
-
- // if curAdj or curProcState improved, then this process was promoted
- return app.curAdj < prevAppAdj || app.getCurProcState() < prevProcState;
- }
-
- /**
- * Checks if for the given app and client, there's a cycle that should skip over the client
- * for now or use partial values to evaluate the effect of the client binding.
- * @param app
- * @param client
- * @param procState procstate evaluated so far for this app
- * @param adj oom_adj evaluated so far for this app
- * @param cycleReEval whether we're currently re-evaluating due to a cycle, and not the first
- * evaluation.
- * @return whether to skip using the client connection at this time
- */
- private boolean shouldSkipDueToCycle(ProcessRecord app, ProcessRecord client,
- int procState, int adj, boolean cycleReEval) {
- if (client.containsCycle) {
- // We've detected a cycle. We should retry computeOomAdjLocked later in
- // case a later-checked connection from a client would raise its
- // priority legitimately.
- app.containsCycle = true;
- // If the client has not been completely evaluated, check if it's worth
- // using the partial values.
- if (client.completedAdjSeq < mAdjSeq) {
- if (cycleReEval) {
- // If the partial values are no better, skip until the next
- // attempt
- if (client.getCurRawProcState() >= procState
- && client.getCurRawAdj() >= adj) {
- return true;
- }
- // Else use the client's partial procstate and adj to adjust the
- // effect of the binding
- } else {
- return true;
- }
- }
- }
- return false;
- }
-
private static final class RecordPssRunnable implements Runnable {
private final ActivityManagerService mService;
private final ProcessRecord mProc;
@@ -17016,287 +15886,6 @@
}
}
- private final boolean applyOomAdjLocked(ProcessRecord app, boolean doingAll, long now,
- long nowElapsed) {
- boolean success = true;
-
- if (app.getCurRawAdj() != app.setRawAdj) {
- app.setRawAdj = app.getCurRawAdj();
- }
-
- int changes = 0;
-
- if (app.curAdj != app.setAdj) {
- // don't compact during bootup
- if (mConstants.USE_COMPACTION && mBooted) {
- // Perform a minor compaction when a perceptible app becomes the prev/home app
- // Perform a major compaction when any app enters cached
- // reminder: here, setAdj is previous state, curAdj is upcoming state
- if (app.setAdj <= ProcessList.PERCEPTIBLE_APP_ADJ &&
- (app.curAdj == ProcessList.PREVIOUS_APP_ADJ ||
- app.curAdj == ProcessList.HOME_APP_ADJ)) {
- mAppCompact.compactAppSome(app);
- } else if (app.setAdj < ProcessList.CACHED_APP_MIN_ADJ &&
- app.curAdj >= ProcessList.CACHED_APP_MIN_ADJ) {
- mAppCompact.compactAppFull(app);
- }
- }
- ProcessList.setOomAdj(app.pid, app.uid, app.curAdj);
- if (DEBUG_SWITCH || DEBUG_OOM_ADJ || mCurOomAdjUid == app.info.uid) {
- String msg = "Set " + app.pid + " " + app.processName + " adj "
- + app.curAdj + ": " + app.adjType;
- reportOomAdjMessageLocked(TAG_OOM_ADJ, msg);
- }
- app.setAdj = app.curAdj;
- app.verifiedAdj = ProcessList.INVALID_ADJ;
- }
-
- final int curSchedGroup = app.getCurrentSchedulingGroup();
- if (app.setSchedGroup != curSchedGroup) {
- int oldSchedGroup = app.setSchedGroup;
- app.setSchedGroup = curSchedGroup;
- if (DEBUG_SWITCH || DEBUG_OOM_ADJ || mCurOomAdjUid == app.uid) {
- String msg = "Setting sched group of " + app.processName
- + " to " + curSchedGroup + ": " + app.adjType;
- reportOomAdjMessageLocked(TAG_OOM_ADJ, msg);
- }
- if (app.waitingToKill != null && app.curReceivers.isEmpty()
- && app.setSchedGroup == ProcessList.SCHED_GROUP_BACKGROUND) {
- app.kill(app.waitingToKill, true);
- success = false;
- } else {
- int processGroup;
- switch (curSchedGroup) {
- case ProcessList.SCHED_GROUP_BACKGROUND:
- processGroup = THREAD_GROUP_BG_NONINTERACTIVE;
- break;
- case ProcessList.SCHED_GROUP_TOP_APP:
- case ProcessList.SCHED_GROUP_TOP_APP_BOUND:
- processGroup = THREAD_GROUP_TOP_APP;
- break;
- case ProcessList.SCHED_GROUP_RESTRICTED:
- processGroup = THREAD_GROUP_RESTRICTED;
- break;
- default:
- processGroup = THREAD_GROUP_DEFAULT;
- break;
- }
- long oldId = Binder.clearCallingIdentity();
- try {
- setProcessGroup(app.pid, processGroup);
- if (curSchedGroup == ProcessList.SCHED_GROUP_TOP_APP) {
- // do nothing if we already switched to RT
- if (oldSchedGroup != ProcessList.SCHED_GROUP_TOP_APP) {
- app.getWindowProcessController().onTopProcChanged();
- if (mUseFifoUiScheduling) {
- // Switch UI pipeline for app to SCHED_FIFO
- app.savedPriority = Process.getThreadPriority(app.pid);
- scheduleAsFifoPriority(app.pid, /* suppressLogs */true);
- if (app.renderThreadTid != 0) {
- scheduleAsFifoPriority(app.renderThreadTid,
- /* suppressLogs */true);
- if (DEBUG_OOM_ADJ) {
- Slog.d("UI_FIFO", "Set RenderThread (TID " +
- app.renderThreadTid + ") to FIFO");
- }
- } else {
- if (DEBUG_OOM_ADJ) {
- Slog.d("UI_FIFO", "Not setting RenderThread TID");
- }
- }
- } else {
- // Boost priority for top app UI and render threads
- setThreadPriority(app.pid, TOP_APP_PRIORITY_BOOST);
- if (app.renderThreadTid != 0) {
- try {
- setThreadPriority(app.renderThreadTid,
- TOP_APP_PRIORITY_BOOST);
- } catch (IllegalArgumentException e) {
- // thread died, ignore
- }
- }
- }
- }
- } else if (oldSchedGroup == ProcessList.SCHED_GROUP_TOP_APP &&
- curSchedGroup != ProcessList.SCHED_GROUP_TOP_APP) {
- app.getWindowProcessController().onTopProcChanged();
- if (mUseFifoUiScheduling) {
- try {
- // Reset UI pipeline to SCHED_OTHER
- setThreadScheduler(app.pid, SCHED_OTHER, 0);
- setThreadPriority(app.pid, app.savedPriority);
- if (app.renderThreadTid != 0) {
- setThreadScheduler(app.renderThreadTid,
- SCHED_OTHER, 0);
- setThreadPriority(app.renderThreadTid, -4);
- }
- } catch (IllegalArgumentException e) {
- Slog.w(TAG,
- "Failed to set scheduling policy, thread does not exist:\n"
- + e);
- } catch (SecurityException e) {
- Slog.w(TAG, "Failed to set scheduling policy, not allowed:\n" + e);
- }
- } else {
- // Reset priority for top app UI and render threads
- setThreadPriority(app.pid, 0);
- if (app.renderThreadTid != 0) {
- setThreadPriority(app.renderThreadTid, 0);
- }
- }
- }
- } catch (Exception e) {
- if (false) {
- Slog.w(TAG, "Failed setting process group of " + app.pid
- + " to " + app.getCurrentSchedulingGroup());
- Slog.w(TAG, "at location", e);
- }
- } finally {
- Binder.restoreCallingIdentity(oldId);
- }
- }
- }
- if (app.repForegroundActivities != app.hasForegroundActivities()) {
- app.repForegroundActivities = app.hasForegroundActivities();
- changes |= ProcessChangeItem.CHANGE_ACTIVITIES;
- }
- if (app.getReportedProcState() != app.getCurProcState()) {
- app.setReportedProcState(app.getCurProcState());
- if (app.thread != null) {
- try {
- if (false) {
- //RuntimeException h = new RuntimeException("here");
- Slog.i(TAG, "Sending new process state " + app.getReportedProcState()
- + " to " + app /*, h*/);
- }
- app.thread.setProcessState(app.getReportedProcState());
- } catch (RemoteException e) {
- }
- }
- }
- if (app.setProcState == PROCESS_STATE_NONEXISTENT
- || ProcessList.procStatesDifferForMem(app.getCurProcState(), app.setProcState)) {
- if (false && mTestPssMode && app.setProcState >= 0 && app.lastStateTime <= (now-200)) {
- // Experimental code to more aggressively collect pss while
- // running test... the problem is that this tends to collect
- // the data right when a process is transitioning between process
- // states, which will tend to give noisy data.
- long start = SystemClock.uptimeMillis();
- long startTime = SystemClock.currentThreadTimeMillis();
- long pss = Debug.getPss(app.pid, mTmpLong, null);
- long endTime = SystemClock.currentThreadTimeMillis();
- recordPssSampleLocked(app, app.getCurProcState(), pss, mTmpLong[0], mTmpLong[1],
- mTmpLong[2], ProcessStats.ADD_PSS_INTERNAL_SINGLE, endTime-startTime, now);
- mPendingPssProcesses.remove(app);
- Slog.i(TAG, "Recorded pss for " + app + " state " + app.setProcState
- + " to " + app.getCurProcState() + ": "
- + (SystemClock.uptimeMillis()-start) + "ms");
- }
- app.lastStateTime = now;
- app.nextPssTime = ProcessList.computeNextPssTime(app.getCurProcState(),
- app.procStateMemTracker, mTestPssMode, mAtmInternal.isSleeping(), now);
- if (DEBUG_PSS) Slog.d(TAG_PSS, "Process state change from "
- + ProcessList.makeProcStateString(app.setProcState) + " to "
- + ProcessList.makeProcStateString(app.getCurProcState()) + " next pss in "
- + (app.nextPssTime-now) + ": " + app);
- } else {
- if (now > app.nextPssTime || (now > (app.lastPssTime+ProcessList.PSS_MAX_INTERVAL)
- && now > (app.lastStateTime+ProcessList.minTimeFromStateChange(
- mTestPssMode)))) {
- if (requestPssLocked(app, app.setProcState)) {
- app.nextPssTime = ProcessList.computeNextPssTime(app.getCurProcState(),
- app.procStateMemTracker, mTestPssMode, mAtmInternal.isSleeping(), now);
- }
- } else if (false && DEBUG_PSS) Slog.d(TAG_PSS,
- "Not requesting pss of " + app + ": next=" + (app.nextPssTime-now));
- }
- if (app.setProcState != app.getCurProcState()) {
- if (DEBUG_SWITCH || DEBUG_OOM_ADJ || mCurOomAdjUid == app.uid) {
- String msg = "Proc state change of " + app.processName
- + " to " + ProcessList.makeProcStateString(app.getCurProcState())
- + " (" + app.getCurProcState() + ")" + ": " + app.adjType;
- reportOomAdjMessageLocked(TAG_OOM_ADJ, msg);
- }
- boolean setImportant = app.setProcState < ActivityManager.PROCESS_STATE_SERVICE;
- boolean curImportant = app.getCurProcState() < ActivityManager.PROCESS_STATE_SERVICE;
- if (setImportant && !curImportant) {
- // This app is no longer something we consider important enough to allow to use
- // arbitrary amounts of battery power. Note its current CPU time to later know to
- // kill it if it is not behaving well.
- app.setWhenUnimportant(now);
- app.lastCpuTime = 0;
- }
- // Inform UsageStats of important process state change
- // Must be called before updating setProcState
- maybeUpdateUsageStatsLocked(app, nowElapsed);
-
- maybeUpdateLastTopTime(app, now);
-
- app.setProcState = app.getCurProcState();
- if (app.setProcState >= ActivityManager.PROCESS_STATE_HOME) {
- app.notCachedSinceIdle = false;
- }
- if (!doingAll) {
- setProcessTrackerStateLocked(app, mProcessStats.getMemFactorLocked(), now);
- } else {
- app.procStateChanged = true;
- }
- } else if (app.reportedInteraction && (nowElapsed - app.getInteractionEventTime())
- > mConstants.USAGE_STATS_INTERACTION_INTERVAL) {
- // For apps that sit around for a long time in the interactive state, we need
- // to report this at least once a day so they don't go idle.
- maybeUpdateUsageStatsLocked(app, nowElapsed);
- }
-
- if (changes != 0) {
- if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS,
- "Changes in " + app + ": " + changes);
- int i = mPendingProcessChanges.size()-1;
- ProcessChangeItem item = null;
- while (i >= 0) {
- item = mPendingProcessChanges.get(i);
- if (item.pid == app.pid) {
- if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS,
- "Re-using existing item: " + item);
- break;
- }
- i--;
- }
- if (i < 0) {
- // No existing item in pending changes; need a new one.
- final int NA = mAvailProcessChanges.size();
- if (NA > 0) {
- item = mAvailProcessChanges.remove(NA-1);
- if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS,
- "Retrieving available item: " + item);
- } else {
- item = new ProcessChangeItem();
- if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS,
- "Allocating new item: " + item);
- }
- item.changes = 0;
- item.pid = app.pid;
- item.uid = app.info.uid;
- if (mPendingProcessChanges.size() == 0) {
- if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS,
- "*** Enqueueing dispatch processes changed!");
- mUiHandler.obtainMessage(DISPATCH_PROCESSES_CHANGED_UI_MSG).sendToTarget();
- }
- mPendingProcessChanges.add(item);
- }
- item.changes |= changes;
- item.foregroundActivities = app.repForegroundActivities;
- if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS,
- "Item " + Integer.toHexString(System.identityHashCode(item))
- + " " + app.toShortString() + ": changes=" + item.changes
- + " foreground=" + item.foregroundActivities
- + " type=" + app.adjType + " source=" + app.adjSource
- + " target=" + app.adjTarget);
- }
-
- return success;
- }
-
private boolean isEphemeralLocked(int uid) {
String packages[] = mContext.getPackageManager().getPackagesForUid(uid);
if (packages == null || packages.length != 1) { // Ephemeral apps cannot share uid
@@ -17410,78 +15999,13 @@
}
}
- private void maybeUpdateUsageStatsLocked(ProcessRecord app, long nowElapsed) {
- if (DEBUG_USAGE_STATS) {
- Slog.d(TAG, "Checking proc [" + Arrays.toString(app.getPackageList())
- + "] state changes: old = " + app.setProcState + ", new = "
- + app.getCurProcState());
- }
- if (mUsageStatsService == null) {
- return;
- }
- boolean isInteraction;
- // To avoid some abuse patterns, we are going to be careful about what we consider
- // to be an app interaction. Being the top activity doesn't count while the display
- // is sleeping, nor do short foreground services.
- if (app.getCurProcState() <= ActivityManager.PROCESS_STATE_TOP) {
- isInteraction = true;
- app.setFgInteractionTime(0);
- } else if (app.getCurProcState() <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
- if (app.getFgInteractionTime() == 0) {
- app.setFgInteractionTime(nowElapsed);
- isInteraction = false;
- } else {
- isInteraction = nowElapsed > app.getFgInteractionTime()
- + mConstants.SERVICE_USAGE_INTERACTION_TIME;
- }
- } else {
- isInteraction =
- app.getCurProcState() <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
- app.setFgInteractionTime(0);
- }
- if (isInteraction
- && (!app.reportedInteraction || (nowElapsed - app.getInteractionEventTime())
- > mConstants.USAGE_STATS_INTERACTION_INTERVAL)) {
- app.setInteractionEventTime(nowElapsed);
- String[] packages = app.getPackageList();
- if (packages != null) {
- for (int i = 0; i < packages.length; i++) {
- mUsageStatsService.reportEvent(packages[i], app.userId,
- UsageEvents.Event.SYSTEM_INTERACTION);
- }
- }
- }
- app.reportedInteraction = isInteraction;
- if (!isInteraction) {
- app.setInteractionEventTime(0);
- }
- }
-
- private void maybeUpdateLastTopTime(ProcessRecord app, long nowUptime) {
- if (app.setProcState <= ActivityManager.PROCESS_STATE_TOP
- && app.getCurProcState() > ActivityManager.PROCESS_STATE_TOP) {
- app.lastTopTime = nowUptime;
- }
- }
-
- private final void setProcessTrackerStateLocked(ProcessRecord proc, int memFactor, long now) {
+ final void setProcessTrackerStateLocked(ProcessRecord proc, int memFactor, long now) {
if (proc.thread != null && proc.baseProcessTracker != null) {
proc.baseProcessTracker.setState(
proc.getReportedProcState(), memFactor, now, proc.pkgList.mPkgList);
}
}
- private final boolean updateOomAdjLocked(ProcessRecord app, int cachedAdj,
- ProcessRecord TOP_APP, boolean doingAll, long now) {
- if (app.thread == null) {
- return false;
- }
-
- computeOomAdjLocked(app, cachedAdj, TOP_APP, doingAll, now, false);
-
- return applyOomAdjLocked(app, doingAll, now, SystemClock.elapsedRealtime());
- }
-
@GuardedBy("this")
final void updateProcessForegroundLocked(ProcessRecord proc, boolean isForeground,
boolean oomAdj) {
@@ -17564,29 +16088,10 @@
*/
@GuardedBy("this")
final boolean updateOomAdjLocked(ProcessRecord app, boolean oomAdjAll) {
- final ProcessRecord TOP_APP = getTopAppLocked();
- final boolean wasCached = app.cached;
-
- mAdjSeq++;
-
- // This is the desired cached adjusment we want to tell it to use.
- // If our app is currently cached, we know it, and that is it. Otherwise,
- // we don't know it yet, and it needs to now be cached we will then
- // need to do a complete oom adj.
- final int cachedAdj = app.getCurRawAdj() >= ProcessList.CACHED_APP_MIN_ADJ
- ? app.getCurRawAdj() : ProcessList.UNKNOWN_ADJ;
- boolean success = updateOomAdjLocked(app, cachedAdj, TOP_APP, false,
- SystemClock.uptimeMillis());
- if (oomAdjAll
- && (wasCached != app.cached || app.getCurRawAdj() == ProcessList.UNKNOWN_ADJ)) {
- // Changed to/from cached state, so apps after it in the LRU
- // list may also be changed.
- updateOomAdjLocked();
- }
- return success;
+ return mOomAdjuster.updateOomAdjLocked(app, oomAdjAll);
}
- private static final class ProcStatsRunnable implements Runnable {
+ static final class ProcStatsRunnable implements Runnable {
private final ActivityManagerService mService;
private final ProcessStatsService mProcessStats;
@@ -17777,387 +16282,7 @@
@GuardedBy("this")
final void updateOomAdjLocked() {
- mOomAdjProfiler.oomAdjStarted();
- final ProcessRecord TOP_APP = getTopAppLocked();
- final long now = SystemClock.uptimeMillis();
- final long nowElapsed = SystemClock.elapsedRealtime();
- final long oldTime = now - ProcessList.MAX_EMPTY_TIME;
- final int N = mProcessList.getLruSizeLocked();
-
- // Reset state in all uid records.
- for (int i=mActiveUids.size()-1; i>=0; i--) {
- final UidRecord uidRec = mActiveUids.valueAt(i);
- if (false && DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
- "Starting update of " + uidRec);
- uidRec.reset();
- }
-
- if (mAtmInternal != null) {
- mAtmInternal.rankTaskLayersIfNeeded();
- }
-
- mAdjSeq++;
- mNewNumServiceProcs = 0;
- mNewNumAServiceProcs = 0;
-
- final int emptyProcessLimit = mConstants.CUR_MAX_EMPTY_PROCESSES;
- final int cachedProcessLimit = mConstants.CUR_MAX_CACHED_PROCESSES - emptyProcessLimit;
-
- // Let's determine how many processes we have running vs.
- // how many slots we have for background processes; we may want
- // to put multiple processes in a slot of there are enough of
- // them.
- final int numSlots = (ProcessList.CACHED_APP_MAX_ADJ
- - ProcessList.CACHED_APP_MIN_ADJ + 1) / 2
- / ProcessList.CACHED_APP_IMPORTANCE_LEVELS;
- int numEmptyProcs = N - mNumNonCachedProcs - mNumCachedHiddenProcs;
- if (numEmptyProcs > cachedProcessLimit) {
- // If there are more empty processes than our limit on cached
- // processes, then use the cached process limit for the factor.
- // This ensures that the really old empty processes get pushed
- // down to the bottom, so if we are running low on memory we will
- // have a better chance at keeping around more cached processes
- // instead of a gazillion empty processes.
- numEmptyProcs = cachedProcessLimit;
- }
- int emptyFactor = (numEmptyProcs + numSlots - 1) / numSlots;
- if (emptyFactor < 1) emptyFactor = 1;
- int cachedFactor = (mNumCachedHiddenProcs > 0 ? (mNumCachedHiddenProcs + numSlots - 1) : 1)
- / numSlots;
- if (cachedFactor < 1) cachedFactor = 1;
- int stepCached = -1;
- int stepEmpty = -1;
- int numCached = 0;
- int numCachedExtraGroup = 0;
- int numEmpty = 0;
- int numTrimming = 0;
- int lastCachedGroup = 0;
- int lastCachedGroupImportance = 0;
- int lastCachedGroupUid = 0;
-
- mNumNonCachedProcs = 0;
- mNumCachedHiddenProcs = 0;
-
- // First update the OOM adjustment for each of the
- // application processes based on their current state.
- int curCachedAdj = ProcessList.CACHED_APP_MIN_ADJ;
- int nextCachedAdj = curCachedAdj + (ProcessList.CACHED_APP_IMPORTANCE_LEVELS * 2);
- int curCachedImpAdj = 0;
- int curEmptyAdj = ProcessList.CACHED_APP_MIN_ADJ + ProcessList.CACHED_APP_IMPORTANCE_LEVELS;
- int nextEmptyAdj = curEmptyAdj + (ProcessList.CACHED_APP_IMPORTANCE_LEVELS * 2);
-
- boolean retryCycles = false;
-
- // need to reset cycle state before calling computeOomAdjLocked because of service connections
- for (int i=N-1; i>=0; i--) {
- ProcessRecord app = mProcessList.mLruProcesses.get(i);
- app.containsCycle = false;
- app.setCurRawProcState(PROCESS_STATE_CACHED_EMPTY);
- app.setCurRawAdj(ProcessList.UNKNOWN_ADJ);
- }
- for (int i=N-1; i>=0; i--) {
- ProcessRecord app = mProcessList.mLruProcesses.get(i);
- if (!app.killedByAm && app.thread != null) {
- app.procStateChanged = false;
- computeOomAdjLocked(app, ProcessList.UNKNOWN_ADJ, TOP_APP, true, now, false);
-
- // if any app encountered a cycle, we need to perform an additional loop later
- retryCycles |= app.containsCycle;
-
- // If we haven't yet assigned the final cached adj
- // to the process, do that now.
- if (app.curAdj >= ProcessList.UNKNOWN_ADJ) {
- switch (app.getCurProcState()) {
- case PROCESS_STATE_CACHED_ACTIVITY:
- case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT:
- case ActivityManager.PROCESS_STATE_CACHED_RECENT:
- // Figure out the next cached level, taking into account groups.
- boolean inGroup = false;
- if (app.connectionGroup != 0) {
- if (lastCachedGroupUid == app.uid
- && lastCachedGroup == app.connectionGroup) {
- // This is in the same group as the last process, just tweak
- // adjustment by importance.
- if (app.connectionImportance > lastCachedGroupImportance) {
- lastCachedGroupImportance = app.connectionImportance;
- if (curCachedAdj < nextCachedAdj
- && curCachedAdj < ProcessList.CACHED_APP_MAX_ADJ) {
- curCachedImpAdj++;
- }
- }
- inGroup = true;
- } else {
- lastCachedGroupUid = app.uid;
- lastCachedGroup = app.connectionGroup;
- lastCachedGroupImportance = app.connectionImportance;
- }
- }
- if (!inGroup && curCachedAdj != nextCachedAdj) {
- stepCached++;
- curCachedImpAdj = 0;
- if (stepCached >= cachedFactor) {
- stepCached = 0;
- curCachedAdj = nextCachedAdj;
- nextCachedAdj += ProcessList.CACHED_APP_IMPORTANCE_LEVELS * 2;
- if (nextCachedAdj > ProcessList.CACHED_APP_MAX_ADJ) {
- nextCachedAdj = ProcessList.CACHED_APP_MAX_ADJ;
- }
- }
- }
- // This process is a cached process holding activities...
- // assign it the next cached value for that type, and then
- // step that cached level.
- app.setCurRawAdj(curCachedAdj + curCachedImpAdj);
- app.curAdj = app.modifyRawOomAdj(curCachedAdj + curCachedImpAdj);
- if (DEBUG_LRU && false) Slog.d(TAG_LRU, "Assigning activity LRU #" + i
- + " adj: " + app.curAdj + " (curCachedAdj=" + curCachedAdj
- + " curCachedImpAdj=" + curCachedImpAdj + ")");
- break;
- default:
- // Figure out the next cached level.
- if (curEmptyAdj != nextEmptyAdj) {
- stepEmpty++;
- if (stepEmpty >= emptyFactor) {
- stepEmpty = 0;
- curEmptyAdj = nextEmptyAdj;
- nextEmptyAdj += ProcessList.CACHED_APP_IMPORTANCE_LEVELS * 2;
- if (nextEmptyAdj > ProcessList.CACHED_APP_MAX_ADJ) {
- nextEmptyAdj = ProcessList.CACHED_APP_MAX_ADJ;
- }
- }
- }
- // For everything else, assign next empty cached process
- // level and bump that up. Note that this means that
- // long-running services that have dropped down to the
- // cached level will be treated as empty (since their process
- // state is still as a service), which is what we want.
- app.setCurRawAdj(curEmptyAdj);
- app.curAdj = app.modifyRawOomAdj(curEmptyAdj);
- if (DEBUG_LRU && false) Slog.d(TAG_LRU, "Assigning empty LRU #" + i
- + " adj: " + app.curAdj + " (curEmptyAdj=" + curEmptyAdj
- + ")");
- break;
- }
- }
- }
- }
-
- // Cycle strategy:
- // - Retry computing any process that has encountered a cycle.
- // - Continue retrying until no process was promoted.
- // - Iterate from least important to most important.
- int cycleCount = 0;
- while (retryCycles && cycleCount < 10) {
- cycleCount++;
- retryCycles = false;
-
- for (int i=0; i<N; i++) {
- ProcessRecord app = mProcessList.mLruProcesses.get(i);
- if (!app.killedByAm && app.thread != null && app.containsCycle == true) {
- app.adjSeq--;
- app.completedAdjSeq--;
- }
- }
-
- for (int i=0; i<N; i++) {
- ProcessRecord app = mProcessList.mLruProcesses.get(i);
- if (!app.killedByAm && app.thread != null && app.containsCycle == true) {
- if (computeOomAdjLocked(app, ProcessList.UNKNOWN_ADJ, TOP_APP, true, now,
- true)) {
- retryCycles = true;
- }
- }
- }
- }
-
- lastCachedGroup = lastCachedGroupUid = 0;
-
- for (int i=N-1; i>=0; i--) {
- ProcessRecord app = mProcessList.mLruProcesses.get(i);
- if (!app.killedByAm && app.thread != null) {
- applyOomAdjLocked(app, true, now, nowElapsed);
-
- // Count the number of process types.
- switch (app.getCurProcState()) {
- case PROCESS_STATE_CACHED_ACTIVITY:
- case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT:
- mNumCachedHiddenProcs++;
- numCached++;
- if (app.connectionGroup != 0) {
- if (lastCachedGroupUid == app.uid
- && lastCachedGroup == app.connectionGroup) {
- // If this process is the next in the same group, we don't
- // want it to count against our limit of the number of cached
- // processes, so bump up the group count to account for it.
- numCachedExtraGroup++;
- } else {
- lastCachedGroupUid = app.uid;
- lastCachedGroup = app.connectionGroup;
- }
- } else {
- lastCachedGroupUid = lastCachedGroup = 0;
- }
- if ((numCached - numCachedExtraGroup) > cachedProcessLimit) {
- app.kill("cached #" + numCached, true);
- }
- break;
- case ActivityManager.PROCESS_STATE_CACHED_EMPTY:
- if (numEmpty > mConstants.CUR_TRIM_EMPTY_PROCESSES
- && app.lastActivityTime < oldTime) {
- app.kill("empty for "
- + ((oldTime + ProcessList.MAX_EMPTY_TIME - app.lastActivityTime)
- / 1000) + "s", true);
- } else {
- numEmpty++;
- if (numEmpty > emptyProcessLimit) {
- app.kill("empty #" + numEmpty, true);
- }
- }
- break;
- default:
- mNumNonCachedProcs++;
- break;
- }
-
- if (app.isolated && app.services.size() <= 0 && app.isolatedEntryPoint == null) {
- // If this is an isolated process, there are no services
- // running in it, and it's not a special process with a
- // custom entry point, then the process is no longer
- // needed. We agressively kill these because we can by
- // definition not re-use the same process again, and it is
- // good to avoid having whatever code was running in them
- // left sitting around after no longer needed.
- app.kill("isolated not needed", true);
- } else {
- // Keeping this process, update its uid.
- final UidRecord uidRec = app.uidRecord;
- if (uidRec != null) {
- uidRec.ephemeral = app.info.isInstantApp();
- if (uidRec.getCurProcState() > app.getCurProcState()) {
- uidRec.setCurProcState(app.getCurProcState());
- }
- if (app.hasForegroundServices()) {
- uidRec.foregroundServices = true;
- }
- }
- }
-
- if (app.getCurProcState() >= ActivityManager.PROCESS_STATE_HOME
- && !app.killedByAm) {
- numTrimming++;
- }
- }
- }
-
- incrementProcStateSeqAndNotifyAppsLocked();
-
- mNumServiceProcs = mNewNumServiceProcs;
-
- boolean allChanged = updateLowMemStateLocked(numCached, numEmpty, numTrimming);
-
- if (mAlwaysFinishActivities) {
- // Need to do this on its own message because the stack may not
- // be in a consistent state at this point.
- mAtmInternal.scheduleDestroyAllActivities("always-finish");
- }
-
- if (allChanged) {
- requestPssAllProcsLocked(now, false, mProcessStats.isMemFactorLowered());
- }
-
- ArrayList<UidRecord> becameIdle = null;
-
- // Update from any uid changes.
- if (mLocalPowerManager != null) {
- mLocalPowerManager.startUidChanges();
- }
- for (int i=mActiveUids.size()-1; i>=0; i--) {
- final UidRecord uidRec = mActiveUids.valueAt(i);
- int uidChange = UidRecord.CHANGE_PROCSTATE;
- if (uidRec.getCurProcState() != PROCESS_STATE_NONEXISTENT
- && (uidRec.setProcState != uidRec.getCurProcState()
- || uidRec.setWhitelist != uidRec.curWhitelist)) {
- if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS, "Changes in " + uidRec
- + ": proc state from " + uidRec.setProcState + " to "
- + uidRec.getCurProcState() + ", whitelist from " + uidRec.setWhitelist
- + " to " + uidRec.curWhitelist);
- if (ActivityManager.isProcStateBackground(uidRec.getCurProcState())
- && !uidRec.curWhitelist) {
- // UID is now in the background (and not on the temp whitelist). Was it
- // previously in the foreground (or on the temp whitelist)?
- if (!ActivityManager.isProcStateBackground(uidRec.setProcState)
- || uidRec.setWhitelist) {
- uidRec.lastBackgroundTime = nowElapsed;
- if (!mHandler.hasMessages(IDLE_UIDS_MSG)) {
- // Note: the background settle time is in elapsed realtime, while
- // the handler time base is uptime. All this means is that we may
- // stop background uids later than we had intended, but that only
- // happens because the device was sleeping so we are okay anyway.
- mHandler.sendEmptyMessageDelayed(IDLE_UIDS_MSG,
- mConstants.BACKGROUND_SETTLE_TIME);
- }
- }
- if (uidRec.idle && !uidRec.setIdle) {
- uidChange = UidRecord.CHANGE_IDLE;
- if (becameIdle == null) {
- becameIdle = new ArrayList<>();
- }
- becameIdle.add(uidRec);
- }
- } else {
- if (uidRec.idle) {
- uidChange = UidRecord.CHANGE_ACTIVE;
- EventLogTags.writeAmUidActive(uidRec.uid);
- uidRec.idle = false;
- }
- uidRec.lastBackgroundTime = 0;
- }
- final boolean wasCached = uidRec.setProcState
- > ActivityManager.PROCESS_STATE_RECEIVER;
- final boolean isCached = uidRec.getCurProcState()
- > ActivityManager.PROCESS_STATE_RECEIVER;
- if (wasCached != isCached || uidRec.setProcState == PROCESS_STATE_NONEXISTENT) {
- uidChange |= isCached ? UidRecord.CHANGE_CACHED : UidRecord.CHANGE_UNCACHED;
- }
- uidRec.setProcState = uidRec.getCurProcState();
- uidRec.setWhitelist = uidRec.curWhitelist;
- uidRec.setIdle = uidRec.idle;
- enqueueUidChangeLocked(uidRec, -1, uidChange);
- noteUidProcessState(uidRec.uid, uidRec.getCurProcState());
- if (uidRec.foregroundServices) {
- mServices.foregroundServiceProcStateChangedLocked(uidRec);
- }
- }
- }
- if (mLocalPowerManager != null) {
- mLocalPowerManager.finishUidChanges();
- }
-
- if (becameIdle != null) {
- // If we have any new uids that became idle this time, we need to make sure
- // they aren't left with running services.
- for (int i = becameIdle.size() - 1; i >= 0; i--) {
- mServices.stopInBackgroundLocked(becameIdle.get(i).uid);
- }
- }
-
- if (mProcessStats.shouldWriteNowLocked(now)) {
- mHandler.post(new ProcStatsRunnable(ActivityManagerService.this, mProcessStats));
- }
-
- // Run this after making sure all procstates are updated.
- mProcessStats.updateTrackingAssociationsLocked(mAdjSeq, now);
-
- if (DEBUG_OOM_ADJ) {
- final long duration = SystemClock.uptimeMillis() - now;
- if (false) {
- Slog.d(TAG_OOM_ADJ, "Did OOM ADJ in " + duration + "ms",
- new RuntimeException("here").fillInStackTrace());
- } else {
- Slog.d(TAG_OOM_ADJ, "Did OOM ADJ in " + duration + "ms");
- }
- }
- mOomAdjProfiler.oomAdjEnded();
+ mOomAdjuster.updateOomAdjLocked();
}
@Override
@@ -18192,9 +16317,9 @@
mLocalPowerManager.startUidChanges();
}
final int appId = UserHandle.getAppId(pkgUid);
- final int N = mActiveUids.size();
- for (int i=N-1; i>=0; i--) {
- final UidRecord uidRec = mActiveUids.valueAt(i);
+ final int N = mProcessList.mActiveUids.size();
+ for (int i = N - 1; i >= 0; i--) {
+ final UidRecord uidRec = mProcessList.mActiveUids.valueAt(i);
final long bgTime = uidRec.lastBackgroundTime;
if (bgTime > 0 && !uidRec.idle) {
if (UserHandle.getAppId(uidRec.uid) == appId) {
@@ -18219,49 +16344,17 @@
}
}
+ /** Make the currently active UIDs idle after a certain grace period. */
final void idleUids() {
synchronized (this) {
- final int N = mActiveUids.size();
- if (N <= 0) {
- return;
- }
- final long nowElapsed = SystemClock.elapsedRealtime();
- final long maxBgTime = nowElapsed - mConstants.BACKGROUND_SETTLE_TIME;
- long nextTime = 0;
- if (mLocalPowerManager != null) {
- mLocalPowerManager.startUidChanges();
- }
- for (int i=N-1; i>=0; i--) {
- final UidRecord uidRec = mActiveUids.valueAt(i);
- final long bgTime = uidRec.lastBackgroundTime;
- if (bgTime > 0 && !uidRec.idle) {
- if (bgTime <= maxBgTime) {
- EventLogTags.writeAmUidIdle(uidRec.uid);
- uidRec.idle = true;
- uidRec.setIdle = true;
- doStopUidLocked(uidRec.uid, uidRec);
- } else {
- if (nextTime == 0 || nextTime > bgTime) {
- nextTime = bgTime;
- }
- }
- }
- }
- if (mLocalPowerManager != null) {
- mLocalPowerManager.finishUidChanges();
- }
- if (nextTime > 0) {
- mHandler.removeMessages(IDLE_UIDS_MSG);
- mHandler.sendEmptyMessageDelayed(IDLE_UIDS_MSG,
- nextTime + mConstants.BACKGROUND_SETTLE_TIME - nowElapsed);
- }
+ mOomAdjuster.idleUidsLocked();
}
}
/**
* Checks if any uid is coming from background to foreground or vice versa and if so, increments
* the {@link UidRecord#curProcStateSeq} corresponding to that uid using global seq counter
- * {@link #mProcStateSeqCounter} and notifies the app if it needs to block.
+ * {@link ProcessList#mProcStateSeqCounter} and notifies the app if it needs to block.
*/
@VisibleForTesting
@GuardedBy("this")
@@ -18271,8 +16364,8 @@
}
// Used for identifying which uids need to block for network.
ArrayList<Integer> blockingUids = null;
- for (int i = mActiveUids.size() - 1; i >= 0; --i) {
- final UidRecord uidRec = mActiveUids.valueAt(i);
+ for (int i = mProcessList.mActiveUids.size() - 1; i >= 0; --i) {
+ final UidRecord uidRec = mProcessList.mActiveUids.valueAt(i);
// If the network is not restricted for uid, then nothing to do here.
if (!mInjector.isNetworkRestrictedForUid(uidRec.uid)) {
continue;
@@ -18320,13 +16413,15 @@
continue;
}
if (!app.killedByAm && app.thread != null) {
- final UidRecord uidRec = mActiveUids.get(app.uid);
+ final UidRecord uidRec = mProcessList.getUidRecordLocked(app.uid);
try {
if (DEBUG_NETWORK) {
Slog.d(TAG_NETWORK, "Informing app thread that it needs to block: "
+ uidRec);
}
- app.thread.setNetworkBlockSeq(uidRec.curProcStateSeq);
+ if (uidRec != null) {
+ app.thread.setNetworkBlockSeq(uidRec.curProcStateSeq);
+ }
} catch (RemoteException ignored) {
}
}
@@ -18367,7 +16462,7 @@
final void runInBackgroundDisabled(int uid) {
synchronized (this) {
- UidRecord uidRec = mActiveUids.get(uid);
+ UidRecord uidRec = mProcessList.getUidRecordLocked(uid);
if (uidRec != null) {
// This uid is actually running... should it be considered background now?
if (uidRec.idle) {
@@ -18380,24 +16475,7 @@
}
}
- /**
- * Call {@link #doStopUidLocked} (which will also stop background services) for all idle UIDs.
- */
- void doStopUidForIdleUidsLocked() {
- final int size = mActiveUids.size();
- for (int i = 0; i < size; i++) {
- final int uid = mActiveUids.keyAt(i);
- if (UserHandle.isCore(uid)) {
- continue;
- }
- final UidRecord uidRec = mActiveUids.valueAt(i);
- if (!uidRec.idle) {
- continue;
- }
- doStopUidLocked(uidRec.uid, uidRec);
- }
- }
-
+ @GuardedBy("this")
final void doStopUidLocked(int uid, final UidRecord uidRec) {
mServices.stopInBackgroundLocked(uid);
enqueueUidChangeLocked(uidRec, uid, UidRecord.CHANGE_IDLE);
@@ -18481,27 +16559,12 @@
@GuardedBy("this")
final void setAppIdTempWhitelistStateLocked(int appId, boolean onWhitelist) {
- boolean changed = false;
- for (int i=mActiveUids.size()-1; i>=0; i--) {
- final UidRecord uidRec = mActiveUids.valueAt(i);
- if (UserHandle.getAppId(uidRec.uid) == appId && uidRec.curWhitelist != onWhitelist) {
- uidRec.curWhitelist = onWhitelist;
- changed = true;
- }
- }
- if (changed) {
- updateOomAdjLocked();
- }
+ mOomAdjuster.setAppIdTempWhitelistStateLocked(appId, onWhitelist);
}
@GuardedBy("this")
final void setUidTempWhitelistStateLocked(int uid, boolean onWhitelist) {
- boolean changed = false;
- final UidRecord uidRec = mActiveUids.get(uid);
- if (uidRec != null && uidRec.curWhitelist != onWhitelist) {
- uidRec.curWhitelist = onWhitelist;
- updateOomAdjLocked();
- }
+ mOomAdjuster.setUidTempWhitelistStateLocked(uid, onWhitelist);
}
final void trimApplications() {
@@ -19164,7 +17227,7 @@
}
UidRecord record;
synchronized (ActivityManagerService.this) {
- record = mActiveUids.get(uid);
+ record = mProcessList.getUidRecordLocked(uid);
if (record == null) {
if (DEBUG_NETWORK) {
Slog.d(TAG_NETWORK, "No active uidRecord for uid: " + uid
@@ -19783,7 +17846,7 @@
}
UidRecord record;
synchronized (this) {
- record = mActiveUids.get(callingUid);
+ record = mProcessList.getUidRecordLocked(callingUid);
if (record == null) {
return;
}
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
new file mode 100644
index 0000000..cb4cac9
--- /dev/null
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -0,0 +1,2100 @@
+/*
+ * Copyright (C) 2019 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.am;
+
+import static android.app.ActivityManager.PROCESS_STATE_CACHED_ACTIVITY;
+import static android.app.ActivityManager.PROCESS_STATE_CACHED_EMPTY;
+import static android.app.ActivityManager.PROCESS_STATE_LAST_ACTIVITY;
+import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
+import static android.os.Process.SCHED_OTHER;
+import static android.os.Process.THREAD_GROUP_BG_NONINTERACTIVE;
+import static android.os.Process.THREAD_GROUP_DEFAULT;
+import static android.os.Process.THREAD_GROUP_RESTRICTED;
+import static android.os.Process.THREAD_GROUP_TOP_APP;
+import static android.os.Process.setProcessGroup;
+import static android.os.Process.setThreadPriority;
+import static android.os.Process.setThreadScheduler;
+
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BACKUP;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LRU;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_OOM_ADJ_REASON;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PROCESS_OBSERVERS;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_PSS;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_UID_OBSERVERS;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_USAGE_STATS;
+import static com.android.server.am.ActivityManagerService.DISPATCH_OOM_ADJ_OBSERVER_MSG;
+import static com.android.server.am.ActivityManagerService.DISPATCH_PROCESSES_CHANGED_UI_MSG;
+import static com.android.server.am.ActivityManagerService.IDLE_UIDS_MSG;
+import static com.android.server.am.ActivityManagerService.TAG_BACKUP;
+import static com.android.server.am.ActivityManagerService.TAG_LRU;
+import static com.android.server.am.ActivityManagerService.TAG_OOM_ADJ;
+import static com.android.server.am.ActivityManagerService.TAG_PROCESS_OBSERVERS;
+import static com.android.server.am.ActivityManagerService.TAG_PSS;
+import static com.android.server.am.ActivityManagerService.TAG_UID_OBSERVERS;
+import static com.android.server.am.ActivityManagerService.TOP_APP_PRIORITY_BOOST;
+import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH;
+
+import android.app.ActivityManager;
+import android.app.usage.UsageEvents;
+import android.content.Context;
+import android.os.Binder;
+import android.os.Debug;
+import android.os.PowerManagerInternal;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.util.ArraySet;
+import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.app.procstats.ProcessStats;
+import com.android.server.LocalServices;
+import com.android.server.wm.ActivityServiceConnectionsHolder;
+import com.android.server.wm.WindowProcessController;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * All of the code required to compute proc states and oom_adj values.
+ */
+public final class OomAdjuster {
+ private static final String TAG = "OomAdjuster";
+
+ /**
+ * For some direct access we need to power manager.
+ */
+ PowerManagerInternal mLocalPowerManager;
+
+ /**
+ * Service for compacting background apps.
+ */
+ AppCompactor mAppCompact;
+
+ ActivityManagerConstants mConstants;
+
+ final long[] mTmpLong = new long[3];
+
+ /**
+ * Current sequence id for oom_adj computation traversal.
+ */
+ int mAdjSeq = 0;
+
+ /**
+ * Keep track of the number of service processes we last found, to
+ * determine on the next iteration which should be B services.
+ */
+ int mNumServiceProcs = 0;
+ int mNewNumAServiceProcs = 0;
+ int mNewNumServiceProcs = 0;
+
+ /**
+ * Keep track of the non-cached/empty process we last found, to help
+ * determine how to distribute cached/empty processes next time.
+ */
+ int mNumNonCachedProcs = 0;
+
+ /**
+ * Keep track of the number of cached hidden procs, to balance oom adj
+ * distribution between those and empty procs.
+ */
+ int mNumCachedHiddenProcs = 0;
+
+ /** Track all uids that have actively running processes. */
+ ActiveUids mActiveUids;
+
+ private final ArraySet<BroadcastQueue> mTmpBroadcastQueue = new ArraySet();
+
+ private final ActivityManagerService mService;
+ private final ProcessList mProcessList;
+
+ OomAdjuster(ActivityManagerService service, ProcessList processList, ActiveUids activeUids) {
+ mService = service;
+ mProcessList = processList;
+ mActiveUids = activeUids;
+
+ mLocalPowerManager = LocalServices.getService(PowerManagerInternal.class);
+ mAppCompact = new AppCompactor(mService);
+ mConstants = mService.mConstants;
+ }
+
+ /**
+ * Update OomAdj for a specific process.
+ * @param app The process to update
+ * @param oomAdjAll If it's ok to call updateOomAdjLocked() for all running apps
+ * if necessary, or skip.
+ * @return whether updateOomAdjLocked(app) was successful.
+ */
+ @GuardedBy("mService")
+ final boolean updateOomAdjLocked(ProcessRecord app, boolean oomAdjAll) {
+ final ProcessRecord TOP_APP = mService.getTopAppLocked();
+ final boolean wasCached = app.cached;
+
+ mAdjSeq++;
+
+ // This is the desired cached adjusment we want to tell it to use.
+ // If our app is currently cached, we know it, and that is it. Otherwise,
+ // we don't know it yet, and it needs to now be cached we will then
+ // need to do a complete oom adj.
+ final int cachedAdj = app.getCurRawAdj() >= ProcessList.CACHED_APP_MIN_ADJ
+ ? app.getCurRawAdj() : ProcessList.UNKNOWN_ADJ;
+ boolean success = updateOomAdjLocked(app, cachedAdj, TOP_APP, false,
+ SystemClock.uptimeMillis());
+ if (oomAdjAll
+ && (wasCached != app.cached || app.getCurRawAdj() == ProcessList.UNKNOWN_ADJ)) {
+ // Changed to/from cached state, so apps after it in the LRU
+ // list may also be changed.
+ updateOomAdjLocked();
+ }
+ return success;
+ }
+
+ @GuardedBy("mService")
+ private final boolean updateOomAdjLocked(ProcessRecord app, int cachedAdj,
+ ProcessRecord TOP_APP, boolean doingAll, long now) {
+ if (app.thread == null) {
+ return false;
+ }
+
+ computeOomAdjLocked(app, cachedAdj, TOP_APP, doingAll, now, false);
+
+ return applyOomAdjLocked(app, doingAll, now, SystemClock.elapsedRealtime());
+ }
+
+ @GuardedBy("mService")
+ final void updateOomAdjLocked() {
+ mService.mOomAdjProfiler.oomAdjStarted();
+ final ProcessRecord TOP_APP = mService.getTopAppLocked();
+ final long now = SystemClock.uptimeMillis();
+ final long nowElapsed = SystemClock.elapsedRealtime();
+ final long oldTime = now - ProcessList.MAX_EMPTY_TIME;
+ final int N = mProcessList.getLruSizeLocked();
+
+ // Reset state in all uid records.
+ for (int i = mActiveUids.size() - 1; i >= 0; i--) {
+ final UidRecord uidRec = mActiveUids.valueAt(i);
+ if (false && DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS,
+ "Starting update of " + uidRec);
+ uidRec.reset();
+ }
+
+ if (mService.mAtmInternal != null) {
+ mService.mAtmInternal.rankTaskLayersIfNeeded();
+ }
+
+ mAdjSeq++;
+ mNewNumServiceProcs = 0;
+ mNewNumAServiceProcs = 0;
+
+ final int emptyProcessLimit = mConstants.CUR_MAX_EMPTY_PROCESSES;
+ final int cachedProcessLimit = mConstants.CUR_MAX_CACHED_PROCESSES
+ - emptyProcessLimit;
+
+ // Let's determine how many processes we have running vs.
+ // how many slots we have for background processes; we may want
+ // to put multiple processes in a slot of there are enough of
+ // them.
+ final int numSlots = (ProcessList.CACHED_APP_MAX_ADJ
+ - ProcessList.CACHED_APP_MIN_ADJ + 1) / 2
+ / ProcessList.CACHED_APP_IMPORTANCE_LEVELS;
+ int numEmptyProcs = N - mNumNonCachedProcs - mNumCachedHiddenProcs;
+ if (numEmptyProcs > cachedProcessLimit) {
+ // If there are more empty processes than our limit on cached
+ // processes, then use the cached process limit for the factor.
+ // This ensures that the really old empty processes get pushed
+ // down to the bottom, so if we are running low on memory we will
+ // have a better chance at keeping around more cached processes
+ // instead of a gazillion empty processes.
+ numEmptyProcs = cachedProcessLimit;
+ }
+ int emptyFactor = (numEmptyProcs + numSlots - 1) / numSlots;
+ if (emptyFactor < 1) emptyFactor = 1;
+ int cachedFactor = (mNumCachedHiddenProcs > 0 ? (mNumCachedHiddenProcs + numSlots - 1) : 1)
+ / numSlots;
+ if (cachedFactor < 1) cachedFactor = 1;
+ int stepCached = -1;
+ int stepEmpty = -1;
+ int numCached = 0;
+ int numCachedExtraGroup = 0;
+ int numEmpty = 0;
+ int numTrimming = 0;
+ int lastCachedGroup = 0;
+ int lastCachedGroupImportance = 0;
+ int lastCachedGroupUid = 0;
+
+ mNumNonCachedProcs = 0;
+ mNumCachedHiddenProcs = 0;
+
+ // First update the OOM adjustment for each of the
+ // application processes based on their current state.
+ int curCachedAdj = ProcessList.CACHED_APP_MIN_ADJ;
+ int nextCachedAdj = curCachedAdj + (ProcessList.CACHED_APP_IMPORTANCE_LEVELS * 2);
+ int curCachedImpAdj = 0;
+ int curEmptyAdj = ProcessList.CACHED_APP_MIN_ADJ + ProcessList.CACHED_APP_IMPORTANCE_LEVELS;
+ int nextEmptyAdj = curEmptyAdj + (ProcessList.CACHED_APP_IMPORTANCE_LEVELS * 2);
+
+ boolean retryCycles = false;
+
+ // need to reset cycle state before calling computeOomAdjLocked because of service conns
+ for (int i = N - 1; i >= 0; i--) {
+ ProcessRecord app = mProcessList.mLruProcesses.get(i);
+ app.containsCycle = false;
+ app.setCurRawProcState(PROCESS_STATE_CACHED_EMPTY);
+ app.setCurRawAdj(ProcessList.UNKNOWN_ADJ);
+ }
+ for (int i = N - 1; i >= 0; i--) {
+ ProcessRecord app = mProcessList.mLruProcesses.get(i);
+ if (!app.killedByAm && app.thread != null) {
+ app.procStateChanged = false;
+ computeOomAdjLocked(app, ProcessList.UNKNOWN_ADJ, TOP_APP, true, now, false);
+
+ // if any app encountered a cycle, we need to perform an additional loop later
+ retryCycles |= app.containsCycle;
+
+ // If we haven't yet assigned the final cached adj
+ // to the process, do that now.
+ if (app.curAdj >= ProcessList.UNKNOWN_ADJ) {
+ switch (app.getCurProcState()) {
+ case PROCESS_STATE_CACHED_ACTIVITY:
+ case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT:
+ case ActivityManager.PROCESS_STATE_CACHED_RECENT:
+ // Figure out the next cached level, taking into account groups.
+ boolean inGroup = false;
+ if (app.connectionGroup != 0) {
+ if (lastCachedGroupUid == app.uid
+ && lastCachedGroup == app.connectionGroup) {
+ // This is in the same group as the last process, just tweak
+ // adjustment by importance.
+ if (app.connectionImportance > lastCachedGroupImportance) {
+ lastCachedGroupImportance = app.connectionImportance;
+ if (curCachedAdj < nextCachedAdj
+ && curCachedAdj < ProcessList.CACHED_APP_MAX_ADJ) {
+ curCachedImpAdj++;
+ }
+ }
+ inGroup = true;
+ } else {
+ lastCachedGroupUid = app.uid;
+ lastCachedGroup = app.connectionGroup;
+ lastCachedGroupImportance = app.connectionImportance;
+ }
+ }
+ if (!inGroup && curCachedAdj != nextCachedAdj) {
+ stepCached++;
+ curCachedImpAdj = 0;
+ if (stepCached >= cachedFactor) {
+ stepCached = 0;
+ curCachedAdj = nextCachedAdj;
+ nextCachedAdj += ProcessList.CACHED_APP_IMPORTANCE_LEVELS * 2;
+ if (nextCachedAdj > ProcessList.CACHED_APP_MAX_ADJ) {
+ nextCachedAdj = ProcessList.CACHED_APP_MAX_ADJ;
+ }
+ }
+ }
+ // This process is a cached process holding activities...
+ // assign it the next cached value for that type, and then
+ // step that cached level.
+ app.setCurRawAdj(curCachedAdj + curCachedImpAdj);
+ app.curAdj = app.modifyRawOomAdj(curCachedAdj + curCachedImpAdj);
+ if (DEBUG_LRU && false) Slog.d(TAG_LRU, "Assigning activity LRU #" + i
+ + " adj: " + app.curAdj + " (curCachedAdj=" + curCachedAdj
+ + " curCachedImpAdj=" + curCachedImpAdj + ")");
+ break;
+ default:
+ // Figure out the next cached level.
+ if (curEmptyAdj != nextEmptyAdj) {
+ stepEmpty++;
+ if (stepEmpty >= emptyFactor) {
+ stepEmpty = 0;
+ curEmptyAdj = nextEmptyAdj;
+ nextEmptyAdj += ProcessList.CACHED_APP_IMPORTANCE_LEVELS * 2;
+ if (nextEmptyAdj > ProcessList.CACHED_APP_MAX_ADJ) {
+ nextEmptyAdj = ProcessList.CACHED_APP_MAX_ADJ;
+ }
+ }
+ }
+ // For everything else, assign next empty cached process
+ // level and bump that up. Note that this means that
+ // long-running services that have dropped down to the
+ // cached level will be treated as empty (since their process
+ // state is still as a service), which is what we want.
+ app.setCurRawAdj(curEmptyAdj);
+ app.curAdj = app.modifyRawOomAdj(curEmptyAdj);
+ if (DEBUG_LRU && false) Slog.d(TAG_LRU, "Assigning empty LRU #" + i
+ + " adj: " + app.curAdj + " (curEmptyAdj=" + curEmptyAdj
+ + ")");
+ break;
+ }
+ }
+ }
+ }
+
+ // Cycle strategy:
+ // - Retry computing any process that has encountered a cycle.
+ // - Continue retrying until no process was promoted.
+ // - Iterate from least important to most important.
+ int cycleCount = 0;
+ while (retryCycles && cycleCount < 10) {
+ cycleCount++;
+ retryCycles = false;
+
+ for (int i = 0; i < N; i++) {
+ ProcessRecord app = mProcessList.mLruProcesses.get(i);
+ if (!app.killedByAm && app.thread != null && app.containsCycle == true) {
+ app.adjSeq--;
+ app.completedAdjSeq--;
+ }
+ }
+
+ for (int i = 0; i < N; i++) {
+ ProcessRecord app = mProcessList.mLruProcesses.get(i);
+ if (!app.killedByAm && app.thread != null && app.containsCycle == true) {
+ if (computeOomAdjLocked(app, ProcessList.UNKNOWN_ADJ, TOP_APP, true, now,
+ true)) {
+ retryCycles = true;
+ }
+ }
+ }
+ }
+
+ lastCachedGroup = lastCachedGroupUid = 0;
+
+ for (int i = N - 1; i >= 0; i--) {
+ ProcessRecord app = mProcessList.mLruProcesses.get(i);
+ if (!app.killedByAm && app.thread != null) {
+ applyOomAdjLocked(app, true, now, nowElapsed);
+
+ // Count the number of process types.
+ switch (app.getCurProcState()) {
+ case PROCESS_STATE_CACHED_ACTIVITY:
+ case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT:
+ mNumCachedHiddenProcs++;
+ numCached++;
+ if (app.connectionGroup != 0) {
+ if (lastCachedGroupUid == app.uid
+ && lastCachedGroup == app.connectionGroup) {
+ // If this process is the next in the same group, we don't
+ // want it to count against our limit of the number of cached
+ // processes, so bump up the group count to account for it.
+ numCachedExtraGroup++;
+ } else {
+ lastCachedGroupUid = app.uid;
+ lastCachedGroup = app.connectionGroup;
+ }
+ } else {
+ lastCachedGroupUid = lastCachedGroup = 0;
+ }
+ if ((numCached - numCachedExtraGroup) > cachedProcessLimit) {
+ app.kill("cached #" + numCached, true);
+ }
+ break;
+ case ActivityManager.PROCESS_STATE_CACHED_EMPTY:
+ if (numEmpty > mConstants.CUR_TRIM_EMPTY_PROCESSES
+ && app.lastActivityTime < oldTime) {
+ app.kill("empty for "
+ + ((oldTime + ProcessList.MAX_EMPTY_TIME - app.lastActivityTime)
+ / 1000) + "s", true);
+ } else {
+ numEmpty++;
+ if (numEmpty > emptyProcessLimit) {
+ app.kill("empty #" + numEmpty, true);
+ }
+ }
+ break;
+ default:
+ mNumNonCachedProcs++;
+ break;
+ }
+
+ if (app.isolated && app.services.size() <= 0 && app.isolatedEntryPoint == null) {
+ // If this is an isolated process, there are no services
+ // running in it, and it's not a special process with a
+ // custom entry point, then the process is no longer
+ // needed. We agressively kill these because we can by
+ // definition not re-use the same process again, and it is
+ // good to avoid having whatever code was running in them
+ // left sitting around after no longer needed.
+ app.kill("isolated not needed", true);
+ } else {
+ // Keeping this process, update its uid.
+ final UidRecord uidRec = app.uidRecord;
+ if (uidRec != null) {
+ uidRec.ephemeral = app.info.isInstantApp();
+ if (uidRec.getCurProcState() > app.getCurProcState()) {
+ uidRec.setCurProcState(app.getCurProcState());
+ }
+ if (app.hasForegroundServices()) {
+ uidRec.foregroundServices = true;
+ }
+ }
+ }
+
+ if (app.getCurProcState() >= ActivityManager.PROCESS_STATE_HOME
+ && !app.killedByAm) {
+ numTrimming++;
+ }
+ }
+ }
+
+ mService.incrementProcStateSeqAndNotifyAppsLocked();
+
+ mNumServiceProcs = mNewNumServiceProcs;
+
+ boolean allChanged = mService.updateLowMemStateLocked(numCached, numEmpty, numTrimming);
+
+ if (mService.mAlwaysFinishActivities) {
+ // Need to do this on its own message because the stack may not
+ // be in a consistent state at this point.
+ mService.mAtmInternal.scheduleDestroyAllActivities("always-finish");
+ }
+
+ if (allChanged) {
+ mService.requestPssAllProcsLocked(now, false,
+ mService.mProcessStats.isMemFactorLowered());
+ }
+
+ ArrayList<UidRecord> becameIdle = null;
+
+ // Update from any uid changes.
+ if (mLocalPowerManager != null) {
+ mLocalPowerManager.startUidChanges();
+ }
+ for (int i = mActiveUids.size() - 1; i >= 0; i--) {
+ final UidRecord uidRec = mActiveUids.valueAt(i);
+ int uidChange = UidRecord.CHANGE_PROCSTATE;
+ if (uidRec.getCurProcState() != PROCESS_STATE_NONEXISTENT
+ && (uidRec.setProcState != uidRec.getCurProcState()
+ || uidRec.setWhitelist != uidRec.curWhitelist)) {
+ if (DEBUG_UID_OBSERVERS) Slog.i(TAG_UID_OBSERVERS, "Changes in " + uidRec
+ + ": proc state from " + uidRec.setProcState + " to "
+ + uidRec.getCurProcState() + ", whitelist from " + uidRec.setWhitelist
+ + " to " + uidRec.curWhitelist);
+ if (ActivityManager.isProcStateBackground(uidRec.getCurProcState())
+ && !uidRec.curWhitelist) {
+ // UID is now in the background (and not on the temp whitelist). Was it
+ // previously in the foreground (or on the temp whitelist)?
+ if (!ActivityManager.isProcStateBackground(uidRec.setProcState)
+ || uidRec.setWhitelist) {
+ uidRec.lastBackgroundTime = nowElapsed;
+ if (!mService.mHandler.hasMessages(IDLE_UIDS_MSG)) {
+ // Note: the background settle time is in elapsed realtime, while
+ // the handler time base is uptime. All this means is that we may
+ // stop background uids later than we had intended, but that only
+ // happens because the device was sleeping so we are okay anyway.
+ mService.mHandler.sendEmptyMessageDelayed(IDLE_UIDS_MSG,
+ mConstants.BACKGROUND_SETTLE_TIME);
+ }
+ }
+ if (uidRec.idle && !uidRec.setIdle) {
+ uidChange = UidRecord.CHANGE_IDLE;
+ if (becameIdle == null) {
+ becameIdle = new ArrayList<>();
+ }
+ becameIdle.add(uidRec);
+ }
+ } else {
+ if (uidRec.idle) {
+ uidChange = UidRecord.CHANGE_ACTIVE;
+ EventLogTags.writeAmUidActive(uidRec.uid);
+ uidRec.idle = false;
+ }
+ uidRec.lastBackgroundTime = 0;
+ }
+ final boolean wasCached = uidRec.setProcState
+ > ActivityManager.PROCESS_STATE_RECEIVER;
+ final boolean isCached = uidRec.getCurProcState()
+ > ActivityManager.PROCESS_STATE_RECEIVER;
+ if (wasCached != isCached || uidRec.setProcState == PROCESS_STATE_NONEXISTENT) {
+ uidChange |= isCached ? UidRecord.CHANGE_CACHED : UidRecord.CHANGE_UNCACHED;
+ }
+ uidRec.setProcState = uidRec.getCurProcState();
+ uidRec.setWhitelist = uidRec.curWhitelist;
+ uidRec.setIdle = uidRec.idle;
+ mService.enqueueUidChangeLocked(uidRec, -1, uidChange);
+ mService.noteUidProcessState(uidRec.uid, uidRec.getCurProcState());
+ if (uidRec.foregroundServices) {
+ mService.mServices.foregroundServiceProcStateChangedLocked(uidRec);
+ }
+ }
+ }
+ if (mLocalPowerManager != null) {
+ mLocalPowerManager.finishUidChanges();
+ }
+
+ if (becameIdle != null) {
+ // If we have any new uids that became idle this time, we need to make sure
+ // they aren't left with running services.
+ for (int i = becameIdle.size() - 1; i >= 0; i--) {
+ mService.mServices.stopInBackgroundLocked(becameIdle.get(i).uid);
+ }
+ }
+
+ if (mService.mProcessStats.shouldWriteNowLocked(now)) {
+ mService.mHandler.post(new ActivityManagerService.ProcStatsRunnable(mService,
+ mService.mProcessStats));
+ }
+
+ // Run this after making sure all procstates are updated.
+ mService.mProcessStats.updateTrackingAssociationsLocked(mAdjSeq, now);
+
+ if (DEBUG_OOM_ADJ) {
+ final long duration = SystemClock.uptimeMillis() - now;
+ if (false) {
+ Slog.d(TAG_OOM_ADJ, "Did OOM ADJ in " + duration + "ms",
+ new RuntimeException("here").fillInStackTrace());
+ } else {
+ Slog.d(TAG_OOM_ADJ, "Did OOM ADJ in " + duration + "ms");
+ }
+ }
+ mService.mOomAdjProfiler.oomAdjEnded();
+ }
+
+ private final ComputeOomAdjWindowCallback mTmpComputeOomAdjWindowCallback =
+ new ComputeOomAdjWindowCallback();
+
+ /** These methods are called inline during computeOomAdjLocked(), on the same thread */
+ private final class ComputeOomAdjWindowCallback
+ implements WindowProcessController.ComputeOomAdjCallback {
+
+ ProcessRecord app;
+ int adj;
+ boolean foregroundActivities;
+ int procState;
+ int schedGroup;
+ int appUid;
+ int logUid;
+ int processStateCurTop;
+
+ void initialize(ProcessRecord app, int adj, boolean foregroundActivities,
+ int procState, int schedGroup, int appUid, int logUid, int processStateCurTop) {
+ this.app = app;
+ this.adj = adj;
+ this.foregroundActivities = foregroundActivities;
+ this.procState = procState;
+ this.schedGroup = schedGroup;
+ this.appUid = appUid;
+ this.logUid = logUid;
+ this.processStateCurTop = processStateCurTop;
+ }
+
+ @Override
+ public void onVisibleActivity() {
+ // App has a visible activity; only upgrade adjustment.
+ if (adj > ProcessList.VISIBLE_APP_ADJ) {
+ adj = ProcessList.VISIBLE_APP_ADJ;
+ app.adjType = "vis-activity";
+ if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+ reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to vis-activity: " + app);
+ }
+ }
+ if (procState > processStateCurTop) {
+ procState = processStateCurTop;
+ app.adjType = "vis-activity";
+ if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+ reportOomAdjMessageLocked(TAG_OOM_ADJ,
+ "Raise procstate to vis-activity (top): " + app);
+ }
+ }
+ if (schedGroup < ProcessList.SCHED_GROUP_DEFAULT) {
+ schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
+ }
+ app.cached = false;
+ app.empty = false;
+ foregroundActivities = true;
+ }
+
+ @Override
+ public void onPausedActivity() {
+ if (adj > ProcessList.PERCEPTIBLE_APP_ADJ) {
+ adj = ProcessList.PERCEPTIBLE_APP_ADJ;
+ app.adjType = "pause-activity";
+ if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+ reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to pause-activity: " + app);
+ }
+ }
+ if (procState > processStateCurTop) {
+ procState = processStateCurTop;
+ app.adjType = "pause-activity";
+ if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+ reportOomAdjMessageLocked(TAG_OOM_ADJ,
+ "Raise procstate to pause-activity (top): " + app);
+ }
+ }
+ if (schedGroup < ProcessList.SCHED_GROUP_DEFAULT) {
+ schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
+ }
+ app.cached = false;
+ app.empty = false;
+ foregroundActivities = true;
+ }
+
+ @Override
+ public void onStoppingActivity(boolean finishing) {
+ if (adj > ProcessList.PERCEPTIBLE_APP_ADJ) {
+ adj = ProcessList.PERCEPTIBLE_APP_ADJ;
+ app.adjType = "stop-activity";
+ if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+ reportOomAdjMessageLocked(TAG_OOM_ADJ,
+ "Raise adj to stop-activity: " + app);
+ }
+ }
+
+ // For the process state, we will at this point consider the process to be cached. It
+ // will be cached either as an activity or empty depending on whether the activity is
+ // finishing. We do this so that we can treat the process as cached for purposes of
+ // memory trimming (determining current memory level, trim command to send to process)
+ // since there can be an arbitrary number of stopping processes and they should soon all
+ // go into the cached state.
+ if (!finishing) {
+ if (procState > PROCESS_STATE_LAST_ACTIVITY) {
+ procState = PROCESS_STATE_LAST_ACTIVITY;
+ app.adjType = "stop-activity";
+ if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+ reportOomAdjMessageLocked(TAG_OOM_ADJ,
+ "Raise procstate to stop-activity: " + app);
+ }
+ }
+ }
+ app.cached = false;
+ app.empty = false;
+ foregroundActivities = true;
+ }
+
+ @Override
+ public void onOtherActivity() {
+ if (procState > PROCESS_STATE_CACHED_ACTIVITY) {
+ procState = PROCESS_STATE_CACHED_ACTIVITY;
+ app.adjType = "cch-act";
+ if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+ reportOomAdjMessageLocked(TAG_OOM_ADJ,
+ "Raise procstate to cached activity: " + app);
+ }
+ }
+ }
+ }
+
+ private final boolean computeOomAdjLocked(ProcessRecord app, int cachedAdj,
+ ProcessRecord TOP_APP, boolean doingAll, long now, boolean cycleReEval) {
+ if (mAdjSeq == app.adjSeq) {
+ if (app.adjSeq == app.completedAdjSeq) {
+ // This adjustment has already been computed successfully.
+ return false;
+ } else {
+ // The process is being computed, so there is a cycle. We cannot
+ // rely on this process's state.
+ app.containsCycle = true;
+
+ return false;
+ }
+ }
+
+ if (app.thread == null) {
+ app.adjSeq = mAdjSeq;
+ app.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_BACKGROUND);
+ app.setCurProcState(ActivityManager.PROCESS_STATE_CACHED_EMPTY);
+ app.curAdj = ProcessList.CACHED_APP_MAX_ADJ;
+ app.setCurRawAdj(ProcessList.CACHED_APP_MAX_ADJ);
+ app.completedAdjSeq = app.adjSeq;
+ return false;
+ }
+
+ app.adjTypeCode = ActivityManager.RunningAppProcessInfo.REASON_UNKNOWN;
+ app.adjSource = null;
+ app.adjTarget = null;
+ app.empty = false;
+ app.cached = false;
+
+ final WindowProcessController wpc = app.getWindowProcessController();
+ final int appUid = app.info.uid;
+ final int logUid = mService.mCurOomAdjUid;
+
+ int prevAppAdj = app.curAdj;
+ int prevProcState = app.getCurProcState();
+
+ if (app.maxAdj <= ProcessList.FOREGROUND_APP_ADJ) {
+ // The max adjustment doesn't allow this app to be anything
+ // below foreground, so it is not worth doing work for it.
+ if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+ mService.reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making fixed: " + app);
+ }
+ app.adjType = "fixed";
+ app.adjSeq = mAdjSeq;
+ app.setCurRawAdj(app.maxAdj);
+ app.setHasForegroundActivities(false);
+ app.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_DEFAULT);
+ app.setCurProcState(ActivityManager.PROCESS_STATE_PERSISTENT);
+ // System processes can do UI, and when they do we want to have
+ // them trim their memory after the user leaves the UI. To
+ // facilitate this, here we need to determine whether or not it
+ // is currently showing UI.
+ app.systemNoUi = true;
+ if (app == TOP_APP) {
+ app.systemNoUi = false;
+ app.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_TOP_APP);
+ app.adjType = "pers-top-activity";
+ } else if (app.hasTopUi()) {
+ // sched group/proc state adjustment is below
+ app.systemNoUi = false;
+ app.adjType = "pers-top-ui";
+ } else if (wpc.hasVisibleActivities()) {
+ app.systemNoUi = false;
+ }
+ if (!app.systemNoUi) {
+ if (mService.mWakefulness == PowerManagerInternal.WAKEFULNESS_AWAKE) {
+ // screen on, promote UI
+ app.setCurProcState(ActivityManager.PROCESS_STATE_PERSISTENT_UI);
+ app.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_TOP_APP);
+ } else {
+ // screen off, restrict UI scheduling
+ app.setCurProcState(ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE);
+ app.setCurrentSchedulingGroup(ProcessList.SCHED_GROUP_RESTRICTED);
+ }
+ }
+ app.setCurRawProcState(app.getCurProcState());
+ app.curAdj = app.maxAdj;
+ app.completedAdjSeq = app.adjSeq;
+ // if curAdj is less than prevAppAdj, then this process was promoted
+ return app.curAdj < prevAppAdj || app.getCurProcState() < prevProcState;
+ }
+
+ app.systemNoUi = false;
+
+ final int PROCESS_STATE_CUR_TOP = mService.mAtmInternal.getTopProcessState();
+
+ // Determine the importance of the process, starting with most
+ // important to least, and assign an appropriate OOM adjustment.
+ int adj;
+ int schedGroup;
+ int procState;
+ int cachedAdjSeq;
+
+ boolean foregroundActivities = false;
+ mTmpBroadcastQueue.clear();
+ if (PROCESS_STATE_CUR_TOP == ActivityManager.PROCESS_STATE_TOP && app == TOP_APP) {
+ // The last app on the list is the foreground app.
+ adj = ProcessList.FOREGROUND_APP_ADJ;
+ schedGroup = ProcessList.SCHED_GROUP_TOP_APP;
+ app.adjType = "top-activity";
+ foregroundActivities = true;
+ procState = PROCESS_STATE_CUR_TOP;
+ if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+ reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making top: " + app);
+ }
+ } else if (app.runningRemoteAnimation) {
+ adj = ProcessList.VISIBLE_APP_ADJ;
+ schedGroup = ProcessList.SCHED_GROUP_TOP_APP;
+ app.adjType = "running-remote-anim";
+ procState = PROCESS_STATE_CUR_TOP;
+ if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+ reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making running remote anim: " + app);
+ }
+ } else if (app.getActiveInstrumentation() != null) {
+ // Don't want to kill running instrumentation.
+ adj = ProcessList.FOREGROUND_APP_ADJ;
+ schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
+ app.adjType = "instrumentation";
+ procState = ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
+ if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+ reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making instrumentation: " + app);
+ }
+ } else if (mService.isReceivingBroadcastLocked(app, mTmpBroadcastQueue)) {
+ // An app that is currently receiving a broadcast also
+ // counts as being in the foreground for OOM killer purposes.
+ // It's placed in a sched group based on the nature of the
+ // broadcast as reflected by which queue it's active in.
+ adj = ProcessList.FOREGROUND_APP_ADJ;
+ schedGroup = (mTmpBroadcastQueue.contains(mService.mFgBroadcastQueue))
+ ? ProcessList.SCHED_GROUP_DEFAULT : ProcessList.SCHED_GROUP_BACKGROUND;
+ app.adjType = "broadcast";
+ procState = ActivityManager.PROCESS_STATE_RECEIVER;
+ if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+ reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making broadcast: " + app);
+ }
+ } else if (app.executingServices.size() > 0) {
+ // An app that is currently executing a service callback also
+ // counts as being in the foreground.
+ adj = ProcessList.FOREGROUND_APP_ADJ;
+ schedGroup = app.execServicesFg ?
+ ProcessList.SCHED_GROUP_DEFAULT : ProcessList.SCHED_GROUP_BACKGROUND;
+ app.adjType = "exec-service";
+ procState = ActivityManager.PROCESS_STATE_SERVICE;
+ if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+ reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making exec-service: " + app);
+ }
+ //Slog.i(TAG, "EXEC " + (app.execServicesFg ? "FG" : "BG") + ": " + app);
+ } else if (app == TOP_APP) {
+ adj = ProcessList.FOREGROUND_APP_ADJ;
+ schedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
+ app.adjType = "top-sleeping";
+ foregroundActivities = true;
+ procState = PROCESS_STATE_CUR_TOP;
+ if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+ reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making top (sleeping): " + app);
+ }
+ } else {
+ // As far as we know the process is empty. We may change our mind later.
+ schedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
+ // At this point we don't actually know the adjustment. Use the cached adj
+ // value that the caller wants us to.
+ adj = cachedAdj;
+ procState = ActivityManager.PROCESS_STATE_CACHED_EMPTY;
+ app.cached = true;
+ app.empty = true;
+ app.adjType = "cch-empty";
+ if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+ reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making empty: " + app);
+ }
+ }
+
+ // Examine all activities if not already foreground.
+ if (!foregroundActivities && wpc.hasActivities()) {
+ mTmpComputeOomAdjWindowCallback.initialize(app, adj, foregroundActivities, procState,
+ schedGroup, appUid, logUid, PROCESS_STATE_CUR_TOP);
+ final int minLayer = wpc.computeOomAdjFromActivities(
+ ProcessList.VISIBLE_APP_LAYER_MAX, mTmpComputeOomAdjWindowCallback);
+
+ adj = mTmpComputeOomAdjWindowCallback.adj;
+ foregroundActivities = mTmpComputeOomAdjWindowCallback.foregroundActivities;
+ procState = mTmpComputeOomAdjWindowCallback.procState;
+ schedGroup = mTmpComputeOomAdjWindowCallback.schedGroup;
+
+ if (adj == ProcessList.VISIBLE_APP_ADJ) {
+ adj += minLayer;
+ }
+ }
+
+ if (procState > ActivityManager.PROCESS_STATE_CACHED_RECENT && app.hasRecentTasks()) {
+ procState = ActivityManager.PROCESS_STATE_CACHED_RECENT;
+ app.adjType = "cch-rec";
+ if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+ reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise procstate to cached recent: " + app);
+ }
+ }
+
+ if (adj > ProcessList.PERCEPTIBLE_APP_ADJ
+ || procState > ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
+ if (app.hasForegroundServices()) {
+ // The user is aware of this app, so make it visible.
+ adj = ProcessList.PERCEPTIBLE_APP_ADJ;
+ procState = ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
+ app.cached = false;
+ app.adjType = "fg-service";
+ schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
+ if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+ reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to fg service: " + app);
+ }
+ } else if (app.hasOverlayUi()) {
+ // The process is display an overlay UI.
+ adj = ProcessList.PERCEPTIBLE_APP_ADJ;
+ procState = ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
+ app.cached = false;
+ app.adjType = "has-overlay-ui";
+ schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
+ if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+ reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to overlay ui: " + app);
+ }
+ }
+ }
+
+ // If the app was recently in the foreground and moved to a foreground service status,
+ // allow it to get a higher rank in memory for some time, compared to other foreground
+ // services so that it can finish performing any persistence/processing of in-memory state.
+ if (app.hasForegroundServices() && adj > ProcessList.PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ
+ && (app.lastTopTime + mConstants.TOP_TO_FGS_GRACE_DURATION > now
+ || app.setProcState <= ActivityManager.PROCESS_STATE_TOP)) {
+ adj = ProcessList.PERCEPTIBLE_RECENT_FOREGROUND_APP_ADJ;
+ app.adjType = "fg-service-act";
+ if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+ reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to recent fg: " + app);
+ }
+ }
+
+ if (adj > ProcessList.PERCEPTIBLE_APP_ADJ
+ || procState > ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND) {
+ if (app.forcingToImportant != null) {
+ // This is currently used for toasts... they are not interactive, and
+ // we don't want them to cause the app to become fully foreground (and
+ // thus out of background check), so we yes the best background level we can.
+ adj = ProcessList.PERCEPTIBLE_APP_ADJ;
+ procState = ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND;
+ app.cached = false;
+ app.adjType = "force-imp";
+ app.adjSource = app.forcingToImportant;
+ schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
+ if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+ reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to force imp: " + app);
+ }
+ }
+ }
+
+ if (mService.mAtmInternal.isHeavyWeightProcess(app.getWindowProcessController())) {
+ if (adj > ProcessList.HEAVY_WEIGHT_APP_ADJ) {
+ // We don't want to kill the current heavy-weight process.
+ adj = ProcessList.HEAVY_WEIGHT_APP_ADJ;
+ schedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
+ app.cached = false;
+ app.adjType = "heavy";
+ if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+ reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to heavy: " + app);
+ }
+ }
+ if (procState > ActivityManager.PROCESS_STATE_HEAVY_WEIGHT) {
+ procState = ActivityManager.PROCESS_STATE_HEAVY_WEIGHT;
+ app.adjType = "heavy";
+ if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+ reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise procstate to heavy: " + app);
+ }
+ }
+ }
+
+ if (wpc.isHomeProcess()) {
+ if (adj > ProcessList.HOME_APP_ADJ) {
+ // This process is hosting what we currently consider to be the
+ // home app, so we don't want to let it go into the background.
+ adj = ProcessList.HOME_APP_ADJ;
+ schedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
+ app.cached = false;
+ app.adjType = "home";
+ if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+ reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to home: " + app);
+ }
+ }
+ if (procState > ActivityManager.PROCESS_STATE_HOME) {
+ procState = ActivityManager.PROCESS_STATE_HOME;
+ app.adjType = "home";
+ if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+ reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise procstate to home: " + app);
+ }
+ }
+ }
+
+ if (wpc.isPreviousProcess() && app.hasActivities()) {
+ if (adj > ProcessList.PREVIOUS_APP_ADJ) {
+ // This was the previous process that showed UI to the user.
+ // We want to try to keep it around more aggressively, to give
+ // a good experience around switching between two apps.
+ adj = ProcessList.PREVIOUS_APP_ADJ;
+ schedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
+ app.cached = false;
+ app.adjType = "previous";
+ if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+ reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to prev: " + app);
+ }
+ }
+ if (procState > PROCESS_STATE_LAST_ACTIVITY) {
+ procState = PROCESS_STATE_LAST_ACTIVITY;
+ app.adjType = "previous";
+ if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+ reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise procstate to prev: " + app);
+ }
+ }
+ }
+
+ if (false) Slog.i(TAG, "OOM " + app + ": initial adj=" + adj
+ + " reason=" + app.adjType);
+
+ // By default, we use the computed adjustment. It may be changed if
+ // there are applications dependent on our services or providers, but
+ // this gives us a baseline and makes sure we don't get into an
+ // infinite recursion. If we're re-evaluating due to cycles, use the previously computed
+ // values.
+ app.setCurRawAdj(!cycleReEval ? adj : Math.min(adj, app.getCurRawAdj()));
+ app.setCurRawProcState(!cycleReEval
+ ? procState
+ : Math.min(procState, app.getCurRawProcState()));
+
+ app.hasStartedServices = false;
+ app.adjSeq = mAdjSeq;
+
+ if (mService.mBackupTarget != null && app == mService.mBackupTarget.app) {
+ // If possible we want to avoid killing apps while they're being backed up
+ if (adj > ProcessList.BACKUP_APP_ADJ) {
+ if (DEBUG_BACKUP) Slog.v(TAG_BACKUP, "oom BACKUP_APP_ADJ for " + app);
+ adj = ProcessList.BACKUP_APP_ADJ;
+ if (procState > ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND) {
+ procState = ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND;
+ }
+ app.adjType = "backup";
+ if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+ reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise adj to backup: " + app);
+ }
+ app.cached = false;
+ }
+ if (procState > ActivityManager.PROCESS_STATE_BACKUP) {
+ procState = ActivityManager.PROCESS_STATE_BACKUP;
+ app.adjType = "backup";
+ if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+ reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise procstate to backup: " + app);
+ }
+ }
+ }
+
+ boolean mayBeTop = false;
+ String mayBeTopType = null;
+ Object mayBeTopSource = null;
+ Object mayBeTopTarget = null;
+
+ for (int is = app.services.size() - 1;
+ is >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ
+ || schedGroup == ProcessList.SCHED_GROUP_BACKGROUND
+ || procState > ActivityManager.PROCESS_STATE_TOP);
+ is--) {
+ ServiceRecord s = app.services.valueAt(is);
+ if (s.startRequested) {
+ app.hasStartedServices = true;
+ if (procState > ActivityManager.PROCESS_STATE_SERVICE) {
+ procState = ActivityManager.PROCESS_STATE_SERVICE;
+ app.adjType = "started-services";
+ if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+ reportOomAdjMessageLocked(TAG_OOM_ADJ,
+ "Raise procstate to started service: " + app);
+ }
+ }
+ if (app.hasShownUi && !wpc.isHomeProcess()) {
+ // If this process has shown some UI, let it immediately
+ // go to the LRU list because it may be pretty heavy with
+ // UI stuff. We'll tag it with a label just to help
+ // debug and understand what is going on.
+ if (adj > ProcessList.SERVICE_ADJ) {
+ app.adjType = "cch-started-ui-services";
+ }
+ } else {
+ if (now < (s.lastActivity + mConstants.MAX_SERVICE_INACTIVITY)) {
+ // This service has seen some activity within
+ // recent memory, so we will keep its process ahead
+ // of the background processes.
+ if (adj > ProcessList.SERVICE_ADJ) {
+ adj = ProcessList.SERVICE_ADJ;
+ app.adjType = "started-services";
+ if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+ reportOomAdjMessageLocked(TAG_OOM_ADJ,
+ "Raise adj to started service: " + app);
+ }
+ app.cached = false;
+ }
+ }
+ // If we have let the service slide into the background
+ // state, still have some text describing what it is doing
+ // even though the service no longer has an impact.
+ if (adj > ProcessList.SERVICE_ADJ) {
+ app.adjType = "cch-started-services";
+ }
+ }
+ }
+
+ for (int conni = s.connections.size() - 1;
+ conni >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ
+ || schedGroup == ProcessList.SCHED_GROUP_BACKGROUND
+ || procState > ActivityManager.PROCESS_STATE_TOP);
+ conni--) {
+ ArrayList<ConnectionRecord> clist = s.connections.valueAt(conni);
+ for (int i = 0;
+ i < clist.size() && (adj > ProcessList.FOREGROUND_APP_ADJ
+ || schedGroup == ProcessList.SCHED_GROUP_BACKGROUND
+ || procState > ActivityManager.PROCESS_STATE_TOP);
+ i++) {
+ // XXX should compute this based on the max of
+ // all connected clients.
+ ConnectionRecord cr = clist.get(i);
+ if (cr.binding.client == app) {
+ // Binding to oneself is not interesting.
+ continue;
+ }
+
+ boolean trackedProcState = false;
+ if ((cr.flags& Context.BIND_WAIVE_PRIORITY) == 0) {
+ ProcessRecord client = cr.binding.client;
+ computeOomAdjLocked(client, cachedAdj, TOP_APP, doingAll, now, cycleReEval);
+
+ if (shouldSkipDueToCycle(app, client, procState, adj, cycleReEval)) {
+ continue;
+ }
+
+ int clientAdj = client.getCurRawAdj();
+ int clientProcState = client.getCurRawProcState();
+
+ if (clientProcState >= PROCESS_STATE_CACHED_ACTIVITY) {
+ // If the other app is cached for any reason, for purposes here
+ // we are going to consider it empty. The specific cached state
+ // doesn't propagate except under certain conditions.
+ clientProcState = ActivityManager.PROCESS_STATE_CACHED_EMPTY;
+ }
+ String adjType = null;
+ if ((cr.flags&Context.BIND_ALLOW_OOM_MANAGEMENT) != 0) {
+ // Not doing bind OOM management, so treat
+ // this guy more like a started service.
+ if (app.hasShownUi && !wpc.isHomeProcess()) {
+ // If this process has shown some UI, let it immediately
+ // go to the LRU list because it may be pretty heavy with
+ // UI stuff. We'll tag it with a label just to help
+ // debug and understand what is going on.
+ if (adj > clientAdj) {
+ adjType = "cch-bound-ui-services";
+ }
+ app.cached = false;
+ clientAdj = adj;
+ clientProcState = procState;
+ } else {
+ if (now >= (s.lastActivity
+ + mConstants.MAX_SERVICE_INACTIVITY)) {
+ // This service has not seen activity within
+ // recent memory, so allow it to drop to the
+ // LRU list if there is no other reason to keep
+ // it around. We'll also tag it with a label just
+ // to help debug and undertand what is going on.
+ if (adj > clientAdj) {
+ adjType = "cch-bound-services";
+ }
+ clientAdj = adj;
+ }
+ }
+ }
+ if (adj > clientAdj) {
+ // If this process has recently shown UI, and
+ // the process that is binding to it is less
+ // important than being visible, then we don't
+ // care about the binding as much as we care
+ // about letting this process get into the LRU
+ // list to be killed and restarted if needed for
+ // memory.
+ if (app.hasShownUi && !wpc.isHomeProcess()
+ && clientAdj > ProcessList.PERCEPTIBLE_APP_ADJ) {
+ if (adj >= ProcessList.CACHED_APP_MIN_ADJ) {
+ adjType = "cch-bound-ui-services";
+ }
+ } else {
+ int newAdj;
+ if ((cr.flags&(Context.BIND_ABOVE_CLIENT
+ |Context.BIND_IMPORTANT)) != 0) {
+ if (clientAdj >= ProcessList.PERSISTENT_SERVICE_ADJ) {
+ newAdj = clientAdj;
+ } else {
+ // make this service persistent
+ newAdj = ProcessList.PERSISTENT_SERVICE_ADJ;
+ schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
+ procState = ActivityManager.PROCESS_STATE_PERSISTENT;
+ cr.trackProcState(procState, mAdjSeq, now);
+ trackedProcState = true;
+ }
+ } else if ((cr.flags & Context.BIND_ADJUST_BELOW_PERCEPTIBLE) != 0
+ && clientAdj < ProcessList.PERCEPTIBLE_APP_ADJ
+ && adj > ProcessList.PERCEPTIBLE_APP_ADJ + 1) {
+ newAdj = ProcessList.PERCEPTIBLE_APP_ADJ + 1;
+ } else if ((cr.flags&Context.BIND_NOT_VISIBLE) != 0
+ && clientAdj < ProcessList.PERCEPTIBLE_APP_ADJ
+ && adj > ProcessList.PERCEPTIBLE_APP_ADJ) {
+ newAdj = ProcessList.PERCEPTIBLE_APP_ADJ;
+ } else if (clientAdj >= ProcessList.PERCEPTIBLE_APP_ADJ) {
+ newAdj = clientAdj;
+ } else {
+ if (adj > ProcessList.VISIBLE_APP_ADJ) {
+ newAdj = Math.max(clientAdj, ProcessList.VISIBLE_APP_ADJ);
+ } else {
+ newAdj = adj;
+ }
+ }
+ if (!client.cached) {
+ app.cached = false;
+ }
+ if (adj > newAdj) {
+ adj = newAdj;
+ app.setCurRawAdj(adj);
+ adjType = "service";
+ }
+ }
+ }
+ if ((cr.flags & (Context.BIND_NOT_FOREGROUND
+ | Context.BIND_IMPORTANT_BACKGROUND)) == 0) {
+ // This will treat important bound services identically to
+ // the top app, which may behave differently than generic
+ // foreground work.
+ final int curSchedGroup = client.getCurrentSchedulingGroup();
+ if (curSchedGroup > schedGroup) {
+ if ((cr.flags&Context.BIND_IMPORTANT) != 0) {
+ schedGroup = curSchedGroup;
+ } else {
+ schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
+ }
+ }
+ if (clientProcState <= ActivityManager.PROCESS_STATE_TOP) {
+ if (clientProcState == ActivityManager.PROCESS_STATE_TOP) {
+ // Special handling of clients who are in the top state.
+ // We *may* want to consider this process to be in the
+ // top state as well, but only if there is not another
+ // reason for it to be running. Being on the top is a
+ // special state, meaning you are specifically running
+ // for the current top app. If the process is already
+ // running in the background for some other reason, it
+ // is more important to continue considering it to be
+ // in the background state.
+ mayBeTop = true;
+ mayBeTopType = "service";
+ mayBeTopSource = cr.binding.client;
+ mayBeTopTarget = s.instanceName;
+ clientProcState = ActivityManager.PROCESS_STATE_CACHED_EMPTY;
+ } else {
+ // Special handling for above-top states (persistent
+ // processes). These should not bring the current process
+ // into the top state, since they are not on top. Instead
+ // give them the best state after that.
+ if ((cr.flags&Context.BIND_FOREGROUND_SERVICE) != 0) {
+ clientProcState =
+ ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
+ } else if (mService.mWakefulness
+ == PowerManagerInternal.WAKEFULNESS_AWAKE &&
+ (cr.flags&Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE)
+ != 0) {
+ clientProcState =
+ ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
+ } else {
+ clientProcState =
+ ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
+ }
+ }
+ }
+ } else if ((cr.flags & Context.BIND_IMPORTANT_BACKGROUND) == 0) {
+ if (clientProcState <
+ ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND) {
+ clientProcState =
+ ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND;
+ }
+ } else {
+ if (clientProcState <
+ ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND) {
+ clientProcState =
+ ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND;
+ }
+ }
+ if (!trackedProcState) {
+ cr.trackProcState(clientProcState, mAdjSeq, now);
+ }
+ if (procState > clientProcState) {
+ procState = clientProcState;
+ app.setCurRawProcState(procState);
+ if (adjType == null) {
+ adjType = "service";
+ }
+ }
+ if (procState < ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
+ && (cr.flags & Context.BIND_SHOWING_UI) != 0) {
+ app.setPendingUiClean(true);
+ }
+ if (adjType != null) {
+ app.adjType = adjType;
+ app.adjTypeCode = ActivityManager.RunningAppProcessInfo
+ .REASON_SERVICE_IN_USE;
+ app.adjSource = cr.binding.client;
+ app.adjSourceProcState = clientProcState;
+ app.adjTarget = s.instanceName;
+ if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+ reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to " + adjType
+ + ": " + app + ", due to " + cr.binding.client
+ + " adj=" + adj + " procState="
+ + ProcessList.makeProcStateString(procState));
+ }
+ }
+ }
+ if ((cr.flags&Context.BIND_TREAT_LIKE_ACTIVITY) != 0) {
+ app.treatLikeActivity = true;
+ }
+ final ActivityServiceConnectionsHolder a = cr.activity;
+ if ((cr.flags&Context.BIND_ADJUST_WITH_ACTIVITY) != 0) {
+ if (a != null && adj > ProcessList.FOREGROUND_APP_ADJ
+ && a.isActivityVisible()) {
+ adj = ProcessList.FOREGROUND_APP_ADJ;
+ app.setCurRawAdj(adj);
+ if ((cr.flags&Context.BIND_NOT_FOREGROUND) == 0) {
+ if ((cr.flags&Context.BIND_IMPORTANT) != 0) {
+ schedGroup = ProcessList.SCHED_GROUP_TOP_APP_BOUND;
+ } else {
+ schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
+ }
+ }
+ app.cached = false;
+ app.adjType = "service";
+ app.adjTypeCode = ActivityManager.RunningAppProcessInfo
+ .REASON_SERVICE_IN_USE;
+ app.adjSource = a;
+ app.adjSourceProcState = procState;
+ app.adjTarget = s.instanceName;
+ if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+ reportOomAdjMessageLocked(TAG_OOM_ADJ,
+ "Raise to service w/activity: " + app);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ for (int provi = app.pubProviders.size() - 1;
+ provi >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ
+ || schedGroup == ProcessList.SCHED_GROUP_BACKGROUND
+ || procState > ActivityManager.PROCESS_STATE_TOP);
+ provi--) {
+ ContentProviderRecord cpr = app.pubProviders.valueAt(provi);
+ for (int i = cpr.connections.size() - 1;
+ i >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ
+ || schedGroup == ProcessList.SCHED_GROUP_BACKGROUND
+ || procState > ActivityManager.PROCESS_STATE_TOP);
+ i--) {
+ ContentProviderConnection conn = cpr.connections.get(i);
+ ProcessRecord client = conn.client;
+ if (client == app) {
+ // Being our own client is not interesting.
+ continue;
+ }
+ computeOomAdjLocked(client, cachedAdj, TOP_APP, doingAll, now, cycleReEval);
+
+ if (shouldSkipDueToCycle(app, client, procState, adj, cycleReEval)) {
+ continue;
+ }
+
+ int clientAdj = client.getCurRawAdj();
+ int clientProcState = client.getCurRawProcState();
+
+ if (clientProcState >= PROCESS_STATE_CACHED_ACTIVITY) {
+ // If the other app is cached for any reason, for purposes here
+ // we are going to consider it empty.
+ clientProcState = ActivityManager.PROCESS_STATE_CACHED_EMPTY;
+ }
+ String adjType = null;
+ if (adj > clientAdj) {
+ if (app.hasShownUi && !wpc.isHomeProcess()
+ && clientAdj > ProcessList.PERCEPTIBLE_APP_ADJ) {
+ adjType = "cch-ui-provider";
+ } else {
+ adj = clientAdj > ProcessList.FOREGROUND_APP_ADJ
+ ? clientAdj : ProcessList.FOREGROUND_APP_ADJ;
+ app.setCurRawAdj(adj);
+ adjType = "provider";
+ }
+ app.cached &= client.cached;
+ }
+ if (clientProcState <= ActivityManager.PROCESS_STATE_TOP) {
+ if (clientProcState == ActivityManager.PROCESS_STATE_TOP) {
+ // Special handling of clients who are in the top state.
+ // We *may* want to consider this process to be in the
+ // top state as well, but only if there is not another
+ // reason for it to be running. Being on the top is a
+ // special state, meaning you are specifically running
+ // for the current top app. If the process is already
+ // running in the background for some other reason, it
+ // is more important to continue considering it to be
+ // in the background state.
+ mayBeTop = true;
+ clientProcState = ActivityManager.PROCESS_STATE_CACHED_EMPTY;
+ mayBeTopType = adjType = "provider-top";
+ mayBeTopSource = client;
+ mayBeTopTarget = cpr.name;
+ } else {
+ // Special handling for above-top states (persistent
+ // processes). These should not bring the current process
+ // into the top state, since they are not on top. Instead
+ // give them the best state after that.
+ clientProcState =
+ ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
+ if (adjType == null) {
+ adjType = "provider";
+ }
+ }
+ }
+ conn.trackProcState(clientProcState, mAdjSeq, now);
+ if (procState > clientProcState) {
+ procState = clientProcState;
+ app.setCurRawProcState(procState);
+ }
+ if (client.getCurrentSchedulingGroup() > schedGroup) {
+ schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
+ }
+ if (adjType != null) {
+ app.adjType = adjType;
+ app.adjTypeCode = ActivityManager.RunningAppProcessInfo
+ .REASON_PROVIDER_IN_USE;
+ app.adjSource = client;
+ app.adjSourceProcState = clientProcState;
+ app.adjTarget = cpr.name;
+ if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+ reportOomAdjMessageLocked(TAG_OOM_ADJ, "Raise to " + adjType
+ + ": " + app + ", due to " + client
+ + " adj=" + adj + " procState="
+ + ProcessList.makeProcStateString(procState));
+ }
+ }
+ }
+ // If the provider has external (non-framework) process
+ // dependencies, ensure that its adjustment is at least
+ // FOREGROUND_APP_ADJ.
+ if (cpr.hasExternalProcessHandles()) {
+ if (adj > ProcessList.FOREGROUND_APP_ADJ) {
+ adj = ProcessList.FOREGROUND_APP_ADJ;
+ app.setCurRawAdj(adj);
+ schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
+ app.cached = false;
+ app.adjType = "ext-provider";
+ app.adjTarget = cpr.name;
+ if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+ reportOomAdjMessageLocked(TAG_OOM_ADJ,
+ "Raise adj to external provider: " + app);
+ }
+ }
+ if (procState > ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
+ procState = ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
+ app.setCurRawProcState(procState);
+ if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+ reportOomAdjMessageLocked(TAG_OOM_ADJ,
+ "Raise procstate to external provider: " + app);
+ }
+ }
+ }
+ }
+
+ if (app.lastProviderTime > 0 &&
+ (app.lastProviderTime + mConstants.CONTENT_PROVIDER_RETAIN_TIME) > now) {
+ if (adj > ProcessList.PREVIOUS_APP_ADJ) {
+ adj = ProcessList.PREVIOUS_APP_ADJ;
+ schedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
+ app.cached = false;
+ app.adjType = "recent-provider";
+ if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+ reportOomAdjMessageLocked(TAG_OOM_ADJ,
+ "Raise adj to recent provider: " + app);
+ }
+ }
+ if (procState > PROCESS_STATE_LAST_ACTIVITY) {
+ procState = PROCESS_STATE_LAST_ACTIVITY;
+ app.adjType = "recent-provider";
+ if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+ reportOomAdjMessageLocked(TAG_OOM_ADJ,
+ "Raise procstate to recent provider: " + app);
+ }
+ }
+ }
+
+ if (mayBeTop && procState > ActivityManager.PROCESS_STATE_TOP) {
+ // A client of one of our services or providers is in the top state. We
+ // *may* want to be in the top state, but not if we are already running in
+ // the background for some other reason. For the decision here, we are going
+ // to pick out a few specific states that we want to remain in when a client
+ // is top (states that tend to be longer-term) and otherwise allow it to go
+ // to the top state.
+ switch (procState) {
+ case ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE:
+ case ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE:
+ // Something else is keeping it at this level, just leave it.
+ break;
+ case ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND:
+ case ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND:
+ case ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND:
+ case ActivityManager.PROCESS_STATE_SERVICE:
+ // These all are longer-term states, so pull them up to the top
+ // of the background states, but not all the way to the top state.
+ procState = ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
+ app.adjType = mayBeTopType;
+ app.adjSource = mayBeTopSource;
+ app.adjTarget = mayBeTopTarget;
+ if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+ reportOomAdjMessageLocked(TAG_OOM_ADJ, "May be top raise to " + mayBeTopType
+ + ": " + app + ", due to " + mayBeTopSource
+ + " adj=" + adj + " procState="
+ + ProcessList.makeProcStateString(procState));
+ }
+ break;
+ default:
+ // Otherwise, top is a better choice, so take it.
+ procState = ActivityManager.PROCESS_STATE_TOP;
+ app.adjType = mayBeTopType;
+ app.adjSource = mayBeTopSource;
+ app.adjTarget = mayBeTopTarget;
+ if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
+ reportOomAdjMessageLocked(TAG_OOM_ADJ, "May be top raise to " + mayBeTopType
+ + ": " + app + ", due to " + mayBeTopSource
+ + " adj=" + adj + " procState="
+ + ProcessList.makeProcStateString(procState));
+ }
+ break;
+ }
+ }
+
+ if (procState >= ActivityManager.PROCESS_STATE_CACHED_EMPTY) {
+ if (app.hasClientActivities()) {
+ // This is a cached process, but with client activities. Mark it so.
+ procState = ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT;
+ app.adjType = "cch-client-act";
+ } else if (app.treatLikeActivity) {
+ // This is a cached process, but somebody wants us to treat it like it has
+ // an activity, okay!
+ procState = PROCESS_STATE_CACHED_ACTIVITY;
+ app.adjType = "cch-as-act";
+ }
+ }
+
+ if (adj == ProcessList.SERVICE_ADJ) {
+ if (doingAll) {
+ app.serviceb = mNewNumAServiceProcs > (mNumServiceProcs/3);
+ mNewNumServiceProcs++;
+ //Slog.i(TAG, "ADJ " + app + " serviceb=" + app.serviceb);
+ if (!app.serviceb) {
+ // This service isn't far enough down on the LRU list to
+ // normally be a B service, but if we are low on RAM and it
+ // is large we want to force it down since we would prefer to
+ // keep launcher over it.
+ if (mService.mLastMemoryLevel > ProcessStats.ADJ_MEM_FACTOR_NORMAL
+ && app.lastPss >= mProcessList.getCachedRestoreThresholdKb()) {
+ app.serviceHighRam = true;
+ app.serviceb = true;
+ //Slog.i(TAG, "ADJ " + app + " high ram!");
+ } else {
+ mNewNumAServiceProcs++;
+ //Slog.i(TAG, "ADJ " + app + " not high ram!");
+ }
+ } else {
+ app.serviceHighRam = false;
+ }
+ }
+ if (app.serviceb) {
+ adj = ProcessList.SERVICE_B_ADJ;
+ }
+ }
+
+ app.setCurRawAdj(adj);
+
+ //Slog.i(TAG, "OOM ADJ " + app + ": pid=" + app.pid +
+ // " adj=" + adj + " curAdj=" + app.curAdj + " maxAdj=" + app.maxAdj);
+ if (adj > app.maxAdj) {
+ adj = app.maxAdj;
+ if (app.maxAdj <= ProcessList.PERCEPTIBLE_APP_ADJ) {
+ schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
+ }
+ }
+
+ // Put bound foreground services in a special sched group for additional
+ // restrictions on screen off
+ if (procState >= ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE &&
+ mService.mWakefulness != PowerManagerInternal.WAKEFULNESS_AWAKE) {
+ if (schedGroup > ProcessList.SCHED_GROUP_RESTRICTED) {
+ schedGroup = ProcessList.SCHED_GROUP_RESTRICTED;
+ }
+ }
+
+ // Do final modification to adj. Everything we do between here and applying
+ // the final setAdj must be done in this function, because we will also use
+ // it when computing the final cached adj later. Note that we don't need to
+ // worry about this for max adj above, since max adj will always be used to
+ // keep it out of the cached vaues.
+ app.curAdj = app.modifyRawOomAdj(adj);
+ app.setCurrentSchedulingGroup(schedGroup);
+ app.setCurProcState(procState);
+ app.setCurRawProcState(procState);
+ app.setHasForegroundActivities(foregroundActivities);
+ app.completedAdjSeq = mAdjSeq;
+
+ // if curAdj or curProcState improved, then this process was promoted
+ return app.curAdj < prevAppAdj || app.getCurProcState() < prevProcState;
+ }
+
+ /**
+ * Checks if for the given app and client, there's a cycle that should skip over the client
+ * for now or use partial values to evaluate the effect of the client binding.
+ * @param app
+ * @param client
+ * @param procState procstate evaluated so far for this app
+ * @param adj oom_adj evaluated so far for this app
+ * @param cycleReEval whether we're currently re-evaluating due to a cycle, and not the first
+ * evaluation.
+ * @return whether to skip using the client connection at this time
+ */
+ private boolean shouldSkipDueToCycle(ProcessRecord app, ProcessRecord client,
+ int procState, int adj, boolean cycleReEval) {
+ if (client.containsCycle) {
+ // We've detected a cycle. We should retry computeOomAdjLocked later in
+ // case a later-checked connection from a client would raise its
+ // priority legitimately.
+ app.containsCycle = true;
+ // If the client has not been completely evaluated, check if it's worth
+ // using the partial values.
+ if (client.completedAdjSeq < mAdjSeq) {
+ if (cycleReEval) {
+ // If the partial values are no better, skip until the next
+ // attempt
+ if (client.getCurRawProcState() >= procState
+ && client.getCurRawAdj() >= adj) {
+ return true;
+ }
+ // Else use the client's partial procstate and adj to adjust the
+ // effect of the binding
+ } else {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /** Inform the oomadj observer of changes to oomadj. Used by tests. */
+ @GuardedBy("mService")
+ void reportOomAdjMessageLocked(String tag, String msg) {
+ Slog.d(tag, msg);
+ if (mService.mCurOomAdjObserver != null) {
+ mService.mUiHandler.obtainMessage(DISPATCH_OOM_ADJ_OBSERVER_MSG, msg).sendToTarget();
+ }
+ }
+
+ /** Applies the computed oomadj, procstate and sched group values and freezes them in set* */
+ @GuardedBy("mService")
+ private final boolean applyOomAdjLocked(ProcessRecord app, boolean doingAll, long now,
+ long nowElapsed) {
+ boolean success = true;
+
+ if (app.getCurRawAdj() != app.setRawAdj) {
+ app.setRawAdj = app.getCurRawAdj();
+ }
+
+ int changes = 0;
+
+ if (app.curAdj != app.setAdj) {
+ // don't compact during bootup
+ if (mConstants.USE_COMPACTION && mService.mBooted) {
+ // Perform a minor compaction when a perceptible app becomes the prev/home app
+ // Perform a major compaction when any app enters cached
+ // reminder: here, setAdj is previous state, curAdj is upcoming state
+ if (app.setAdj <= ProcessList.PERCEPTIBLE_APP_ADJ &&
+ (app.curAdj == ProcessList.PREVIOUS_APP_ADJ ||
+ app.curAdj == ProcessList.HOME_APP_ADJ)) {
+ mAppCompact.compactAppSome(app);
+ } else if (app.setAdj < ProcessList.CACHED_APP_MIN_ADJ &&
+ app.curAdj >= ProcessList.CACHED_APP_MIN_ADJ) {
+ mAppCompact.compactAppFull(app);
+ }
+ }
+ ProcessList.setOomAdj(app.pid, app.uid, app.curAdj);
+ if (DEBUG_SWITCH || DEBUG_OOM_ADJ || mService.mCurOomAdjUid == app.info.uid) {
+ String msg = "Set " + app.pid + " " + app.processName + " adj "
+ + app.curAdj + ": " + app.adjType;
+ reportOomAdjMessageLocked(TAG_OOM_ADJ, msg);
+ }
+ app.setAdj = app.curAdj;
+ app.verifiedAdj = ProcessList.INVALID_ADJ;
+ }
+
+ final int curSchedGroup = app.getCurrentSchedulingGroup();
+ if (app.setSchedGroup != curSchedGroup) {
+ int oldSchedGroup = app.setSchedGroup;
+ app.setSchedGroup = curSchedGroup;
+ if (DEBUG_SWITCH || DEBUG_OOM_ADJ || mService.mCurOomAdjUid == app.uid) {
+ String msg = "Setting sched group of " + app.processName
+ + " to " + curSchedGroup + ": " + app.adjType;
+ reportOomAdjMessageLocked(TAG_OOM_ADJ, msg);
+ }
+ if (app.waitingToKill != null && app.curReceivers.isEmpty()
+ && app.setSchedGroup == ProcessList.SCHED_GROUP_BACKGROUND) {
+ app.kill(app.waitingToKill, true);
+ success = false;
+ } else {
+ int processGroup;
+ switch (curSchedGroup) {
+ case ProcessList.SCHED_GROUP_BACKGROUND:
+ processGroup = THREAD_GROUP_BG_NONINTERACTIVE;
+ break;
+ case ProcessList.SCHED_GROUP_TOP_APP:
+ case ProcessList.SCHED_GROUP_TOP_APP_BOUND:
+ processGroup = THREAD_GROUP_TOP_APP;
+ break;
+ case ProcessList.SCHED_GROUP_RESTRICTED:
+ processGroup = THREAD_GROUP_RESTRICTED;
+ break;
+ default:
+ processGroup = THREAD_GROUP_DEFAULT;
+ break;
+ }
+ long oldId = Binder.clearCallingIdentity();
+ try {
+ setProcessGroup(app.pid, processGroup);
+ if (curSchedGroup == ProcessList.SCHED_GROUP_TOP_APP) {
+ // do nothing if we already switched to RT
+ if (oldSchedGroup != ProcessList.SCHED_GROUP_TOP_APP) {
+ app.getWindowProcessController().onTopProcChanged();
+ if (mService.mUseFifoUiScheduling) {
+ // Switch UI pipeline for app to SCHED_FIFO
+ app.savedPriority = Process.getThreadPriority(app.pid);
+ mService.scheduleAsFifoPriority(app.pid, /* suppressLogs */true);
+ if (app.renderThreadTid != 0) {
+ mService.scheduleAsFifoPriority(app.renderThreadTid,
+ /* suppressLogs */true);
+ if (DEBUG_OOM_ADJ) {
+ Slog.d("UI_FIFO", "Set RenderThread (TID " +
+ app.renderThreadTid + ") to FIFO");
+ }
+ } else {
+ if (DEBUG_OOM_ADJ) {
+ Slog.d("UI_FIFO", "Not setting RenderThread TID");
+ }
+ }
+ } else {
+ // Boost priority for top app UI and render threads
+ setThreadPriority(app.pid, TOP_APP_PRIORITY_BOOST);
+ if (app.renderThreadTid != 0) {
+ try {
+ setThreadPriority(app.renderThreadTid,
+ TOP_APP_PRIORITY_BOOST);
+ } catch (IllegalArgumentException e) {
+ // thread died, ignore
+ }
+ }
+ }
+ }
+ } else if (oldSchedGroup == ProcessList.SCHED_GROUP_TOP_APP &&
+ curSchedGroup != ProcessList.SCHED_GROUP_TOP_APP) {
+ app.getWindowProcessController().onTopProcChanged();
+ if (mService.mUseFifoUiScheduling) {
+ try {
+ // Reset UI pipeline to SCHED_OTHER
+ setThreadScheduler(app.pid, SCHED_OTHER, 0);
+ setThreadPriority(app.pid, app.savedPriority);
+ if (app.renderThreadTid != 0) {
+ setThreadScheduler(app.renderThreadTid,
+ SCHED_OTHER, 0);
+ setThreadPriority(app.renderThreadTid, -4);
+ }
+ } catch (IllegalArgumentException e) {
+ Slog.w(TAG,
+ "Failed to set scheduling policy, thread does not exist:\n"
+ + e);
+ } catch (SecurityException e) {
+ Slog.w(TAG, "Failed to set scheduling policy, not allowed:\n" + e);
+ }
+ } else {
+ // Reset priority for top app UI and render threads
+ setThreadPriority(app.pid, 0);
+ if (app.renderThreadTid != 0) {
+ setThreadPriority(app.renderThreadTid, 0);
+ }
+ }
+ }
+ } catch (Exception e) {
+ if (false) {
+ Slog.w(TAG, "Failed setting process group of " + app.pid
+ + " to " + app.getCurrentSchedulingGroup());
+ Slog.w(TAG, "at location", e);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(oldId);
+ }
+ }
+ }
+ if (app.repForegroundActivities != app.hasForegroundActivities()) {
+ app.repForegroundActivities = app.hasForegroundActivities();
+ changes |= ActivityManagerService.ProcessChangeItem.CHANGE_ACTIVITIES;
+ }
+ if (app.getReportedProcState() != app.getCurProcState()) {
+ app.setReportedProcState(app.getCurProcState());
+ if (app.thread != null) {
+ try {
+ if (false) {
+ //RuntimeException h = new RuntimeException("here");
+ Slog.i(TAG, "Sending new process state " + app.getReportedProcState()
+ + " to " + app /*, h*/);
+ }
+ app.thread.setProcessState(app.getReportedProcState());
+ } catch (RemoteException e) {
+ }
+ }
+ }
+ if (app.setProcState == PROCESS_STATE_NONEXISTENT
+ || ProcessList.procStatesDifferForMem(app.getCurProcState(), app.setProcState)) {
+ if (false && mService.mTestPssMode
+ && app.setProcState >= 0 && app.lastStateTime <= (now-200)) {
+ // Experimental code to more aggressively collect pss while
+ // running test... the problem is that this tends to collect
+ // the data right when a process is transitioning between process
+ // states, which will tend to give noisy data.
+ long start = SystemClock.uptimeMillis();
+ long startTime = SystemClock.currentThreadTimeMillis();
+ long pss = Debug.getPss(app.pid, mTmpLong, null);
+ long endTime = SystemClock.currentThreadTimeMillis();
+ mService.recordPssSampleLocked(app, app.getCurProcState(), pss,
+ mTmpLong[0], mTmpLong[1], mTmpLong[2],
+ ProcessStats.ADD_PSS_INTERNAL_SINGLE, endTime-startTime, now);
+ mService.mPendingPssProcesses.remove(app);
+ Slog.i(TAG, "Recorded pss for " + app + " state " + app.setProcState
+ + " to " + app.getCurProcState() + ": "
+ + (SystemClock.uptimeMillis()-start) + "ms");
+ }
+ app.lastStateTime = now;
+ app.nextPssTime = ProcessList.computeNextPssTime(app.getCurProcState(),
+ app.procStateMemTracker, mService.mTestPssMode,
+ mService.mAtmInternal.isSleeping(), now);
+ if (DEBUG_PSS) Slog.d(TAG_PSS, "Process state change from "
+ + ProcessList.makeProcStateString(app.setProcState) + " to "
+ + ProcessList.makeProcStateString(app.getCurProcState()) + " next pss in "
+ + (app.nextPssTime-now) + ": " + app);
+ } else {
+ if (now > app.nextPssTime || (now > (app.lastPssTime+ProcessList.PSS_MAX_INTERVAL)
+ && now > (app.lastStateTime+ProcessList.minTimeFromStateChange(
+ mService.mTestPssMode)))) {
+ if (mService.requestPssLocked(app, app.setProcState)) {
+ app.nextPssTime = ProcessList.computeNextPssTime(app.getCurProcState(),
+ app.procStateMemTracker, mService.mTestPssMode,
+ mService.mAtmInternal.isSleeping(), now);
+ }
+ } else if (false && DEBUG_PSS) {
+ Slog.d(TAG_PSS,
+ "Not requesting pss of " + app + ": next=" + (app.nextPssTime-now));
+ }
+ }
+ if (app.setProcState != app.getCurProcState()) {
+ if (DEBUG_SWITCH || DEBUG_OOM_ADJ || mService.mCurOomAdjUid == app.uid) {
+ String msg = "Proc state change of " + app.processName
+ + " to " + ProcessList.makeProcStateString(app.getCurProcState())
+ + " (" + app.getCurProcState() + ")" + ": " + app.adjType;
+ reportOomAdjMessageLocked(TAG_OOM_ADJ, msg);
+ }
+ boolean setImportant = app.setProcState < ActivityManager.PROCESS_STATE_SERVICE;
+ boolean curImportant = app.getCurProcState() < ActivityManager.PROCESS_STATE_SERVICE;
+ if (setImportant && !curImportant) {
+ // This app is no longer something we consider important enough to allow to use
+ // arbitrary amounts of battery power. Note its current CPU time to later know to
+ // kill it if it is not behaving well.
+ app.setWhenUnimportant(now);
+ app.lastCpuTime = 0;
+ }
+ // Inform UsageStats of important process state change
+ // Must be called before updating setProcState
+ maybeUpdateUsageStatsLocked(app, nowElapsed);
+
+ maybeUpdateLastTopTime(app, now);
+
+ app.setProcState = app.getCurProcState();
+ if (app.setProcState >= ActivityManager.PROCESS_STATE_HOME) {
+ app.notCachedSinceIdle = false;
+ }
+ if (!doingAll) {
+ mService.setProcessTrackerStateLocked(app,
+ mService.mProcessStats.getMemFactorLocked(), now);
+ } else {
+ app.procStateChanged = true;
+ }
+ } else if (app.reportedInteraction && (nowElapsed - app.getInteractionEventTime())
+ > mConstants.USAGE_STATS_INTERACTION_INTERVAL) {
+ // For apps that sit around for a long time in the interactive state, we need
+ // to report this at least once a day so they don't go idle.
+ maybeUpdateUsageStatsLocked(app, nowElapsed);
+ }
+
+ if (changes != 0) {
+ if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS,
+ "Changes in " + app + ": " + changes);
+ int i = mService.mPendingProcessChanges.size()-1;
+ ActivityManagerService.ProcessChangeItem item = null;
+ while (i >= 0) {
+ item = mService.mPendingProcessChanges.get(i);
+ if (item.pid == app.pid) {
+ if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS,
+ "Re-using existing item: " + item);
+ break;
+ }
+ i--;
+ }
+ if (i < 0) {
+ // No existing item in pending changes; need a new one.
+ final int NA = mService.mAvailProcessChanges.size();
+ if (NA > 0) {
+ item = mService.mAvailProcessChanges.remove(NA-1);
+ if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS,
+ "Retrieving available item: " + item);
+ } else {
+ item = new ActivityManagerService.ProcessChangeItem();
+ if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS,
+ "Allocating new item: " + item);
+ }
+ item.changes = 0;
+ item.pid = app.pid;
+ item.uid = app.info.uid;
+ if (mService.mPendingProcessChanges.size() == 0) {
+ if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS,
+ "*** Enqueueing dispatch processes changed!");
+ mService.mUiHandler.obtainMessage(DISPATCH_PROCESSES_CHANGED_UI_MSG)
+ .sendToTarget();
+ }
+ mService.mPendingProcessChanges.add(item);
+ }
+ item.changes |= changes;
+ item.foregroundActivities = app.repForegroundActivities;
+ if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS,
+ "Item " + Integer.toHexString(System.identityHashCode(item))
+ + " " + app.toShortString() + ": changes=" + item.changes
+ + " foreground=" + item.foregroundActivities
+ + " type=" + app.adjType + " source=" + app.adjSource
+ + " target=" + app.adjTarget);
+ }
+
+ return success;
+ }
+
+ @GuardedBy("mService")
+ private void maybeUpdateUsageStatsLocked(ProcessRecord app, long nowElapsed) {
+ if (DEBUG_USAGE_STATS) {
+ Slog.d(TAG, "Checking proc [" + Arrays.toString(app.getPackageList())
+ + "] state changes: old = " + app.setProcState + ", new = "
+ + app.getCurProcState());
+ }
+ if (mService.mUsageStatsService == null) {
+ return;
+ }
+ boolean isInteraction;
+ // To avoid some abuse patterns, we are going to be careful about what we consider
+ // to be an app interaction. Being the top activity doesn't count while the display
+ // is sleeping, nor do short foreground services.
+ if (app.getCurProcState() <= ActivityManager.PROCESS_STATE_TOP) {
+ isInteraction = true;
+ app.setFgInteractionTime(0);
+ } else if (app.getCurProcState() <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
+ if (app.getFgInteractionTime() == 0) {
+ app.setFgInteractionTime(nowElapsed);
+ isInteraction = false;
+ } else {
+ isInteraction = nowElapsed > app.getFgInteractionTime()
+ + mConstants.SERVICE_USAGE_INTERACTION_TIME;
+ }
+ } else {
+ isInteraction =
+ app.getCurProcState() <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
+ app.setFgInteractionTime(0);
+ }
+ if (isInteraction
+ && (!app.reportedInteraction || (nowElapsed - app.getInteractionEventTime())
+ > mConstants.USAGE_STATS_INTERACTION_INTERVAL)) {
+ app.setInteractionEventTime(nowElapsed);
+ String[] packages = app.getPackageList();
+ if (packages != null) {
+ for (int i = 0; i < packages.length; i++) {
+ mService.mUsageStatsService.reportEvent(packages[i], app.userId,
+ UsageEvents.Event.SYSTEM_INTERACTION);
+ }
+ }
+ }
+ app.reportedInteraction = isInteraction;
+ if (!isInteraction) {
+ app.setInteractionEventTime(0);
+ }
+ }
+
+ private void maybeUpdateLastTopTime(ProcessRecord app, long nowUptime) {
+ if (app.setProcState <= ActivityManager.PROCESS_STATE_TOP
+ && app.getCurProcState() > ActivityManager.PROCESS_STATE_TOP) {
+ app.lastTopTime = nowUptime;
+ }
+ }
+
+ /**
+ * Look for recently inactive apps and mark them idle after a grace period. If idled, stop
+ * any background services and inform listeners.
+ */
+ @GuardedBy("mService")
+ void idleUidsLocked() {
+ final int N = mActiveUids.size();
+ if (N <= 0) {
+ return;
+ }
+ final long nowElapsed = SystemClock.elapsedRealtime();
+ final long maxBgTime = nowElapsed - mConstants.BACKGROUND_SETTLE_TIME;
+ long nextTime = 0;
+ if (mLocalPowerManager != null) {
+ mLocalPowerManager.startUidChanges();
+ }
+ for (int i = N - 1; i >= 0; i--) {
+ final UidRecord uidRec = mActiveUids.valueAt(i);
+ final long bgTime = uidRec.lastBackgroundTime;
+ if (bgTime > 0 && !uidRec.idle) {
+ if (bgTime <= maxBgTime) {
+ EventLogTags.writeAmUidIdle(uidRec.uid);
+ uidRec.idle = true;
+ uidRec.setIdle = true;
+ mService.doStopUidLocked(uidRec.uid, uidRec);
+ } else {
+ if (nextTime == 0 || nextTime > bgTime) {
+ nextTime = bgTime;
+ }
+ }
+ }
+ }
+ if (mLocalPowerManager != null) {
+ mLocalPowerManager.finishUidChanges();
+ }
+ if (nextTime > 0) {
+ mService.mHandler.removeMessages(IDLE_UIDS_MSG);
+ mService.mHandler.sendEmptyMessageDelayed(IDLE_UIDS_MSG,
+ nextTime + mConstants.BACKGROUND_SETTLE_TIME - nowElapsed);
+ }
+ }
+
+ @GuardedBy("mService")
+ final void setAppIdTempWhitelistStateLocked(int appId, boolean onWhitelist) {
+ boolean changed = false;
+ for (int i = mActiveUids.size() - 1; i >= 0; i--) {
+ final UidRecord uidRec = mActiveUids.valueAt(i);
+ if (UserHandle.getAppId(uidRec.uid) == appId && uidRec.curWhitelist != onWhitelist) {
+ uidRec.curWhitelist = onWhitelist;
+ changed = true;
+ }
+ }
+ if (changed) {
+ updateOomAdjLocked();
+ }
+ }
+
+ @GuardedBy("mService")
+ final void setUidTempWhitelistStateLocked(int uid, boolean onWhitelist) {
+ boolean changed = false;
+ final UidRecord uidRec = mActiveUids.get(uid);
+ if (uidRec != null && uidRec.curWhitelist != onWhitelist) {
+ uidRec.curWhitelist = onWhitelist;
+ updateOomAdjLocked();
+ }
+ }
+
+ @GuardedBy("mService")
+ void dumpProcessListVariablesLocked(ProtoOutputStream proto) {
+ proto.write(ActivityManagerServiceDumpProcessesProto.ADJ_SEQ, mAdjSeq);
+ proto.write(ActivityManagerServiceDumpProcessesProto.LRU_SEQ, mProcessList.mLruSeq);
+ proto.write(ActivityManagerServiceDumpProcessesProto.NUM_NON_CACHED_PROCS,
+ mNumNonCachedProcs);
+ proto.write(ActivityManagerServiceDumpProcessesProto.NUM_SERVICE_PROCS, mNumServiceProcs);
+ proto.write(ActivityManagerServiceDumpProcessesProto.NEW_NUM_SERVICE_PROCS,
+ mNewNumServiceProcs);
+
+ }
+
+ @GuardedBy("mService")
+ void dumpSequenceNumbersLocked(PrintWriter pw) {
+ pw.println(" mAdjSeq=" + mAdjSeq + " mLruSeq=" + mProcessList.mLruSeq);
+ }
+
+ @GuardedBy("mService")
+ void dumpProcCountsLocked(PrintWriter pw) {
+ pw.println(" mNumNonCachedProcs=" + mNumNonCachedProcs
+ + " (" + mProcessList.getLruSizeLocked() + " total)"
+ + " mNumCachedHiddenProcs=" + mNumCachedHiddenProcs
+ + " mNumServiceProcs=" + mNumServiceProcs
+ + " mNewNumServiceProcs=" + mNewNumServiceProcs);
+ }
+
+}
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 9898d06..5bc8845 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -17,6 +17,7 @@
package com.android.server.am;
import static android.app.ActivityManager.PROCESS_STATE_CACHED_ACTIVITY;
+import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
import static android.app.ActivityThread.PROC_START_SEQ_IDENT;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AUTO;
import static android.os.Process.SYSTEM_UID;
@@ -123,7 +124,7 @@
* </ul>
*/
public final class ProcessList {
- private static final String TAG = TAG_WITH_CLASS_NAME ? "ProcessList" : TAG_AM;
+ static final String TAG = TAG_WITH_CLASS_NAME ? "ProcessList" : TAG_AM;
// The minimum time we allow between crashes, for us to consider this
// application to be bad and stop and its services and reject broadcasts.
@@ -352,6 +353,8 @@
*/
int mLruSeq = 0;
+ ActiveUids mActiveUids;
+
/**
* The currently running isolated processes.
*/
@@ -549,8 +552,10 @@
updateOomLevels(0, 0, false);
}
- void init(ActivityManagerService service) {
+ void init(ActivityManagerService service, ActiveUids activeUids) {
mService = service;
+ mActiveUids = activeUids;
+
if (sKillHandler == null) {
sKillThread = new ServiceThread(TAG + ":kill",
THREAD_PRIORITY_BACKGROUND, true /* allowIo */);
@@ -2176,7 +2181,7 @@
} else if (old != null) {
Slog.wtf(TAG, "Already have existing proc " + old + " when adding " + proc);
}
- UidRecord uidRec = mService.mActiveUids.get(proc.uid);
+ UidRecord uidRec = mActiveUids.get(proc.uid);
if (uidRec == null) {
uidRec = new UidRecord(proc.uid, mService.mAtmInternal);
// This is the first appearance of the uid, report it now!
@@ -2188,7 +2193,7 @@
uidRec.setWhitelist = uidRec.curWhitelist = true;
}
uidRec.updateHasInternetPermission();
- mService.mActiveUids.put(proc.uid, uidRec);
+ mActiveUids.put(proc.uid, uidRec);
EventLogTags.writeAmUidRunning(uidRec.uid);
mService.noteUidProcessState(uidRec.uid, uidRec.getCurProcState());
}
@@ -2290,7 +2295,7 @@
"No more processes in " + old.uidRecord);
mService.enqueueUidChangeLocked(old.uidRecord, -1, UidRecord.CHANGE_GONE);
EventLogTags.writeAmUidStopped(uid);
- mService.mActiveUids.remove(uid);
+ mActiveUids.remove(uid);
mService.noteUidProcessState(uid, ActivityManager.PROCESS_STATE_NONEXISTENT);
}
old.uidRecord = null;
@@ -3050,4 +3055,37 @@
}
}
}
+
+ /** Returns the uid's process state or PROCESS_STATE_NONEXISTENT if not running */
+ @GuardedBy("mService")
+ int getUidProcStateLocked(int uid) {
+ UidRecord uidRec = mActiveUids.get(uid);
+ return uidRec == null ? PROCESS_STATE_NONEXISTENT : uidRec.getCurProcState();
+ }
+
+ /** Returns the UidRecord for the given uid, if it exists. */
+ @GuardedBy("mService")
+ UidRecord getUidRecordLocked(int uid) {
+ return mActiveUids.get(uid);
+ }
+
+ /**
+ * Call {@link ActivityManagerService#doStopUidLocked}
+ * (which will also stop background services) for all idle UIDs.
+ */
+ @GuardedBy("mService")
+ void doStopUidForIdleUidsLocked() {
+ final int size = mActiveUids.size();
+ for (int i = 0; i < size; i++) {
+ final int uid = mActiveUids.keyAt(i);
+ if (UserHandle.isCore(uid)) {
+ continue;
+ }
+ final UidRecord uidRec = mActiveUids.valueAt(i);
+ if (!uidRec.idle) {
+ continue;
+ }
+ mService.doStopUidLocked(uidRec.uid, uidRec);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index d5ede5b..c2117a7 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -77,6 +77,7 @@
// permission in the corresponding .te file your feature belongs to.
@VisibleForTesting
static final String[] sDeviceConfigScopes = new String[] {
+ DeviceConfig.NAMESPACE_INPUT_NATIVE_BOOT,
};
private final String[] mGlobalSettings;
diff --git a/services/core/java/com/android/server/hdmi/Constants.java b/services/core/java/com/android/server/hdmi/Constants.java
index 6f5a196..ff029c1 100644
--- a/services/core/java/com/android/server/hdmi/Constants.java
+++ b/services/core/java/com/android/server/hdmi/Constants.java
@@ -343,14 +343,6 @@
static final String PROPERTY_HDMI_CEC_NEVER_ASSIGN_LOGICAL_ADDRESSES =
"ro.hdmi.property_hdmi_cec_never_assign_logical_addresses";
- /**
- * Property to indicate if the current device is a cec switch device.
- *
- * <p> Default is false.
- */
- static final String PROPERTY_HDMI_IS_DEVICE_HDMI_CEC_SWITCH =
- "ro.hdmi.property_is_device_hdmi_cec_switch";
-
// Set to false to allow playback device to go to suspend mode even
// when it's an active source. True by default.
static final String PROPERTY_KEEP_AWAKE = "persist.sys.hdmi.keep_awake";
diff --git a/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
index b75e75f..ba21b78 100755
--- a/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
+++ b/services/core/java/com/android/server/hdmi/DeviceDiscoveryAction.java
@@ -28,7 +28,7 @@
/**
* Feature action that handles device discovery sequences.
- * Device discovery is launched when TV device is woken from "Standby" state
+ * Device discovery is launched when device is woken from "Standby" state
* or enabled "Control for Hdmi" from disabled state.
*
* <p>Device discovery goes through the following steps.
@@ -89,6 +89,7 @@
private final DeviceDiscoveryCallback mCallback;
private int mProcessedDeviceCount = 0;
private int mTimeoutRetry = 0;
+ private boolean mIsTvDevice = source().mService.isTvDevice();
/**
* Constructor.
@@ -266,15 +267,19 @@
current.mPortId = getPortId(current.mPhysicalAddress);
current.mDeviceType = params[2] & 0xFF;
- tv().updateCecSwitchInfo(current.mLogicalAddress, current.mDeviceType,
+ // TODO(amyjojo): check if non-TV device needs to update cec switch info.
+ // This is to manager CEC device separately in case they don't have address.
+ if (mIsTvDevice) {
+ tv().updateCecSwitchInfo(current.mLogicalAddress, current.mDeviceType,
current.mPhysicalAddress);
-
+ }
increaseProcessedDeviceCount();
checkAndProceedStage();
}
private int getPortId(int physicalAddress) {
- return tv().getPortId(physicalAddress);
+ return mIsTvDevice ? tv().getPortId(physicalAddress)
+ : source().getPortId(physicalAddress);
}
private void handleSetOsdName(HdmiCecMessage cmd) {
@@ -345,7 +350,9 @@
mCallback.onDeviceDiscoveryDone(result);
finish();
// Process any commands buffered while device discovery action was in progress.
- tv().processAllDelayedMessages();
+ if (mIsTvDevice) {
+ tv().processAllDelayedMessages();
+ }
}
private void checkAndProceedStage() {
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
index 6e1b018..32dc0261 100755
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
@@ -916,6 +916,11 @@
setActivePath(mService.portIdToPath(portId));
}
+ // Returns the id of the port that the target device is connected to.
+ int getPortId(int physicalAddress) {
+ return mService.pathToPortId(physicalAddress);
+ }
+
@ServiceThreadOnly
HdmiCecMessageCache getCecMessageCache() {
assertRunOnServiceThread();
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
index 8cfe47f..0e4e334 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystem.java
@@ -23,19 +23,25 @@
import android.content.Intent;
import android.hardware.hdmi.HdmiControlManager;
import android.hardware.hdmi.HdmiDeviceInfo;
+import android.hardware.hdmi.HdmiPortInfo;
import android.hardware.hdmi.IHdmiControlCallback;
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.media.AudioSystem;
import android.media.tv.TvContract;
import android.os.SystemProperties;
+import android.util.Slog;
+import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.hdmi.Constants.AudioCodec;
+import com.android.server.hdmi.DeviceDiscoveryAction.DeviceDiscoveryCallback;
import com.android.server.hdmi.HdmiAnnotations.ServiceThreadOnly;
+import java.io.UnsupportedEncodingException;
import java.util.HashMap;
+import java.util.List;
/**
* Represent a logical device of type {@link HdmiDeviceInfo#DEVICE_AUDIO_SYSTEM} residing in Android
@@ -71,6 +77,10 @@
// processing.
private final HashMap<Integer, String> mTvInputs = new HashMap<>();
+ // Map-like container of all cec devices.
+ // device id is used as key of container.
+ private final SparseArray<HdmiDeviceInfo> mDeviceInfos = new SparseArray<>();
+
protected HdmiCecLocalDeviceAudioSystem(HdmiControlService service) {
super(service, HdmiDeviceInfo.DEVICE_AUDIO_SYSTEM);
mSystemAudioControlFeatureEnabled = true;
@@ -86,6 +96,132 @@
"com.droidlogic.tvinput/.services.Hdmi3InputService/HW7");
}
+ /**
+ * Called when a device is newly added or a new device is detected or
+ * an existing device is updated.
+ *
+ * @param info device info of a new device.
+ */
+ @ServiceThreadOnly
+ final void addCecDevice(HdmiDeviceInfo info) {
+ assertRunOnServiceThread();
+ HdmiDeviceInfo old = addDeviceInfo(info);
+ if (info.getPhysicalAddress() == mService.getPhysicalAddress()) {
+ // The addition of the device itself should not be notified.
+ // Note that different logical address could still be the same local device.
+ return;
+ }
+ if (old == null) {
+ invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_ADD_DEVICE);
+ } else if (!old.equals(info)) {
+ invokeDeviceEventListener(old, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE);
+ invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_ADD_DEVICE);
+ }
+ }
+
+ /**
+ * Called when a device is removed or removal of device is detected.
+ *
+ * @param address a logical address of a device to be removed
+ */
+ @ServiceThreadOnly
+ final void removeCecDevice(int address) {
+ assertRunOnServiceThread();
+ HdmiDeviceInfo info = removeDeviceInfo(HdmiDeviceInfo.idForCecDevice(address));
+
+ mCecMessageCache.flushMessagesFrom(address);
+ invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE);
+ }
+
+ /**
+ * Called when a device is updated.
+ *
+ * @param info device info of the updating device.
+ */
+ @ServiceThreadOnly
+ final void updateCecDevice(HdmiDeviceInfo info) {
+ assertRunOnServiceThread();
+ HdmiDeviceInfo old = addDeviceInfo(info);
+
+ if (old == null) {
+ invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_ADD_DEVICE);
+ } else if (!old.equals(info)) {
+ invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE);
+ }
+ }
+
+ /**
+ * Add a new {@link HdmiDeviceInfo}. It returns old device info which has the same
+ * logical address as new device info's.
+ *
+ * @param deviceInfo a new {@link HdmiDeviceInfo} to be added.
+ * @return {@code null} if it is new device. Otherwise, returns old {@HdmiDeviceInfo}
+ * that has the same logical address as new one has.
+ */
+ @ServiceThreadOnly
+ @VisibleForTesting
+ protected HdmiDeviceInfo addDeviceInfo(HdmiDeviceInfo deviceInfo) {
+ assertRunOnServiceThread();
+ HdmiDeviceInfo oldDeviceInfo = getCecDeviceInfo(deviceInfo.getLogicalAddress());
+ if (oldDeviceInfo != null) {
+ removeDeviceInfo(deviceInfo.getId());
+ }
+ mDeviceInfos.append(deviceInfo.getId(), deviceInfo);
+ return oldDeviceInfo;
+ }
+
+ /**
+ * Remove a device info corresponding to the given {@code logicalAddress}.
+ * It returns removed {@link HdmiDeviceInfo} if exists.
+ *
+ * @param id id of device to be removed
+ * @return removed {@link HdmiDeviceInfo} it exists. Otherwise, returns {@code null}
+ */
+ @ServiceThreadOnly
+ private HdmiDeviceInfo removeDeviceInfo(int id) {
+ assertRunOnServiceThread();
+ HdmiDeviceInfo deviceInfo = mDeviceInfos.get(id);
+ if (deviceInfo != null) {
+ mDeviceInfos.remove(id);
+ }
+ return deviceInfo;
+ }
+
+ /**
+ * Return a {@link HdmiDeviceInfo} corresponding to the given {@code logicalAddress}.
+ *
+ * @param logicalAddress logical address of the device to be retrieved
+ * @return {@link HdmiDeviceInfo} matched with the given {@code logicalAddress}.
+ * Returns null if no logical address matched
+ */
+ @ServiceThreadOnly
+ HdmiDeviceInfo getCecDeviceInfo(int logicalAddress) {
+ assertRunOnServiceThread();
+ return mDeviceInfos.get(HdmiDeviceInfo.idForCecDevice(logicalAddress));
+ }
+
+ private void invokeDeviceEventListener(HdmiDeviceInfo info, int status) {
+ mService.invokeDeviceEventListeners(info, status);
+ }
+
+ @Override
+ @ServiceThreadOnly
+ void onHotplug(int portId, boolean connected) {
+ assertRunOnServiceThread();
+ if (connected) {
+ mService.wakeUp();
+ }
+ if (mService.getPortInfo(portId).getType() == HdmiPortInfo.PORT_OUTPUT) {
+ mCecMessageCache.flushAll();
+ } else {
+ if (connected) {
+ launchDeviceDiscovery();
+ } else {
+ // TODO(amyjojo): remove device from mDeviceInfo
+ }
+ }
+ }
+
@Override
@ServiceThreadOnly
protected void onStandby(boolean initiatedByCec, int standbyAction) {
@@ -116,6 +252,8 @@
boolean lastSystemAudioControlStatus =
SystemProperties.getBoolean(Constants.PROPERTY_LAST_SYSTEM_AUDIO_CONTROL, true);
systemAudioControlOnPowerOn(systemAudioControlOnPowerOnProp, lastSystemAudioControlStatus);
+ clearDeviceInfoList();
+ launchDeviceDiscovery();
startQueuedActions();
}
@@ -152,6 +290,78 @@
@Override
@ServiceThreadOnly
+ protected boolean handleReportPhysicalAddress(HdmiCecMessage message) {
+ assertRunOnServiceThread();
+ int path = HdmiUtils.twoBytesToInt(message.getParams());
+ int address = message.getSource();
+ int type = message.getParams()[2];
+
+ // Ignore if [Device Discovery Action] is going on.
+ if (hasAction(DeviceDiscoveryAction.class)) {
+ Slog.i(TAG, "Ignored while Device Discovery Action is in progress: " + message);
+ return true;
+ }
+
+ // Update the device info with TIF, note that the same device info could have added in
+ // device discovery and we do not want to override it with default OSD name. Therefore we
+ // need the following check to skip redundant device info updating.
+ HdmiDeviceInfo oldDevice = getCecDeviceInfo(address);
+ if (oldDevice == null || oldDevice.getPhysicalAddress() != path) {
+ addCecDevice(new HdmiDeviceInfo(
+ address, path, mService.pathToPortId(path), type,
+ Constants.UNKNOWN_VENDOR_ID, HdmiUtils.getDefaultDeviceName(address)));
+ // if we are adding a new device info, send out a give osd name command
+ // to update the name of the device in TIF
+ mService.sendCecCommand(
+ HdmiCecMessageBuilder.buildGiveOsdNameCommand(mAddress, address));
+ return true;
+ }
+
+ Slog.w(TAG, "Device info exists. Not updating on Physical Address.");
+ return true;
+ }
+
+ @Override
+ protected boolean handleReportPowerStatus(HdmiCecMessage command) {
+ int newStatus = command.getParams()[0] & 0xFF;
+ updateDevicePowerStatus(command.getSource(), newStatus);
+ return true;
+ }
+
+ @Override
+ @ServiceThreadOnly
+ protected boolean handleSetOsdName(HdmiCecMessage message) {
+ int source = message.getSource();
+ String osdName;
+ HdmiDeviceInfo deviceInfo = getCecDeviceInfo(source);
+ // If the device is not in device list, ignore it.
+ if (deviceInfo == null) {
+ Slog.i(TAG, "No source device info for <Set Osd Name>." + message);
+ return true;
+ }
+ try {
+ osdName = new String(message.getParams(), "US-ASCII");
+ } catch (UnsupportedEncodingException e) {
+ Slog.e(TAG, "Invalid <Set Osd Name> request:" + message, e);
+ return true;
+ }
+
+ if (deviceInfo.getDisplayName().equals(osdName)) {
+ Slog.d(TAG, "Ignore incoming <Set Osd Name> having same osd name:" + message);
+ return true;
+ }
+
+ Slog.d(TAG, "Updating device OSD name from "
+ + deviceInfo.getDisplayName()
+ + " to " + osdName);
+ updateCecDevice(new HdmiDeviceInfo(deviceInfo.getLogicalAddress(),
+ deviceInfo.getPhysicalAddress(), deviceInfo.getPortId(),
+ deviceInfo.getDeviceType(), deviceInfo.getVendorId(), osdName));
+ return true;
+ }
+
+ @Override
+ @ServiceThreadOnly
protected boolean handleReportAudioStatus(HdmiCecMessage message) {
assertRunOnServiceThread();
// TODO(amyjojo): implement report audio status handler
@@ -407,8 +617,8 @@
HdmiLogger.debug(
"System Audio Mode change[old:%b new:%b]",
mSystemAudioActivated, newSystemAudioMode);
- // Wake up device if System Audio Control is turned on but device is still on standby
- if (newSystemAudioMode && mService.isPowerStandbyOrTransient()) {
+ // Wake up device if System Audio Control is turned on
+ if (newSystemAudioMode) {
mService.wakeUp();
}
setSystemAudioMode(newSystemAudioMode);
@@ -494,19 +704,20 @@
invokeCallback(callback, HdmiControlManager.RESULT_SUCCESS);
return;
}
- getActiveSource().invalidate();
if (!mService.isControlEnabled()) {
+ setRoutingPort(portId);
setLocalActivePort(portId);
invokeCallback(callback, HdmiControlManager.RESULT_INCORRECT_MODE);
return;
}
- int oldPath = getLocalActivePort() != Constants.CEC_SWITCH_HOME
- ? getActivePathOnSwitchFromActivePortId(getLocalActivePort())
+ int oldPath = getRoutingPort() != Constants.CEC_SWITCH_HOME
+ ? mService.portIdToPath(getRoutingPort())
: getDeviceInfo().getPhysicalAddress();
- int newPath = getActivePathOnSwitchFromActivePortId(portId);
+ int newPath = mService.portIdToPath(portId);
if (oldPath == newPath) {
return;
}
+ setRoutingPort(portId);
setLocalActivePort(portId);
HdmiCecMessage routingChange =
HdmiCecMessageBuilder.buildRoutingChange(mAddress, oldPath, newPath);
@@ -575,10 +786,8 @@
mService.maySendFeatureAbortCommand(message, Constants.ABORT_REFUSED);
return;
}
- // Wake up device if it is still on standby
- if (mService.isPowerStandbyOrTransient()) {
- mService.wakeUp();
- }
+ // Wake up device
+ mService.wakeUp();
// Check if TV supports System Audio Control.
// Handle broadcasting setSystemAudioMode on or aborting message on callback.
queryTvSystemAudioModeSupport(new TvSystemAudioModeSupportedCallback() {
@@ -635,9 +844,7 @@
return;
}
// Wake up if the current device if ready to route.
- if (mService.isPowerStandbyOrTransient()) {
- mService.wakeUp();
- }
+ mService.wakeUp();
if (portId == Constants.CEC_SWITCH_HOME && mService.isPlaybackDevice()) {
switchToHomeTvInput();
} else if (portId == Constants.CEC_SWITCH_ARC) {
@@ -725,4 +932,50 @@
mAddress, routingInformationPath));
routeToInputFromPortId(getRoutingPort());
}
+
+ protected void updateDevicePowerStatus(int logicalAddress, int newPowerStatus) {
+ HdmiDeviceInfo info = getCecDeviceInfo(logicalAddress);
+ if (info == null) {
+ Slog.w(TAG, "Can not update power status of non-existing device:" + logicalAddress);
+ return;
+ }
+
+ if (info.getDevicePowerStatus() == newPowerStatus) {
+ return;
+ }
+
+ HdmiDeviceInfo newInfo = HdmiUtils.cloneHdmiDeviceInfo(info, newPowerStatus);
+ // addDeviceInfo replaces old device info with new one if exists.
+ addDeviceInfo(newInfo);
+
+ invokeDeviceEventListener(newInfo, HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE);
+ }
+
+ @ServiceThreadOnly
+ private void launchDeviceDiscovery() {
+ assertRunOnServiceThread();
+ DeviceDiscoveryAction action = new DeviceDiscoveryAction(this,
+ new DeviceDiscoveryCallback() {
+ @Override
+ public void onDeviceDiscoveryDone(List<HdmiDeviceInfo> deviceInfos) {
+ for (HdmiDeviceInfo info : deviceInfos) {
+ addCecDevice(info);
+ }
+ }
+ });
+ addAndStartAction(action);
+ }
+
+ // Clear all device info.
+ @ServiceThreadOnly
+ private void clearDeviceInfoList() {
+ assertRunOnServiceThread();
+ for (HdmiDeviceInfo info : HdmiUtils.sparseArrayToList(mDeviceInfos)) {
+ if (info.getPhysicalAddress() == mService.getPhysicalAddress()) {
+ continue;
+ }
+ invokeDeviceEventListener(info, HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE);
+ }
+ mDeviceInfos.clear();
+ }
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
index 6532e16..a95f7f1 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
@@ -16,7 +16,10 @@
package com.android.server.hdmi;
+import static com.android.internal.os.RoSystemProperties.PROPERTY_HDMI_IS_DEVICE_HDMI_CEC_SWITCH;
+
import android.hardware.hdmi.HdmiControlManager;
+import android.hardware.hdmi.HdmiPortInfo;
import android.hardware.hdmi.IHdmiControlCallback;
import android.os.SystemProperties;
import android.util.Slog;
@@ -42,7 +45,7 @@
// Device has cec switch functionality or not.
// Default is false.
protected boolean mIsSwitchDevice = SystemProperties.getBoolean(
- Constants.PROPERTY_HDMI_IS_DEVICE_HDMI_CEC_SWITCH, false);
+ PROPERTY_HDMI_IS_DEVICE_HDMI_CEC_SWITCH, false);
// Routing port number used for Routing Control.
// This records the default routing port or the previous valid routing port.
@@ -71,9 +74,11 @@
@ServiceThreadOnly
void onHotplug(int portId, boolean connected) {
assertRunOnServiceThread();
- mCecMessageCache.flushAll();
+ if (mService.getPortInfo(portId).getType() == HdmiPortInfo.PORT_OUTPUT) {
+ mCecMessageCache.flushAll();
+ }
// We'll not clear mIsActiveSource on the hotplug event to pass CETC 11.2.2-2 ~ 3.
- if (mService.isPowerStandbyOrTransient()) {
+ if (connected) {
mService.wakeUp();
}
}
@@ -117,6 +122,7 @@
setActiveSource(activeSource);
}
setIsActiveSource(physicalAddress == mService.getPhysicalAddress());
+ updateDevicePowerStatus(logicalAddress, HdmiControlManager.POWER_STATUS_ON);
switchInputOnReceivingNewActivePath(physicalAddress);
return true;
}
@@ -185,6 +191,13 @@
// do nothing
}
+ // Update the power status of the devices connected to the current device.
+ // This only works if the current device is a switch and keeps tracking the device info
+ // of the device connected to it.
+ protected void updateDevicePowerStatus(int logicalAddress, int newPowerStatus) {
+ // do nothing
+ }
+
// Active source claiming needs to be handled in Service
// since service can decide who will be the active source when the device supports
// multiple device types in this method.
@@ -204,10 +217,8 @@
if (!mIsActiveSource) {
return;
}
- // Wake up the device if the power is in standby mode
- if (mService.isPowerStandbyOrTransient()) {
- mService.wakeUp();
- }
+ // Wake up the device
+ mService.wakeUp();
return;
}
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index b91d8c6..a8c4350 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -346,10 +346,6 @@
}
}
- int getPortId(int physicalAddress) {
- return mService.pathToPortId(physicalAddress);
- }
-
/**
* Returns the previous port id kept to handle input switching on <Inactive Source>.
*/
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 833091d..2d6e762 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -19,6 +19,7 @@
import static android.hardware.hdmi.HdmiControlManager.DEVICE_EVENT_ADD_DEVICE;
import static android.hardware.hdmi.HdmiControlManager.DEVICE_EVENT_REMOVE_DEVICE;
+import static com.android.internal.os.RoSystemProperties.PROPERTY_HDMI_IS_DEVICE_HDMI_CEC_SWITCH;
import static com.android.server.hdmi.Constants.DISABLED;
import static com.android.server.hdmi.Constants.ENABLED;
import static com.android.server.hdmi.Constants.OPTION_MHL_ENABLE;
@@ -754,6 +755,9 @@
mPortInfoMap = new UnmodifiableSparseArray<>(portInfoMap);
mPortDeviceMap = new UnmodifiableSparseArray<>(portDeviceMap);
+ if (mMhlController == null) {
+ return;
+ }
HdmiPortInfo[] mhlPortInfo = mMhlController.getPortInfos();
ArraySet<Integer> mhlSupportedPorts = new ArraySet<Integer>(mhlPortInfo.length);
for (HdmiPortInfo info : mhlPortInfo) {
@@ -808,13 +812,31 @@
}
/**
- * Returns the id of HDMI port located at the top of the hierarchy of
- * the specified routing path. For the routing path 0x1220 (1.2.2.0), for instance,
- * the port id to be returned is the ID associated with the port address
- * 0x1000 (1.0.0.0) which is the topmost path of the given routing path.
+ * Returns the id of HDMI port located at the current device that runs this method.
+ *
+ * For TV with physical address 0x0000, target device 0x1120, we want port physical address
+ * 0x1000 to get the correct port id from {@link #mPortIdMap}. For device with Physical Address
+ * 0x2000, target device 0x2420, we want port address 0x24000 to get the port id.
+ *
+ * <p>Return {@link Constants#INVALID_PORT_ID} if target device does not connect to.
+ *
+ * @param path the target device's physical address.
+ * @return the id of the port that the target device eventually connects to
+ * on the current device.
*/
int pathToPortId(int path) {
- int portAddress = path & Constants.ROUTING_PATH_TOP_MASK;
+ int mask = 0xF000;
+ int finalMask = 0xF000;
+ int physicalAddress = getPhysicalAddress();
+ int maskedAddress = physicalAddress;
+
+ while (maskedAddress != 0) {
+ maskedAddress = physicalAddress & mask;
+ finalMask |= mask;
+ mask >>= 4;
+ }
+
+ int portAddress = path & finalMask;
return mPortIdMap.get(portAddress, Constants.INVALID_PORT_ID);
}
@@ -1007,8 +1029,9 @@
void onHotplug(int portId, boolean connected) {
assertRunOnServiceThread();
- if (connected && !isTvDevice()) {
- if (getPortInfo(portId).getType() == HdmiPortInfo.PORT_OUTPUT && isSwitchDevice()) {
+ if (connected && !isTvDevice()
+ && getPortInfo(portId).getType() == HdmiPortInfo.PORT_OUTPUT) {
+ if (isSwitchDevice()) {
initPortInfo();
HdmiLogger.debug("initPortInfo for switch device when onHotplug from tx.");
}
@@ -2130,7 +2153,7 @@
boolean isSwitchDevice() {
return SystemProperties.getBoolean(
- Constants.PROPERTY_HDMI_IS_DEVICE_HDMI_CEC_SWITCH, false);
+ PROPERTY_HDMI_IS_DEVICE_HDMI_CEC_SWITCH, false);
}
boolean isTvDeviceEnabled() {
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index df28f30..979de66 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -1761,12 +1761,15 @@
// Native callback
private void notifyFocusChanged(IBinder oldToken, IBinder newToken) {
- if (mFocusedWindow.asBinder() == newToken) {
- Log.w(TAG, "notifyFocusChanged called with unchanged mFocusedWindow=" + mFocusedWindow);
- return;
+ if (mFocusedWindow != null) {
+ if (mFocusedWindow.asBinder() == newToken) {
+ Slog.w(TAG, "notifyFocusChanged called with unchanged mFocusedWindow="
+ + mFocusedWindow);
+ return;
+ }
+ setPointerCapture(false);
}
- setPointerCapture(false);
mFocusedWindow = IWindow.Stub.asInterface(newToken);
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 7b7a3ec..20eebe7 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -2317,8 +2317,8 @@
@Override
public boolean areAppOverlaysAllowedForPackage(String pkg, int uid) {
- checkCallerIsSystemOrSameApp(pkg);
-
+ enforceSystemOrSystemUIOrSamePackage("Caller not system or systemui or same package",
+ pkg);
return mPreferencesHelper.areAppOverlaysAllowed(pkg, uid);
}
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index b1e9144..b145e1e 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -106,9 +106,7 @@
synchronized (mProxyByGroupTmp) {
// record individual ranking result and nominate proxies for each group
- // Note: iteration is done backwards such that the index can be used as a sort key
- // in a string compare below
- for (int i = N - 1; i >= 0; i--) {
+ for (int i = 0; i < N; i++) {
final NotificationRecord record = notificationList.get(i);
record.setAuthoritativeRank(i);
final String groupKey = record.getGroupKey();
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index aa9bc26..eb3017d 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -24,6 +24,7 @@
import android.app.AppGlobals;
import android.app.IApplicationThread;
import android.app.PendingIntent;
+import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -115,6 +116,7 @@
private final ShortcutServiceInternal mShortcutServiceInternal;
private final PackageCallbackList<IOnAppsChangedListener> mListeners
= new PackageCallbackList<IOnAppsChangedListener>();
+ private final DevicePolicyManager mDpm;
private final MyPackageMonitor mPackageMonitor = new MyPackageMonitor();
@@ -133,6 +135,7 @@
LocalServices.getService(ShortcutServiceInternal.class));
mShortcutServiceInternal.addListener(mPackageMonitor);
mCallbackHandler = BackgroundThread.getHandler();
+ mDpm = (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
}
@VisibleForTesting
@@ -319,28 +322,36 @@
}
final int callingUid = injectBinderCallingUid();
- final ArrayList<ResolveInfo> result = new ArrayList<>(launcherActivities.getList());
- final PackageManagerInternal pmInt =
- LocalServices.getService(PackageManagerInternal.class);
- if (packageName != null) {
- // If this hidden app should not be shown, return the original list.
- // Otherwise, inject hidden activity that forwards user to app details page.
- if (result.size() > 0) {
+ final long ident = injectClearCallingIdentity();
+ try {
+ if (mUm.getUserInfo(user.getIdentifier()).isManagedProfile()) {
+ // Managed profile should not show hidden apps
return launcherActivities;
}
- ApplicationInfo appInfo = pmInt.getApplicationInfo(packageName, /*flags*/ 0,
- callingUid, user.getIdentifier());
- if (shouldShowHiddenApp(appInfo)) {
- ResolveInfo info = getHiddenAppActivityInfo(packageName, callingUid, user);
- if (info != null) {
- result.add(info);
- }
+ if (mDpm.getDeviceOwnerComponentOnAnyUser() != null) {
+ // Device owner devices should not show hidden apps
+ return launcherActivities;
}
- return new ParceledListSlice<>(result);
- }
- long ident = injectClearCallingIdentity();
- try {
+ final ArrayList<ResolveInfo> result = new ArrayList<>(launcherActivities.getList());
+ final PackageManagerInternal pmInt =
+ LocalServices.getService(PackageManagerInternal.class);
+ if (packageName != null) {
+ // If this hidden app should not be shown, return the original list.
+ // Otherwise, inject hidden activity that forwards user to app details page.
+ if (result.size() > 0) {
+ return launcherActivities;
+ }
+ ApplicationInfo appInfo = pmInt.getApplicationInfo(packageName, /*flags*/ 0,
+ callingUid, user.getIdentifier());
+ if (shouldShowHiddenApp(appInfo)) {
+ ResolveInfo info = getHiddenAppActivityInfo(packageName, callingUid, user);
+ if (info != null) {
+ result.add(info);
+ }
+ }
+ return new ParceledListSlice<>(result);
+ }
final HashSet<String> visiblePackages = new HashSet<>();
for (ResolveInfo info : result) {
visiblePackages.add(info.activityInfo.packageName);
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index b100ecd..a207354 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -945,7 +945,7 @@
/** Returns true if uid has a visible window or its process is in a top state. */
private boolean isUidForeground(int uid) {
return (mService.getUidStateLocked(uid) == ActivityManager.PROCESS_STATE_TOP)
- || mService.mWindowManager.isAnyWindowVisibleForUid(uid);
+ || mService.mWindowManager.mRoot.isAnyNonToastWindowVisibleForUid(uid);
}
/** Returns true if uid is in a persistent state. */
@@ -968,18 +968,19 @@
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "logActivityStart");
final int callingUidProcState = mService.getUidStateLocked(callingUid);
final boolean callingUidHasAnyVisibleWindow =
- mService.mWindowManager.isAnyWindowVisibleForUid(callingUid);
+ mService.mWindowManager.mRoot.isAnyNonToastWindowVisibleForUid(callingUid);
final int realCallingUidProcState = (callingUid == realCallingUid)
? callingUidProcState
: mService.getUidStateLocked(realCallingUid);
final boolean realCallingUidHasAnyVisibleWindow = (callingUid == realCallingUid)
? callingUidHasAnyVisibleWindow
- : mService.mWindowManager.isAnyWindowVisibleForUid(realCallingUid);
+ : mService.mWindowManager.mRoot.isAnyNonToastWindowVisibleForUid(
+ realCallingUid);
final String targetPackage = (r != null) ? r.packageName : null;
final int targetUid = (r!= null) ? ((r.appInfo != null) ? r.appInfo.uid : -1) : -1;
final int targetUidProcState = mService.getUidStateLocked(targetUid);
final boolean targetUidHasAnyVisibleWindow = (targetUid != -1)
- ? mService.mWindowManager.isAnyWindowVisibleForUid(targetUid)
+ ? mService.mWindowManager.mRoot.isAnyNonToastWindowVisibleForUid(targetUid)
: false;
final String targetWhitelistTag = (targetUid != -1)
? mService.getPendingTempWhitelistTagForUidLocked(targetUid)
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 4e70bbc..8fb7947 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -23,6 +23,7 @@
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE;
import static android.view.WindowManager.LayoutParams.TYPE_DREAM;
import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG;
+import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
@@ -280,6 +281,15 @@
}
/**
+ * Returns true if the callingUid has any non-toast window currently visible to the user.
+ */
+ boolean isAnyNonToastWindowVisibleForUid(int callingUid) {
+ return forAllWindows(w -> {
+ return w.getOwningUid() == callingUid && w.isVisible() && w.mAttrs.type != TYPE_TOAST;
+ }, true /* traverseTopToBottom */);
+ }
+
+ /**
* Returns the app window token for the input binder if it exist in the system.
* NOTE: Only one AppWindowToken is allowed to exist in the system for a binder token, since
* AppWindowToken represents an activity which can only exist on one display.
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 9b82f5c..90506e7 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -5716,17 +5716,6 @@
}
/**
- * Returns true if the callingUid has any window currently visible to the user.
- */
- public boolean isAnyWindowVisibleForUid(int callingUid) {
- synchronized (mGlobalLock) {
- return mRoot.forAllWindows(w -> {
- return w.getOwningUid() == callingUid && w.isVisible();
- }, true /* traverseTopToBottom */);
- }
- }
-
- /**
* Called when a task has been removed from the recent tasks list.
* <p>
* Note: This doesn't go through {@link TaskWindowContainerController} yet as the window
diff --git a/services/robotests/Android.mk b/services/robotests/Android.mk
index 9159f0d..0cf0d34 100644
--- a/services/robotests/Android.mk
+++ b/services/robotests/Android.mk
@@ -26,9 +26,6 @@
LOCAL_PRIVILEGED_MODULE := true
LOCAL_STATIC_JAVA_LIBRARIES := \
- bmgrlib \
- bu \
- services.backup \
services.core \
services.net
@@ -41,8 +38,7 @@
LOCAL_MODULE := FrameworksServicesRoboTests
-LOCAL_SRC_FILES := $(call all-java-files-under, src) \
- $(call all-java-files-under, backup/src)
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_RESOURCE_DIR := \
$(LOCAL_PATH)/res
@@ -82,8 +78,7 @@
LOCAL_TEST_PACKAGE := FrameworksServicesLib
-LOCAL_ROBOTEST_FILES := $(call find-files-in-subdirs,$(LOCAL_PATH)/src,*Test.java,.) \
- $(call find-files-in-subdirs,$(LOCAL_PATH)/backup/src,*Test.java,.)
+LOCAL_ROBOTEST_FILES := $(call find-files-in-subdirs,$(LOCAL_PATH)/src,*Test.java,.)
include external/robolectric-shadows/run_robotests.mk
diff --git a/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceTest.java b/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceTest.java
index 769a9d4..0d3b6b2 100644
--- a/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceTest.java
@@ -1075,7 +1075,7 @@
createServiceAndRegisterUser(UserHandle.USER_SYSTEM, mUserOneService);
FullBackupJob job = new FullBackupJob();
- backupManagerService.beginFullBackup(job);
+ backupManagerService.beginFullBackup(UserHandle.USER_SYSTEM, job);
verify(mUserOneService).beginFullBackup(job);
}
@@ -1086,7 +1086,7 @@
BackupManagerService backupManagerService = createService();
FullBackupJob job = new FullBackupJob();
- backupManagerService.beginFullBackup(job);
+ backupManagerService.beginFullBackup(UserHandle.USER_SYSTEM, job);
verify(mUserOneService, never()).beginFullBackup(job);
}
@@ -1097,7 +1097,7 @@
BackupManagerService backupManagerService =
createServiceAndRegisterUser(UserHandle.USER_SYSTEM, mUserOneService);
- backupManagerService.endFullBackup();
+ backupManagerService.endFullBackup(UserHandle.USER_SYSTEM);
verify(mUserOneService).endFullBackup();
}
@@ -1107,7 +1107,7 @@
public void testEndFullBackup_onUnknownUser_doesNotPropagateCall() throws Exception {
BackupManagerService backupManagerService = createService();
- backupManagerService.endFullBackup();
+ backupManagerService.endFullBackup(UserHandle.USER_SYSTEM);
verify(mUserOneService, never()).endFullBackup();
}
diff --git a/services/robotests/backup/src/com/android/server/backup/FullBackupJobTest.java b/services/robotests/backup/src/com/android/server/backup/FullBackupJobTest.java
new file mode 100644
index 0000000..9a78d0b3
--- /dev/null
+++ b/services/robotests/backup/src/com/android/server/backup/FullBackupJobTest.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2019 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.backup;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.annotation.UserIdInt;
+import android.app.job.JobScheduler;
+import android.content.Context;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.platform.test.annotations.Presubmit;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.Shadows;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowJobScheduler;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(shadows = {ShadowJobScheduler.class})
+@Presubmit
+public class FullBackupJobTest {
+ private Context mContext;
+ private BackupManagerConstants mConstants;
+ private ShadowJobScheduler mShadowJobScheduler;
+
+ @UserIdInt private int mUserOneId;
+ @UserIdInt private int mUserTwoId;
+
+ @Before
+ public void setUp() throws Exception {
+ mContext = RuntimeEnvironment.application;
+ mConstants = new BackupManagerConstants(Handler.getMain(), mContext.getContentResolver());
+ mConstants.start();
+
+ mShadowJobScheduler = Shadows.shadowOf(mContext.getSystemService(JobScheduler.class));
+
+ mUserOneId = UserHandle.USER_SYSTEM;
+ mUserTwoId = mUserOneId + 1;
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mConstants.stop();
+ FullBackupJob.cancel(mUserOneId, mContext);
+ FullBackupJob.cancel(mUserTwoId, mContext);
+ }
+
+ @Test
+ public void testSchedule_afterScheduling_jobExists() {
+ FullBackupJob.schedule(mUserOneId, mContext, 0, mConstants);
+ FullBackupJob.schedule(mUserTwoId, mContext, 0, mConstants);
+
+ assertThat(mShadowJobScheduler.getPendingJob(getJobIdForUserId(mUserOneId))).isNotNull();
+ assertThat(mShadowJobScheduler.getPendingJob(getJobIdForUserId(mUserTwoId))).isNotNull();
+ }
+
+ @Test
+ public void testCancel_afterCancelling_jobDoesntExist() {
+ FullBackupJob.schedule(mUserOneId, mContext, 0, mConstants);
+ FullBackupJob.schedule(mUserTwoId, mContext, 0, mConstants);
+ FullBackupJob.cancel(mUserOneId, mContext);
+ FullBackupJob.cancel(mUserTwoId, mContext);
+
+ assertThat(mShadowJobScheduler.getPendingJob(getJobIdForUserId(mUserOneId))).isNull();
+ assertThat(mShadowJobScheduler.getPendingJob(getJobIdForUserId(mUserTwoId))).isNull();
+ }
+//
+ @Test
+ public void testSchedule_onlySchedulesForRequestedUser() {
+ FullBackupJob.schedule(mUserOneId, mContext, 0, mConstants);
+
+ assertThat(mShadowJobScheduler.getPendingJob(getJobIdForUserId(mUserOneId))).isNotNull();
+ assertThat(mShadowJobScheduler.getPendingJob(getJobIdForUserId(mUserTwoId))).isNull();
+ }
+//
+ @Test
+ public void testCancel_onlyCancelsForRequestedUser() {
+ FullBackupJob.schedule(mUserOneId, mContext, 0, mConstants);
+ FullBackupJob.schedule(mUserTwoId, mContext, 0, mConstants);
+ FullBackupJob.cancel(mUserOneId, mContext);
+
+ assertThat(mShadowJobScheduler.getPendingJob(getJobIdForUserId(mUserOneId))).isNull();
+ assertThat(mShadowJobScheduler.getPendingJob(getJobIdForUserId(mUserTwoId))).isNotNull();
+ }
+
+ private static int getJobIdForUserId(int userId) {
+ return JobIdManager.getJobIdForUserId(FullBackupJob.MIN_JOB_ID, FullBackupJob.MAX_JOB_ID,
+ userId);
+ }
+}
diff --git a/services/robotests/backup/src/com/android/server/backup/JobIdManagerTest.java b/services/robotests/backup/src/com/android/server/backup/JobIdManagerTest.java
new file mode 100644
index 0000000..f8bb1ee
--- /dev/null
+++ b/services/robotests/backup/src/com/android/server/backup/JobIdManagerTest.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2019 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.backup;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.annotation.UserIdInt;
+import android.os.UserHandle;
+import android.platform.test.annotations.Presubmit;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import static org.testng.Assert.expectThrows;
+
+@RunWith(RobolectricTestRunner.class)
+@Presubmit
+public class JobIdManagerTest {
+ private static final int MIN_JOB_ID = 10;
+ private static final int MAX_JOB_ID = 20;
+
+ @UserIdInt private int mUserOneId;
+ @UserIdInt private int mUserTwoId;
+
+ @Before
+ public void setUp() {
+ mUserOneId = UserHandle.USER_SYSTEM;
+ mUserTwoId = mUserOneId + 1;
+ }
+
+ @Test
+ public void testGetJobIdForUserId_returnsDifferentJobIdsForDifferentUsers() {
+ int jobIdOne = JobIdManager.getJobIdForUserId(MIN_JOB_ID, MAX_JOB_ID, mUserOneId);
+ int jobIdTwo = JobIdManager.getJobIdForUserId(MIN_JOB_ID, MAX_JOB_ID, mUserTwoId);
+
+ assertThat(jobIdOne).isNotEqualTo(jobIdTwo);
+ }
+
+ @Test
+ public void testGetJobIdForUserId_returnsSameJobIdForSameUser() {
+ int jobIdOne = JobIdManager.getJobIdForUserId(MIN_JOB_ID, MAX_JOB_ID, mUserOneId);
+ int jobIdTwo = JobIdManager.getJobIdForUserId(MIN_JOB_ID, MAX_JOB_ID, mUserOneId);
+
+ assertThat(jobIdOne).isEqualTo(jobIdTwo);
+ }
+
+ @Test
+ public void testGetJobIdForUserId_throwsExceptionIfRangeIsExceeded() {
+ expectThrows(
+ RuntimeException.class,
+ () -> JobIdManager.getJobIdForUserId(MIN_JOB_ID, MAX_JOB_ID,
+ MAX_JOB_ID + 1));
+ }
+}
diff --git a/services/robotests/backup/src/com/android/server/backup/KeyValueBackupJobTest.java b/services/robotests/backup/src/com/android/server/backup/KeyValueBackupJobTest.java
index 8e17209..8d9e44f 100644
--- a/services/robotests/backup/src/com/android/server/backup/KeyValueBackupJobTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/KeyValueBackupJobTest.java
@@ -18,8 +18,10 @@
import static com.google.common.truth.Truth.assertThat;
+import android.annotation.UserIdInt;
import android.content.Context;
import android.os.Handler;
+import android.os.UserHandle;
import android.platform.test.annotations.Presubmit;
import org.junit.After;
@@ -35,32 +37,67 @@
private Context mContext;
private BackupManagerConstants mConstants;
+ @UserIdInt private int mUserOneId;
+ @UserIdInt private int mUserTwoId;
+
@Before
public void setUp() throws Exception {
mContext = RuntimeEnvironment.application;
mConstants = new BackupManagerConstants(Handler.getMain(), mContext.getContentResolver());
mConstants.start();
+
+ mUserOneId = UserHandle.USER_SYSTEM;
+ mUserTwoId = mUserOneId + 1;
}
@After
public void tearDown() throws Exception {
mConstants.stop();
- KeyValueBackupJob.cancel(mContext);
+ KeyValueBackupJob.cancel(mUserOneId, mContext);
+ KeyValueBackupJob.cancel(mUserTwoId, mContext);
}
@Test
public void testIsScheduled_beforeScheduling_returnsFalse() {
- boolean isScheduled = KeyValueBackupJob.isScheduled();
-
- assertThat(isScheduled).isFalse();
+ assertThat(KeyValueBackupJob.isScheduled(mUserOneId)).isFalse();
+ assertThat(KeyValueBackupJob.isScheduled(mUserTwoId)).isFalse();
}
@Test
public void testIsScheduled_afterScheduling_returnsTrue() {
- KeyValueBackupJob.schedule(mContext, mConstants);
+ KeyValueBackupJob.schedule(mUserOneId, mContext, mConstants);
+ KeyValueBackupJob.schedule(mUserTwoId, mContext, mConstants);
- boolean isScheduled = KeyValueBackupJob.isScheduled();
+ assertThat(KeyValueBackupJob.isScheduled(mUserOneId)).isTrue();
+ assertThat(KeyValueBackupJob.isScheduled(mUserTwoId)).isTrue();
+ }
- assertThat(isScheduled).isTrue();
+ @Test
+ public void testIsScheduled_afterCancelling_returnsFalse() {
+ KeyValueBackupJob.schedule(mUserOneId, mContext, mConstants);
+ KeyValueBackupJob.schedule(mUserTwoId, mContext, mConstants);
+ KeyValueBackupJob.cancel(mUserOneId, mContext);
+ KeyValueBackupJob.cancel(mUserTwoId, mContext);
+
+ assertThat(KeyValueBackupJob.isScheduled(mUserOneId)).isFalse();
+ assertThat(KeyValueBackupJob.isScheduled(mUserTwoId)).isFalse();
+ }
+
+ @Test
+ public void testIsScheduled_afterScheduling_returnsTrueOnlyForScheduledUser() {
+ KeyValueBackupJob.schedule(mUserOneId, mContext, mConstants);
+
+ assertThat(KeyValueBackupJob.isScheduled(mUserOneId)).isTrue();
+ assertThat(KeyValueBackupJob.isScheduled(mUserTwoId)).isFalse();
+ }
+
+ @Test
+ public void testIsScheduled_afterCancelling_returnsFalseOnlyForCancelledUser() {
+ KeyValueBackupJob.schedule(mUserOneId, mContext, mConstants);
+ KeyValueBackupJob.schedule(mUserTwoId, mContext, mConstants);
+ KeyValueBackupJob.cancel(mUserOneId, mContext);
+
+ assertThat(KeyValueBackupJob.isScheduled(mUserOneId)).isFalse();
+ assertThat(KeyValueBackupJob.isScheduled(mUserTwoId)).isTrue();
}
}
diff --git a/services/robotests/backup/src/com/android/server/backup/TransportManagerTest.java b/services/robotests/backup/src/com/android/server/backup/TransportManagerTest.java
index 693092d..7559560 100644
--- a/services/robotests/backup/src/com/android/server/backup/TransportManagerTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/TransportManagerTest.java
@@ -41,6 +41,7 @@
import static java.util.stream.Stream.concat;
import android.annotation.Nullable;
+import android.annotation.UserIdInt;
import android.app.backup.BackupManager;
import android.app.backup.BackupTransport;
import android.content.ComponentName;
@@ -48,6 +49,7 @@
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
+import android.os.UserHandle;
import android.platform.test.annotations.Presubmit;
import com.android.server.backup.testing.TransportData;
@@ -84,12 +86,14 @@
private TransportData mTransportA2;
private TransportData mTransportB1;
private ShadowPackageManager mShadowPackageManager;
+ private @UserIdInt int mUserId;
private Context mContext;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
+ mUserId = UserHandle.USER_SYSTEM;
mContext = RuntimeEnvironment.application;
mShadowPackageManager = shadowOf(mContext.getPackageManager());
@@ -684,6 +688,7 @@
@Nullable TransportData selectedTransport, TransportData... transports) {
TransportManager transportManager =
new TransportManager(
+ mUserId,
mContext,
merge(selectedTransport, transports)
.stream()
diff --git a/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java b/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java
index 8d5c301..4968176 100644
--- a/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java
@@ -1137,8 +1137,9 @@
* Implementation of {@link ShadowKeyValueBackupJob#schedule(Context, long,
* BackupManagerConstants)} that throws an {@link IllegalArgumentException}.
*/
- public static void schedule(Context ctx, long delay, BackupManagerConstants constants) {
- ShadowKeyValueBackupJob.schedule(ctx, delay, constants);
+ public static void schedule(int userId, Context ctx, long delay,
+ BackupManagerConstants constants) {
+ ShadowKeyValueBackupJob.schedule(userId, ctx, delay, constants);
throw new IllegalArgumentException();
}
}
diff --git a/services/robotests/backup/src/com/android/server/backup/internal/SetupObserverTest.java b/services/robotests/backup/src/com/android/server/backup/internal/SetupObserverTest.java
index b754356..b08ae6d 100644
--- a/services/robotests/backup/src/com/android/server/backup/internal/SetupObserverTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/internal/SetupObserverTest.java
@@ -125,7 +125,7 @@
setupObserver.onChange(true);
- assertThat(KeyValueBackupJob.isScheduled()).isTrue();
+ assertThat(KeyValueBackupJob.isScheduled(mUserBackupManagerService.getUserId())).isTrue();
// Verifies that the full backup job is scheduled. The job is scheduled via a posted message
// on the backup handler so we verify that a message exists.
assertThat(mUserBackupManagerService.getBackupHandler().hasMessagesOrCallbacks()).isTrue();
diff --git a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
index 7dac795..b923bb0 100644
--- a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java
@@ -2727,7 +2727,7 @@
throws RemoteException, IOException {
verify(transportMock.transport).requestBackupTime();
assertBackupPendingFor(packages);
- assertThat(KeyValueBackupJob.isScheduled()).isTrue();
+ assertThat(KeyValueBackupJob.isScheduled(mBackupManagerService.getUserId())).isTrue();
}
private void assertBackupPendingFor(PackageData... packages) throws IOException {
diff --git a/services/robotests/backup/src/com/android/server/backup/transport/TransportClientManagerTest.java b/services/robotests/backup/src/com/android/server/backup/transport/TransportClientManagerTest.java
index 7dd0d92..f033af8 100644
--- a/services/robotests/backup/src/com/android/server/backup/transport/TransportClientManagerTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/transport/TransportClientManagerTest.java
@@ -25,6 +25,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.annotation.UserIdInt;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -49,6 +50,7 @@
@Mock private Context mContext;
@Mock private TransportConnectionListener mTransportConnectionListener;
+ private @UserIdInt int mUserId;
private TransportClientManager mTransportClientManager;
private ComponentName mTransportComponent;
private Intent mBindIntent;
@@ -57,7 +59,9 @@
public void setUp() {
MockitoAnnotations.initMocks(this);
- mTransportClientManager = new TransportClientManager(mContext, new TransportStats());
+ mUserId = UserHandle.USER_SYSTEM;
+ mTransportClientManager =
+ new TransportClientManager(mUserId, mContext, new TransportStats());
mTransportComponent = new ComponentName(PACKAGE_NAME, CLASS_NAME);
mBindIntent = new Intent(SERVICE_ACTION_TRANSPORT_HOST).setComponent(mTransportComponent);
diff --git a/services/robotests/backup/src/com/android/server/backup/transport/TransportClientTest.java b/services/robotests/backup/src/com/android/server/backup/transport/TransportClientTest.java
index 7281a3c..392f2ca 100644
--- a/services/robotests/backup/src/com/android/server/backup/transport/TransportClientTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/transport/TransportClientTest.java
@@ -36,6 +36,7 @@
import static org.testng.Assert.expectThrows;
import android.annotation.Nullable;
+import android.annotation.UserIdInt;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -83,6 +84,7 @@
@Mock private TransportConnectionListener mTransportConnectionListener;
@Mock private TransportConnectionListener mTransportConnectionListener2;
@Mock private IBackupTransport.Stub mTransportBinder;
+ @UserIdInt private int mUserId;
private TransportStats mTransportStats;
private TransportClient mTransportClient;
private ComponentName mTransportComponent;
@@ -96,6 +98,7 @@
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
+ mUserId = UserHandle.USER_SYSTEM;
Looper mainLooper = Looper.getMainLooper();
mShadowMainLooper = extract(mainLooper);
mTransportComponent =
@@ -105,6 +108,7 @@
mBindIntent = new Intent(SERVICE_ACTION_TRANSPORT_HOST).setComponent(mTransportComponent);
mTransportClient =
new TransportClient(
+ mUserId,
mContext,
mTransportStats,
mBindIntent,
diff --git a/services/robotests/src/com/android/server/testing/shadows/ShadowKeyValueBackupJob.java b/services/robotests/src/com/android/server/testing/shadows/ShadowKeyValueBackupJob.java
index 23c44b0e..f90ea6a 100644
--- a/services/robotests/src/com/android/server/testing/shadows/ShadowKeyValueBackupJob.java
+++ b/services/robotests/src/com/android/server/testing/shadows/ShadowKeyValueBackupJob.java
@@ -34,7 +34,8 @@
}
@Implementation
- protected static void schedule(Context ctx, long delay, BackupManagerConstants constants) {
+ protected static void schedule(int userId, Context ctx, long delay,
+ BackupManagerConstants constants) {
callingUid = Binder.getCallingUid();
}
}
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityManagerInternalTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityManagerInternalTest.java
index e4fe599..00a60b9 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityManagerInternalTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerInternalTest.java
@@ -117,7 +117,7 @@
thread2.assertWaiting("Unexpected state for " + record2);
thread2.interrupt();
- mAms.mActiveUids.clear();
+ mAms.mProcessList.mActiveUids.clear();
}
private UidRecord addActiveUidRecord(int uid, long curProcStateSeq,
@@ -126,7 +126,7 @@
record.lastNetworkUpdatedProcStateSeq = lastNetworkUpdatedProcStateSeq;
record.curProcStateSeq = curProcStateSeq;
record.waitingForNetwork = true;
- mAms.mActiveUids.put(uid, record);
+ mAms.mProcessList.mActiveUids.put(uid, record);
return record;
}
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
index 2cc338c..419c736 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerServiceTest.java
@@ -253,7 +253,7 @@
final UidRecord uidRec = new UidRecord(uid, null /* atmInternal */);
uidRec.waitingForNetwork = true;
uidRec.hasInternetPermission = true;
- mAms.mActiveUids.put(uid, uidRec);
+ mAms.mProcessList.mActiveUids.put(uid, uidRec);
final ProcessRecord appRec = new ProcessRecord(mAms, new ApplicationInfo(), TAG, uid);
appRec.thread = Mockito.mock(IApplicationThread.class);
@@ -715,7 +715,8 @@
// Verify that when uid state changes to CHANGE_GONE or CHANGE_GONE_IDLE, then it
// will be removed from validateUids.
- assertNotEquals("validateUids should not be empty", 0, mAms.mValidateUids.size());
+ assertNotEquals("validateUids should not be empty", 0,
+ mAms.mValidateUids.size());
for (int i = 0; i < pendingItemsForUids.size(); ++i) {
final UidRecord.ChangeItem item = pendingItemsForUids.get(i);
// Assign CHANGE_GONE_IDLE to some items and CHANGE_GONE to the others, using even/odd
@@ -853,7 +854,7 @@
record.curProcStateSeq = curProcStateSeq;
record.lastDispatchedProcStateSeq = lastDispatchedProcStateSeq;
record.lastNetworkUpdatedProcStateSeq = lastNetworkUpdatedProcStateSeq;
- mAms.mActiveUids.put(Process.myUid(), record);
+ mAms.mProcessList.mActiveUids.put(Process.myUid(), record);
CustomThread thread = new CustomThread(record.networkStateLock, new Runnable() {
@Override
@@ -876,7 +877,7 @@
thread.assertTerminated(errMsg);
}
- mAms.mActiveUids.clear();
+ mAms.mProcessList.mActiveUids.clear();
}
private static class TestHandler extends Handler {
diff --git a/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java b/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java
index db83505..6a9a121 100644
--- a/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java
+++ b/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java
@@ -1149,31 +1149,31 @@
@Test
public void beginFullBackup_calledBeforeInitialize_ignored() throws RemoteException {
- mTrampoline.beginFullBackup(new FullBackupJob());
+ mTrampoline.beginFullBackup(mUserId, new FullBackupJob());
verifyNoMoreInteractions(mBackupManagerServiceMock);
}
@Test
public void beginFullBackup_forwarded() throws RemoteException {
FullBackupJob fullBackupJob = new FullBackupJob();
- when(mBackupManagerServiceMock.beginFullBackup(fullBackupJob)).thenReturn(true);
+ when(mBackupManagerServiceMock.beginFullBackup(mUserId, fullBackupJob)).thenReturn(true);
mTrampoline.initializeService();
- assertTrue(mTrampoline.beginFullBackup(fullBackupJob));
- verify(mBackupManagerServiceMock).beginFullBackup(fullBackupJob);
+ assertTrue(mTrampoline.beginFullBackup(mUserId, fullBackupJob));
+ verify(mBackupManagerServiceMock).beginFullBackup(mUserId, fullBackupJob);
}
@Test
public void endFullBackup_calledBeforeInitialize_ignored() throws RemoteException {
- mTrampoline.endFullBackup();
+ mTrampoline.endFullBackup(mUserId);
verifyNoMoreInteractions(mBackupManagerServiceMock);
}
@Test
public void endFullBackup_forwarded() throws RemoteException {
mTrampoline.initializeService();
- mTrampoline.endFullBackup();
- verify(mBackupManagerServiceMock).endFullBackup();
+ mTrampoline.endFullBackup(mUserId);
+ verify(mBackupManagerServiceMock).endFullBackup(mUserId);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java b/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java
index 4255e37..8607ec6 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/FakeNativeWrapper.java
@@ -50,6 +50,7 @@
private final List<HdmiCecMessage> mResultMessages = new ArrayList<>();
private int mMyPhysicalAddress = 0;
+ private HdmiPortInfo[] mHdmiPortInfo = null;
@Override
public long nativeInit(HdmiCecController handler, MessageQueue messageQueue) {
@@ -92,9 +93,11 @@
@Override
public HdmiPortInfo[] nativeGetPortInfos(long controllerPtr) {
- HdmiPortInfo[] hdmiPortInfo = new HdmiPortInfo[1];
- hdmiPortInfo[0] = new HdmiPortInfo(1, 1, 0x1000, true, true, true);
- return hdmiPortInfo;
+ if (mHdmiPortInfo == null) {
+ mHdmiPortInfo = new HdmiPortInfo[1];
+ mHdmiPortInfo[0] = new HdmiPortInfo(1, 1, 0x1000, true, true, true);
+ }
+ return mHdmiPortInfo;
}
@Override
@@ -131,4 +134,10 @@
protected void setPhysicalAddress(int physicalAddress) {
mMyPhysicalAddress = physicalAddress;
}
+
+ @VisibleForTesting
+ protected void setPortInfo(HdmiPortInfo[] hdmiPortInfo) {
+ mHdmiPortInfo = new HdmiPortInfo[hdmiPortInfo.length];
+ System.arraycopy(hdmiPortInfo, 0, mHdmiPortInfo, 0, hdmiPortInfo.length);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
index b47f269..3b51a2a 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceAudioSystemTest.java
@@ -15,6 +15,9 @@
*/
package com.android.server.hdmi;
+import static android.hardware.hdmi.HdmiControlManager.DEVICE_EVENT_ADD_DEVICE;
+import static android.hardware.hdmi.HdmiControlManager.DEVICE_EVENT_UPDATE_DEVICE;
+
import static com.android.server.hdmi.Constants.ADDR_AUDIO_SYSTEM;
import static com.android.server.hdmi.Constants.ADDR_BROADCAST;
import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_1;
@@ -25,6 +28,7 @@
import static com.google.common.truth.Truth.assertThat;
+import android.hardware.hdmi.HdmiDeviceInfo;
import android.media.AudioManager;
import android.os.Looper;
import android.os.SystemProperties;
@@ -64,6 +68,8 @@
private int mMusicMaxVolume;
private boolean mMusicMute;
private int mAvrPhysicalAddress;
+ private int mInvokeDeviceEventState;
+ private HdmiDeviceInfo mDeviceInfo;
@Before
public void setUp() {
@@ -127,6 +133,18 @@
@Override
void wakeUp() {}
+
+ @Override
+ void invokeDeviceEventListeners(HdmiDeviceInfo device, int status) {
+ mDeviceInfo = device;
+ mInvokeDeviceEventState = status;
+ }
+
+ @Override
+ int pathToPortId(int path) {
+ // port id is not useful for the test right now
+ return 1;
+ }
};
mMyLooper = mTestLooper.getLooper();
@@ -157,6 +175,8 @@
mNativeWrapper.setPhysicalAddress(mAvrPhysicalAddress);
SystemProperties.set(Constants.PROPERTY_ARC_SUPPORT, "true");
SystemProperties.set(Constants.PROPERTY_SYSTEM_AUDIO_MODE_MUTING_ENABLE, "true");
+ mInvokeDeviceEventState = 0;
+ mDeviceInfo = null;
}
@Test
@@ -611,4 +631,73 @@
mTestLooper.dispatchAll();
assertThat(mNativeWrapper.getResultMessages()).contains(expectedMessage);
}
+
+ @Test
+ public void updateCecDevice_deviceNotExists_addDevice() {
+ assertThat(mInvokeDeviceEventState).isNotEqualTo(DEVICE_EVENT_ADD_DEVICE);
+ HdmiDeviceInfo newDevice = new HdmiDeviceInfo(
+ ADDR_PLAYBACK_1, 0x2100, 2, HdmiDeviceInfo.DEVICE_PLAYBACK,
+ Constants.UNKNOWN_VENDOR_ID, HdmiUtils.getDefaultDeviceName(ADDR_PLAYBACK_1));
+
+ mHdmiCecLocalDeviceAudioSystem.updateCecDevice(newDevice);
+ assertThat(mDeviceInfo).isEqualTo(newDevice);
+ assertThat(mHdmiCecLocalDeviceAudioSystem
+ .getCecDeviceInfo(newDevice.getLogicalAddress())).isEqualTo(newDevice);
+ assertThat(mInvokeDeviceEventState).isEqualTo(DEVICE_EVENT_ADD_DEVICE);
+ }
+
+ @Test
+ public void updateCecDevice_deviceExists_doNothing() {
+ mInvokeDeviceEventState = 0;
+ HdmiDeviceInfo oldDevice = new HdmiDeviceInfo(
+ ADDR_PLAYBACK_1, 0x2100, 2, HdmiDeviceInfo.DEVICE_PLAYBACK,
+ Constants.UNKNOWN_VENDOR_ID, HdmiUtils.getDefaultDeviceName(ADDR_PLAYBACK_1));
+ mHdmiCecLocalDeviceAudioSystem.addDeviceInfo(oldDevice);
+
+ mHdmiCecLocalDeviceAudioSystem.updateCecDevice(oldDevice);
+ assertThat(mInvokeDeviceEventState).isEqualTo(0);
+ }
+
+ @Test
+ public void updateCecDevice_deviceInfoDifferent_updateDevice() {
+ assertThat(mInvokeDeviceEventState).isNotEqualTo(DEVICE_EVENT_UPDATE_DEVICE);
+ HdmiDeviceInfo oldDevice = new HdmiDeviceInfo(
+ ADDR_PLAYBACK_1, 0x2100, 2, HdmiDeviceInfo.DEVICE_PLAYBACK,
+ Constants.UNKNOWN_VENDOR_ID, HdmiUtils.getDefaultDeviceName(ADDR_PLAYBACK_1));
+ mHdmiCecLocalDeviceAudioSystem.addDeviceInfo(oldDevice);
+
+ HdmiDeviceInfo differentDevice = new HdmiDeviceInfo(
+ ADDR_PLAYBACK_1, 0x2100, 4, HdmiDeviceInfo.DEVICE_PLAYBACK,
+ Constants.UNKNOWN_VENDOR_ID, HdmiUtils.getDefaultDeviceName(ADDR_PLAYBACK_1));
+
+ mHdmiCecLocalDeviceAudioSystem.updateCecDevice(differentDevice);
+ assertThat(mDeviceInfo).isEqualTo(differentDevice);
+ assertThat(mHdmiCecLocalDeviceAudioSystem
+ .getCecDeviceInfo(differentDevice.getLogicalAddress())).isEqualTo(differentDevice);
+ assertThat(mInvokeDeviceEventState).isEqualTo(DEVICE_EVENT_UPDATE_DEVICE);
+ }
+
+ @Test
+ public void handleReportPhysicalAddress_differentPath_addDevice() {
+ assertThat(mInvokeDeviceEventState).isNotEqualTo(DEVICE_EVENT_ADD_DEVICE);
+ HdmiDeviceInfo oldDevice = new HdmiDeviceInfo(
+ ADDR_PLAYBACK_1, 0x2100, 2, HdmiDeviceInfo.DEVICE_PLAYBACK,
+ Constants.UNKNOWN_VENDOR_ID, HdmiUtils.getDefaultDeviceName(ADDR_PLAYBACK_1));
+ mHdmiCecLocalDeviceAudioSystem.addDeviceInfo(oldDevice);
+
+ HdmiDeviceInfo differentDevice = new HdmiDeviceInfo(
+ ADDR_PLAYBACK_1, 0x2200, 1, HdmiDeviceInfo.DEVICE_PLAYBACK,
+ Constants.UNKNOWN_VENDOR_ID, HdmiUtils.getDefaultDeviceName(ADDR_PLAYBACK_1));
+ HdmiCecMessage reportPhysicalAddress = HdmiCecMessageBuilder
+ .buildReportPhysicalAddressCommand(
+ ADDR_PLAYBACK_1, 0x2200, HdmiDeviceInfo.DEVICE_PLAYBACK);
+ mHdmiCecLocalDeviceAudioSystem.handleReportPhysicalAddress(reportPhysicalAddress);
+
+ mHdmiCecLocalDeviceAudioSystem.addDeviceInfo(oldDevice);
+ mTestLooper.dispatchAll();
+ assertThat(mDeviceInfo).isEqualTo(differentDevice);
+ assertThat(mHdmiCecLocalDeviceAudioSystem
+ .getCecDeviceInfo(differentDevice.getLogicalAddress())).isEqualTo(differentDevice);
+ assertThat(mInvokeDeviceEventState).isEqualTo(DEVICE_EVENT_ADD_DEVICE);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
index 18c9a65..67ce13f 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
@@ -20,13 +20,18 @@
import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
+import static com.google.common.truth.Truth.assertThat;
+
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
+import android.hardware.hdmi.HdmiPortInfo;
import android.os.Looper;
import android.os.test.TestLooper;
+
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -102,6 +107,7 @@
private TestLooper mTestLooper = new TestLooper();
private ArrayList<HdmiCecLocalDevice> mLocalDevices = new ArrayList<>();
private boolean mStandbyMessageReceived;
+ private HdmiPortInfo[] mHdmiPortInfo;
@Before
public void SetUp() {
@@ -131,6 +137,16 @@
mLocalDevices.add(mMyAudioSystemDevice);
mLocalDevices.add(mMyPlaybackDevice);
+ mHdmiPortInfo = new HdmiPortInfo[4];
+ mHdmiPortInfo[0] =
+ new HdmiPortInfo(1, HdmiPortInfo.PORT_INPUT, 0x2100, true, false, false);
+ mHdmiPortInfo[1] =
+ new HdmiPortInfo(2, HdmiPortInfo.PORT_INPUT, 0x2200, true, false, false);
+ mHdmiPortInfo[2] =
+ new HdmiPortInfo(3, HdmiPortInfo.PORT_INPUT, 0x2000, true, false, false);
+ mHdmiPortInfo[3] =
+ new HdmiPortInfo(4, HdmiPortInfo.PORT_INPUT, 0x3000, true, false, false);
+ mNativeWrapper.setPortInfo(mHdmiPortInfo);
mHdmiControlService.initPortInfo();
mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
@@ -159,4 +175,24 @@
assertTrue(mMyPlaybackDevice.isDisabled());
assertTrue(mMyAudioSystemDevice.isDisabled());
}
+
+ @Test
+ public void pathToPort_pathExists_weAreNonTv() {
+ mNativeWrapper.setPhysicalAddress(0x2000);
+ assertThat(mHdmiControlService.pathToPortId(0x2120)).isEqualTo(1);
+ assertThat(mHdmiControlService.pathToPortId(0x2234)).isEqualTo(2);
+ }
+
+ @Test
+ public void pathToPort_pathExists_weAreTv() {
+ mNativeWrapper.setPhysicalAddress(0x0000);
+ assertThat(mHdmiControlService.pathToPortId(0x2120)).isEqualTo(3);
+ assertThat(mHdmiControlService.pathToPortId(0x3234)).isEqualTo(4);
+ }
+
+ @Test
+ public void pathToPort_pathInvalid() {
+ mNativeWrapper.setPhysicalAddress(0x2000);
+ assertThat(mHdmiControlService.pathToPortId(0x1000)).isEqualTo(Constants.INVALID_PORT_ID);
+ }
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java
index 5638cb36..c79e1db0 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/RankingHelperTest.java
@@ -15,8 +15,11 @@
*/
package com.android.server.notification;
+import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
import static android.app.NotificationManager.IMPORTANCE_LOW;
+import static junit.framework.TestCase.assertEquals;
+
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Matchers.anyInt;
@@ -153,7 +156,7 @@
.build();
mRecordGroupGSortA = new NotificationRecord(mContext, new StatusBarNotification(
PKG, PKG, 1, null, 0, 0, mNotiGroupGSortA, user,
- null, System.currentTimeMillis()), getDefaultChannel());
+ null, System.currentTimeMillis()), getLowChannel());
mNotiGroupGSortB = new Notification.Builder(mContext, TEST_CHANNEL_ID)
.setContentTitle("B")
@@ -163,7 +166,7 @@
.build();
mRecordGroupGSortB = new NotificationRecord(mContext, new StatusBarNotification(
PKG, PKG, 1, null, 0, 0, mNotiGroupGSortB, user,
- null, System.currentTimeMillis()), getDefaultChannel());
+ null, System.currentTimeMillis()), getLowChannel());
mNotiNoGroup = new Notification.Builder(mContext, TEST_CHANNEL_ID)
.setContentTitle("C")
@@ -171,7 +174,7 @@
.build();
mRecordNoGroup = new NotificationRecord(mContext, new StatusBarNotification(
PKG, PKG, 1, null, 0, 0, mNotiNoGroup, user,
- null, System.currentTimeMillis()), getDefaultChannel());
+ null, System.currentTimeMillis()), getLowChannel());
mNotiNoGroup2 = new Notification.Builder(mContext, TEST_CHANNEL_ID)
.setContentTitle("D")
@@ -179,7 +182,7 @@
.build();
mRecordNoGroup2 = new NotificationRecord(mContext, new StatusBarNotification(
PKG, PKG, 1, null, 0, 0, mNotiNoGroup2, user,
- null, System.currentTimeMillis()), getDefaultChannel());
+ null, System.currentTimeMillis()), getLowChannel());
mNotiNoGroupSortA = new Notification.Builder(mContext, TEST_CHANNEL_ID)
.setContentTitle("E")
@@ -188,7 +191,7 @@
.build();
mRecordNoGroupSortA = new NotificationRecord(mContext, new StatusBarNotification(
PKG, PKG, 1, null, 0, 0, mNotiNoGroupSortA, user,
- null, System.currentTimeMillis()), getDefaultChannel());
+ null, System.currentTimeMillis()), getLowChannel());
mAudioAttributes = new AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_UNKNOWN)
@@ -197,11 +200,16 @@
.build();
}
- private NotificationChannel getDefaultChannel() {
+ private NotificationChannel getLowChannel() {
return new NotificationChannel(NotificationChannel.DEFAULT_CHANNEL_ID, "name",
IMPORTANCE_LOW);
}
+ private NotificationChannel getDefaultChannel() {
+ return new NotificationChannel(NotificationChannel.DEFAULT_CHANNEL_ID, "name",
+ IMPORTANCE_DEFAULT);
+ }
+
@Test
public void testSortShouldRespectCritical() throws Exception {
ArrayList<NotificationRecord> notificationList = new ArrayList<NotificationRecord>(7);
@@ -285,4 +293,40 @@
ArrayList<NotificationRecord> notificationList = new ArrayList<NotificationRecord>();
mHelper.sort(notificationList);
}
+
+ @Test
+ public void testGroupNotifications_highestIsProxy() {
+ ArrayList<NotificationRecord> notificationList = new ArrayList<>();
+ // this should be the last in the list, except it's in a group with a high child
+ Notification lowSummaryN = new Notification.Builder(mContext, "")
+ .setGroup("group")
+ .setGroupSummary(true)
+ .build();
+ NotificationRecord lowSummary = new NotificationRecord(mContext, new StatusBarNotification(
+ PKG, PKG, 1, "summary", 0, 0, lowSummaryN, USER,
+ null, System.currentTimeMillis()), getLowChannel());
+ notificationList.add(lowSummary);
+
+ Notification lowN = new Notification.Builder(mContext, "").build();
+ NotificationRecord low = new NotificationRecord(mContext, new StatusBarNotification(
+ PKG, PKG, 1, "low", 0, 0, lowN, USER,
+ null, System.currentTimeMillis()), getLowChannel());
+ low.setContactAffinity(0.5f);
+ notificationList.add(low);
+
+ Notification highChildN = new Notification.Builder(mContext, "")
+ .setGroup("group")
+ .setGroupSummary(false)
+ .build();
+ NotificationRecord highChild = new NotificationRecord(mContext, new StatusBarNotification(
+ PKG, PKG, 1, "child", 0, 0, highChildN, USER,
+ null, System.currentTimeMillis()), getDefaultChannel());
+ notificationList.add(highChild);
+
+ mHelper.sort(notificationList);
+
+ assertEquals(lowSummary, notificationList.get(0));
+ assertEquals(highChild, notificationList.get(1));
+ assertEquals(low, notificationList.get(2));
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index 7a6b2b5..ec88718 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -643,10 +643,10 @@
boolean hasForegroundActivities, boolean callerIsRecents,
boolean callerIsTempWhitelisted) {
// window visibility
- doReturn(callingUidHasVisibleWindow).when(mService.mWindowManager).isAnyWindowVisibleForUid(
- callingUid);
- doReturn(realCallingUidHasVisibleWindow).when(mService.mWindowManager)
- .isAnyWindowVisibleForUid(realCallingUid);
+ doReturn(callingUidHasVisibleWindow).when(mService.mWindowManager.mRoot)
+ .isAnyNonToastWindowVisibleForUid(callingUid);
+ doReturn(realCallingUidHasVisibleWindow).when(mService.mWindowManager.mRoot)
+ .isAnyNonToastWindowVisibleForUid(realCallingUid);
// process importance
doReturn(callingUidProcState).when(mService).getUidStateLocked(callingUid);
doReturn(realCallingUidProcState).when(mService).getUidStateLocked(realCallingUid);
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
new file mode 100644
index 0000000..45fe5d2
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2019 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.wm;
+
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
+import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+
+/**
+ * Tests for RootWindowContainer.
+ *
+ * Build/Install/Run:
+ * atest WmTests:RootWindowContainerTests
+ */
+@SmallTest
+@Presubmit
+public class RootWindowContainerTests extends WindowTestsBase {
+
+ private static final int FAKE_CALLING_UID = 667;
+
+ @Test
+ public void testIsAnyNonToastWindowVisibleForUid_oneToastOneNonToastBothVisible() {
+ final WindowState toastyToast = createWindow(null, TYPE_TOAST, "toast", FAKE_CALLING_UID);
+ final WindowState app = createWindow(null, TYPE_APPLICATION, "app", FAKE_CALLING_UID);
+ toastyToast.mHasSurface = true;
+ app.mHasSurface = true;
+
+ assertTrue(toastyToast.isVisible());
+ assertTrue(app.isVisible());
+ assertTrue(mWm.mRoot.isAnyNonToastWindowVisibleForUid(FAKE_CALLING_UID));
+ }
+
+ @Test
+ public void testIsAnyNonToastWindowVisibleForUid_onlyToastVisible() {
+ final WindowState toastyToast = createWindow(null, TYPE_TOAST, "toast", FAKE_CALLING_UID);
+ toastyToast.mHasSurface = true;
+
+ assertTrue(toastyToast.isVisible());
+ assertFalse(mWm.mRoot.isAnyNonToastWindowVisibleForUid(FAKE_CALLING_UID));
+ }
+
+ @Test
+ public void testIsAnyNonToastWindowVisibleForUid_aFewNonToastButNoneVisible() {
+ final WindowState topBar = createWindow(null, TYPE_STATUS_BAR, "topBar", FAKE_CALLING_UID);
+ final WindowState app = createWindow(null, TYPE_APPLICATION, "app", FAKE_CALLING_UID);
+
+ assertFalse(topBar.isVisible());
+ assertFalse(app.isVisible());
+ assertFalse(mWm.mRoot.isAnyNonToastWindowVisibleForUid(FAKE_CALLING_UID));
+ }
+}
+
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 638cb03..cdc0a47 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -257,6 +257,14 @@
}
}
+ WindowState createWindow(WindowState parent, int type, String name, int ownerId) {
+ synchronized (mWm.mGlobalLock) {
+ return (parent == null)
+ ? createWindow(parent, type, mDisplayContent, name, ownerId)
+ : createWindow(parent, type, parent.mToken, name, ownerId);
+ }
+ }
+
WindowState createWindowOnStack(WindowState parent, int windowingMode, int activityType,
int type, DisplayContent dc, String name) {
synchronized (mWm.mGlobalLock) {
@@ -277,7 +285,16 @@
synchronized (mWm.mGlobalLock) {
final WindowToken token = createWindowToken(
dc, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, type);
- return createWindow(parent, type, token, name);
+ return createWindow(parent, type, token, name, 0 /* ownerId */);
+ }
+ }
+
+ WindowState createWindow(WindowState parent, int type, DisplayContent dc, String name,
+ int ownerId) {
+ synchronized (mWm.mGlobalLock) {
+ final WindowToken token = createWindowToken(
+ dc, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, type);
+ return createWindow(parent, type, token, name, ownerId);
}
}
@@ -299,6 +316,14 @@
}
WindowState createWindow(WindowState parent, int type, WindowToken token, String name,
+ int ownerId) {
+ synchronized (mWm.mGlobalLock) {
+ return createWindow(parent, type, token, name, ownerId,
+ false /* ownerCanAddInternalSystemWindow */);
+ }
+ }
+
+ WindowState createWindow(WindowState parent, int type, WindowToken token, String name,
int ownerId, boolean ownerCanAddInternalSystemWindow) {
return createWindow(parent, type, token, name, ownerId, ownerCanAddInternalSystemWindow,
mWm, mMockSession, mIWindow);
diff --git a/telephony/java/android/telephony/ICellInfoCallback.aidl b/telephony/java/android/telephony/ICellInfoCallback.aidl
index 7fb62682..ee3c1b1 100644
--- a/telephony/java/android/telephony/ICellInfoCallback.aidl
+++ b/telephony/java/android/telephony/ICellInfoCallback.aidl
@@ -16,6 +16,7 @@
package android.telephony;
+import android.os.ParcelableException;
import android.telephony.CellInfo;
import java.util.List;
@@ -27,4 +28,5 @@
oneway interface ICellInfoCallback
{
void onCellInfo(in List<CellInfo> state);
+ void onError(in int errorCode, in ParcelableException detail);
}
diff --git a/telephony/java/android/telephony/SignalStrength.java b/telephony/java/android/telephony/SignalStrength.java
index 2271069..e77042d 100644
--- a/telephony/java/android/telephony/SignalStrength.java
+++ b/telephony/java/android/telephony/SignalStrength.java
@@ -24,6 +24,8 @@
import android.os.Parcelable;
import android.os.PersistableBundle;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Objects;
/**
@@ -178,6 +180,35 @@
return mLte;
}
+ /**
+ * Returns a List of CellSignalStrength Components of this SignalStrength Report.
+ *
+ * Use this API to access underlying
+ * {@link android.telephony#CellSignalStrength CellSignalStrength} objects that provide more
+ * granular information about the SignalStrength report. Only valid (non-empty)
+ * CellSignalStrengths will be returned. The order of any returned elements is not guaranteed,
+ * and the list may contain more than one instance of a CellSignalStrength type.
+ *
+ * @return a List of CellSignalStrength or an empty List if there are no valid measurements.
+ *
+ * @see android.telephony#CellSignalStrength
+ * @see android.telephony#CellSignalStrengthNr
+ * @see android.telephony#CellSignalStrengthLte
+ * @see android.telephony#CellSignalStrengthTdscdma
+ * @see android.telephony#CellSignalStrengthWcdma
+ * @see android.telephony#CellSignalStrengthCdma
+ * @see android.telephony#CellSignalStrengthGsm
+ */
+ public @NonNull List<CellSignalStrength> getCellSignalStrengths() {
+ List<CellSignalStrength> cssList = new ArrayList<>(2); // Usually have 2 or fewer elems
+ if (mLte.isValid()) cssList.add(mLte);
+ if (mCdma.isValid()) cssList.add(mCdma);
+ if (mTdscdma.isValid()) cssList.add(mTdscdma);
+ if (mWcdma.isValid()) cssList.add(mWcdma);
+ if (mGsm.isValid()) cssList.add(mGsm);
+ return cssList;
+ }
+
/** @hide */
public void updateLevel(PersistableBundle cc, ServiceState ss) {
mLteRsrpBoost = ss.getLteEarfcnRsrpBoost();
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index b48a1ce..7f7d5e7 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -4898,19 +4898,53 @@
/** Callback for providing asynchronous {@link CellInfo} on request */
public abstract static class CellInfoCallback {
/**
- * Response to
+ * Success response to
* {@link android.telephony.TelephonyManager#requestCellInfoUpdate requestCellInfoUpdate()}.
*
- * <p>Invoked when there is a response to
+ * Invoked when there is a response to
* {@link android.telephony.TelephonyManager#requestCellInfoUpdate requestCellInfoUpdate()}
* to provide a list of {@link CellInfo}. If no {@link CellInfo} is available then an empty
- * list will be provided. If an error occurs, null will be provided.
+ * list will be provided. If an error occurs, null will be provided unless the onError
+ * callback is overridden.
*
* @param cellInfo a list of {@link CellInfo}, an empty list, or null.
*
* {@see android.telephony.TelephonyManager#getAllCellInfo getAllCellInfo()}
*/
- public abstract void onCellInfo(List<CellInfo> cellInfo);
+ public abstract void onCellInfo(@NonNull List<CellInfo> cellInfo);
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"ERROR_"}, value = {ERROR_TIMEOUT, ERROR_MODEM_ERROR})
+ public @interface CellInfoCallbackError {}
+
+ /**
+ * The system timed out waiting for a response from the Radio.
+ */
+ public static final int ERROR_TIMEOUT = 1;
+
+ /**
+ * The modem returned a failure.
+ */
+ public static final int ERROR_MODEM_ERROR = 2;
+
+ /**
+ * Error response to
+ * {@link android.telephony.TelephonyManager#requestCellInfoUpdate requestCellInfoUpdate()}.
+ *
+ * Invoked when an error condition prevents updated {@link CellInfo} from being fetched
+ * and returned from the modem. Callers of requestCellInfoUpdate() should override this
+ * function to receive detailed status information in the event of an error. By default,
+ * this function will invoke onCellInfo() with null.
+ *
+ * @param errorCode an error code indicating the type of failure.
+ * @param detail a Throwable object with additional detail regarding the failure if
+ * available, otherwise null.
+ */
+ public void onError(@CellInfoCallbackError int errorCode, @Nullable Throwable detail) {
+ // By default, simply invoke the success callback with an empty list.
+ onCellInfo(new ArrayList<CellInfo>());
+ }
};
/**
@@ -4937,6 +4971,12 @@
Binder.withCleanCallingIdentity(() ->
executor.execute(() -> callback.onCellInfo(cellInfo)));
}
+
+ public void onError(int errorCode, android.os.ParcelableException detail) {
+ Binder.withCleanCallingIdentity(() ->
+ executor.execute(() -> callback.onError(
+ errorCode, detail.getCause())));
+ }
}, getOpPackageName());
} catch (RemoteException ex) {
@@ -4971,6 +5011,12 @@
Binder.withCleanCallingIdentity(() ->
executor.execute(() -> callback.onCellInfo(cellInfo)));
}
+
+ public void onError(int errorCode, android.os.ParcelableException detail) {
+ Binder.withCleanCallingIdentity(() ->
+ executor.execute(() -> callback.onError(
+ errorCode, detail.getCause())));
+ }
}, getOpPackageName(), workSource);
} catch (RemoteException ex) {
}
diff --git a/wifi/java/android/net/wifi/aware/WifiAwareManager.java b/wifi/java/android/net/wifi/aware/WifiAwareManager.java
index 26a6c08..1fa1fd5 100644
--- a/wifi/java/android/net/wifi/aware/WifiAwareManager.java
+++ b/wifi/java/android/net/wifi/aware/WifiAwareManager.java
@@ -35,6 +35,7 @@
import android.os.Message;
import android.os.Process;
import android.os.RemoteException;
+import android.text.TextUtils;
import android.util.Log;
import libcore.util.HexEncoding;
@@ -434,6 +435,8 @@
null, // peerMac (not used in this method)
pmk,
passphrase,
+ 0, // no port info for deprecated IB APIs
+ -1, // no transport info for deprecated IB APIs
Process.myUid());
}
@@ -473,6 +476,8 @@
peer,
pmk,
passphrase,
+ 0, // no port info for OOB APIs
+ -1, // no transport protocol info for OOB APIs
Process.myUid());
}
@@ -824,6 +829,8 @@
private PeerHandle mPeerHandle;
private String mPskPassphrase;
private byte[] mPmk;
+ private int mPort = 0; // invalid value
+ private int mTransportProtocol = -1; // invalid value
/**
* Configure the {@link PublishDiscoverySession} or {@link SubscribeDiscoverySession}
@@ -902,6 +909,55 @@
}
/**
+ * Configure the port number which will be used to create a connection over this link. This
+ * configuration should only be done on the server device, e.g. the device creating the
+ * {@link java.net.ServerSocket}.
+ * <p>Notes:
+ * <ul>
+ * <li>The server device must be the Publisher device!
+ * <li>The port information can only be specified on secure links, specified using
+ * {@link #setPskPassphrase(String)}.
+ * </ul>
+ *
+ * @param port A positive integer indicating the port to be used for communication.
+ * @return the current {@link NetworkSpecifierBuilder} builder, enabling chaining of builder
+ * methods.
+ */
+ public @NonNull NetworkSpecifierBuilder setPort(int port) {
+ if (port <= 0 || port > 65535) {
+ throw new IllegalArgumentException("The port must be a positive value (0, 65535]");
+ }
+ mPort = port;
+ return this;
+ }
+
+ /**
+ * Configure the transport protocol which will be used to create a connection over this
+ * link. This configuration should only be done on the server device, e.g. the device
+ * creating the {@link java.net.ServerSocket} for TCP.
+ * <p>Notes:
+ * <ul>
+ * <li>The server device must be the Publisher device!
+ * <li>The transport protocol information can only be specified on secure links,
+ * specified using {@link #setPskPassphrase(String)}.
+ * </ul>
+ * The transport protocol number is assigned by the Internet Assigned Numbers Authority
+ * (IANA) https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml.
+ *
+ * @param transportProtocol The transport protocol to be used for communication.
+ * @return the current {@link NetworkSpecifierBuilder} builder, enabling chaining of builder
+ * methods.
+ */
+ public @NonNull NetworkSpecifierBuilder setTransportProtocol(int transportProtocol) {
+ if (transportProtocol < 0 || transportProtocol > 255) {
+ throw new IllegalArgumentException(
+ "The transport protocol must be in range [0, 255]");
+ }
+ mTransportProtocol = transportProtocol;
+ return this;
+ }
+
+ /**
* Create a {@link android.net.NetworkRequest.Builder#setNetworkSpecifier(NetworkSpecifier)}
* for a WiFi Aware connection (link) to the specified peer. The
* {@link android.net.NetworkRequest.Builder#addTransportType(int)} should be set to
@@ -929,6 +985,18 @@
? WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR
: WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER;
+ if (mPort != 0 || mTransportProtocol != -1) {
+ if (role != WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER) {
+ throw new IllegalStateException(
+ "Port and transport protocol information can only "
+ + "be specified on the Publisher device (which is the server");
+ }
+ if (TextUtils.isEmpty(mPskPassphrase) && mPmk == null) {
+ throw new IllegalStateException("Port and transport protocol information can "
+ + "only be specified on a secure link");
+ }
+ }
+
if (role == WIFI_AWARE_DATA_PATH_ROLE_INITIATOR && mPeerHandle == null) {
throw new IllegalStateException("Null peerHandle!?");
}
@@ -936,7 +1004,7 @@
return new WifiAwareNetworkSpecifier(
WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_IB, role,
mDiscoverySession.mClientId, mDiscoverySession.mSessionId, mPeerHandle.peerId,
- null, mPmk, mPskPassphrase, Process.myUid());
+ null, mPmk, mPskPassphrase, mPort, mTransportProtocol, Process.myUid());
}
}
}
diff --git a/wifi/java/android/net/wifi/aware/WifiAwareNetworkInfo.java b/wifi/java/android/net/wifi/aware/WifiAwareNetworkInfo.java
index 0f29e08..b258906 100644
--- a/wifi/java/android/net/wifi/aware/WifiAwareNetworkInfo.java
+++ b/wifi/java/android/net/wifi/aware/WifiAwareNetworkInfo.java
@@ -38,17 +38,30 @@
* android.net.NetworkCapabilities)} callback.
* <p>
* The Wi-Fi Aware-specific network information include the peer's scoped link-local IPv6 address
- * for the Wi-Fi Aware link. The scoped link-local IPv6 can then be used to create a
+ * for the Wi-Fi Aware link, as well as (optionally) the port and transport protocol specified by
+ * the peer.
+ * The scoped link-local IPv6, port, and transport protocol can then be used to create a
* {@link java.net.Socket} connection to the peer.
+ * <p>
+ * Note: these are the peer's IPv6 and port information - not the local device's!
*/
public final class WifiAwareNetworkInfo implements TransportInfo, Parcelable {
private Inet6Address mIpv6Addr;
+ private int mPort = 0; // a value of 0 is considered invalid
+ private int mTransportProtocol = -1; // a value of -1 is considered invalid
/** @hide */
public WifiAwareNetworkInfo(Inet6Address ipv6Addr) {
mIpv6Addr = ipv6Addr;
}
+ /** @hide */
+ public WifiAwareNetworkInfo(Inet6Address ipv6Addr, int port, int transportProtocol) {
+ mIpv6Addr = ipv6Addr;
+ mPort = port;
+ mTransportProtocol = transportProtocol;
+ }
+
/**
* Get the scoped link-local IPv6 address of the Wi-Fi Aware peer (not of the local device!).
*
@@ -59,6 +72,34 @@
return mIpv6Addr;
}
+ /**
+ * Get the port number to be used to create a network connection to the Wi-Fi Aware peer.
+ * The port information is provided by the app running on the peer which requested the
+ * connection, using the {@link WifiAwareManager.NetworkSpecifierBuilder#setPort(int)}.
+ *
+ * @return A port number on the peer. A value of 0 indicates that no port was specified by the
+ * peer.
+ */
+ public int getPort() {
+ return mPort;
+ }
+
+ /**
+ * Get the transport protocol to be used to communicate over a network connection to the Wi-Fi
+ * Aware peer. The transport protocol is provided by the app running on the peer which requested
+ * the connection, using the
+ * {@link WifiAwareManager.NetworkSpecifierBuilder#setTransportProtocol(int)}.
+ * <p>
+ * The transport protocol number is assigned by the Internet Assigned Numbers Authority
+ * (IANA) https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml.
+ *
+ * @return A transport protocol id. A value of -1 indicates that no transport protocol was
+ * specified by the peer.
+ */
+ public int getTransportProtocol() {
+ return mTransportProtocol;
+ }
+
// parcelable methods
@Override
@@ -71,6 +112,8 @@
dest.writeByteArray(mIpv6Addr.getAddress());
NetworkInterface ni = mIpv6Addr.getScopedInterface();
dest.writeString(ni == null ? null : ni.getName());
+ dest.writeInt(mPort);
+ dest.writeInt(mTransportProtocol);
}
public static final Creator<WifiAwareNetworkInfo> CREATOR =
@@ -94,8 +137,10 @@
e.printStackTrace();
return null;
}
+ int port = in.readInt();
+ int transportProtocol = in.readInt();
- return new WifiAwareNetworkInfo(ipv6Addr);
+ return new WifiAwareNetworkInfo(ipv6Addr, port, transportProtocol);
}
@Override
@@ -109,7 +154,9 @@
@Override
public String toString() {
- return new StringBuilder("AwareNetworkInfo: IPv6=").append(mIpv6Addr).toString();
+ return new StringBuilder("AwareNetworkInfo: IPv6=").append(mIpv6Addr).append(
+ ", port=").append(mPort).append(", transportProtocol=").append(
+ mTransportProtocol).toString();
}
/** @hide */
@@ -124,12 +171,13 @@
}
WifiAwareNetworkInfo lhs = (WifiAwareNetworkInfo) obj;
- return Objects.equals(mIpv6Addr, lhs.mIpv6Addr);
+ return Objects.equals(mIpv6Addr, lhs.mIpv6Addr) && mPort == lhs.mPort
+ && mTransportProtocol == lhs.mTransportProtocol;
}
/** @hide */
@Override
public int hashCode() {
- return Objects.hash(mIpv6Addr);
+ return Objects.hash(mIpv6Addr, mPort, mTransportProtocol);
}
}
diff --git a/wifi/java/android/net/wifi/aware/WifiAwareNetworkSpecifier.java b/wifi/java/android/net/wifi/aware/WifiAwareNetworkSpecifier.java
index 6e37fcf..a93a6d5 100644
--- a/wifi/java/android/net/wifi/aware/WifiAwareNetworkSpecifier.java
+++ b/wifi/java/android/net/wifi/aware/WifiAwareNetworkSpecifier.java
@@ -19,7 +19,6 @@
import android.net.NetworkSpecifier;
import android.os.Parcel;
import android.os.Parcelable;
-import android.util.Log;
import java.util.Arrays;
import java.util.Objects;
@@ -117,6 +116,32 @@
public final String passphrase;
/**
+ * The port information to be used for this link. This information will be communicated to the
+ * peer as part of the layer 2 link setup.
+ *
+ * Information only allowed on secure links since a single layer-2 link is set up for all
+ * requestors. Therefore if multiple apps on a single device request links to the same peer
+ * device they all get the same link. However, the link is only set up on the first request -
+ * hence only the first can transmit the port information. But we don't want to expose that
+ * information to other apps. Limiting to secure links would (usually) imply single app usage.
+ *
+ * @hide
+ */
+ public final int port;
+
+ /**
+ * The transport protocol information to be used for this link. This information will be
+ * communicated to the peer as part of the layer 2 link setup.
+ *
+ * Information only allowed on secure links since a single layer-2 link is set up for all
+ * requestors. Therefore if multiple apps on a single device request links to the same peer
+ * device they all get the same link. However, the link is only set up on the first request -
+ * hence only the first can transmit the port information. But we don't want to expose that
+ * information to other apps. Limiting to secure links would (usually) imply single app usage.
+ */
+ public final int transportProtocol;
+
+ /**
* The UID of the process initializing this network specifier. Validated by receiver using
* checkUidIfNecessary() and is used by satisfiedBy() to determine whether matches the
* offered network.
@@ -127,7 +152,8 @@
/** @hide */
public WifiAwareNetworkSpecifier(int type, int role, int clientId, int sessionId, int peerId,
- byte[] peerMac, byte[] pmk, String passphrase, int requestorUid) {
+ byte[] peerMac, byte[] pmk, String passphrase, int port, int transportProtocol,
+ int requestorUid) {
this.type = type;
this.role = role;
this.clientId = clientId;
@@ -136,6 +162,8 @@
this.peerMac = peerMac;
this.pmk = pmk;
this.passphrase = passphrase;
+ this.port = port;
+ this.transportProtocol = transportProtocol;
this.requestorUid = requestorUid;
}
@@ -152,6 +180,8 @@
in.createByteArray(), // peerMac
in.createByteArray(), // pmk
in.readString(), // passphrase
+ in.readInt(), // port
+ in.readInt(), // transportProtocol
in.readInt()); // requestorUid
}
@@ -186,6 +216,8 @@
dest.writeByteArray(peerMac);
dest.writeByteArray(pmk);
dest.writeString(passphrase);
+ dest.writeInt(port);
+ dest.writeInt(transportProtocol);
dest.writeInt(requestorUid);
}
@@ -202,19 +234,8 @@
/** @hide */
@Override
public int hashCode() {
- int result = 17;
-
- result = 31 * result + type;
- result = 31 * result + role;
- result = 31 * result + clientId;
- result = 31 * result + sessionId;
- result = 31 * result + peerId;
- result = 31 * result + Arrays.hashCode(peerMac);
- result = 31 * result + Arrays.hashCode(pmk);
- result = 31 * result + Objects.hashCode(passphrase);
- result = 31 * result + requestorUid;
-
- return result;
+ return Objects.hash(type, role, clientId, sessionId, peerId, Arrays.hashCode(peerMac),
+ Arrays.hashCode(pmk), passphrase, port, transportProtocol, requestorUid);
}
/** @hide */
@@ -238,6 +259,8 @@
&& Arrays.equals(peerMac, lhs.peerMac)
&& Arrays.equals(pmk, lhs.pmk)
&& Objects.equals(passphrase, lhs.passphrase)
+ && port == lhs.port
+ && transportProtocol == lhs.transportProtocol
&& requestorUid == lhs.requestorUid;
}
@@ -256,7 +279,8 @@
.append(", pmk=").append((pmk == null) ? "<null>" : "<non-null>")
// masking PII
.append(", passphrase=").append((passphrase == null) ? "<null>" : "<non-null>")
- .append(", requestorUid=").append(requestorUid)
+ .append(", port=").append(port).append(", transportProtocol=")
+ .append(transportProtocol).append(", requestorUid=").append(requestorUid)
.append("]");
return sb.toString();
}
diff --git a/wifi/java/android/net/wifi/hotspot2/pps/Credential.java b/wifi/java/android/net/wifi/hotspot2/pps/Credential.java
index 7689fc3..59a7290 100644
--- a/wifi/java/android/net/wifi/hotspot2/pps/Credential.java
+++ b/wifi/java/android/net/wifi/hotspot2/pps/Credential.java
@@ -572,7 +572,7 @@
@Override
public int hashCode() {
- return Objects.hash(mCertType, mCertSha256Fingerprint);
+ return Objects.hash(mCertType, Arrays.hashCode(mCertSha256Fingerprint));
}
@Override
@@ -842,24 +842,50 @@
}
/**
- * CA (Certificate Authority) X509 certificate.
+ * CA (Certificate Authority) X509 certificates.
*/
- private X509Certificate mCaCertificate = null;
+ private X509Certificate[] mCaCertificates = null;
+
/**
* Set the CA (Certification Authority) certificate associated with this credential.
*
* @param caCertificate The CA certificate to set to
*/
public void setCaCertificate(X509Certificate caCertificate) {
- mCaCertificate = caCertificate;
+ mCaCertificates = null;
+ if (caCertificate != null) {
+ mCaCertificates = new X509Certificate[] {caCertificate};
+ }
}
+
+ /**
+ * Set the CA (Certification Authority) certificates associated with this credential.
+ *
+ * @param caCertificates The list of CA certificates to set to
+ * @hide
+ */
+ public void setCaCertificates(X509Certificate[] caCertificates) {
+ mCaCertificates = caCertificates;
+ }
+
/**
* Get the CA (Certification Authority) certificate associated with this credential.
*
- * @return CA certificate associated with this credential
+ * @return CA certificate associated with this credential, {@code null} if certificate is not
+ * set or certificate is more than one.
*/
public X509Certificate getCaCertificate() {
- return mCaCertificate;
+ return mCaCertificates == null || mCaCertificates.length > 1 ? null : mCaCertificates[0];
+ }
+
+ /**
+ * Get the CA (Certification Authority) certificates associated with this credential.
+ *
+ * @return The list of CA certificates associated with this credential
+ * @hide
+ */
+ public X509Certificate[] getCaCertificates() {
+ return mCaCertificates;
}
/**
@@ -933,7 +959,11 @@
mClientCertificateChain = Arrays.copyOf(source.mClientCertificateChain,
source.mClientCertificateChain.length);
}
- mCaCertificate = source.mCaCertificate;
+ if (source.mCaCertificates != null) {
+ mCaCertificates = Arrays.copyOf(source.mCaCertificates,
+ source.mCaCertificates.length);
+ }
+
mClientPrivateKey = source.mClientPrivateKey;
}
}
@@ -952,7 +982,7 @@
dest.writeParcelable(mUserCredential, flags);
dest.writeParcelable(mCertCredential, flags);
dest.writeParcelable(mSimCredential, flags);
- ParcelUtil.writeCertificate(dest, mCaCertificate);
+ ParcelUtil.writeCertificates(dest, mCaCertificates);
ParcelUtil.writeCertificates(dest, mClientCertificateChain);
ParcelUtil.writePrivateKey(dest, mClientPrivateKey);
}
@@ -977,16 +1007,17 @@
: mCertCredential.equals(that.mCertCredential))
&& (mSimCredential == null ? that.mSimCredential == null
: mSimCredential.equals(that.mSimCredential))
- && isX509CertificateEquals(mCaCertificate, that.mCaCertificate)
+ && isX509CertificatesEquals(mCaCertificates, that.mCaCertificates)
&& isX509CertificatesEquals(mClientCertificateChain, that.mClientCertificateChain)
&& isPrivateKeyEquals(mClientPrivateKey, that.mClientPrivateKey);
}
@Override
public int hashCode() {
- return Objects.hash(mRealm, mCreationTimeInMillis, mExpirationTimeInMillis,
+ return Objects.hash(mCreationTimeInMillis, mExpirationTimeInMillis, mRealm,
mCheckAaaServerCertStatus, mUserCredential, mCertCredential, mSimCredential,
- mCaCertificate, mClientCertificateChain, mClientPrivateKey);
+ mClientPrivateKey, Arrays.hashCode(mCaCertificates),
+ Arrays.hashCode(mClientCertificateChain));
}
@Override
@@ -1067,7 +1098,7 @@
credential.setUserCredential(in.readParcelable(null));
credential.setCertCredential(in.readParcelable(null));
credential.setSimCredential(in.readParcelable(null));
- credential.setCaCertificate(ParcelUtil.readCertificate(in));
+ credential.setCaCertificates(ParcelUtil.readCertificates(in));
credential.setClientCertificateChain(ParcelUtil.readCertificates(in));
credential.setClientPrivateKey(ParcelUtil.readPrivateKey(in));
return credential;
@@ -1100,7 +1131,7 @@
// CA certificate is required for R1 Passpoint profile.
// For R2, it is downloaded using cert URL provided in PPS MO after validation completes.
- if (isR1 && mCaCertificate == null) {
+ if (isR1 && mCaCertificates == null) {
Log.d(TAG, "Missing CA Certificate for user credential");
return false;
}
@@ -1131,7 +1162,7 @@
// Verify required key and certificates for certificate credential.
// CA certificate is required for R1 Passpoint profile.
// For R2, it is downloaded using cert URL provided in PPS MO after validation completes.
- if (isR1 && mCaCertificate == null) {
+ if (isR1 && mCaCertificates == null) {
Log.d(TAG, "Missing CA Certificate for certificate credential");
return false;
}
diff --git a/wifi/tests/src/android/net/wifi/aware/WifiAwareAgentNetworkSpecifierTest.java b/wifi/tests/src/android/net/wifi/aware/WifiAwareAgentNetworkSpecifierTest.java
index 4189e40..c3b6285 100644
--- a/wifi/tests/src/android/net/wifi/aware/WifiAwareAgentNetworkSpecifierTest.java
+++ b/wifi/tests/src/android/net/wifi/aware/WifiAwareAgentNetworkSpecifierTest.java
@@ -62,6 +62,7 @@
WifiAwareAgentNetworkSpecifier.CREATOR.createFromParcel(parcelR);
assertEquals(dut, rereadDut);
+ assertEquals(dut.hashCode(), rereadDut.hashCode());
// Ensure that individual network specifiers are satisfied by both the original & marshaled
// |WifiAwareNetworkAgentSpecifier instances.
@@ -181,6 +182,6 @@
WifiAwareNetworkSpecifier getDummyNetworkSpecifier(int clientId) {
return new WifiAwareNetworkSpecifier(WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_OOB,
WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_INITIATOR, clientId, 0, 0, new byte[6],
- null, null, 0);
+ null, null, 10, 5, 0);
}
}
diff --git a/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java b/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java
index ed38c76..6da6d4a 100644
--- a/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/aware/WifiAwareManagerTest.java
@@ -16,8 +16,11 @@
package android.net.wifi.aware;
+import static android.net.wifi.aware.WifiAwareNetworkSpecifier.NETWORK_SPECIFIER_TYPE_IB;
+
import static org.hamcrest.core.IsEqual.equalTo;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
@@ -913,9 +916,10 @@
final int clientId = 4565;
final int sessionId = 123;
final PeerHandle peerHandle = new PeerHandle(123412);
- final int role = WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER;
final byte[] pmk = PMK_VALID;
final String passphrase = PASSPHRASE_VALID;
+ final int port = 5;
+ final int transportProtocol = 10;
final ConfigRequest configRequest = new ConfigRequest.Builder().build();
final PublishConfig publishConfig = new PublishConfig.Builder().build();
@@ -959,56 +963,70 @@
.setPeerHandle(peerHandle).build();
// validate format
- collector.checkThat("role", role, equalTo(ns.role));
+ collector.checkThat("role", WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER,
+ equalTo(ns.role));
collector.checkThat("client_id", clientId, equalTo(ns.clientId));
collector.checkThat("session_id", sessionId, equalTo(ns.sessionId));
collector.checkThat("peer_id", peerHandle.peerId, equalTo(ns.peerId));
- collector.checkThat("role", role, equalTo(nsb.role));
+ collector.checkThat("role", WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER,
+ equalTo(nsb.role));
collector.checkThat("client_id", clientId, equalTo(nsb.clientId));
collector.checkThat("session_id", sessionId, equalTo(nsb.sessionId));
collector.checkThat("peer_id", peerHandle.peerId, equalTo(nsb.peerId));
+ collector.checkThat("port", 0, equalTo(nsb.port));
+ collector.checkThat("transportProtocol", -1, equalTo(nsb.transportProtocol));
// (4) request an encrypted (PMK) network specifier from the session
ns = (WifiAwareNetworkSpecifier) publishSession.getValue().createNetworkSpecifierPmk(
peerHandle, pmk);
- nsb =
- (WifiAwareNetworkSpecifier) new WifiAwareManager.NetworkSpecifierBuilder()
- .setDiscoverySession(
- publishSession.getValue()).setPeerHandle(peerHandle).setPmk(pmk).build();
+ nsb = (WifiAwareNetworkSpecifier) new WifiAwareManager.NetworkSpecifierBuilder()
+ .setDiscoverySession(publishSession.getValue()).setPeerHandle(peerHandle)
+ .setPmk(pmk).setPort(port).setTransportProtocol(transportProtocol).build();
// validate format
- collector.checkThat("role", role, equalTo(ns.role));
+ collector.checkThat("role", WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER,
+ equalTo(ns.role));
collector.checkThat("client_id", clientId, equalTo(ns.clientId));
collector.checkThat("session_id", sessionId, equalTo(ns.sessionId));
collector.checkThat("peer_id", peerHandle.peerId, equalTo(ns.peerId));
collector.checkThat("pmk", pmk , equalTo(ns.pmk));
- collector.checkThat("role", role, equalTo(nsb.role));
+ collector.checkThat("role", WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER,
+ equalTo(nsb.role));
collector.checkThat("client_id", clientId, equalTo(nsb.clientId));
collector.checkThat("session_id", sessionId, equalTo(nsb.sessionId));
collector.checkThat("peer_id", peerHandle.peerId, equalTo(nsb.peerId));
collector.checkThat("pmk", pmk , equalTo(nsb.pmk));
+ collector.checkThat("port", port, equalTo(nsb.port));
+ collector.checkThat("transportProtocol", transportProtocol, equalTo(nsb.transportProtocol));
// (5) request an encrypted (Passphrase) network specifier from the session
- ns = (WifiAwareNetworkSpecifier) publishSession.getValue().createNetworkSpecifierPassphrase(
- peerHandle, passphrase);
+ ns =
+ (WifiAwareNetworkSpecifier) publishSession.getValue()
+ .createNetworkSpecifierPassphrase(
+ peerHandle, passphrase);
nsb = (WifiAwareNetworkSpecifier) new WifiAwareManager.NetworkSpecifierBuilder()
.setDiscoverySession(publishSession.getValue()).setPeerHandle(peerHandle)
- .setPskPassphrase(passphrase).build();
+ .setPskPassphrase(passphrase).setPort(port).setTransportProtocol(transportProtocol)
+ .build();
// validate format
- collector.checkThat("role", role, equalTo(ns.role));
+ collector.checkThat("role", WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER,
+ equalTo(ns.role));
collector.checkThat("client_id", clientId, equalTo(ns.clientId));
collector.checkThat("session_id", sessionId, equalTo(ns.sessionId));
collector.checkThat("peer_id", peerHandle.peerId, equalTo(ns.peerId));
collector.checkThat("passphrase", passphrase, equalTo(ns.passphrase));
- collector.checkThat("role", role, equalTo(nsb.role));
+ collector.checkThat("role", WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER,
+ equalTo(nsb.role));
collector.checkThat("client_id", clientId, equalTo(nsb.clientId));
collector.checkThat("session_id", sessionId, equalTo(nsb.sessionId));
collector.checkThat("peer_id", peerHandle.peerId, equalTo(nsb.peerId));
collector.checkThat("passphrase", passphrase, equalTo(nsb.passphrase));
+ collector.checkThat("port", port, equalTo(nsb.port));
+ collector.checkThat("transportProtocol", transportProtocol, equalTo(nsb.transportProtocol));
verifyNoMoreInteractions(mockCallback, mockSessionCallback, mockAwareService,
mockPublishSession, mockRttListener);
@@ -1325,6 +1343,140 @@
executeNetworkSpecifierDirect(null, false, null, PASSPHRASE_VALID, false);
}
+ /**
+ * Validate that get an exception when creating a network specifier with an invalid port number
+ * (<=0).
+ */
+ @Test(expected = IllegalArgumentException.class)
+ public void testNetworkSpecifierBuilderInvalidPortNumber() throws Exception {
+ final PeerHandle peerHandle = new PeerHandle(123412);
+ final byte[] pmk = PMK_VALID;
+ final int port = 0;
+
+ DiscoverySession publishSession = executeSessionStartup(true);
+
+ WifiAwareNetworkSpecifier nsb =
+ (WifiAwareNetworkSpecifier) new WifiAwareManager.NetworkSpecifierBuilder()
+ .setDiscoverySession(publishSession).setPeerHandle(peerHandle)
+ .setPmk(pmk).setPort(port).build();
+ }
+
+ /**
+ * Validate that get an exception when creating a network specifier with port information
+ * without also requesting a secure link.
+ */
+ @Test(expected = IllegalStateException.class)
+ public void testNetworkSpecifierBuilderInvalidPortOnInsecure() throws Exception {
+ final PeerHandle peerHandle = new PeerHandle(123412);
+ final int port = 5;
+
+ DiscoverySession publishSession = executeSessionStartup(true);
+
+ WifiAwareNetworkSpecifier nsb =
+ (WifiAwareNetworkSpecifier) new WifiAwareManager.NetworkSpecifierBuilder()
+ .setDiscoverySession(publishSession).setPeerHandle(peerHandle)
+ .setPort(port).build();
+ }
+
+ /**
+ * Validate that get an exception when creating a network specifier with port information on
+ * a responder.
+ */
+ @Test(expected = IllegalStateException.class)
+ public void testNetworkSpecifierBuilderInvalidPortOnResponder() throws Exception {
+ final PeerHandle peerHandle = new PeerHandle(123412);
+ final int port = 5;
+
+ DiscoverySession subscribeSession = executeSessionStartup(false);
+
+ WifiAwareNetworkSpecifier nsb =
+ (WifiAwareNetworkSpecifier) new WifiAwareManager.NetworkSpecifierBuilder()
+ .setDiscoverySession(subscribeSession).setPeerHandle(peerHandle)
+ .setPort(port).build();
+ }
+
+ /**
+ * Validate that get an exception when creating a network specifier with an invalid transport
+ * protocol number (not in [0, 255]).
+ */
+ @Test
+ public void testNetworkSpecifierBuilderInvalidTransportProtocolNumber() throws Exception {
+ final PeerHandle peerHandle = new PeerHandle(123412);
+ final byte[] pmk = PMK_VALID;
+ final int tpNegative = -1;
+ final int tpTooLarge = 256;
+ final int tpSmallest = 0;
+ final int tpLargest = 255;
+
+ DiscoverySession publishSession = executeSessionStartup(true);
+
+ try {
+ WifiAwareNetworkSpecifier nsb =
+ (WifiAwareNetworkSpecifier) new WifiAwareManager.NetworkSpecifierBuilder()
+ .setDiscoverySession(publishSession).setPeerHandle(peerHandle)
+ .setPmk(pmk).setTransportProtocol(tpNegative).build();
+ assertTrue("No exception on negative transport protocol!", false);
+ } catch (IllegalArgumentException e) {
+ // nop - exception is correct!
+ }
+ try {
+ WifiAwareNetworkSpecifier nsb =
+ (WifiAwareNetworkSpecifier) new WifiAwareManager.NetworkSpecifierBuilder()
+ .setDiscoverySession(publishSession).setPeerHandle(peerHandle)
+ .setPmk(pmk).setTransportProtocol(tpTooLarge).build();
+ assertTrue("No exception on >255 transport protocol!", false);
+ } catch (IllegalArgumentException e) {
+ // nop - exception is correct!
+ }
+ WifiAwareNetworkSpecifier nsb =
+ (WifiAwareNetworkSpecifier) new WifiAwareManager.NetworkSpecifierBuilder()
+ .setDiscoverySession(publishSession).setPeerHandle(peerHandle)
+ .setPmk(pmk).setTransportProtocol(tpSmallest).build();
+ nsb =
+ (WifiAwareNetworkSpecifier) new WifiAwareManager.NetworkSpecifierBuilder()
+ .setDiscoverySession(
+ publishSession).setPeerHandle(peerHandle).setPmk(
+ pmk).setTransportProtocol(tpLargest).build();
+ }
+
+ /**
+ * Validate that get an exception when creating a network specifier with transport protocol
+ * information without also requesting a secure link.
+ */
+ @Test(expected = IllegalStateException.class)
+ public void testNetworkSpecifierBuilderInvalidTransportProtocolOnInsecure() throws Exception {
+ final PeerHandle peerHandle = new PeerHandle(123412);
+ final int transportProtocol = 5;
+
+ DiscoverySession publishSession = executeSessionStartup(true);
+
+ WifiAwareNetworkSpecifier nsb =
+ (WifiAwareNetworkSpecifier) new WifiAwareManager.NetworkSpecifierBuilder()
+ .setDiscoverySession(publishSession).setPeerHandle(peerHandle)
+ .setTransportProtocol(transportProtocol).build();
+ }
+
+ /**
+ * Validate that get an exception when creating a network specifier with transport protocol
+ * information on a responder.
+ */
+ @Test(expected = IllegalStateException.class)
+ public void testNetworkSpecifierBuilderInvalidTransportProtocolOnResponder() throws Exception {
+ final PeerHandle peerHandle = new PeerHandle(123412);
+ final int transportProtocol = 5;
+
+ DiscoverySession subscribeSession = executeSessionStartup(false);
+
+ WifiAwareNetworkSpecifier nsb =
+ (WifiAwareNetworkSpecifier) new WifiAwareManager.NetworkSpecifierBuilder()
+ .setDiscoverySession(subscribeSession).setPeerHandle(peerHandle)
+ .setTransportProtocol(transportProtocol).build();
+ }
+
+ /*
+ * Utilities
+ */
+
private void executeNetworkSpecifierDirect(byte[] someMac, boolean doPmk, byte[] pmk,
String passphrase, boolean doInitiator) throws Exception {
final int clientId = 134;
@@ -1356,7 +1508,83 @@
}
}
- // WifiAwareNetworkInfo tests
+ private DiscoverySession executeSessionStartup(boolean isPublish) throws Exception {
+ final int clientId = 4565;
+ final int sessionId = 123;
+ final PeerHandle peerHandle = new PeerHandle(123412);
+ final int port = 5;
+ final ConfigRequest configRequest = new ConfigRequest.Builder().build();
+ final SubscribeConfig subscribeConfig = new SubscribeConfig.Builder().build();
+ final PublishConfig publishConfig = new PublishConfig.Builder().build();
+
+ ArgumentCaptor<WifiAwareSession> sessionCaptor = ArgumentCaptor.forClass(
+ WifiAwareSession.class);
+ ArgumentCaptor<IWifiAwareEventCallback> clientProxyCallback = ArgumentCaptor
+ .forClass(IWifiAwareEventCallback.class);
+ ArgumentCaptor<IWifiAwareDiscoverySessionCallback> sessionProxyCallback = ArgumentCaptor
+ .forClass(IWifiAwareDiscoverySessionCallback.class);
+ ArgumentCaptor<PublishDiscoverySession> publishSession = ArgumentCaptor
+ .forClass(PublishDiscoverySession.class);
+ ArgumentCaptor<SubscribeDiscoverySession> subscribeSession = ArgumentCaptor
+ .forClass(SubscribeDiscoverySession.class);
+
+
+ InOrder inOrder = inOrder(mockCallback, mockSessionCallback, mockAwareService,
+ mockPublishSession, mockRttListener);
+
+ // (1) connect successfully
+ mDut.attach(mMockLooperHandler, configRequest, mockCallback, null);
+ inOrder.verify(mockAwareService).connect(any(), any(), clientProxyCallback.capture(),
+ eq(configRequest), eq(false));
+ clientProxyCallback.getValue().onConnectSuccess(clientId);
+ mMockLooper.dispatchAll();
+ inOrder.verify(mockCallback).onAttached(sessionCaptor.capture());
+ WifiAwareSession session = sessionCaptor.getValue();
+
+ if (isPublish) {
+ // (2) publish successfully
+ session.publish(publishConfig, mockSessionCallback, mMockLooperHandler);
+ inOrder.verify(mockAwareService).publish(any(), eq(clientId), eq(publishConfig),
+ sessionProxyCallback.capture());
+ sessionProxyCallback.getValue().onSessionStarted(sessionId);
+ mMockLooper.dispatchAll();
+ inOrder.verify(mockSessionCallback).onPublishStarted(publishSession.capture());
+ return publishSession.getValue();
+ } else {
+ // (2) subscribe successfully
+ session.subscribe(subscribeConfig, mockSessionCallback, mMockLooperHandler);
+ inOrder.verify(mockAwareService).subscribe(any(), eq(clientId), eq(subscribeConfig),
+ sessionProxyCallback.capture());
+ sessionProxyCallback.getValue().onSessionStarted(sessionId);
+ mMockLooper.dispatchAll();
+ inOrder.verify(mockSessionCallback).onSubscribeStarted(subscribeSession.capture());
+ return subscribeSession.getValue();
+ }
+ }
+
+ // WifiAwareNetworkSpecifier && WifiAwareNetworkInfo tests
+
+ @Test
+ public void testWifiAwareNetworkSpecifierParcel() {
+ WifiAwareNetworkSpecifier ns = new WifiAwareNetworkSpecifier(NETWORK_SPECIFIER_TYPE_IB,
+ WifiAwareManager.WIFI_AWARE_DATA_PATH_ROLE_RESPONDER, 5, 568, 334,
+ HexEncoding.decode("000102030405".toCharArray(), false),
+ "01234567890123456789012345678901".getBytes(), "blah blah", 666, 4, 10001);
+
+ Parcel parcelW = Parcel.obtain();
+ ns.writeToParcel(parcelW, 0);
+ byte[] bytes = parcelW.marshall();
+ parcelW.recycle();
+
+ Parcel parcelR = Parcel.obtain();
+ parcelR.unmarshall(bytes, 0, bytes.length);
+ parcelR.setDataPosition(0);
+ WifiAwareNetworkSpecifier rereadNs =
+ WifiAwareNetworkSpecifier.CREATOR.createFromParcel(parcelR);
+
+ assertEquals(ns, rereadNs);
+ assertEquals(ns.hashCode(), rereadNs.hashCode());
+ }
@Test
public void testWifiAwareNetworkCapabilitiesParcel() throws UnknownHostException {
@@ -1364,9 +1592,11 @@
"11:22:33:44:55:66").getLinkLocalIpv6FromEui48Mac();
// note: dummy scope = 5
final Inet6Address inet6Scoped = Inet6Address.getByAddress(null, inet6.getAddress(), 5);
+ final int port = 5;
+ final int transportProtocol = 6;
assertEquals(inet6Scoped.toString(), "/fe80::1322:33ff:fe44:5566%5");
- WifiAwareNetworkInfo cap = new WifiAwareNetworkInfo(inet6Scoped);
+ WifiAwareNetworkInfo cap = new WifiAwareNetworkInfo(inet6Scoped, port, transportProtocol);
Parcel parcelW = Parcel.obtain();
cap.writeToParcel(parcelW, 0);
@@ -1380,6 +1610,7 @@
WifiAwareNetworkInfo.CREATOR.createFromParcel(parcelR);
assertEquals(cap.getPeerIpv6Addr().toString(), "/fe80::1322:33ff:fe44:5566%5");
+ assertEquals(cap, rereadCap);
assertEquals(cap.hashCode(), rereadCap.hashCode());
}
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java b/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java
index a9d4b8f..1ecc3fe 100644
--- a/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java
+++ b/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java
@@ -16,6 +16,7 @@
package android.net.wifi.hotspot2.pps;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -44,17 +45,16 @@
* @param userCred Instance of UserCredential
* @param certCred Instance of CertificateCredential
* @param simCred Instance of SimCredential
- * @param caCert CA certificate
* @param clientCertificateChain Chain of client certificates
* @param clientPrivateKey Client private key
+ * @param caCerts CA certificates
* @return {@link Credential}
*/
private static Credential createCredential(Credential.UserCredential userCred,
- Credential.CertificateCredential certCred,
- Credential.SimCredential simCred,
- X509Certificate caCert,
- X509Certificate[] clientCertificateChain,
- PrivateKey clientPrivateKey) {
+ Credential.CertificateCredential certCred,
+ Credential.SimCredential simCred,
+ X509Certificate[] clientCertificateChain, PrivateKey clientPrivateKey,
+ X509Certificate... caCerts) {
Credential cred = new Credential();
cred.setCreationTimeInMillis(123455L);
cred.setExpirationTimeInMillis(2310093L);
@@ -63,7 +63,11 @@
cred.setUserCredential(userCred);
cred.setCertCredential(certCred);
cred.setSimCredential(simCred);
- cred.setCaCertificate(caCert);
+ if (caCerts != null && caCerts.length == 1) {
+ cred.setCaCertificate(caCerts[0]);
+ } else {
+ cred.setCaCertificates(caCerts);
+ }
cred.setClientCertificateChain(clientCertificateChain);
cred.setClientPrivateKey(clientPrivateKey);
return cred;
@@ -80,8 +84,8 @@
certCred.setCertType("x509v3");
certCred.setCertSha256Fingerprint(
MessageDigest.getInstance("SHA-256").digest(FakeKeys.CLIENT_CERT.getEncoded()));
- return createCredential(null, certCred, null, FakeKeys.CA_CERT0,
- new X509Certificate[] {FakeKeys.CLIENT_CERT}, FakeKeys.RSA_KEY1);
+ return createCredential(null, certCred, null, new X509Certificate[] {FakeKeys.CLIENT_CERT},
+ FakeKeys.RSA_KEY1, FakeKeys.CA_CERT0, FakeKeys.CA_CERT1);
}
/**
@@ -93,7 +97,7 @@
Credential.SimCredential simCred = new Credential.SimCredential();
simCred.setImsi("1234*");
simCred.setEapType(EAPConstants.EAP_SIM);
- return createCredential(null, null, simCred, null, null, null);
+ return createCredential(null, null, simCred, null, null, (X509Certificate[]) null);
}
/**
@@ -110,7 +114,7 @@
userCred.setSoftTokenApp("TestApp");
userCred.setEapType(EAPConstants.EAP_TTLS);
userCred.setNonEapInnerMethod("MS-CHAP");
- return createCredential(userCred, null, null, FakeKeys.CA_CERT0, null, null);
+ return createCredential(userCred, null, null, null, null, FakeKeys.CA_CERT0);
}
private static void verifyParcel(Credential writeCred) {
@@ -120,6 +124,7 @@
parcel.setDataPosition(0); // Rewind data position back to the beginning for read.
Credential readCred = Credential.CREATOR.createFromParcel(parcel);
assertTrue(readCred.equals(writeCred));
+ assertEquals(writeCred.hashCode(), readCred.hashCode());
}
/**