diff options
28 files changed, 691 insertions, 103 deletions
diff --git a/Android.bp b/Android.bp index 1c4f628c32b7..003f0adccce0 100644 --- a/Android.bp +++ b/Android.bp @@ -359,6 +359,7 @@ filegroup { "core/java/com/android/internal/util/IState.java", "core/java/com/android/internal/util/State.java", "core/java/com/android/internal/util/StateMachine.java", + "core/java/com/android/internal/util/WakeupMessage.java", "services/core/java/com/android/server/vcn/util/PersistableBundleUtils.java", "telephony/java/android/telephony/Annotation.java", ], @@ -528,4 +529,5 @@ build = [ "StubLibraries.bp", "ApiDocs.bp", "ProtoLibraries.bp", + "TestProtoLibraries.bp", ] diff --git a/TestProtoLibraries.bp b/TestProtoLibraries.bp new file mode 100644 index 000000000000..513d45f3b7f5 --- /dev/null +++ b/TestProtoLibraries.bp @@ -0,0 +1,35 @@ +// Copyright 2021 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. + +java_library_host { + name: "platformtestprotos", + srcs: [ + ":libstats_atom_enum_protos", + ":libstats_internal_protos", + ":statsd_internal_protos", + ], + libs: [ + "libprotobuf-java-full", + ], + proto: { + include_dirs: [ + "external/protobuf/src", + "frameworks/proto_logging/stats", + ], + type: "full", + }, + errorprone: { + javacflags: ["-Xep:MissingOverride:OFF"], // b/72714520 + }, +} diff --git a/core/api/current.txt b/core/api/current.txt index 536c11855458..734d64f93d68 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -9094,6 +9094,7 @@ package android.bluetooth { field public static final int STATE_CONNECTING = 1; // 0x1 field public static final int STATE_DISCONNECTED = 0; // 0x0 field public static final int STATE_DISCONNECTING = 3; // 0x3 + field public static final int VOLUME_CONTROL = 23; // 0x17 } public static interface BluetoothProfile.ServiceListener { diff --git a/core/api/system-current.txt b/core/api/system-current.txt index f5b40f5689d8..400601ff4570 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -1666,6 +1666,16 @@ package android.bluetooth { field @NonNull public static final android.os.ParcelUuid VOLUME_CONTROL; } + public final class BluetoothVolumeControl implements java.lang.AutoCloseable android.bluetooth.BluetoothProfile { + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public void close(); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) protected void finalize(); + method @NonNull @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices(); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public int getConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public void setVolume(@Nullable android.bluetooth.BluetoothDevice, @IntRange(from=0, to=255) int); + field @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.volume-control.profile.action.CONNECTION_STATE_CHANGED"; + } + public final class BufferConstraint implements android.os.Parcelable { ctor public BufferConstraint(int, int, int); method public int describeContents(); diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java index 331fd07a6afe..e305aa8e33a0 100644 --- a/core/java/android/bluetooth/BluetoothAdapter.java +++ b/core/java/android/bluetooth/BluetoothAdapter.java @@ -2815,6 +2815,9 @@ public final class BluetoothAdapter { return true; } return false; + } else if (profile == BluetoothProfile.VOLUME_CONTROL) { + BluetoothVolumeControl vcs = new BluetoothVolumeControl(context, listener, this); + return true; } else { return false; } @@ -2899,6 +2902,11 @@ public final class BluetoothAdapter { case BluetoothProfile.HEARING_AID: BluetoothHearingAid hearingAid = (BluetoothHearingAid) proxy; hearingAid.close(); + break; + case BluetoothProfile.VOLUME_CONTROL: + BluetoothVolumeControl vcs = (BluetoothVolumeControl) proxy; + vcs.close(); + break; } } diff --git a/core/java/android/bluetooth/BluetoothProfile.java b/core/java/android/bluetooth/BluetoothProfile.java index 201d6c495d98..d9791026ad6d 100644 --- a/core/java/android/bluetooth/BluetoothProfile.java +++ b/core/java/android/bluetooth/BluetoothProfile.java @@ -215,12 +215,18 @@ public interface BluetoothProfile { int LE_AUDIO = 22; /** + * Volume Control profile + * + */ + int VOLUME_CONTROL = 23; + + /** * Max profile ID. This value should be updated whenever a new profile is added to match * the largest value assigned to a profile. * * @hide */ - int MAX_PROFILE_ID = 22; + int MAX_PROFILE_ID = 23; /** * Default priority for devices that we try to auto-connect to and diff --git a/core/java/android/bluetooth/BluetoothVolumeControl.java b/core/java/android/bluetooth/BluetoothVolumeControl.java new file mode 100644 index 000000000000..4eb28ad39952 --- /dev/null +++ b/core/java/android/bluetooth/BluetoothVolumeControl.java @@ -0,0 +1,320 @@ +/* + * Copyright 2021 HIMSA II K/S - www.himsa.com. + * Represented by EHIMA - www.ehima.com + * + * 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.bluetooth; + +import android.Manifest; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.RequiresPermission; +import android.annotation.SdkConstant; +import android.annotation.SdkConstant.SdkConstantType; +import android.annotation.SuppressLint; +import android.annotation.SystemApi; +import android.compat.annotation.UnsupportedAppUsage; +import android.content.Context; +import android.os.Binder; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.CloseGuard; +import android.util.Log; + +import java.util.ArrayList; +import java.util.List; + +/** + * This class provides the public APIs to control the Bluetooth Volume Control service. + * + * <p>BluetoothVolumeControl is a proxy object for controlling the Bluetooth VC + * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get + * the BluetoothVolumeControl proxy object. + * @hide + */ +@SystemApi +public final class BluetoothVolumeControl implements BluetoothProfile, AutoCloseable { + private static final String TAG = "BluetoothVolumeControl"; + private static final boolean DBG = true; + private static final boolean VDBG = false; + + private CloseGuard mCloseGuard; + + /** + * Intent used to broadcast the change in connection state of the Volume Control + * profile. + * + * <p>This intent will have 3 extras: + * <ul> + * <li> {@link #EXTRA_STATE} - The current state of the profile. </li> + * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.</li> + * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li> + * </ul> + * + * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of + * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, + * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}. + * + * @hide + */ + @SystemApi + @SuppressLint("ActionValue") + @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_CONNECTION_STATE_CHANGED = + "android.bluetooth.volume-control.profile.action.CONNECTION_STATE_CHANGED"; + + private BluetoothAdapter mAdapter; + private final BluetoothProfileConnector<IBluetoothVolumeControl> mProfileConnector = + new BluetoothProfileConnector(this, BluetoothProfile.VOLUME_CONTROL, TAG, + IBluetoothVolumeControl.class.getName()) { + @Override + public IBluetoothVolumeControl getServiceInterface(IBinder service) { + return IBluetoothVolumeControl.Stub.asInterface(Binder.allowBlocking(service)); + } + }; + + /** + * Create a BluetoothVolumeControl proxy object for interacting with the local + * Bluetooth Volume Control service. + */ + /*package*/ BluetoothVolumeControl(Context context, ServiceListener listener, + BluetoothAdapter adapter) { + mAdapter = adapter; + mProfileConnector.connect(context, listener); + mCloseGuard = new CloseGuard(); + mCloseGuard.open("close"); + } + + @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + protected void finalize() { + if (mCloseGuard != null) { + mCloseGuard.warnIfOpen(); + } + close(); + } + + /*package*/ + @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + public void close() { + mProfileConnector.disconnect(); + } + + private IBluetoothVolumeControl getService() { return mProfileConnector.getService(); } + + /** + * Get the list of connected devices. Currently at most one. + * + * @return list of connected devices + * + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) + public @NonNull List<BluetoothDevice> getConnectedDevices() { + if (DBG) log("getConnectedDevices()"); + final IBluetoothVolumeControl service = getService(); + if (service != null && isEnabled()) { + try { + return service.getConnectedDevices(); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + return new ArrayList<BluetoothDevice>(); + } + } + if (service == null) Log.w(TAG, "Proxy not attached to service"); + return new ArrayList<BluetoothDevice>(); + } + + /** + * Get the list of devices matching specified states. Currently at most one. + * + * @return list of matching devices + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) + public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { + if (DBG) log("getDevicesMatchingStates()"); + final IBluetoothVolumeControl service = getService(); + if (service != null && isEnabled()) { + try { + return service.getDevicesMatchingConnectionStates(states); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + return new ArrayList<BluetoothDevice>(); + } + } + if (service == null) Log.w(TAG, "Proxy not attached to service"); + return new ArrayList<BluetoothDevice>(); + } + + /** + * Get connection state of device + * + * @return device connection state + * + * @hide + */ + @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) + public int getConnectionState(BluetoothDevice device) { + if (DBG) log("getConnectionState(" + device + ")"); + final IBluetoothVolumeControl service = getService(); + if (service != null && isEnabled() && isValidDevice(device)) { + try { + return service.getConnectionState(device); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + return BluetoothProfile.STATE_DISCONNECTED; + } + } + if (service == null) Log.w(TAG, "Proxy not attached to service"); + return BluetoothProfile.STATE_DISCONNECTED; + } + + /** + * Tells remote device to set an absolute volume. + * + * @param volume Absolute volume to be set on remote device. + * Minimum value is 0 and maximum value is 255 + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) + public void setVolume(@Nullable BluetoothDevice device, + @IntRange(from = 0, to = 255) int volume) { + if (DBG) + log("setVolume(" + volume + ")"); + final IBluetoothVolumeControl service = getService(); + try { + if (service != null && isEnabled()) { + service.setVolume(device, volume); + return; + } + if (service == null) + Log.w(TAG, "Proxy not attached to service"); + } catch (RemoteException e) { + Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable())); + } + } + + /** + * Set priority of the profile + * + * <p> The device should already be paired. + * Priority can be one of {@link #PRIORITY_ON} or {@link #PRIORITY_OFF}, + * + * @param device Paired bluetooth device + * @param priority + * @return true if priority is set, false on error + * @hide + */ + @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) + public boolean setPriority(BluetoothDevice device, int priority) { + if (DBG) log("setPriority(" + device + ", " + priority + ")"); + return setConnectionPolicy(device, BluetoothAdapter.priorityToConnectionPolicy(priority)); + } + + /** + * Set connection policy of the profile + * + * <p> The device should already be paired. + * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED}, + * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN} + * + * @param device Paired bluetooth device + * @param connectionPolicy is the connection policy to set to for this profile + * @return true if connectionPolicy is set, false on error + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) + public boolean setConnectionPolicy(@NonNull BluetoothDevice device, + @ConnectionPolicy int connectionPolicy) { + if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")"); + final IBluetoothVolumeControl service = getService(); + if (service != null && isEnabled() && isValidDevice(device)) { + if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN + && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) { + return false; + } + try { + return service.setConnectionPolicy(device, connectionPolicy); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + return false; + } + } + if (service == null) Log.w(TAG, "Proxy not attached to service"); + return false; + } + + /** + * Get the priority of the profile. + * + * <p> The priority can be any of: + * {@link #PRIORITY_OFF}, {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED} + * + * @param device Bluetooth device + * @return priority of the device + * @hide + */ + @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) + public int getPriority(BluetoothDevice device) { + if (VDBG) log("getPriority(" + device + ")"); + return BluetoothAdapter.connectionPolicyToPriority(getConnectionPolicy(device)); + } + + /** + * Get the connection policy of the profile. + * + * <p> The connection policy can be any of: + * {@link #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN}, + * {@link #CONNECTION_POLICY_UNKNOWN} + * + * @param device Bluetooth device + * @return connection policy of the device + * @hide + */ + @SystemApi + @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) + public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) { + if (VDBG) log("getConnectionPolicy(" + device + ")"); + final IBluetoothVolumeControl service = getService(); + if (service != null && isEnabled() && isValidDevice(device)) { + try { + return service.getConnectionPolicy(device); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + } + } + if (service == null) Log.w(TAG, "Proxy not attached to service"); + return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; + } + + private boolean isEnabled() { + return mAdapter.getState() == BluetoothAdapter.STATE_ON; + } + + private static boolean isValidDevice(@Nullable BluetoothDevice device) { + return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress()); + } + + private static void log(String msg) { + Log.d(TAG, msg); + } +} diff --git a/core/java/android/bluetooth/OobData.java b/core/java/android/bluetooth/OobData.java index 4e5ede74ce46..bb0b95649b17 100644 --- a/core/java/android/bluetooth/OobData.java +++ b/core/java/android/bluetooth/OobData.java @@ -937,17 +937,18 @@ public final class OobData implements Parcelable { } @NonNull - private String toHexString(@NonNull int b) { + private String toHexString(int b) { return toHexString(new byte[] {(byte) b}); } @NonNull - private String toHexString(@NonNull byte b) { + private String toHexString(byte b) { return toHexString(new byte[] {b}); } @NonNull - private String toHexString(@NonNull byte[] array) { + private String toHexString(byte[] array) { + if (array == null) return "null"; StringBuilder builder = new StringBuilder(array.length * 2); for (byte b: array) { builder.append(String.format("%02x", b)); diff --git a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java index d59ad6f88864..86cd23d1e942 100644 --- a/core/java/android/net/vcn/VcnGatewayConnectionConfig.java +++ b/core/java/android/net/vcn/VcnGatewayConnectionConfig.java @@ -83,7 +83,12 @@ public final class VcnGatewayConnectionConfig { @VisibleForTesting(visibility = Visibility.PRIVATE) static final int MIN_MTU_V6 = 1280; - private static final Set<Integer> ALLOWED_CAPABILITIES; + /** + * The set of allowed capabilities for exposed capabilities. + * + * @hide + */ + public static final Set<Integer> ALLOWED_CAPABILITIES; static { Set<Integer> allowedCaps = new ArraySet<>(); diff --git a/core/java/android/view/accessibility/AccessibilityCache.java b/core/java/android/view/accessibility/AccessibilityCache.java index 9ab2c2b8bcb1..d14dc6e71c00 100644 --- a/core/java/android/view/accessibility/AccessibilityCache.java +++ b/core/java/android/view/accessibility/AccessibilityCache.java @@ -158,6 +158,7 @@ public class AccessibilityCache { * @param event An event. */ public void onAccessibilityEvent(AccessibilityEvent event) { + AccessibilityNodeInfo nodeToRefresh = null; synchronized (mLock) { if (DEBUG) { Log.i(LOG_TAG, "onAccessibilityEvent(" + event + ")"); @@ -166,17 +167,19 @@ public class AccessibilityCache { switch (eventType) { case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED: { if (mAccessibilityFocus != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) { - refreshCachedNodeLocked(mAccessibilityFocusedWindow, mAccessibilityFocus); + removeCachedNodeLocked(mAccessibilityFocusedWindow, mAccessibilityFocus); } mAccessibilityFocus = event.getSourceNodeId(); mAccessibilityFocusedWindow = event.getWindowId(); - refreshCachedNodeLocked(mAccessibilityFocusedWindow, mAccessibilityFocus); + nodeToRefresh = removeCachedNodeLocked(mAccessibilityFocusedWindow, + mAccessibilityFocus); } break; case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED: { if (mAccessibilityFocus == event.getSourceNodeId() && mAccessibilityFocusedWindow == event.getWindowId()) { - refreshCachedNodeLocked(mAccessibilityFocusedWindow, mAccessibilityFocus); + nodeToRefresh = removeCachedNodeLocked(mAccessibilityFocusedWindow, + mAccessibilityFocus); mAccessibilityFocus = AccessibilityNodeInfo.UNDEFINED_ITEM_ID; mAccessibilityFocusedWindow = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID; } @@ -184,17 +187,18 @@ public class AccessibilityCache { case AccessibilityEvent.TYPE_VIEW_FOCUSED: { if (mInputFocus != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) { - refreshCachedNodeLocked(event.getWindowId(), mInputFocus); + removeCachedNodeLocked(event.getWindowId(), mInputFocus); } mInputFocus = event.getSourceNodeId(); - refreshCachedNodeLocked(event.getWindowId(), mInputFocus); + nodeToRefresh = removeCachedNodeLocked(event.getWindowId(), mInputFocus); } break; case AccessibilityEvent.TYPE_VIEW_SELECTED: case AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED: case AccessibilityEvent.TYPE_VIEW_CLICKED: case AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED: { - refreshCachedNodeLocked(event.getWindowId(), event.getSourceNodeId()); + nodeToRefresh = removeCachedNodeLocked(event.getWindowId(), + event.getSourceNodeId()); } break; case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED: { @@ -205,7 +209,7 @@ public class AccessibilityCache { & AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE) != 0) { clearSubTreeLocked(windowId, sourceId); } else { - refreshCachedNodeLocked(windowId, sourceId); + nodeToRefresh = removeCachedNodeLocked(windowId, sourceId); } } } break; @@ -218,8 +222,8 @@ public class AccessibilityCache { if (event.getWindowChanges() == AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED) { // Don't need to clear all cache. Unless the changes are related to - // content, we won't clear all cache here. - refreshCachedWindowLocked(event.getWindowId()); + // content, we won't clear all cache here with clear(). + clearWindowCacheLocked(); break; } case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED: { @@ -228,59 +232,34 @@ public class AccessibilityCache { } } + if (nodeToRefresh != null) { + if (DEBUG) { + Log.i(LOG_TAG, "Refreshing and re-adding cached node."); + } + if (mAccessibilityNodeRefresher.refreshNode(nodeToRefresh, true)) { + add(nodeToRefresh); + } + } if (CHECK_INTEGRITY) { checkIntegrity(); } } - private void refreshCachedNodeLocked(int windowId, long sourceId) { + private AccessibilityNodeInfo removeCachedNodeLocked(int windowId, long sourceId) { if (DEBUG) { - Log.i(LOG_TAG, "Refreshing cached node."); + Log.i(LOG_TAG, "Removing cached node."); } - LongSparseArray<AccessibilityNodeInfo> nodes = mNodeCache.get(windowId); if (nodes == null) { - return; + return null; } AccessibilityNodeInfo cachedInfo = nodes.get(sourceId); // If the source is not in the cache - nothing to do. if (cachedInfo == null) { - return; - } - // The node changed so we will just refresh it right now. - if (mAccessibilityNodeRefresher.refreshNode(cachedInfo, true)) { - return; - } - // Weird, we could not refresh. Just evict the entire sub-tree. - clearSubTreeLocked(windowId, sourceId); - } - - private void refreshCachedWindowLocked(int windowId) { - if (DEBUG) { - Log.i(LOG_TAG, "Refreshing cached window."); - } - - if (windowId == AccessibilityWindowInfo.UNDEFINED_WINDOW_ID) { - return; - } - - final int displayCounts = mWindowCacheByDisplay.size(); - for (int i = 0; i < displayCounts; i++) { - final SparseArray<AccessibilityWindowInfo> windowsOfDisplay = - mWindowCacheByDisplay.valueAt(i); - if (windowsOfDisplay == null) { - continue; - } - final AccessibilityWindowInfo window = windowsOfDisplay.get(windowId); - if (window == null) { - continue; - } - if (!mAccessibilityNodeRefresher.refreshWindow(window)) { - // If we fail to refresh the window, clear all windows. - clearWindowCacheLocked(); - } - return; + return null; } + nodes.remove(sourceId); + return cachedInfo; } /** @@ -450,7 +429,7 @@ public class AccessibilityCache { if (clone.isAccessibilityFocused()) { if (mAccessibilityFocus != AccessibilityNodeInfo.UNDEFINED_ITEM_ID && mAccessibilityFocus != sourceId) { - refreshCachedNodeLocked(windowId, mAccessibilityFocus); + removeCachedNodeLocked(windowId, mAccessibilityFocus); } mAccessibilityFocus = sourceId; mAccessibilityFocusedWindow = windowId; diff --git a/core/proto/android/server/OWNERS b/core/proto/android/server/OWNERS new file mode 100644 index 000000000000..72d39bfb3cd6 --- /dev/null +++ b/core/proto/android/server/OWNERS @@ -0,0 +1 @@ +per-file window*.proto = file:/services/core/java/com/android/server/wm/OWNERS diff --git a/core/proto/android/server/inputmethod/OWNERS b/core/proto/android/server/inputmethod/OWNERS new file mode 100644 index 000000000000..5deb2ce8f24b --- /dev/null +++ b/core/proto/android/server/inputmethod/OWNERS @@ -0,0 +1 @@ +include /core/java/android/view/inputmethod/OWNERS diff --git a/core/proto/android/view/OWNERS b/core/proto/android/view/OWNERS new file mode 100644 index 000000000000..d72a0f00c752 --- /dev/null +++ b/core/proto/android/view/OWNERS @@ -0,0 +1,3 @@ +include /services/core/java/com/android/server/wm/OWNERS + +per-file ime*.proto = file:/core/java/android/view/inputmethod/OWNERS diff --git a/core/proto/android/view/inputmethod/OWNERS b/core/proto/android/view/inputmethod/OWNERS new file mode 100644 index 000000000000..5deb2ce8f24b --- /dev/null +++ b/core/proto/android/view/inputmethod/OWNERS @@ -0,0 +1 @@ +include /core/java/android/view/inputmethod/OWNERS diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 8db1bbfbec1d..33f259878a24 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -191,6 +191,8 @@ <protected-broadcast android:name="android.bluetooth.hearingaid.profile.action.ACTIVE_DEVICE_CHANGED" /> <protected-broadcast + android:name="android.bluetooth.volume-control.profile.action.CONNECTION_STATE_CHANGED" /> + <protected-broadcast android:name="android.bluetooth.a2dp.profile.action.CONNECTION_STATE_CHANGED" /> <protected-broadcast android:name="android.bluetooth.a2dp.profile.action.ACTIVE_DEVICE_CHANGED" /> diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java index 0d5db6d791f1..d2b52ba35073 100644 --- a/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java +++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java @@ -563,7 +563,7 @@ public class AccessibilityCacheTest { } @Test - public void nodeWithA11yFocusWhenAnotherNodeGetsFocus_getsRefreshed() { + public void nodeWithA11yFocusWhenAnotherNodeGetsFocus_getsRemoved() { AccessibilityNodeInfo nodeInfo = getNodeWithA11yAndWindowId(SINGLE_VIEW_ID, WINDOW_ID_1); nodeInfo.setAccessibilityFocused(true); mAccessibilityCache.add(nodeInfo); @@ -573,7 +573,7 @@ public class AccessibilityCacheTest { mAccessibilityCache.onAccessibilityEvent(event); event.recycle(); try { - verify(mAccessibilityNodeRefresher).refreshNode(nodeInfo, true); + assertNull(mAccessibilityCache.getNode(WINDOW_ID_1, SINGLE_VIEW_ID)); } finally { nodeInfo.recycle(); } @@ -614,7 +614,7 @@ public class AccessibilityCacheTest { } @Test - public void nodeWithInputFocusWhenAnotherNodeGetsFocus_getsRefreshed() { + public void nodeWithInputFocusWhenAnotherNodeGetsFocus_getsRemoved() { AccessibilityNodeInfo nodeInfo = getNodeWithA11yAndWindowId(SINGLE_VIEW_ID, WINDOW_ID_1); nodeInfo.setFocused(true); mAccessibilityCache.add(nodeInfo); @@ -624,7 +624,7 @@ public class AccessibilityCacheTest { mAccessibilityCache.onAccessibilityEvent(event); event.recycle(); try { - verify(mAccessibilityNodeRefresher).refreshNode(nodeInfo, true); + assertNull(mAccessibilityCache.getNode(WINDOW_ID_1, SINGLE_VIEW_ID)); } finally { nodeInfo.recycle(); } @@ -733,20 +733,15 @@ public class AccessibilityCacheTest { } @Test - public void addA11yFocusNodeBeforeFocusClearedEvent_previousA11yFocusNodeGetsRefreshed() { + public void addA11yFocusNodeBeforeFocusClearedEvent_previousA11yFocusNodeGetsRemoved() { AccessibilityNodeInfo nodeInfo1 = getNodeWithA11yAndWindowId(SINGLE_VIEW_ID, WINDOW_ID_1); nodeInfo1.setAccessibilityFocused(true); mAccessibilityCache.add(nodeInfo1); AccessibilityNodeInfo nodeInfo2 = getNodeWithA11yAndWindowId(OTHER_VIEW_ID, WINDOW_ID_1); nodeInfo2.setAccessibilityFocused(true); mAccessibilityCache.add(nodeInfo2); - AccessibilityEvent event = AccessibilityEvent.obtain( - AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED); - event.setSource(getMockViewWithA11yAndWindowIds(SINGLE_VIEW_ID, WINDOW_ID_1)); - mAccessibilityCache.onAccessibilityEvent(event); - event.recycle(); try { - verify(mAccessibilityNodeRefresher).refreshNode(nodeInfo1, true); + assertNull(mAccessibilityCache.getNode(WINDOW_ID_1, SINGLE_VIEW_ID)); } finally { nodeInfo1.recycle(); nodeInfo2.recycle(); diff --git a/services/core/java/com/android/server/VcnManagementService.java b/services/core/java/com/android/server/VcnManagementService.java index 171805245e45..70b0fc126ed1 100644 --- a/services/core/java/com/android/server/VcnManagementService.java +++ b/services/core/java/com/android/server/VcnManagementService.java @@ -372,8 +372,7 @@ public class VcnManagementService extends IVcnManagementService.Stub { /** Notifies the VcnManagementService that external dependencies can be set up. */ public void systemReady() { - mContext.getSystemService(ConnectivityManager.class) - .registerNetworkProvider(mNetworkProvider); + mNetworkProvider.register(); mContext.getSystemService(ConnectivityManager.class) .registerNetworkCallback( new NetworkRequest.Builder().clearCapabilities().build(), diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java index 5ac86ca3f6da..f9dd826e4b99 100644 --- a/services/core/java/com/android/server/pm/Installer.java +++ b/services/core/java/com/android/server/pm/Installer.java @@ -75,6 +75,17 @@ public class Installer extends SystemService { /** Indicates that dexopt may be run with different performance / priority tuned for restore */ public static final int DEXOPT_FOR_RESTORE = 1 << 13; // TODO(b/135202722): remove + /** The result of the profile analysis indicating that the app should be optimized. */ + public static final int PROFILE_ANALYSIS_OPTIMIZE = 1; + /** The result of the profile analysis indicating that the app should not be optimized. */ + public static final int PROFILE_ANALYSIS_DONT_OPTIMIZE_SMALL_DELTA = 2; + /** + * The result of the profile analysis indicating that the app should not be optimized because + * the profiles are empty. + */ + public static final int PROFILE_ANALYSIS_DONT_OPTIMIZE_EMPTY_PROFILES = 3; + + public static final int FLAG_STORAGE_DE = IInstalld.FLAG_STORAGE_DE; public static final int FLAG_STORAGE_CE = IInstalld.FLAG_STORAGE_CE; public static final int FLAG_STORAGE_EXTERNAL = IInstalld.FLAG_STORAGE_EXTERNAL; @@ -388,9 +399,18 @@ public class Installer extends SystemService { } } - public boolean mergeProfiles(int uid, String packageName, String profileName) + /** + * Analyzes the ART profiles of the given package, possibly merging the information + * into the reference profile. Returns whether or not we should optimize the package + * based on how much information is in the profile. + * + * @return one of {@link #PROFILE_ANALYSIS_OPTIMIZE}, + * {@link #PROFILE_ANALYSIS_DONT_OPTIMIZE_SMALL_DELTA}, + * {@link #PROFILE_ANALYSIS_DONT_OPTIMIZE_EMPTY_PROFILES} + */ + public int mergeProfiles(int uid, String packageName, String profileName) throws InstallerException { - if (!checkBeforeRemote()) return false; + if (!checkBeforeRemote()) return PROFILE_ANALYSIS_DONT_OPTIMIZE_SMALL_DELTA; try { return mInstalld.mergeProfiles(uid, packageName, profileName); } catch (Exception e) { @@ -537,13 +557,17 @@ public class Installer extends SystemService { } } - public void deleteOdex(String apkPath, String instructionSet, String outputPath) + /** + * Deletes the optimized artifacts generated by ART and returns the number + * of freed bytes. + */ + public long deleteOdex(String apkPath, String instructionSet, String outputPath) throws InstallerException { - if (!checkBeforeRemote()) return; + if (!checkBeforeRemote()) return -1; BlockGuard.getVmPolicy().onPathAccess(apkPath); BlockGuard.getVmPolicy().onPathAccess(outputPath); try { - mInstalld.deleteOdex(apkPath, instructionSet, outputPath); + return mInstalld.deleteOdex(apkPath, instructionSet, outputPath); } catch (Exception e) { throw InstallerException.from(e); } diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java index 5ef3d7629cdd..50be856e3070 100644 --- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java +++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java @@ -31,6 +31,9 @@ import static com.android.server.pm.Installer.DEXOPT_PUBLIC; import static com.android.server.pm.Installer.DEXOPT_SECONDARY_DEX; import static com.android.server.pm.Installer.DEXOPT_STORAGE_CE; import static com.android.server.pm.Installer.DEXOPT_STORAGE_DE; +import static com.android.server.pm.Installer.PROFILE_ANALYSIS_DONT_OPTIMIZE_EMPTY_PROFILES; +import static com.android.server.pm.Installer.PROFILE_ANALYSIS_DONT_OPTIMIZE_SMALL_DELTA; +import static com.android.server.pm.Installer.PROFILE_ANALYSIS_OPTIMIZE; import static com.android.server.pm.InstructionSets.getAppDexInstructionSets; import static com.android.server.pm.InstructionSets.getDexCodeInstructionSets; import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME; @@ -248,8 +251,12 @@ public class PackageDexOptimizer { || packageUseInfo.isUsedByOtherApps(path); final String compilerFilter = getRealCompilerFilter(pkg, options.getCompilerFilter(), isUsedByOtherApps); - final boolean profileUpdated = options.isCheckForProfileUpdates() && - isProfileUpdated(pkg, sharedGid, profileName, compilerFilter); + // If we don't have to check for profiles updates assume + // PROFILE_ANALYSIS_DONT_OPTIMIZE_SMALL_DELTA which will be a no-op with respect to + // profiles. + final int profileAnalysisResult = options.isCheckForProfileUpdates() + ? analyseProfiles(pkg, sharedGid, profileName, compilerFilter) + : PROFILE_ANALYSIS_DONT_OPTIMIZE_SMALL_DELTA; // Get the dexopt flags after getRealCompilerFilter to make sure we get the correct // flags. @@ -257,7 +264,7 @@ public class PackageDexOptimizer { for (String dexCodeIsa : dexCodeInstructionSets) { int newResult = dexOptPath(pkg, pkgSetting, path, dexCodeIsa, compilerFilter, - profileUpdated, classLoaderContexts[i], dexoptFlags, sharedGid, + profileAnalysisResult, classLoaderContexts[i], dexoptFlags, sharedGid, packageStats, options.isDowngrade(), profileName, dexMetadataPath, options.getCompilationReason()); @@ -305,11 +312,11 @@ public class PackageDexOptimizer { */ @GuardedBy("mInstallLock") private int dexOptPath(AndroidPackage pkg, @NonNull PackageSetting pkgSetting, String path, - String isa, String compilerFilter, boolean profileUpdated, String classLoaderContext, + String isa, String compilerFilter, int profileAnalysisResult, String classLoaderContext, int dexoptFlags, int uid, CompilerStats.PackageStats packageStats, boolean downgrade, String profileName, String dexMetadataPath, int compilationReason) { int dexoptNeeded = getDexoptNeeded(path, isa, compilerFilter, classLoaderContext, - profileUpdated, downgrade); + profileAnalysisResult, downgrade); if (Math.abs(dexoptNeeded) == DexFile.NO_DEXOPT_NEEDED) { return DEX_OPT_SKIPPED; } @@ -363,7 +370,7 @@ public class PackageDexOptimizer { isa, options.getCompilerFilter(), dexUseInfo.getClassLoaderContext(), - /* newProfile= */false, + PROFILE_ANALYSIS_DONT_OPTIMIZE_EMPTY_PROFILES, /* downgrade= */ false); if (dexoptNeeded == DexFile.NO_DEXOPT_NEEDED) { @@ -749,11 +756,25 @@ public class PackageDexOptimizer { * configuration (isa, compiler filter, profile). */ private int getDexoptNeeded(String path, String isa, String compilerFilter, - String classLoaderContext, boolean newProfile, boolean downgrade) { + String classLoaderContext, int profileAnalysisResult, boolean downgrade) { int dexoptNeeded; try { - dexoptNeeded = DexFile.getDexOptNeeded(path, isa, compilerFilter, classLoaderContext, - newProfile, downgrade); + // A profile guided optimizations with an empty profile is essentially 'verify' and + // dex2oat already makes this transformation. However DexFile.getDexOptNeeded() cannot + // check the profiles because system server does not have access to them. + // As such, we rely on the previous profile analysis (done with dexoptanalyzer) and + // manually adjust the actual filter before checking. + // + // TODO: ideally. we'd move this check in dexoptanalyzer, but that's a large change, + // and in the interim we can still improve things here. + String actualCompilerFilter = compilerFilter; + if (compilerFilterDependsOnProfiles(compilerFilter) + && profileAnalysisResult == PROFILE_ANALYSIS_DONT_OPTIMIZE_EMPTY_PROFILES) { + actualCompilerFilter = "verify"; + } + boolean newProfile = profileAnalysisResult == PROFILE_ANALYSIS_OPTIMIZE; + dexoptNeeded = DexFile.getDexOptNeeded(path, isa, actualCompilerFilter, + classLoaderContext, newProfile, downgrade); } catch (IOException ioe) { Slog.w(TAG, "IOException reading apk: " + path, ioe); return DEX_OPT_FAILED; @@ -764,27 +785,34 @@ public class PackageDexOptimizer { return adjustDexoptNeeded(dexoptNeeded); } + /** Returns true if the compiler filter depends on profiles (e.g speed-profile). */ + private boolean compilerFilterDependsOnProfiles(String compilerFilter) { + return compilerFilter.endsWith("-profile"); + } + /** * Checks if there is an update on the profile information of the {@code pkg}. - * If the compiler filter is not profile guided the method returns false. + * If the compiler filter is not profile guided the method returns a safe default: + * PROFILE_ANALYSIS_DONT_OPTIMIZE_SMALL_DELTA. * * Note that this is a "destructive" operation with side effects. Under the hood the * current profile and the reference profile will be merged and subsequent calls * may return a different result. */ - private boolean isProfileUpdated(AndroidPackage pkg, int uid, String profileName, + private int analyseProfiles(AndroidPackage pkg, int uid, String profileName, String compilerFilter) { // Check if we are allowed to merge and if the compiler filter is profile guided. if (!isProfileGuidedCompilerFilter(compilerFilter)) { - return false; + return PROFILE_ANALYSIS_DONT_OPTIMIZE_SMALL_DELTA; } // Merge profiles. It returns whether or not there was an updated in the profile info. try { return mInstaller.mergeProfiles(uid, pkg.getPackageName(), profileName); } catch (InstallerException e) { Slog.w(TAG, "Failed to merge profiles", e); + // We don't need to optimize if we failed to merge. + return PROFILE_ANALYSIS_DONT_OPTIMIZE_SMALL_DELTA; } - return false; } /** diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 05ba9dafd928..a3df212c536a 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -25555,14 +25555,14 @@ public class PackageManagerService extends IPackageManager.Stub } } - void deleteOatArtifactsOfPackage(String packageName) { + long deleteOatArtifactsOfPackage(String packageName) { final AndroidPackage pkg; final PackageSetting pkgSetting; synchronized (mLock) { pkg = mPackages.get(packageName); pkgSetting = mSettings.getPackageLPr(packageName); } - mDexManager.deleteOptimizedFiles(ArtUtils.createArtPackageInfo(pkg, pkgSetting)); + return mDexManager.deleteOptimizedFiles(ArtUtils.createArtPackageInfo(pkg, pkgSetting)); } Set<String> getUnusedPackages(long downgradeTimeThresholdMillis) { diff --git a/services/core/java/com/android/server/pm/SELinuxMMAC.java b/services/core/java/com/android/server/pm/SELinuxMMAC.java index c5fbfba9b049..b72da23d6717 100644 --- a/services/core/java/com/android/server/pm/SELinuxMMAC.java +++ b/services/core/java/com/android/server/pm/SELinuxMMAC.java @@ -79,7 +79,7 @@ public final class SELinuxMMAC { /** * Allows opt-in to the latest targetSdkVersion enforced changes without changing target SDK. - * Turning this change off for an app targeting the latest SDK is a no-op. + * Turning this change off for an app targeting >= the latest SDK is a no-op. * * <p>Has no effect for apps using shared user id. * @@ -92,7 +92,7 @@ public final class SELinuxMMAC { /** * This change gates apps access to untrusted_app_R-targetSDK SELinux domain. Allows opt-in * to R targetSdkVersion enforced changes without changing target SDK. Turning this change - * off for an app targeting S is a no-op. + * off for an app targeting >= S is a no-op. * * <p>Has no effect for apps using shared user id. * @@ -364,7 +364,7 @@ public final class SELinuxMMAC { } final ApplicationInfo appInfo = pkg.toAppInfoWithoutState(); if (compatibility.isChangeEnabledInternal(SELINUX_LATEST_CHANGES, appInfo)) { - return android.os.Build.VERSION_CODES.S; + return Math.max(android.os.Build.VERSION_CODES.S, pkg.getTargetSdkVersion()); } else if (compatibility.isChangeEnabledInternal(SELINUX_R_CHANGES, appInfo)) { return Math.max(android.os.Build.VERSION_CODES.R, pkg.getTargetSdkVersion()); } diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java index 32ba26c2d5ed..58204891293c 100644 --- a/services/core/java/com/android/server/pm/dex/DexManager.java +++ b/services/core/java/com/android/server/pm/dex/DexManager.java @@ -1034,18 +1034,26 @@ public class DexManager { /** * Deletes all the optimizations files generated by ART. + * This is best effort, and the method will log but not throw errors + * for individual deletes + * * @param packageInfo the package information. + * @return the number of freed bytes or -1 if there was an error in the process. */ - public void deleteOptimizedFiles(ArtPackageInfo packageInfo) { + public long deleteOptimizedFiles(ArtPackageInfo packageInfo) { + long freedBytes = 0; + boolean hadErrors = false; for (String codePath : packageInfo.getCodePaths()) { for (String isa : packageInfo.getInstructionSets()) { try { - mInstaller.deleteOdex(codePath, isa, packageInfo.getOatDir()); + freedBytes += mInstaller.deleteOdex(codePath, isa, packageInfo.getOatDir()); } catch (InstallerException e) { Log.e(TAG, "Failed deleting oat files for " + codePath, e); + hadErrors = true; } } } + return hadErrors ? -1 : freedBytes; } public static class RegisterDexModuleResult { diff --git a/services/core/java/com/android/server/vcn/Vcn.java b/services/core/java/com/android/server/vcn/Vcn.java index f7d61367c81e..95a06fcff734 100644 --- a/services/core/java/com/android/server/vcn/Vcn.java +++ b/services/core/java/com/android/server/vcn/Vcn.java @@ -453,6 +453,10 @@ public class Vcn extends Handler { for (VcnGatewayConnection gatewayConnection : mVcnGatewayConnections.values()) { gatewayConnection.updateSubscriptionSnapshot(mLastSnapshot); } + + // Update the mobile data state after updating the subscription snapshot as a change in + // subIds for a subGroup may affect the mobile data state. + handleMobileDataToggled(); } private void handleMobileDataToggled() { diff --git a/services/core/java/com/android/server/vcn/VcnNetworkProvider.java b/services/core/java/com/android/server/vcn/VcnNetworkProvider.java index ebabe2a66491..31ee2477fa64 100644 --- a/services/core/java/com/android/server/vcn/VcnNetworkProvider.java +++ b/services/core/java/com/android/server/vcn/VcnNetworkProvider.java @@ -16,12 +16,24 @@ package com.android.server.vcn; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED; +import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN; +import static android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED; +import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; + import static com.android.server.VcnManagementService.VDBG; import android.annotation.NonNull; import android.content.Context; +import android.net.ConnectivityManager; +import android.net.NetworkCapabilities; import android.net.NetworkProvider; import android.net.NetworkRequest; +import android.net.NetworkScore; +import android.net.vcn.VcnGatewayConnectionConfig; +import android.os.Handler; +import android.os.HandlerExecutor; import android.os.Looper; import android.util.ArraySet; import android.util.Slog; @@ -30,7 +42,9 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting.Visibility; import com.android.internal.util.IndentingPrintWriter; +import java.util.Objects; import java.util.Set; +import java.util.concurrent.Executor; /** * VCN Network Provider routes NetworkRequests to listeners to bring up tunnels as needed. @@ -45,6 +59,10 @@ public class VcnNetworkProvider extends NetworkProvider { private final Set<NetworkRequestListener> mListeners = new ArraySet<>(); + private final Context mContext; + private final Handler mHandler; + private final Dependencies mDeps; + /** * Cache of NetworkRequest(s). * @@ -52,8 +70,59 @@ public class VcnNetworkProvider extends NetworkProvider { */ private final Set<NetworkRequest> mRequests = new ArraySet<>(); - public VcnNetworkProvider(Context context, Looper looper) { - super(context, looper, VcnNetworkProvider.class.getSimpleName()); + public VcnNetworkProvider(@NonNull Context context, @NonNull Looper looper) { + this(context, looper, new Dependencies()); + } + + @VisibleForTesting(visibility = Visibility.PRIVATE) + public VcnNetworkProvider( + @NonNull Context context, @NonNull Looper looper, @NonNull Dependencies dependencies) { + super( + Objects.requireNonNull(context, "Missing context"), + Objects.requireNonNull(looper, "Missing looper"), + TAG); + + mContext = context; + mHandler = new Handler(looper); + mDeps = Objects.requireNonNull(dependencies, "Missing dependencies"); + } + + /** Registers this VcnNetworkProvider and a generic network offer with ConnectivityService. */ + public void register() { + mContext.getSystemService(ConnectivityManager.class).registerNetworkProvider(this); + mDeps.registerNetworkOffer( + this, + Vcn.getNetworkScore(), // score filter + buildCapabilityFilter(), + new HandlerExecutor(mHandler), + new NetworkOfferCallback() { + @Override + public void onNetworkNeeded(@NonNull NetworkRequest request) { + handleNetworkRequested(request); + } + + @Override + public void onNetworkUnneeded(@NonNull NetworkRequest request) { + handleNetworkRequestWithdrawn(request); + } + }); + } + + /** Builds the filter for NetworkRequests that can be served by the VcnNetworkProvider. */ + private NetworkCapabilities buildCapabilityFilter() { + final NetworkCapabilities.Builder builder = + new NetworkCapabilities.Builder() + .addTransportType(TRANSPORT_CELLULAR) + .addCapability(NET_CAPABILITY_TRUSTED) + .addCapability(NET_CAPABILITY_NOT_RESTRICTED) + .addCapability(NET_CAPABILITY_NOT_VPN) + .addCapability(NET_CAPABILITY_NOT_VCN_MANAGED); + + for (int cap : VcnGatewayConnectionConfig.ALLOWED_CAPABILITIES) { + builder.addCapability(cap); + } + + return builder.build(); } /** @@ -88,8 +157,7 @@ public class VcnNetworkProvider extends NetworkProvider { listener.onNetworkRequested(request); } - @Override - public void onNetworkRequested(@NonNull NetworkRequest request, int score, int providerId) { + private void handleNetworkRequested(@NonNull NetworkRequest request) { if (VDBG) { Slog.v(TAG, "Network requested: Request = " + request); } @@ -103,8 +171,7 @@ public class VcnNetworkProvider extends NetworkProvider { } } - @Override - public void onNetworkRequestWithdrawn(@NonNull NetworkRequest request) { + private void handleNetworkRequestWithdrawn(@NonNull NetworkRequest request) { if (VDBG) { Slog.v(TAG, "Network request withdrawn: Request = " + request); } @@ -144,4 +211,18 @@ public class VcnNetworkProvider extends NetworkProvider { pw.decreaseIndent(); } + + /** Proxy class for dependencies used for testing. */ + @VisibleForTesting(visibility = Visibility.PRIVATE) + public static class Dependencies { + /** Registers a given network offer for the given provider. */ + public void registerNetworkOffer( + @NonNull VcnNetworkProvider provider, + @NonNull NetworkScore score, + @NonNull NetworkCapabilities capabilitiesFilter, + @NonNull Executor executor, + @NonNull NetworkOfferCallback callback) { + provider.registerNetworkOffer(score, capabilitiesFilter, executor, callback); + } + } } diff --git a/services/net/java/android/net/ip/IpClientManager.java b/services/net/java/android/net/ip/IpClientManager.java index 274b6dc1769b..b45405f39f0f 100644 --- a/services/net/java/android/net/ip/IpClientManager.java +++ b/services/net/java/android/net/ip/IpClientManager.java @@ -88,6 +88,8 @@ public class IpClientManager { } catch (RemoteException e) { log("Error confirming IpClient configuration", e); return false; + } finally { + Binder.restoreCallingIdentity(token); } } diff --git a/services/tests/servicestests/src/com/android/server/pm/SELinuxMMACTest.java b/services/tests/servicestests/src/com/android/server/pm/SELinuxMMACTest.java index f1930d7268d7..cee4cda99e46 100644 --- a/services/tests/servicestests/src/com/android/server/pm/SELinuxMMACTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/SELinuxMMACTest.java @@ -91,6 +91,16 @@ public class SELinuxMMACTest { } @Test + public void getSeInfoTargetingCurDevelopment() { + AndroidPackage pkg = makePackage(Build.VERSION_CODES.CUR_DEVELOPMENT); + when(mMockCompatibility.isChangeEnabledInternal(eq(SELinuxMMAC.SELINUX_LATEST_CHANGES), + argThat(argument -> argument.packageName.equals(pkg.getPackageName())))) + .thenReturn(true); + assertThat(SELinuxMMAC.getSeInfo(pkg, null, mMockCompatibility), + is("default:targetSdkVersion=" + Build.VERSION_CODES.CUR_DEVELOPMENT)); + } + + @Test public void getSeInfoNoOptInButAlreadyR() { AndroidPackage pkg = makePackage(R_OPT_IN_VERSION); when(mMockCompatibility.isChangeEnabledInternal(eq(SELinuxMMAC.SELINUX_R_CHANGES), diff --git a/tests/vcn/java/com/android/server/vcn/VcnNetworkProviderTest.java b/tests/vcn/java/com/android/server/vcn/VcnNetworkProviderTest.java index 79cf746f105f..72db55b3f4c5 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnNetworkProviderTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnNetworkProviderTest.java @@ -16,12 +16,18 @@ package com.android.server.vcn; +import static android.net.NetworkProvider.NetworkOfferCallback; + +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.argThat; +import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import android.annotation.NonNull; import android.content.Context; +import android.net.ConnectivityManager; import android.net.NetworkRequest; import android.os.test.TestLooper; @@ -33,6 +39,7 @@ import com.android.server.vcn.VcnNetworkProvider.NetworkRequestListener; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import java.util.ArrayList; import java.util.List; @@ -47,6 +54,8 @@ public class VcnNetworkProviderTest { @NonNull private final Context mContext; @NonNull private final TestLooper mTestLooper; + @NonNull private VcnNetworkProvider.Dependencies mDeps; + @NonNull private ConnectivityManager mConnMgr; @NonNull private VcnNetworkProvider mVcnNetworkProvider; @NonNull private NetworkRequestListener mListener; @@ -57,16 +66,47 @@ public class VcnNetworkProviderTest { @Before public void setUp() throws Exception { - mVcnNetworkProvider = new VcnNetworkProvider(mContext, mTestLooper.getLooper()); + mDeps = mock(VcnNetworkProvider.Dependencies.class); + mConnMgr = mock(ConnectivityManager.class); + VcnTestUtils.setupSystemService( + mContext, mConnMgr, Context.CONNECTIVITY_SERVICE, ConnectivityManager.class); + + mVcnNetworkProvider = new VcnNetworkProvider(mContext, mTestLooper.getLooper(), mDeps); mListener = mock(NetworkRequestListener.class); } + private NetworkOfferCallback verifyRegisterAndGetOfferCallback() throws Exception { + mVcnNetworkProvider.register(); + + final ArgumentCaptor<NetworkOfferCallback> cbCaptor = + ArgumentCaptor.forClass(NetworkOfferCallback.class); + + verify(mConnMgr).registerNetworkProvider(eq(mVcnNetworkProvider)); + verify(mDeps) + .registerNetworkOffer( + eq(mVcnNetworkProvider), + argThat( + score -> + score.getLegacyInt() + == Vcn.getNetworkScore().getLegacyInt()), + any(), + any(), + cbCaptor.capture()); + + return cbCaptor.getValue(); + } + + @Test + public void testRegister() throws Exception { + verifyRegisterAndGetOfferCallback(); + } + @Test public void testRequestsPassedToRegisteredListeners() throws Exception { mVcnNetworkProvider.registerListener(mListener); final NetworkRequest request = mock(NetworkRequest.class); - mVcnNetworkProvider.onNetworkRequested(request, TEST_SCORE_UNSATISFIED, TEST_PROVIDER_ID); + verifyRegisterAndGetOfferCallback().onNetworkNeeded(request); verify(mListener).onNetworkRequested(request); } @@ -76,13 +116,14 @@ public class VcnNetworkProviderTest { mVcnNetworkProvider.unregisterListener(mListener); final NetworkRequest request = mock(NetworkRequest.class); - mVcnNetworkProvider.onNetworkRequested(request, TEST_SCORE_UNSATISFIED, TEST_PROVIDER_ID); + verifyRegisterAndGetOfferCallback().onNetworkNeeded(request); verifyNoMoreInteractions(mListener); } @Test public void testCachedRequestsPassedOnRegister() throws Exception { final List<NetworkRequest> requests = new ArrayList<>(); + final NetworkOfferCallback offerCb = verifyRegisterAndGetOfferCallback(); for (int i = 0; i < 10; i++) { // Build unique network requests; in this case, iterate down the capabilities as a way @@ -91,12 +132,12 @@ public class VcnNetworkProviderTest { new NetworkRequest.Builder().clearCapabilities().addCapability(i).build(); requests.add(request); - mVcnNetworkProvider.onNetworkRequested(request, i, i + 1); + offerCb.onNetworkNeeded(request); } // Remove one, and verify that it is never sent to the listeners. final NetworkRequest removed = requests.remove(0); - mVcnNetworkProvider.onNetworkRequestWithdrawn(removed); + offerCb.onNetworkUnneeded(removed); mVcnNetworkProvider.registerListener(mListener); for (NetworkRequest request : requests) { diff --git a/tests/vcn/java/com/android/server/vcn/VcnTest.java b/tests/vcn/java/com/android/server/vcn/VcnTest.java index f681ee19ab12..5d2f9d748581 100644 --- a/tests/vcn/java/com/android/server/vcn/VcnTest.java +++ b/tests/vcn/java/com/android/server/vcn/VcnTest.java @@ -242,6 +242,27 @@ public class VcnTest { verifyUpdateSubscriptionSnapshotNotifiesGatewayConnections(VCN_STATUS_CODE_SAFE_MODE); } + @Test + public void testSubscriptionSnapshotUpdatesMobileDataState() { + final NetworkRequestListener requestListener = verifyAndGetRequestListener(); + startVcnGatewayWithCapabilities(requestListener, TEST_CAPS[0]); + + // Expect mobile data enabled from setUp() + assertTrue(mVcn.isMobileDataEnabled()); + + final TelephonySubscriptionSnapshot updatedSnapshot = + mock(TelephonySubscriptionSnapshot.class); + doReturn(TEST_SUB_IDS_IN_GROUP) + .when(updatedSnapshot) + .getAllSubIdsInGroup(eq(TEST_SUB_GROUP)); + doReturn(false).when(mTelephonyManager).isDataEnabled(); + + mVcn.updateSubscriptionSnapshot(updatedSnapshot); + mTestLooper.dispatchAll(); + + assertFalse(mVcn.isMobileDataEnabled()); + } + private void triggerVcnRequestListeners(NetworkRequestListener requestListener) { for (final int[] caps : TEST_CAPS) { startVcnGatewayWithCapabilities(requestListener, caps); |