diff options
45 files changed, 1196 insertions, 184 deletions
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp index 14332528e992..4c97c3454e82 100644 --- a/cmds/statsd/src/StatsService.cpp +++ b/cmds/statsd/src/StatsService.cpp @@ -1202,9 +1202,10 @@ Status StatsService::unregisterPullerCallback(int32_t atomTag, const String16& p return Status::ok(); } -Status StatsService::sendBinaryPushStateChangedAtom(const android::String16& trainName, - int64_t trainVersionCode, int options, - int32_t state, +Status StatsService::sendBinaryPushStateChangedAtom(const android::String16& trainNameIn, + const int64_t trainVersionCodeIn, + const int options, + const int32_t state, const std::vector<int64_t>& experimentIdsIn) { uid_t uid = IPCThreadState::self()->getCallingUid(); // For testing @@ -1224,34 +1225,64 @@ Status StatsService::sendBinaryPushStateChangedAtom(const android::String16& tra // TODO: add verifier permission bool readTrainInfoSuccess = false; - InstallTrainInfo trainInfo; - if (trainVersionCode == -1 || experimentIdsIn.empty() || trainName.size() == 0) { - readTrainInfoSuccess = StorageManager::readTrainInfo(trainInfo); - } + InstallTrainInfo trainInfoOnDisk; + readTrainInfoSuccess = StorageManager::readTrainInfo(trainInfoOnDisk); + + bool resetExperimentIds = false; + int64_t trainVersionCode = trainVersionCodeIn; + std::string trainNameUtf8 = std::string(String8(trainNameIn).string()); + if (readTrainInfoSuccess) { + // Keep the old train version if we received an empty version. + if (trainVersionCodeIn == -1) { + trainVersionCode = trainInfoOnDisk.trainVersionCode; + } else if (trainVersionCodeIn != trainInfoOnDisk.trainVersionCode) { + // Reset experiment ids if we receive a new non-empty train version. + resetExperimentIds = true; + } + + // Keep the old train name if we received an empty train name. + if (trainNameUtf8.size() == 0) { + trainNameUtf8 = trainInfoOnDisk.trainName; + } else if (trainNameUtf8 != trainInfoOnDisk.trainName) { + // Reset experiment ids if we received a new valid train name. + resetExperimentIds = true; + } - if (trainVersionCode == -1 && readTrainInfoSuccess) { - trainVersionCode = trainInfo.trainVersionCode; + // Reset if we received a different experiment id. + if (!experimentIdsIn.empty() && + (trainInfoOnDisk.experimentIds.empty() || + experimentIdsIn[0] != trainInfoOnDisk.experimentIds[0])) { + resetExperimentIds = true; + } } // Find the right experiment IDs std::vector<int64_t> experimentIds; - if (readTrainInfoSuccess && experimentIdsIn.empty()) { - experimentIds = trainInfo.experimentIds; - } else { + if (resetExperimentIds || !readTrainInfoSuccess) { experimentIds = experimentIdsIn; + } else { + experimentIds = trainInfoOnDisk.experimentIds; + } + + if (!experimentIds.empty()) { + int64_t firstId = experimentIds[0]; + switch (state) { + case android::util::BINARY_PUSH_STATE_CHANGED__STATE__INSTALL_SUCCESS: + experimentIds.push_back(firstId + 1); + break; + case android::util::BINARY_PUSH_STATE_CHANGED__STATE__INSTALLER_ROLLBACK_INITIATED: + experimentIds.push_back(firstId + 2); + break; + case android::util::BINARY_PUSH_STATE_CHANGED__STATE__INSTALLER_ROLLBACK_SUCCESS: + experimentIds.push_back(firstId + 3); + break; + } } // Flatten the experiment IDs to proto vector<uint8_t> experimentIdsProtoBuffer; writeExperimentIdsToProto(experimentIds, &experimentIdsProtoBuffer); - - // Find the right train name - std::string trainNameUtf8; - if (readTrainInfoSuccess && trainName.size() == 0) { - trainNameUtf8 = trainInfo.trainName; - } else { - trainNameUtf8 = std::string(String8(trainName).string()); - } + StorageManager::writeTrainInfo(trainVersionCode, trainNameUtf8, state, experimentIds); userid_t userId = multiuser_get_user_id(uid); bool requiresStaging = options & IStatsManager::FLAG_REQUIRE_STAGING; @@ -1260,7 +1291,6 @@ Status StatsService::sendBinaryPushStateChangedAtom(const android::String16& tra LogEvent event(trainNameUtf8, trainVersionCode, requiresStaging, rollbackEnabled, requiresLowLatencyMonitor, state, experimentIdsProtoBuffer, userId); mProcessor->OnLogEvent(&event); - StorageManager::writeTrainInfo(trainVersionCode, trainNameUtf8, state, experimentIds); return Status::ok(); } diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h index 929d260dcd95..0b6df8b0997c 100644 --- a/cmds/statsd/src/StatsService.h +++ b/cmds/statsd/src/StatsService.h @@ -189,8 +189,11 @@ public: * Binder call to log BinaryPushStateChanged atom. */ virtual Status sendBinaryPushStateChangedAtom( - const android::String16& trainName, int64_t trainVersionCode, int options, - int32_t state, const std::vector<int64_t>& experimentIds) override; + const android::String16& trainNameIn, + const int64_t trainVersionCodeIn, + const int options, + const int32_t state, + const std::vector<int64_t>& experimentIdsIn) override; /** * Binder call to get registered experiment IDs. diff --git a/cmds/statsd/src/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp index ca874b57170e..0ade53118d77 100644 --- a/cmds/statsd/src/logd/LogEvent.cpp +++ b/cmds/statsd/src/logd/LogEvent.cpp @@ -678,7 +678,8 @@ void LogEvent::ToProto(ProtoOutputStream& protoOutput) const { writeFieldValueTreeToStream(mTagId, getValues(), &protoOutput); } -void writeExperimentIdsToProto(const std::vector<int64_t>& experimentIds, std::vector<uint8_t>* protoOut) { +void writeExperimentIdsToProto(const std::vector<int64_t>& experimentIds, + std::vector<uint8_t>* protoOut) { ProtoOutputStream proto; for (const auto& expId : experimentIds) { proto.write(FIELD_TYPE_INT64 | FIELD_COUNT_REPEATED | FIELD_ID_EXPERIMENT_ID, diff --git a/packages/NetworkPermissionConfig/AndroidManifest.xml b/packages/NetworkPermissionConfig/AndroidManifest.xml index 34f987c8f0d4..496fa4defcde 100644 --- a/packages/NetworkPermissionConfig/AndroidManifest.xml +++ b/packages/NetworkPermissionConfig/AndroidManifest.xml @@ -19,7 +19,7 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.networkstack.permissionconfig" android:sharedUserId="android.uid.networkstack" - android:versionCode="11" + android:versionCode="210000000" android:versionName="Q-initial"> <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="28" /> <!-- diff --git a/packages/NetworkStack/src/android/net/apf/ApfFilter.java b/packages/NetworkStack/src/android/net/apf/ApfFilter.java index 663e2f10ffe2..359c85983a94 100644 --- a/packages/NetworkStack/src/android/net/apf/ApfFilter.java +++ b/packages/NetworkStack/src/android/net/apf/ApfFilter.java @@ -39,6 +39,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.net.LinkAddress; import android.net.LinkProperties; +import android.net.NattKeepalivePacketDataParcelable; import android.net.TcpKeepalivePacketDataParcelable; import android.net.apf.ApfGenerator.IllegalInstructionException; import android.net.apf.ApfGenerator.Register; @@ -1691,13 +1692,13 @@ public class ApfFilter { } /** - * Add keepalive ack packet filter. + * Add TCP keepalive ack packet filter. * This will add a filter to drop acks to the keepalive packet passed as an argument. * * @param slot The index used to access the filter. * @param sentKeepalivePacket The attributes of the sent keepalive packet. */ - public synchronized void addKeepalivePacketFilter(final int slot, + public synchronized void addTcpKeepalivePacketFilter(final int slot, final TcpKeepalivePacketDataParcelable sentKeepalivePacket) { log("Adding keepalive ack(" + slot + ")"); if (null != mKeepaliveAcks.get(slot)) { @@ -1711,6 +1712,18 @@ public class ApfFilter { } /** + * Add NATT keepalive packet filter. + * This will add a filter to drop NATT keepalive packet which is passed as an argument. + * + * @param slot The index used to access the filter. + * @param sentKeepalivePacket The attributes of the sent keepalive packet. + */ + public synchronized void addNattKeepalivePacketFilter(final int slot, + final NattKeepalivePacketDataParcelable sentKeepalivePacket) { + Log.e(TAG, "APF add NATT keepalive filter is not implemented"); + } + + /** * Remove keepalive packet filter. * * @param slot The index used to access the filter. diff --git a/packages/NetworkStack/src/android/net/ip/IpClient.java b/packages/NetworkStack/src/android/net/ip/IpClient.java index 96e09fafb6b6..dc74c041c35a 100644 --- a/packages/NetworkStack/src/android/net/ip/IpClient.java +++ b/packages/NetworkStack/src/android/net/ip/IpClient.java @@ -29,6 +29,7 @@ import android.net.INetd; import android.net.IpPrefix; import android.net.LinkAddress; import android.net.LinkProperties; +import android.net.NattKeepalivePacketDataParcelable; import android.net.NetworkStackIpMemoryStore; import android.net.ProvisioningConfigurationParcelable; import android.net.ProxyInfo; @@ -371,6 +372,10 @@ public class IpClient extends StateMachine { private boolean mMulticastFiltering; private long mStartTimeMillis; + /* This must match the definition in KeepaliveTracker.KeepaliveInfo */ + private static final int TYPE_NATT = 1; + private static final int TYPE_TCP = 2; + /** * Reading the snapshot is an asynchronous operation initiated by invoking * Callback.startReadPacketFilter() and completed when the WiFi Service responds with an @@ -553,6 +558,11 @@ public class IpClient extends StateMachine { IpClient.this.addKeepalivePacketFilter(slot, pkt); } @Override + public void addNattKeepalivePacketFilter(int slot, NattKeepalivePacketDataParcelable pkt) { + checkNetworkStackCallingPermission(); + IpClient.this.addNattKeepalivePacketFilter(slot, pkt); + } + @Override public void removeKeepalivePacketFilter(int slot) { checkNetworkStackCallingPermission(); IpClient.this.removeKeepalivePacketFilter(slot); @@ -691,11 +701,20 @@ public class IpClient extends StateMachine { } /** - * Called by WifiStateMachine to add keepalive packet filter before setting up + * Called by WifiStateMachine to add TCP keepalive packet filter before setting up * keepalive offload. */ public void addKeepalivePacketFilter(int slot, @NonNull TcpKeepalivePacketDataParcelable pkt) { - sendMessage(CMD_ADD_KEEPALIVE_PACKET_FILTER_TO_APF, slot, 0 /* Unused */, pkt); + sendMessage(CMD_ADD_KEEPALIVE_PACKET_FILTER_TO_APF, slot, TYPE_TCP, pkt); + } + + /** + * Called by WifiStateMachine to add NATT keepalive packet filter before setting up + * keepalive offload. + */ + public void addNattKeepalivePacketFilter(int slot, + @NonNull NattKeepalivePacketDataParcelable pkt) { + sendMessage(CMD_ADD_KEEPALIVE_PACKET_FILTER_TO_APF, slot, TYPE_NATT, pkt); } /** @@ -1607,9 +1626,16 @@ public class IpClient extends StateMachine { case CMD_ADD_KEEPALIVE_PACKET_FILTER_TO_APF: { final int slot = msg.arg1; + final int type = msg.arg2; + if (mApfFilter != null) { - mApfFilter.addKeepalivePacketFilter(slot, - (TcpKeepalivePacketDataParcelable) msg.obj); + if (type == TYPE_NATT) { + mApfFilter.addNattKeepalivePacketFilter(slot, + (NattKeepalivePacketDataParcelable) msg.obj); + } else { + mApfFilter.addTcpKeepalivePacketFilter(slot, + (TcpKeepalivePacketDataParcelable) msg.obj); + } } break; } diff --git a/packages/NetworkStack/tests/src/android/net/apf/ApfTest.java b/packages/NetworkStack/tests/src/android/net/apf/ApfTest.java index a0e508f130a5..93ab3be28fc7 100644 --- a/packages/NetworkStack/tests/src/android/net/apf/ApfTest.java +++ b/packages/NetworkStack/tests/src/android/net/apf/ApfTest.java @@ -1553,7 +1553,7 @@ public class ApfTest { parcel.seq = seqNum; parcel.ack = ackNum; - apfFilter.addKeepalivePacketFilter(slot1, parcel); + apfFilter.addTcpKeepalivePacketFilter(slot1, parcel); program = cb.getApfProgram(); // Verify IPv4 keepalive ack packet is dropped @@ -1592,7 +1592,7 @@ public class ApfTest { ipv6Parcel.seq = seqNum; ipv6Parcel.ack = ackNum; - apfFilter.addKeepalivePacketFilter(slot1, ipv6Parcel); + apfFilter.addTcpKeepalivePacketFilter(slot1, ipv6Parcel); program = cb.getApfProgram(); // Verify IPv6 keepalive ack packet is dropped @@ -1614,8 +1614,8 @@ public class ApfTest { apfFilter.removeKeepalivePacketFilter(slot1); // Verify multiple filters - apfFilter.addKeepalivePacketFilter(slot1, parcel); - apfFilter.addKeepalivePacketFilter(slot2, ipv6Parcel); + apfFilter.addTcpKeepalivePacketFilter(slot1, parcel); + apfFilter.addTcpKeepalivePacketFilter(slot2, ipv6Parcel); program = cb.getApfProgram(); // Verify IPv4 keepalive ack packet is dropped diff --git a/packages/SystemUI/res/layout/contextual.xml b/packages/SystemUI/res/layout/contextual.xml index 9b6ccaeef1e4..90a776884699 100644 --- a/packages/SystemUI/res/layout/contextual.xml +++ b/packages/SystemUI/res/layout/contextual.xml @@ -42,16 +42,10 @@ android:layout_height="match_parent" android:visibility="invisible" /> - <com.android.systemui.statusbar.policy.KeyButtonView - android:id="@+id/rotate_suggestion" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:layout_weight="0" - android:scaleType="center" - android:visibility="invisible" - android:contentDescription="@string/accessibility_rotate_button" - android:paddingStart="@dimen/navigation_key_padding" - android:paddingEnd="@dimen/navigation_key_padding" + <include layout="@layout/rotate_suggestion" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:visibility="invisible" /> <com.android.systemui.statusbar.policy.KeyButtonView android:id="@+id/accessibility_button" diff --git a/packages/SystemUI/res/layout/rotate_suggestion.xml b/packages/SystemUI/res/layout/rotate_suggestion.xml new file mode 100644 index 000000000000..d7f67db0390c --- /dev/null +++ b/packages/SystemUI/res/layout/rotate_suggestion.xml @@ -0,0 +1,29 @@ +<?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 + --> + +<com.android.systemui.statusbar.policy.KeyButtonView + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/rotate_suggestion" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_weight="0" + android:scaleType="center" + android:visibility="invisible" + android:contentDescription="@string/accessibility_rotate_button" + android:paddingStart="@dimen/navigation_key_padding" + android:paddingEnd="@dimen/navigation_key_padding" +/>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/start_contextual.xml b/packages/SystemUI/res/layout/start_contextual.xml new file mode 100644 index 000000000000..e022c7301e3b --- /dev/null +++ b/packages/SystemUI/res/layout/start_contextual.xml @@ -0,0 +1,36 @@ +<?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. +--> +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:systemui="http://schemas.android.com/apk/res-auto" + android:id="@+id/start_menu_container" + android:layout_width="@dimen/navigation_key_width" + android:layout_height="match_parent" + android:importantForAccessibility="no" + android:focusable="false" + android:clipChildren="false" + android:clipToPadding="false" + > + <include layout="@layout/rotate_suggestion" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:visibility="invisible" + /> + <include layout="@layout/back" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:visibility="invisible" + /> +</FrameLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 73386879a20d..4abe9f0bfb5c 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -331,7 +331,7 @@ <!-- Nav bar button default ordering/layout --> <string name="config_navBarLayout" translatable="false">left[.5W],back[1WC];home;recent[1WC],right[.5W]</string> <string name="config_navBarLayoutQuickstep" translatable="false">back[1.7WC];home;contextual[1.7WC]</string> - <string name="config_navBarLayoutHandle" translatable="false">back[1.7WC];home_handle;ime_switcher[1.7WC]</string> + <string name="config_navBarLayoutHandle" translatable="false">start_contextual[.1WC];home_handle;ime_switcher[.1WC]</string> <bool name="quick_settings_show_full_alarm">false</bool> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index acc03c43d34c..60d71260058f 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1345,6 +1345,7 @@ <!-- Screen pinning dialog description. --> <string name="screen_pinning_description">This keeps it in view until you unpin. Touch & hold Back and Overview to unpin.</string> <string name="screen_pinning_description_recents_invisible">This keeps it in view until you unpin. Touch & hold Back and Home to unpin.</string> + <string name="screen_pinning_description_gestural">This keeps it in view until you unpin. Swipe up & hold to unpin.</string> <!-- Screen pinning dialog description. --> <string name="screen_pinning_description_accessible">This keeps it in view until you unpin. Touch & hold Overview to unpin.</string> <string name="screen_pinning_description_recents_invisible_accessible">This keeps it in view until you unpin. Touch & hold Home to unpin.</string> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl index 6f44623eb320..2c5fa60d3516 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl @@ -96,4 +96,9 @@ interface ISystemUiProxy { * Notifies that the accessibility button in the system's navigation area has been long clicked */ void notifyAccessibilityButtonLongClicked() = 16; + + /** + * Ends the system screen pinning. + */ + void stopScreenPinning() = 17; } diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index 3ace7050c743..5ed6a429cc2f 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -35,6 +35,7 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_N import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING; import android.annotation.FloatRange; +import android.app.ActivityTaskManager; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; @@ -151,6 +152,25 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis } @Override + public void stopScreenPinning() { + if (!verifyCaller("stopScreenPinning")) { + return; + } + long token = Binder.clearCallingIdentity(); + try { + mHandler.post(() -> { + try { + ActivityTaskManager.getService().stopSystemLockTaskMode(); + } catch (RemoteException e) { + Log.e(TAG_OPS, "Failed to stop screen pinning"); + } + }); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override public void onStatusBarMotionEvent(MotionEvent event) { if (!verifyCaller("onStatusBarMotionEvent")) { return; diff --git a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java index 07391ed5867f..ade903d7e272 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java +++ b/packages/SystemUI/src/com/android/systemui/recents/ScreenPinningRequest.java @@ -48,14 +48,17 @@ import android.widget.TextView; import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.SysUiServiceProvider; +import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.shared.system.WindowManagerWrapper; import com.android.systemui.statusbar.phone.NavigationBarView; +import com.android.systemui.statusbar.phone.NavigationModeController; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.util.leak.RotationUtils; import java.util.ArrayList; -public class ScreenPinningRequest implements View.OnClickListener { +public class ScreenPinningRequest implements View.OnClickListener, + NavigationModeController.ModeChangedListener { private final Context mContext; @@ -64,6 +67,7 @@ public class ScreenPinningRequest implements View.OnClickListener { private final OverviewProxyService mOverviewProxyService; private RequestWindowView mRequestWindow; + private int mNavBarMode; // Id of task to be pinned or locked. private int taskId; @@ -75,6 +79,7 @@ public class ScreenPinningRequest implements View.OnClickListener { mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); mOverviewProxyService = Dependency.get(OverviewProxyService.class); + mNavBarMode = Dependency.get(NavigationModeController.class).addListener(this); } public void clearPrompt() { @@ -103,6 +108,11 @@ public class ScreenPinningRequest implements View.OnClickListener { mWindowManager.addView(mRequestWindow, lp); } + @Override + public void onNavigationModeChanged(int mode) { + mNavBarMode = mode; + } + public void onConfigurationChanged() { if (mRequestWindow != null) { mRequestWindow.onConfigurationChanged(); @@ -224,7 +234,9 @@ public class ScreenPinningRequest implements View.OnClickListener { mLayout.findViewById(R.id.screen_pinning_text_area) .setLayoutDirection(View.LAYOUT_DIRECTION_LOCALE); View buttons = mLayout.findViewById(R.id.screen_pinning_buttons); - if (WindowManagerWrapper.getInstance().hasSoftNavigationBar(mContext.getDisplayId())) { + WindowManagerWrapper wm = WindowManagerWrapper.getInstance(); + if (!QuickStepContract.isGesturalMode(mNavBarMode) + && wm.hasSoftNavigationBar(mContext.getDisplayId())) { buttons.setLayoutDirection(View.LAYOUT_DIRECTION_LOCALE); swapChildrenIfRtlAndVertical(buttons); } else { @@ -248,7 +260,9 @@ public class ScreenPinningRequest implements View.OnClickListener { && navigationBarView.isRecentsButtonVisible(); boolean touchExplorationEnabled = mAccessibilityService.isTouchExplorationEnabled(); int descriptionStringResId; - if (recentsVisible) { + if (QuickStepContract.isGesturalMode(mNavBarMode)) { + descriptionStringResId = R.string.screen_pinning_description_gestural; + } else if (recentsVisible) { mLayout.findViewById(R.id.screen_pinning_recents_group).setVisibility(VISIBLE); mLayout.findViewById(R.id.screen_pinning_home_bg_light).setVisibility(INVISIBLE); mLayout.findViewById(R.id.screen_pinning_home_bg).setVisibility(INVISIBLE); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java index 5f5fad33fbfe..6a93c7c9e5c4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ButtonDispatcher.java @@ -47,7 +47,7 @@ public class ButtonDispatcher { private Boolean mLongClickable; private Float mAlpha; private Float mDarkIntensity; - private Integer mVisibility = -1; + private Integer mVisibility = View.VISIBLE; private Boolean mDelayTouchFeedback; private KeyButtonDrawable mImageDrawable; private View mCurrentView; @@ -86,7 +86,7 @@ public class ButtonDispatcher { if (mAlpha != null) { view.setAlpha(mAlpha); } - if (mVisibility != null && mVisibility != -1) { + if (mVisibility != null) { view.setVisibility(mVisibility); } if (mAccessibilityDelegate != null) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ContextualButton.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ContextualButton.java index 541c142c422a..5bc17f5bc2c6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ContextualButton.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ContextualButton.java @@ -51,7 +51,7 @@ public class ContextualButton extends ButtonDispatcher { * Reload the drawable from resource id, should reapply the previous dark intensity. */ public void updateIcon() { - if (getCurrentView() == null || !getCurrentView().isAttachedToWindow()) { + if (getCurrentView() == null || !getCurrentView().isAttachedToWindow() || mIconResId == 0) { return; } final KeyButtonDrawable currentDrawable = getImageDrawable(); @@ -92,7 +92,7 @@ public class ContextualButton extends ButtonDispatcher { setVisibility(View.VISIBLE); return true; } - return mGroup.setButtonVisiblity(getId(), true /* visible */) == View.VISIBLE; + return mGroup.setButtonVisibility(getId(), true /* visible */) == View.VISIBLE; } /** @@ -104,7 +104,7 @@ public class ContextualButton extends ButtonDispatcher { setVisibility(View.INVISIBLE); return false; } - return mGroup.setButtonVisiblity(getId(), false /* visible */) != View.VISIBLE; + return mGroup.setButtonVisibility(getId(), false /* visible */) != View.VISIBLE; } /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ContextualButtonGroup.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ContextualButtonGroup.java index 02b660f4734d..9e843f93d00e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ContextualButtonGroup.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ContextualButtonGroup.java @@ -37,7 +37,7 @@ public class ContextualButtonGroup extends ButtonDispatcher { /** * Add a contextual button to the group. The order of adding increases in its priority. The * priority is used to determine which button should be visible when setting multiple button's - * visibility {@see setButtonVisiblity}. + * visibility {@see setButtonVisibility}. * @param button the button added to the group */ public void addButton(@NonNull ContextualButton button) { @@ -71,7 +71,7 @@ public class ContextualButtonGroup extends ButtonDispatcher { * @return if the button is visible after operation * @throws RuntimeException if the input id does not match any of the ids in the group */ - public int setButtonVisiblity(@IdRes int buttonResId, boolean visible) { + public int setButtonVisibility(@IdRes int buttonResId, boolean visible) { final int index = getContextButtonIndex(buttonResId); if (index == INVALID_INDEX) { throw new RuntimeException("Cannot find the button id of " + buttonResId diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java index 404c07b807e5..963fc54ecd2d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarInflaterView.java @@ -71,6 +71,7 @@ public class NavigationBarInflaterView extends FrameLayout public static final String RIGHT = "right"; public static final String CONTEXTUAL = "contextual"; public static final String IME_SWITCHER = "ime_switcher"; + public static final String START_CONTEXTUAL = "start_contextual"; public static final String GRAVITY_SEPARATOR = ";"; public static final String BUTTON_SEPARATOR = ","; @@ -419,6 +420,8 @@ public class NavigationBarInflaterView extends FrameLayout v = inflater.inflate(R.layout.home_handle, parent, false); } else if (IME_SWITCHER.equals(button)) { v = inflater.inflate(R.layout.ime_switcher, parent, false); + } else if (START_CONTEXTUAL.equals(button)) { + v = inflater.inflate(R.layout.start_contextual, parent, false); } else if (button.startsWith(KEY)) { String uri = extractImage(button); int code = extractKeycode(button); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java index 6f1e161cf237..4333200e702a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java @@ -49,6 +49,8 @@ import android.view.MotionEvent; import android.view.Surface; import android.view.View; import android.view.ViewGroup; +import android.view.ViewTreeObserver.InternalInsetsInfo; +import android.view.ViewTreeObserver.OnComputeInternalInsetsListener; import android.view.WindowInsets; import android.view.WindowManager; import android.view.WindowManagerGlobal; @@ -135,6 +137,7 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav private boolean mImeVisible; private final SparseArray<ButtonDispatcher> mButtonDispatchers = new SparseArray<>(); + private final ContextualButtonGroup mStartContextualButtonGroup; private final ContextualButtonGroup mContextualButtonGroup; private Configuration mConfiguration; private Configuration mTmpLastConfiguration; @@ -233,11 +236,36 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav } }; + private final OnComputeInternalInsetsListener mOnComputeInternalInsetsListener = info -> { + // When the nav bar is in 2-button or 3-button mode, or when IME is visible in fully + // gestural mode, the entire nav bar should be touchable. + if (!QuickStepContract.isGesturalMode(mNavBarMode) || mImeVisible) { + info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_FRAME); + return; + } + info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_REGION); + RotationContextButton rotationContextButton = getRotateSuggestionButton(); + // If the rotate suggestion button is not visible in fully gestural mode, the entire nav bar + // is not touchable so that the app underneath can be clicked through. + if (rotationContextButton.getVisibility() != VISIBLE) { + info.touchableRegion.setEmpty(); + } else { + // Set the rotate suggestion button area to be touchable. + rotationContextButton.getCurrentView().getLocationInWindow(mTmpPosition); + Rect rect = new Rect(mTmpPosition[0], mTmpPosition[1], + mTmpPosition[0] + mRotationButtonBounds.width(), + mTmpPosition[1] + mRotationButtonBounds.height()); + info.touchableRegion.union(rect); + } + }; + public NavigationBarView(Context context, AttributeSet attrs) { super(context, attrs); mIsVertical = false; mLongClickableAccessibilityButton = false; + mNavBarMode = Dependency.get(NavigationModeController.class).addListener(this); + boolean isGesturalMode = QuickStepContract.isGesturalMode(mNavBarMode); // Set up the context group of buttons mContextualButtonGroup = new ContextualButtonGroup(R.id.menu_container); @@ -253,12 +281,21 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav R.drawable.ic_sysbar_accessibility_button); mContextualButtonGroup.addButton(menuButton); mContextualButtonGroup.addButton(imeSwitcherButton); - mContextualButtonGroup.addButton(rotateSuggestionButton); + if (!isGesturalMode) { + mContextualButtonGroup.addButton(rotateSuggestionButton); + } mContextualButtonGroup.addButton(accessibilityButton); mOverviewProxyService = Dependency.get(OverviewProxyService.class); mRecentsOnboarding = new RecentsOnboarding(context, mOverviewProxyService); + final ContextualButton backButton = new ContextualButton(R.id.back, 0); + mStartContextualButtonGroup = new ContextualButtonGroup(R.id.start_menu_container); + if (isGesturalMode) { + mStartContextualButtonGroup.addButton(rotateSuggestionButton); + } + mStartContextualButtonGroup.addButton(backButton); + mConfiguration = new Configuration(); mTmpLastConfiguration = new Configuration(); mConfiguration.updateFrom(context.getResources().getConfiguration()); @@ -266,7 +303,7 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav mScreenPinningNotify = new ScreenPinningNotify(mContext); mBarTransitions = new NavigationBarTransitions(this); - mButtonDispatchers.put(R.id.back, new ButtonDispatcher(R.id.back)); + mButtonDispatchers.put(R.id.back, backButton); mButtonDispatchers.put(R.id.home, new ButtonDispatcher(R.id.home)); mButtonDispatchers.put(R.id.home_handle, new ButtonDispatcher(R.id.home_handle)); mButtonDispatchers.put(R.id.recent_apps, new ButtonDispatcher(R.id.recent_apps)); @@ -275,6 +312,7 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav mButtonDispatchers.put(R.id.accessibility_button, accessibilityButton); mButtonDispatchers.put(R.id.rotate_suggestion, rotateSuggestionButton); mButtonDispatchers.put(R.id.menu_container, mContextualButtonGroup); + mButtonDispatchers.put(R.id.start_menu_container, mStartContextualButtonGroup); mDeadZone = new DeadZone(this); mEdgeBackGestureHandler = new EdgeBackGestureHandler(context, mOverviewProxyService); @@ -390,8 +428,11 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav } public RotationContextButton getRotateSuggestionButton() { - return (RotationContextButton) mContextualButtonGroup - .getContextButton(R.id.rotate_suggestion); + return (RotationContextButton) mButtonDispatchers.get(R.id.rotate_suggestion); + } + + public ContextualButtonGroup getStartContextualButtonGroup() { + return mStartContextualButtonGroup; } public ButtonDispatcher getHomeHandle() { @@ -430,6 +471,7 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav if (densityChange || dirChange) { mRecentIcon = getDrawable(R.drawable.ic_sysbar_recent); mContextualButtonGroup.updateIcons(); + mStartContextualButtonGroup.updateIcons(); } if (orientationChange || densityChange || dirChange) { mBackIcon = getBackDrawable(); @@ -437,12 +479,16 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav } public KeyButtonDrawable getBackDrawable() { - KeyButtonDrawable drawable = chooseNavigationIconDrawable(R.drawable.ic_sysbar_back, - R.drawable.ic_sysbar_back_quick_step); + KeyButtonDrawable drawable = getDrawable(getBackDrawableRes()); orientBackButton(drawable); return drawable; } + public @DrawableRes int getBackDrawableRes() { + return chooseNavigationIconDrawableRes(R.drawable.ic_sysbar_back, + R.drawable.ic_sysbar_back_quick_step); + } + public KeyButtonDrawable getHomeDrawable() { final boolean quickStepEnabled = mOverviewProxyService.shouldShowSwipeUpUI(); KeyButtonDrawable drawable = quickStepEnabled @@ -485,8 +531,13 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav private KeyButtonDrawable chooseNavigationIconDrawable(@DrawableRes int icon, @DrawableRes int quickStepIcon) { + return getDrawable(chooseNavigationIconDrawableRes(icon, quickStepIcon)); + } + + private @DrawableRes int chooseNavigationIconDrawableRes(@DrawableRes int icon, + @DrawableRes int quickStepIcon) { final boolean quickStepEnabled = mOverviewProxyService.shouldShowSwipeUpUI(); - return quickStepEnabled ? getDrawable(quickStepIcon) : getDrawable(icon); + return quickStepEnabled ? quickStepIcon : icon; } private KeyButtonDrawable getDrawable(@DrawableRes int icon) { @@ -527,7 +578,6 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav mTransitionListener.onBackAltCleared(); } mImeVisible = visible; - updateWindowTouchable(); } public void setDisabledFlags(int disabledFlags) { @@ -564,7 +614,7 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav updateRecentsIcon(); // Update IME button visibility, a11y and rotate button always overrides the appearance - mContextualButtonGroup.setButtonVisiblity(R.id.ime_switcher, + mContextualButtonGroup.setButtonVisibility(R.id.ime_switcher, (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_IME_SHOWN) != 0); mBarTransitions.reapplyDarkIntensity(); @@ -609,6 +659,7 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav } getBackButton().setVisibility(disableBack ? View.INVISIBLE : View.VISIBLE); + mStartContextualButtonGroup.setButtonVisibility(R.id.back, !disableBack); getHomeButton().setVisibility(disableHome ? View.INVISIBLE : View.VISIBLE); getRecentsButton().setVisibility(disableRecent ? View.INVISIBLE : View.VISIBLE); } @@ -714,11 +765,6 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav setWindowFlag(WindowManager.LayoutParams.FLAG_SLIPPERY, slippery); } - public void updateWindowTouchable() { - boolean touchable = mImeVisible || !QuickStepContract.isGesturalMode(mNavBarMode); - setWindowFlag(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE, !touchable); - } - private void setWindowFlag(int flags, boolean enable) { final ViewGroup navbarView = ((ViewGroup) getParent()); if (navbarView == null) { @@ -743,6 +789,7 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav mBarTransitions.onNavigationModeChanged(mNavBarMode); mEdgeBackGestureHandler.onNavigationModeChanged(mNavBarMode); mRecentsOnboarding.onNavigationModeChanged(mNavBarMode); + getRotateSuggestionButton().onNavigationModeChanged(mNavBarMode); // Color adaption is tied with showing home handle, only avaliable if visible mTintController.onNavigationModeChanged(mNavBarMode); @@ -751,17 +798,16 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav } else { mTintController.stop(); } - updateWindowTouchable(); } public void setMenuVisibility(final boolean show) { - mContextualButtonGroup.setButtonVisiblity(R.id.menu, show); + mContextualButtonGroup.setButtonVisibility(R.id.menu, show); } public void setAccessibilityButtonState(final boolean visible, final boolean longClickable) { mLongClickableAccessibilityButton = longClickable; getAccessibilityButton().setLongClickable(longClickable); - mContextualButtonGroup.setButtonVisiblity(R.id.accessibility_button, visible); + mContextualButtonGroup.setButtonVisibility(R.id.accessibility_button, visible); } void hideRecentsOnboarding() { @@ -1044,12 +1090,11 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav onPluginDisconnected(null); // Create default gesture helper Dependency.get(PluginManager.class).addPluginListener(this, NavGesture.class, false /* Only one */); - int navBarMode = Dependency.get(NavigationModeController.class).addListener(this); - onNavigationModeChanged(navBarMode); + onNavigationModeChanged(mNavBarMode); setUpSwipeUpOnboarding(isQuickStepSwipeUpEnabled()); mEdgeBackGestureHandler.onNavBarAttached(); - updateWindowTouchable(); + getViewTreeObserver().addOnComputeInternalInsetsListener(mOnComputeInternalInsetsListener); } @Override @@ -1065,6 +1110,8 @@ public class NavigationBarView extends FrameLayout implements PluginListener<Nav mButtonDispatchers.valueAt(i).onDestroy(); } mEdgeBackGestureHandler.onNavBarDetached(); + getViewTreeObserver().removeOnComputeInternalInsetsListener( + mOnComputeInternalInsetsListener); } private void setUpSwipeUpOnboarding(boolean connectedToOverviewProxy) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationContextButton.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationContextButton.java index e887f5b573cd..7203e57c3d23 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationContextButton.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/RotationContextButton.java @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.phone; +import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON; + import static com.android.internal.view.RotationPolicy.NATURAL_ROTATION; import android.animation.Animator; @@ -45,6 +47,7 @@ import com.android.systemui.Dependency; import com.android.systemui.Interpolators; import com.android.systemui.R; import com.android.systemui.shared.system.ActivityManagerWrapper; +import com.android.systemui.shared.system.QuickStepContract; import com.android.systemui.shared.system.TaskStackChangeListener; import com.android.systemui.statusbar.policy.KeyButtonDrawable; import com.android.systemui.statusbar.policy.RotationLockController; @@ -52,7 +55,9 @@ import com.android.systemui.statusbar.policy.RotationLockController; import java.util.Optional; import java.util.function.Consumer; -public class RotationContextButton extends ContextualButton { +/** Containing logic for the rotation button in nav bar. */ +public class RotationContextButton extends ContextualButton implements + NavigationModeController.ModeChangedListener { public static final boolean DEBUG_ROTATION = false; private static final int BUTTON_FADE_IN_OUT_DURATION_MS = 100; @@ -76,6 +81,7 @@ public class RotationContextButton extends ContextualButton { () -> mPendingRotationSuggestion = false; private Animator mRotateHideAnimator; private boolean mAccessibilityFeedbackEnabled; + private int mNavBarMode = NAV_BAR_MODE_3BUTTON; private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class); private final ViewRippler mViewRippler = new ViewRippler(); @@ -304,7 +310,8 @@ public class RotationContextButton extends ContextualButton { @Override protected KeyButtonDrawable getNewDrawable() { Context context = new ContextThemeWrapper(getContext().getApplicationContext(), mStyleRes); - return KeyButtonDrawable.create(context, mIconResId, false /* shadow */); + return KeyButtonDrawable.create(context, mIconResId, false /* shadow */, + QuickStepContract.isGesturalMode(mNavBarMode)); } @Override @@ -390,9 +397,9 @@ public class RotationContextButton extends ContextualButton { } private int computeRotationProposalTimeout() { - if (mAccessibilityFeedbackEnabled) return 20000; - if (mHoveringRotationSuggestion) return 16000; - return 10000; + if (mAccessibilityFeedbackEnabled) return 10000; + if (mHoveringRotationSuggestion) return 8000; + return 5000; } private boolean isRotateSuggestionIntroduced() { @@ -414,6 +421,11 @@ public class RotationContextButton extends ContextualButton { } } + @Override + public void onNavigationModeChanged(int mode) { + mNavBarMode = mode; + } + private class TaskStackListenerImpl extends TaskStackChangeListener { // Invalidate any rotation suggestion on task change or activity orientation change // Note: all callbacks happen on main thread diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonDrawable.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonDrawable.java index dd0c3443f2ab..2bfc311f1bd4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonDrawable.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonDrawable.java @@ -30,9 +30,11 @@ import android.graphics.Color; import android.graphics.ColorFilter; import android.graphics.Paint; import android.graphics.PixelFormat; +import android.graphics.PorterDuff; import android.graphics.PorterDuff.Mode; import android.graphics.PorterDuffColorFilter; import android.graphics.Rect; +import android.graphics.RectF; import android.graphics.drawable.AnimatedVectorDrawable; import android.graphics.drawable.Drawable; import android.util.FloatProperty; @@ -77,13 +79,14 @@ public class KeyButtonDrawable extends Drawable { private final Paint mIconPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); private final Paint mShadowPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); + private final Paint mOvalBgPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); private final ShadowDrawableState mState; private AnimatedVectorDrawable mAnimatedDrawable; public KeyButtonDrawable(Drawable d, @ColorInt int lightColor, @ColorInt int darkColor, - boolean horizontalFlip) { + boolean horizontalFlip, boolean hasOvalBg) { this(d, new ShadowDrawableState(lightColor, darkColor, - d instanceof AnimatedVectorDrawable, horizontalFlip)); + d instanceof AnimatedVectorDrawable, horizontalFlip, hasOvalBg)); } private KeyButtonDrawable(Drawable d, ShadowDrawableState state) { @@ -98,6 +101,7 @@ public class KeyButtonDrawable extends Drawable { mAnimatedDrawable = (AnimatedVectorDrawable) mState.mChildState.newDrawable().mutate(); setDrawableBounds(mAnimatedDrawable); } + mOvalBgPaint.setColor(mState.mDarkColor); } public void setDarkIntensity(float intensity) { @@ -165,7 +169,12 @@ public class KeyButtonDrawable extends Drawable { public void setColorFilter(ColorFilter colorFilter) { mIconPaint.setColorFilter(colorFilter); if (mAnimatedDrawable != null) { - mAnimatedDrawable.setColorFilter(colorFilter); + if (mState.mHasOvalBg) { + mAnimatedDrawable.setColorFilter( + new PorterDuffColorFilter(mState.mLightColor, PorterDuff.Mode.SRC_IN)); + } else { + mAnimatedDrawable.setColorFilter(colorFilter); + } } invalidateSelf(); } @@ -235,6 +244,10 @@ public class KeyButtonDrawable extends Drawable { return; } + if (mState.mHasOvalBg) { + canvas.drawOval(new RectF(bounds), mOvalBgPaint); + } + if (mAnimatedDrawable != null) { mAnimatedDrawable.draw(canvas); } else { @@ -379,14 +392,16 @@ public class KeyButtonDrawable extends Drawable { final int mLightColor; final int mDarkColor; final boolean mSupportsAnimation; + final boolean mHasOvalBg; public ShadowDrawableState(@ColorInt int lightColor, @ColorInt int darkColor, - boolean animated, boolean horizontalFlip) { + boolean animated, boolean horizontalFlip, boolean hasOvalBg) { mLightColor = lightColor; mDarkColor = darkColor; mSupportsAnimation = animated; mAlpha = 255; mHorizontalFlip = horizontalFlip; + mHasOvalBg = hasOvalBg; } @Override @@ -411,32 +426,51 @@ public class KeyButtonDrawable extends Drawable { * @param ctx Context to get the drawable and determine the dark and light theme * @param icon the icon resource id * @param hasShadow if a shadow will appear with the drawable + * @param hasOvalBg if an oval bg will be drawn * @return KeyButtonDrawable */ public static KeyButtonDrawable create(@NonNull Context ctx, @DrawableRes int icon, - boolean hasShadow) { + boolean hasShadow, boolean hasOvalBg) { final int dualToneDarkTheme = Utils.getThemeAttr(ctx, R.attr.darkIconTheme); final int dualToneLightTheme = Utils.getThemeAttr(ctx, R.attr.lightIconTheme); Context lightContext = new ContextThemeWrapper(ctx, dualToneLightTheme); Context darkContext = new ContextThemeWrapper(ctx, dualToneDarkTheme); - return KeyButtonDrawable.create(lightContext, darkContext, icon, hasShadow); + return KeyButtonDrawable.create(lightContext, darkContext, icon, hasShadow, hasOvalBg); + } + + /** + * Creates a KeyButtonDrawable with a shadow given its icon. For more information, see + * {@link #create(Context, int, boolean, boolean)}. + */ + public static KeyButtonDrawable create(@NonNull Context ctx, @DrawableRes int icon, + boolean hasShadow) { + return create(ctx, icon, hasShadow, false /* hasOvalBg */); } + /** + * Creates a KeyButtonDrawable with a shadow given its icon. For more information, see + * {@link #create(Context, int, boolean, boolean)}. + */ public static KeyButtonDrawable create(Context lightContext, Context darkContext, - @DrawableRes int iconResId, boolean hasShadow) { + @DrawableRes int iconResId, boolean hasShadow, boolean hasOvalBg) { return create(lightContext, Utils.getColorAttrDefaultColor(lightContext, R.attr.singleToneColor), Utils.getColorAttrDefaultColor(darkContext, R.attr.singleToneColor), - iconResId, hasShadow); + iconResId, hasShadow, hasOvalBg); } + /** + * Creates a KeyButtonDrawable with a shadow given its icon. For more information, see + * {@link #create(Context, int, boolean, boolean)}. + */ public static KeyButtonDrawable create(Context context, @ColorInt int lightColor, - @ColorInt int darkColor, @DrawableRes int iconResId, boolean hasShadow) { + @ColorInt int darkColor, @DrawableRes int iconResId, boolean hasShadow, + boolean hasOvalBg) { final Resources res = context.getResources(); boolean isRtl = res.getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; Drawable d = context.getDrawable(iconResId); final KeyButtonDrawable drawable = new KeyButtonDrawable(d, lightColor, darkColor, - isRtl && d.isAutoMirrored()); + isRtl && d.isAutoMirrored(), hasOvalBg); if (hasShadow) { int offsetX = res.getDimensionPixelSize(R.dimen.nav_key_button_shadow_offset_x); int offsetY = res.getDimensionPixelSize(R.dimen.nav_key_button_shadow_offset_y); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarContextTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarContextTest.java index c837c9ccea95..cb70a1fa3a3b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarContextTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarContextTest.java @@ -80,7 +80,7 @@ public class NavigationBarContextTest extends SysuiTestCase { @Test public void testSetButtonVisibility() throws Exception { - assertFalse("By default the group should be invisible.", mGroup.isVisible()); + assertTrue("By default the group should be visible.", mGroup.isVisible()); // Set button 1 to be visible, make sure it is the only visible button showButton(mBtn1); @@ -89,7 +89,7 @@ public class NavigationBarContextTest extends SysuiTestCase { assertFalse(mBtn2.isVisible()); // Hide button 1 and make sure the group is also invisible - assertNotEquals(mGroup.setButtonVisiblity(BUTTON_1_ID, false /* visible */), View.VISIBLE); + assertNotEquals(mGroup.setButtonVisibility(BUTTON_1_ID, false /* visible */), View.VISIBLE); assertFalse("No buttons are visible, group should also be hidden", mGroup.isVisible()); assertNull("No buttons should be visible", mGroup.getVisibleContextButton()); } @@ -97,7 +97,7 @@ public class NavigationBarContextTest extends SysuiTestCase { @Test(expected = RuntimeException.class) public void testSetButtonVisibilityUnaddedButton() throws Exception { int id = mBtn2.getId() + 1; - mGroup.setButtonVisiblity(id, true /* visible */); + mGroup.setButtonVisibility(id, true /* visible */); fail("Did not throw when setting a button with an invalid id"); } @@ -120,17 +120,17 @@ public class NavigationBarContextTest extends SysuiTestCase { assertTrue(mGroup.isButtonVisibleWithinGroup(mBtn2.getId())); // Hide button 2 - assertNotEquals(mGroup.setButtonVisiblity(BUTTON_2_ID, false /* visible */), View.VISIBLE); + assertNotEquals(mGroup.setButtonVisibility(BUTTON_2_ID, false /* visible */), View.VISIBLE); assertEquals("Hiding button 2 should show button 1", mBtn1, mGroup.getVisibleContextButton()); // Hide button 1 - assertNotEquals(mGroup.setButtonVisiblity(BUTTON_1_ID, false /* visible */), View.VISIBLE); + assertNotEquals(mGroup.setButtonVisibility(BUTTON_1_ID, false /* visible */), View.VISIBLE); assertEquals("Hiding button 1 should show button 0", mBtn0, mGroup.getVisibleContextButton()); // Hide button 0, all buttons are now invisible - assertNotEquals(mGroup.setButtonVisiblity(BUTTON_0_ID, false /* visible */), View.VISIBLE); + assertNotEquals(mGroup.setButtonVisibility(BUTTON_0_ID, false /* visible */), View.VISIBLE); assertFalse("No buttons are visible, group should also be invisible", mGroup.isVisible()); assertNull(mGroup.getVisibleContextButton()); assertFalse(mGroup.isButtonVisibleWithinGroup(mBtn0.getId())); @@ -144,7 +144,7 @@ public class NavigationBarContextTest extends SysuiTestCase { showButton(mBtn2); // Show button 1 - assertNotEquals(mGroup.setButtonVisiblity(BUTTON_1_ID, true /* visible */), View.VISIBLE); + assertNotEquals(mGroup.setButtonVisibility(BUTTON_1_ID, true /* visible */), View.VISIBLE); assertTrue("Showing button 1 lower priority should be hidden but visible underneath", mGroup.isButtonVisibleWithinGroup(BUTTON_1_ID)); assertFalse(mBtn0.isVisible()); @@ -152,7 +152,7 @@ public class NavigationBarContextTest extends SysuiTestCase { assertTrue(mBtn2.isVisible()); // Hide button 1 - assertNotEquals(mGroup.setButtonVisiblity(BUTTON_1_ID, false /* visible */), View.VISIBLE); + assertNotEquals(mGroup.setButtonVisibility(BUTTON_1_ID, false /* visible */), View.VISIBLE); assertFalse("Hiding button 1 with lower priority hides itself underneath", mGroup.isButtonVisibleWithinGroup(BUTTON_1_ID)); assertTrue("A button still visible, group should also be visible", mGroup.isVisible()); @@ -180,9 +180,9 @@ public class NavigationBarContextTest extends SysuiTestCase { final Drawable d = mock(Drawable.class); final ContextualButton button = spy(mBtn0); final KeyButtonDrawable kbd1 = spy(new KeyButtonDrawable(d, unusedColor, unusedColor, - false /* horizontalFlip */)); + false /* horizontalFlip */, false /* hasOvalBg */)); final KeyButtonDrawable kbd2 = spy(new KeyButtonDrawable(d, unusedColor, unusedColor, - false /* horizontalFlip */)); + false /* horizontalFlip */, false /* hasOvalBg */)); kbd1.setDarkIntensity(TEST_DARK_INTENSITY); kbd2.setDarkIntensity(0f); @@ -198,7 +198,7 @@ public class NavigationBarContextTest extends SysuiTestCase { } private void showButton(ContextualButton button) { - assertEquals(View.VISIBLE, mGroup.setButtonVisiblity(button.getId(), true /* visible */)); + assertEquals(View.VISIBLE, mGroup.setButtonVisibility(button.getId(), true /* visible */)); assertTrue("After set a button visible, group should also be visible", mGroup.isVisible()); assertEquals(button, mGroup.getVisibleContextButton()); } diff --git a/services/core/java/com/android/server/attention/AttentionManagerService.java b/services/core/java/com/android/server/attention/AttentionManagerService.java index b50af28b12b3..032af258dbd9 100644 --- a/services/core/java/com/android/server/attention/AttentionManagerService.java +++ b/services/core/java/com/android/server/attention/AttentionManagerService.java @@ -22,7 +22,6 @@ import static android.service.attention.AttentionService.ATTENTION_FAILURE_UNKNO import android.Manifest; import android.annotation.Nullable; -import android.annotation.TestApi; import android.annotation.UserIdInt; import android.app.ActivityManager; import android.attention.AttentionManagerInternal; @@ -43,6 +42,9 @@ import android.os.Looper; import android.os.Message; import android.os.PowerManager; import android.os.RemoteException; +import android.os.ResultReceiver; +import android.os.ShellCallback; +import android.os.ShellCommand; import android.os.SystemClock; import android.os.UserHandle; import android.provider.DeviceConfig; @@ -56,7 +58,6 @@ import android.util.Slog; import android.util.SparseArray; import android.util.StatsLog; -import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.DumpUtils; @@ -76,15 +77,6 @@ public class AttentionManagerService extends SystemService { private static final String LOG_TAG = "AttentionManagerService"; private static final boolean DEBUG = false; - /** - * DeviceConfig flag name, allows a CTS to inject a fake implementation. - * - * @hide - */ - @TestApi - public static final String COMPONENT_NAME = "component_name"; - - /** Default value in absence of {@link DeviceConfig} override. */ private static final boolean DEFAULT_SERVICE_ENABLED = true; @@ -96,6 +88,7 @@ public class AttentionManagerService extends SystemService { /** DeviceConfig flag name, if {@code true}, enables AttentionManagerService features. */ private static final String SERVICE_ENABLED = "service_enabled"; + private static String sTestAttentionServicePackage; private final Context mContext; private final PowerManager mPowerManager; private final Object mLock; @@ -135,7 +128,7 @@ public class AttentionManagerService extends SystemService { /** Returns {@code true} if attention service is configured on this device. */ public static boolean isServiceConfigured(Context context) { - return !TextUtils.isEmpty(getServiceConfig(context)); + return !TextUtils.isEmpty(getServiceConfigPackage(context)); } /** Resolves and sets up the attention service if it had not been done yet. */ @@ -332,8 +325,8 @@ public class AttentionManagerService extends SystemService { return mUserStates.get(userId); } - private static String getServiceConfig(Context context) { - return context.getString(R.string.config_defaultAttentionService); + private static String getServiceConfigPackage(Context context) { + return context.getPackageManager().getAttentionServicePackageName(); } /** @@ -341,28 +334,26 @@ public class AttentionManagerService extends SystemService { * system. */ private static ComponentName resolveAttentionService(Context context) { - final String flag = DeviceConfig.getProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE, - COMPONENT_NAME); - - final String componentNameString = flag != null ? flag : getServiceConfig(context); - if (TextUtils.isEmpty(componentNameString)) { - return null; - } - - final ComponentName componentName = ComponentName.unflattenFromString(componentNameString); - if (componentName == null) { + final String serviceConfigPackage = getServiceConfigPackage(context); + + String resolvedPackage; + int flags = PackageManager.MATCH_SYSTEM_ONLY; + if (!TextUtils.isEmpty(sTestAttentionServicePackage)) { + resolvedPackage = sTestAttentionServicePackage; + flags = PackageManager.GET_META_DATA; + } else if (!TextUtils.isEmpty(serviceConfigPackage)) { + resolvedPackage = serviceConfigPackage; + } else { return null; } final Intent intent = new Intent(AttentionService.SERVICE_INTERFACE).setPackage( - componentName.getPackageName()); + resolvedPackage); - // Make sure that only system apps can declare the AttentionService. - final ResolveInfo resolveInfo = context.getPackageManager().resolveService(intent, - PackageManager.MATCH_SYSTEM_ONLY); + final ResolveInfo resolveInfo = context.getPackageManager().resolveService(intent, flags); if (resolveInfo == null || resolveInfo.serviceInfo == null) { Slog.wtf(LOG_TAG, String.format("Service %s not found in package %s", - AttentionService.SERVICE_INTERFACE, componentName + AttentionService.SERVICE_INTERFACE, serviceConfigPackage )); return null; } @@ -447,6 +438,7 @@ public class AttentionManagerService extends SystemService { } void cancelInternal() { + mIsFulfilled = true; mCallbackInternal.onFailure(ATTENTION_FAILURE_CANCELLED); } } @@ -606,7 +598,6 @@ public class AttentionManagerService extends SystemService { } return; } - userState.mCurrentAttentionCheck.mIsFulfilled = true; if (userState.mService == null) { userState.mCurrentAttentionCheck.cancelInternal(); @@ -654,7 +645,165 @@ public class AttentionManagerService extends SystemService { } } + private final class AttentionManagerServiceShellCommand extends ShellCommand { + class TestableAttentionCallbackInternal extends AttentionCallbackInternal { + private int mLastCallbackCode = -1; + + @Override + public void onSuccess(int result, long timestamp) { + mLastCallbackCode = result; + } + + @Override + public void onFailure(int error) { + mLastCallbackCode = error; + } + + public void reset() { + mLastCallbackCode = -1; + } + + public int getLastCallbackCode() { + return mLastCallbackCode; + } + } + + final TestableAttentionCallbackInternal mTestableAttentionCallback = + new TestableAttentionCallbackInternal(); + + @Override + public int onCommand(@Nullable final String cmd) { + if (cmd == null) { + return handleDefaultCommands(cmd); + } + final PrintWriter err = getErrPrintWriter(); + try { + switch (cmd) { + case "getAttentionServiceComponent": + return cmdResolveAttentionServiceComponent(); + case "call": + switch (getNextArgRequired()) { + case "checkAttention": + return cmdCallCheckAttention(); + case "cancelCheckAttention": + return cmdCallCancelAttention(); + default: + throw new IllegalArgumentException("Invalid argument"); + } + case "setTestableAttentionService": + return cmdSetTestableAttentionService(getNextArgRequired()); + case "clearTestableAttentionService": + return cmdClearTestableAttentionService(); + case "getLastTestCallbackCode": + return cmdGetLastTestCallbackCode(); + default: + return handleDefaultCommands(cmd); + } + } catch (IllegalArgumentException e) { + err.println("Error: " + e.getMessage()); + } + return -1; + } + + private int cmdSetTestableAttentionService(String testingServicePackage) { + final PrintWriter out = getOutPrintWriter(); + if (TextUtils.isEmpty(testingServicePackage)) { + out.println("false"); + } else { + sTestAttentionServicePackage = testingServicePackage; + resetStates(); + out.println(mComponentName != null ? "true" : "false"); + } + return 0; + } + + private int cmdClearTestableAttentionService() { + sTestAttentionServicePackage = ""; + mTestableAttentionCallback.reset(); + resetStates(); + return 0; + } + + private int cmdCallCheckAttention() { + final PrintWriter out = getOutPrintWriter(); + boolean calledSuccessfully = checkAttention(2000, mTestableAttentionCallback); + out.println(calledSuccessfully ? "true" : "false"); + return 0; + } + + private int cmdCallCancelAttention() { + final PrintWriter out = getOutPrintWriter(); + cancelAttentionCheck(mTestableAttentionCallback); + out.println("true"); + return 0; + } + + private int cmdResolveAttentionServiceComponent() { + final PrintWriter out = getOutPrintWriter(); + ComponentName resolvedComponent = resolveAttentionService(mContext); + out.println(resolvedComponent != null ? resolvedComponent.flattenToShortString() : ""); + return 0; + } + + private int cmdGetLastTestCallbackCode() { + final PrintWriter out = getOutPrintWriter(); + out.println(mTestableAttentionCallback.getLastCallbackCode()); + return 0; + } + + private void resetStates() { + mComponentName = resolveAttentionService(mContext); + mUserStates.clear(); + } + + @Override + public void onHelp() { + final PrintWriter out = getOutPrintWriter(); + out.println("Attention commands: "); + out.println(" setTestableAttentionService <service_package>: Bind to a custom" + + " implementation of attention service"); + out.println(" ---<service_package>:"); + out.println( + " := Package containing the Attention Service implementation to bind to"); + out.println(" ---returns:"); + out.println(" := true, if was bound successfully"); + out.println(" := false, if was not bound successfully"); + out.println(" clearTestableAttentionService: Undo custom bindings. Revert to previous" + + " behavior"); + out.println(" getAttentionServiceComponent: Get the current service component string"); + out.println(" ---returns:"); + out.println(" := If valid, the component string (in shorten form) for the" + + " currently bound service."); + out.println(" := else, empty string"); + out.println(" call checkAttention: Calls check attention"); + out.println(" ---returns:"); + out.println( + " := true, if the call was successfully dispatched to the service " + + "implementation." + + " (to see the result, call getLastTestCallbackCode)"); + out.println(" := false, otherwise"); + out.println(" call cancelCheckAttention: Cancels check attention"); + out.println(" getLastTestCallbackCode"); + out.println(" ---returns:"); + out.println( + " := An integer, representing the last callback code received from the " + + "bounded implementation. If none, it will return -1"); + } + } + private final class BinderService extends Binder { + AttentionManagerServiceShellCommand mAttentionManagerServiceShellCommand = + new AttentionManagerServiceShellCommand(); + + @Override + public void onShellCommand(FileDescriptor in, FileDescriptor out, + FileDescriptor err, + String[] args, ShellCallback callback, + ResultReceiver resultReceiver) { + mAttentionManagerServiceShellCommand.exec(this, in, out, err, args, callback, + resultReceiver); + } + @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, pw)) { diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java index 615472661f9e..db2c742b8d4e 100644 --- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java +++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java @@ -70,6 +70,7 @@ import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -588,6 +589,7 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { // rollback sessions been applied. List<RollbackData> enabling = new ArrayList<>(); List<RollbackData> restoreInProgress = new ArrayList<>(); + Set<String> apexPackageNames = new HashSet<>(); synchronized (mLock) { ensureRollbackDataLoadedLocked(); for (RollbackData data : mRollbacks) { @@ -597,6 +599,12 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { } else if (data.restoreUserDataInProgress) { restoreInProgress.add(data); } + + for (PackageRollbackInfo info : data.info.getPackages()) { + if (info.isApex()) { + apexPackageNames.add(info.getPackageName()); + } + } } } } @@ -634,6 +642,14 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub { } } + for (String apexPackageName : apexPackageNames) { + // We will not recieve notifications when an apex is updated, + // so check now in case any rollbacks ought to be expired. The + // onPackagedReplace function is safe to call if the package + // hasn't actually been updated. + onPackageReplaced(apexPackageName); + } + mPackageHealthObserver.onBootCompleted(); }); } diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java index 4ed07c351847..8a834c8f942b 100644 --- a/services/core/java/com/android/server/wm/ActivityStack.java +++ b/services/core/java/com/android/server/wm/ActivityStack.java @@ -5438,7 +5438,7 @@ class ActivityStack extends ConfigurationContainer { if (isAttached()) { getDisplay().positionChildAtBottom(this); } - if (!isActivityTypeHome() || getDisplay().isRemoved()) { + if (!isActivityTypeHome() || !isAttached()) { remove(); } } diff --git a/services/net/java/android/net/ip/IIpClient.aidl b/services/net/java/android/net/ip/IIpClient.aidl index 1e7726472ff6..9989c52fc403 100644 --- a/services/net/java/android/net/ip/IIpClient.aidl +++ b/services/net/java/android/net/ip/IIpClient.aidl @@ -17,6 +17,7 @@ package android.net.ip; import android.net.ProxyInfo; import android.net.ProvisioningConfigurationParcelable; +import android.net.NattKeepalivePacketDataParcelable; import android.net.TcpKeepalivePacketDataParcelable; /** @hide */ @@ -33,4 +34,5 @@ oneway interface IIpClient { void addKeepalivePacketFilter(int slot, in TcpKeepalivePacketDataParcelable pkt); void removeKeepalivePacketFilter(int slot); void setL2KeyAndGroupHint(in String l2Key, in String groupHint); + void addNattKeepalivePacketFilter(int slot, in NattKeepalivePacketDataParcelable pkt); } diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerLogger.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerLogger.java new file mode 100644 index 000000000000..73b4ce767d0d --- /dev/null +++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerLogger.java @@ -0,0 +1,136 @@ +/* + * 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.soundtrigger; + +import android.util.Log; + +import java.io.PrintWriter; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.LinkedList; + +/** +* Constructor SoundTriggerLogger class +*/ +public class SoundTriggerLogger { + + // ring buffer of events to log. + private final LinkedList<Event> mEvents; + + private final String mTitle; + + // the maximum number of events to keep in log + private final int mMemSize; + + /** + * Constructor for Event class. + */ + public abstract static class Event { + // formatter for timestamps + private static final SimpleDateFormat sFormat = new SimpleDateFormat("MM-dd HH:mm:ss:SSS"); + + private final long mTimestamp; + + Event() { + mTimestamp = System.currentTimeMillis(); + } + + /** + * Convert event to String + * @return StringBuilder + */ + public String toString() { + return (new StringBuilder(sFormat.format(new Date(mTimestamp)))) + .append(" ").append(eventToString()).toString(); + } + + /** + * Causes the string message for the event to appear in the logcat. + * Here is an example of how to create a new event (a StringEvent), adding it to the logger + * (an instance of SoundTriggerLogger) while also making it show in the logcat: + * <pre> + * myLogger.log( + * (new StringEvent("something for logcat and logger")).printLog(MyClass.TAG) ); + * </pre> + * @param tag the tag for the android.util.Log.v + * @return the same instance of the event + */ + public Event printLog(String tag) { + Log.i(tag, eventToString()); + return this; + } + + /** + * Convert event to String. + * This method is only called when the logger history is about to the dumped, + * so this method is where expensive String conversions should be made, not when the Event + * subclass is created. + * Timestamp information will be automatically added, do not include it. + * @return a string representation of the event that occurred. + */ + public abstract String eventToString(); + } + + /** + * Constructor StringEvent class + */ + public static class StringEvent extends Event { + private final String mMsg; + + public StringEvent(String msg) { + mMsg = msg; + } + + @Override + public String eventToString() { + return mMsg; + } + } + + /** + * Constructor for logger. + * @param size the maximum number of events to keep in log + * @param title the string displayed before the recorded log + */ + public SoundTriggerLogger(int size, String title) { + mEvents = new LinkedList<Event>(); + mMemSize = size; + mTitle = title; + } + + /** + * Constructor for logger. + * @param evt the maximum number of events to keep in log + */ + public synchronized void log(Event evt) { + if (mEvents.size() >= mMemSize) { + mEvents.removeFirst(); + } + mEvents.add(evt); + } + + /** + * Constructor for logger. + * @param pw the maximum number of events to keep in log + */ + public synchronized void dump(PrintWriter pw) { + pw.println("ST Event log: " + mTitle); + for (Event evt : mEvents) { + pw.println(evt.toString()); + } + } +} diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java index 697469a3c680..9c4c0998b21a 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java @@ -180,9 +180,16 @@ public class SoundTriggerService extends SystemService { Slog.i(TAG, "startRecognition(): Uuid : " + parcelUuid); } + sEventLogger.log(new SoundTriggerLogger.StringEvent("startRecognition(): Uuid : " + + parcelUuid)); + GenericSoundModel model = getSoundModel(parcelUuid); if (model == null) { Slog.e(TAG, "Null model in database for id: " + parcelUuid); + + sEventLogger.log(new SoundTriggerLogger.StringEvent( + "startRecognition(): Null model in database for id: " + parcelUuid)); + return STATUS_ERROR; } @@ -196,6 +203,10 @@ public class SoundTriggerService extends SystemService { if (DEBUG) { Slog.i(TAG, "stopRecognition(): Uuid : " + parcelUuid); } + + sEventLogger.log(new SoundTriggerLogger.StringEvent("stopRecognition(): Uuid : " + + parcelUuid)); + if (!isInitialized()) return STATUS_ERROR; return mSoundTriggerHelper.stopGenericRecognition(parcelUuid.getUuid(), callback); } @@ -206,6 +217,10 @@ public class SoundTriggerService extends SystemService { if (DEBUG) { Slog.i(TAG, "getSoundModel(): id = " + soundModelId); } + + sEventLogger.log(new SoundTriggerLogger.StringEvent("getSoundModel(): id = " + + soundModelId)); + SoundTrigger.GenericSoundModel model = mDbHelper.getGenericSoundModel( soundModelId.getUuid()); return model; @@ -217,6 +232,10 @@ public class SoundTriggerService extends SystemService { if (DEBUG) { Slog.i(TAG, "updateSoundModel(): model = " + soundModel); } + + sEventLogger.log(new SoundTriggerLogger.StringEvent("updateSoundModel(): model = " + + soundModel)); + mDbHelper.updateGenericSoundModel(soundModel); } @@ -226,6 +245,10 @@ public class SoundTriggerService extends SystemService { if (DEBUG) { Slog.i(TAG, "deleteSoundModel(): id = " + soundModelId); } + + sEventLogger.log(new SoundTriggerLogger.StringEvent("deleteSoundModel(): id = " + + soundModelId)); + // Unload the model if it is loaded. mSoundTriggerHelper.unloadGenericSoundModel(soundModelId.getUuid()); mDbHelper.deleteGenericSoundModel(soundModelId.getUuid()); @@ -237,11 +260,19 @@ public class SoundTriggerService extends SystemService { if (!isInitialized()) return STATUS_ERROR; if (soundModel == null || soundModel.uuid == null) { Slog.e(TAG, "Invalid sound model"); + + sEventLogger.log(new SoundTriggerLogger.StringEvent( + "loadGenericSoundModel(): Invalid sound model")); + return STATUS_ERROR; } if (DEBUG) { Slog.i(TAG, "loadGenericSoundModel(): id = " + soundModel.uuid); } + + sEventLogger.log(new SoundTriggerLogger.StringEvent("loadGenericSoundModel(): id = " + + soundModel.uuid)); + synchronized (mLock) { SoundModel oldModel = mLoadedModels.get(soundModel.uuid); // If the model we're loading is actually different than what we had loaded, we @@ -264,15 +295,28 @@ public class SoundTriggerService extends SystemService { if (!isInitialized()) return STATUS_ERROR; if (soundModel == null || soundModel.uuid == null) { Slog.e(TAG, "Invalid sound model"); + + sEventLogger.log(new SoundTriggerLogger.StringEvent( + "loadKeyphraseSoundModel(): Invalid sound model")); + return STATUS_ERROR; } if (soundModel.keyphrases == null || soundModel.keyphrases.length != 1) { Slog.e(TAG, "Only one keyphrase per model is currently supported."); + + sEventLogger.log(new SoundTriggerLogger.StringEvent( + "loadKeyphraseSoundModel(): Only one keyphrase per model" + + " is currently supported.")); + return STATUS_ERROR; } if (DEBUG) { Slog.i(TAG, "loadKeyphraseSoundModel(): id = " + soundModel.uuid); } + + sEventLogger.log(new SoundTriggerLogger.StringEvent("loadKeyphraseSoundModel(): id = " + + soundModel.uuid)); + synchronized (mLock) { SoundModel oldModel = mLoadedModels.get(soundModel.uuid); // If the model we're loading is actually different than what we had loaded, we @@ -303,6 +347,9 @@ public class SoundTriggerService extends SystemService { Slog.i(TAG, "startRecognition(): id = " + soundModelId); } + sEventLogger.log(new SoundTriggerLogger.StringEvent( + "startRecognitionForService(): id = " + soundModelId)); + IRecognitionStatusCallback callback = new RemoteSoundTriggerDetectionService(soundModelId.getUuid(), params, detectionService, Binder.getCallingUserHandle(), config); @@ -311,6 +358,10 @@ public class SoundTriggerService extends SystemService { SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid()); if (soundModel == null) { Slog.e(TAG, soundModelId + " is not loaded"); + + sEventLogger.log(new SoundTriggerLogger.StringEvent( + "startRecognitionForService():" + soundModelId + " is not loaded")); + return STATUS_ERROR; } IRecognitionStatusCallback existingCallback = null; @@ -319,6 +370,11 @@ public class SoundTriggerService extends SystemService { } if (existingCallback != null) { Slog.e(TAG, soundModelId + " is already running"); + + sEventLogger.log(new SoundTriggerLogger.StringEvent( + "startRecognitionForService():" + + soundModelId + " is already running")); + return STATUS_ERROR; } int ret; @@ -329,11 +385,19 @@ public class SoundTriggerService extends SystemService { break; default: Slog.e(TAG, "Unknown model type"); + + sEventLogger.log(new SoundTriggerLogger.StringEvent( + "startRecognitionForService(): Unknown model type")); + return STATUS_ERROR; } if (ret != STATUS_OK) { Slog.e(TAG, "Failed to start model: " + ret); + + sEventLogger.log(new SoundTriggerLogger.StringEvent( + "startRecognitionForService(): Failed to start model:")); + return ret; } synchronized (mCallbacksLock) { @@ -351,10 +415,18 @@ public class SoundTriggerService extends SystemService { Slog.i(TAG, "stopRecognition(): id = " + soundModelId); } + sEventLogger.log(new SoundTriggerLogger.StringEvent( + "stopRecognitionForService(): id = " + soundModelId)); + synchronized (mLock) { SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid()); if (soundModel == null) { Slog.e(TAG, soundModelId + " is not loaded"); + + sEventLogger.log(new SoundTriggerLogger.StringEvent( + "stopRecognitionForService(): " + soundModelId + + " is not loaded")); + return STATUS_ERROR; } IRecognitionStatusCallback callback = null; @@ -363,6 +435,11 @@ public class SoundTriggerService extends SystemService { } if (callback == null) { Slog.e(TAG, soundModelId + " is not running"); + + sEventLogger.log(new SoundTriggerLogger.StringEvent( + "stopRecognitionForService(): " + soundModelId + + " is not running")); + return STATUS_ERROR; } int ret; @@ -372,11 +449,19 @@ public class SoundTriggerService extends SystemService { break; default: Slog.e(TAG, "Unknown model type"); + + sEventLogger.log(new SoundTriggerLogger.StringEvent( + "stopRecognitionForService(): Unknown model type")); + return STATUS_ERROR; } if (ret != STATUS_OK) { Slog.e(TAG, "Failed to stop model: " + ret); + + sEventLogger.log(new SoundTriggerLogger.StringEvent( + "stopRecognitionForService(): Failed to stop model: " + ret)); + return ret; } synchronized (mCallbacksLock) { @@ -394,10 +479,17 @@ public class SoundTriggerService extends SystemService { Slog.i(TAG, "unloadSoundModel(): id = " + soundModelId); } + sEventLogger.log(new SoundTriggerLogger.StringEvent("unloadSoundModel(): id = " + + soundModelId)); + synchronized (mLock) { SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid()); if (soundModel == null) { Slog.e(TAG, soundModelId + " is not loaded"); + + sEventLogger.log(new SoundTriggerLogger.StringEvent( + "unloadSoundModel(): " + soundModelId + " is not loaded")); + return STATUS_ERROR; } int ret; @@ -411,10 +503,18 @@ public class SoundTriggerService extends SystemService { break; default: Slog.e(TAG, "Unknown model type"); + + sEventLogger.log(new SoundTriggerLogger.StringEvent( + "unloadSoundModel(): Unknown model type")); + return STATUS_ERROR; } if (ret != STATUS_OK) { Slog.e(TAG, "Failed to unload model"); + + sEventLogger.log(new SoundTriggerLogger.StringEvent( + "unloadSoundModel(): Failed to unload model")); + return ret; } mLoadedModels.remove(soundModelId.getUuid()); @@ -444,10 +544,17 @@ public class SoundTriggerService extends SystemService { Slog.i(TAG, "getModelState(): id = " + soundModelId); } + sEventLogger.log(new SoundTriggerLogger.StringEvent("getModelState(): id = " + + soundModelId)); + synchronized (mLock) { SoundModel soundModel = mLoadedModels.get(soundModelId.getUuid()); if (soundModel == null) { Slog.e(TAG, soundModelId + " is not loaded"); + + sEventLogger.log(new SoundTriggerLogger.StringEvent("getModelState(): " + + soundModelId + " is not loaded")); + return ret; } switch (soundModel.type) { @@ -459,6 +566,10 @@ public class SoundTriggerService extends SystemService { break; default: Slog.e(TAG, "Unknown model type"); + + sEventLogger.log(new SoundTriggerLogger.StringEvent( + "getModelState(): Unknown model type")); + break; } @@ -708,6 +819,10 @@ public class SoundTriggerService extends SystemService { mService.removeClient(mPuuid); } catch (Exception e) { Slog.e(TAG, mPuuid + ": Cannot remove client", e); + + sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid + + ": Cannot remove client")); + } mService = null; @@ -730,6 +845,8 @@ public class SoundTriggerService extends SystemService { private void destroy() { if (DEBUG) Slog.v(TAG, mPuuid + ": destroy"); + sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid + ": destroy")); + synchronized (mRemoteServiceLock) { disconnectLocked(); @@ -761,6 +878,10 @@ public class SoundTriggerService extends SystemService { } catch (Exception e) { Slog.e(TAG, mPuuid + ": Could not stop operation " + mRunningOpIds.valueAt(i), e); + + sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid + + ": Could not stop operation " + mRunningOpIds.valueAt(i))); + } } @@ -786,6 +907,10 @@ public class SoundTriggerService extends SystemService { if (ri == null) { Slog.w(TAG, mPuuid + ": " + mServiceName + " not found"); + + sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid + + ": " + mServiceName + " not found")); + return; } @@ -793,6 +918,11 @@ public class SoundTriggerService extends SystemService { .equals(ri.serviceInfo.permission)) { Slog.w(TAG, mPuuid + ": " + mServiceName + " does not require " + BIND_SOUND_TRIGGER_DETECTION_SERVICE); + + sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid + + ": " + mServiceName + " does not require " + + BIND_SOUND_TRIGGER_DETECTION_SERVICE)); + return; } @@ -803,6 +933,10 @@ public class SoundTriggerService extends SystemService { mRemoteServiceWakeLock.acquire(); } else { Slog.w(TAG, mPuuid + ": Could not bind to " + mServiceName); + + sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid + + ": Could not bind to " + mServiceName)); + } } finally { Binder.restoreCallingIdentity(token); @@ -821,6 +955,9 @@ public class SoundTriggerService extends SystemService { Slog.w(TAG, mPuuid + ": Dropped operation as already destroyed or marked for " + "destruction"); + sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid + + ":Dropped operation as already destroyed or marked for destruction")); + op.drop(); return; } @@ -847,11 +984,20 @@ public class SoundTriggerService extends SystemService { if (DEBUG || opsAllowed + 10 > opsAdded) { Slog.w(TAG, mPuuid + ": Dropped operation as too many operations " + "were run in last 24 hours"); + + sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid + + ": Dropped operation as too many operations " + + "were run in last 24 hours")); + } op.drop(); } catch (Exception e) { Slog.e(TAG, mPuuid + ": Could not drop operation", e); + + sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid + + ": Could not drop operation")); + } } else { mNumOps.addOp(currentTime); @@ -866,10 +1012,17 @@ public class SoundTriggerService extends SystemService { try { if (DEBUG) Slog.v(TAG, mPuuid + ": runOp " + opId); + sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid + + ": runOp " + opId)); + op.run(opId, mService); mRunningOpIds.add(opId); } catch (Exception e) { Slog.e(TAG, mPuuid + ": Could not run operation " + opId, e); + + sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid + + ": Could not run operation " + opId)); + } } @@ -897,6 +1050,10 @@ public class SoundTriggerService extends SystemService { public void onKeyphraseDetected(SoundTrigger.KeyphraseRecognitionEvent event) { Slog.w(TAG, mPuuid + "->" + mServiceName + ": IGNORED onKeyphraseDetected(" + event + ")"); + + sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid + "->" + mServiceName + + ": IGNORED onKeyphraseDetected(" + event + ")")); + } /** @@ -928,6 +1085,8 @@ public class SoundTriggerService extends SystemService { : AudioFormat.CHANNEL_IN_MONO, captureFormat.getEncoding()); + sEventLogger.log(new SoundTriggerLogger.StringEvent("createAudioRecordForEvent")); + return new AudioRecord(attributes, captureFormat, bufferSize, event.getCaptureSession()); } @@ -936,6 +1095,9 @@ public class SoundTriggerService extends SystemService { public void onGenericSoundTriggerDetected(SoundTrigger.GenericRecognitionEvent event) { if (DEBUG) Slog.v(TAG, mPuuid + ": Generic sound trigger event: " + event); + sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid + + ": Generic sound trigger event: " + event)); + runOrAddOperation(new Operation( // always execute: () -> { @@ -966,6 +1128,9 @@ public class SoundTriggerService extends SystemService { public void onError(int status) { if (DEBUG) Slog.v(TAG, mPuuid + ": onError: " + status); + sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid + + ": onError: " + status)); + runOrAddOperation( new Operation( // always execute: @@ -985,17 +1150,28 @@ public class SoundTriggerService extends SystemService { @Override public void onRecognitionPaused() { Slog.i(TAG, mPuuid + "->" + mServiceName + ": IGNORED onRecognitionPaused"); + + sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid + + "->" + mServiceName + ": IGNORED onRecognitionPaused")); + } @Override public void onRecognitionResumed() { Slog.i(TAG, mPuuid + "->" + mServiceName + ": IGNORED onRecognitionResumed"); + + sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid + + "->" + mServiceName + ": IGNORED onRecognitionResumed")); + } @Override public void onServiceConnected(ComponentName name, IBinder service) { if (DEBUG) Slog.v(TAG, mPuuid + ": onServiceConnected(" + service + ")"); + sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid + + ": onServiceConnected(" + service + ")")); + synchronized (mRemoteServiceLock) { mService = ISoundTriggerDetectionService.Stub.asInterface(service); @@ -1016,6 +1192,9 @@ public class SoundTriggerService extends SystemService { public void onServiceDisconnected(ComponentName name) { if (DEBUG) Slog.v(TAG, mPuuid + ": onServiceDisconnected"); + sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid + + ": onServiceDisconnected")); + synchronized (mRemoteServiceLock) { mService = null; } @@ -1025,6 +1204,9 @@ public class SoundTriggerService extends SystemService { public void onBindingDied(ComponentName name) { if (DEBUG) Slog.v(TAG, mPuuid + ": onBindingDied"); + sEventLogger.log(new SoundTriggerLogger.StringEvent(mPuuid + + ": onBindingDied")); + synchronized (mRemoteServiceLock) { destroy(); } @@ -1034,6 +1216,9 @@ public class SoundTriggerService extends SystemService { public void onNullBinding(ComponentName name) { Slog.w(TAG, name + " for model " + mPuuid + " returned a null binding"); + sEventLogger.log(new SoundTriggerLogger.StringEvent(name + " for model " + + mPuuid + " returned a null binding")); + synchronized (mRemoteServiceLock) { disconnectLocked(); } @@ -1082,11 +1267,17 @@ public class SoundTriggerService extends SystemService { public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (!isInitialized()) return; mSoundTriggerHelper.dump(fd, pw, args); + // log + sEventLogger.dump(pw); } private synchronized boolean isInitialized() { if (mSoundTriggerHelper == null ) { Slog.e(TAG, "SoundTriggerHelper not initialized."); + + sEventLogger.log(new SoundTriggerLogger.StringEvent( + "SoundTriggerHelper not initialized.")); + return false; } return true; @@ -1099,4 +1290,11 @@ public class SoundTriggerService extends SystemService { throw new SecurityException("Caller does not hold the permission " + permission); } } + + //================================================================= + // For logging + + private static final SoundTriggerLogger sEventLogger = new SoundTriggerLogger(200, + "SoundTrigger activity"); + } diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 2d8a280873f6..638733430ede 100755 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -3371,8 +3371,7 @@ public class CarrierConfigManager { * May throw an {@link IllegalArgumentException} if {@code overrideValues} contains invalid * values for the specified config keys. * - * NOTE: This API is meant for testing purposes only and may only be accessed from the shell UID - * during instrumentation testing. + * NOTE: This API is meant for testing purposes only. * * @param subscriptionId The subscription ID for which the override should be done. * @param overrideValues Key-value pairs of the values that are to be overridden. If set to diff --git a/tests/RollbackTest/Android.bp b/tests/RollbackTest/Android.bp index dfc3b6e15b3e..e556b0acb1a3 100644 --- a/tests/RollbackTest/Android.bp +++ b/tests/RollbackTest/Android.bp @@ -88,6 +88,15 @@ apex { installable: false, } +apex { + name: "com.android.tests.rollback.testapex.RollbackTestApexV3", + manifest: "TestApex/RollbackTestApexV3.json", + file_contexts: "apex.test", + prebuilts: ["RollbackTestApex.prebuilt.txt"], + key: "RollbackTestApex.key", + installable: false, +} + apex_key { name: "RollbackTestApex.key", public_key: "TestApex/com.android.tests.rollback.testapex.avbpubkey", @@ -116,6 +125,7 @@ android_test { ":RollbackTestAppASplitV2", ":com.android.tests.rollback.testapex.RollbackTestApexV1", ":com.android.tests.rollback.testapex.RollbackTestApexV2", + ":com.android.tests.rollback.testapex.RollbackTestApexV3", ], test_config: "RollbackTest.xml", sdk_version: "test_current", diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java index 7e711c290e5a..3b0e2a56db3c 100644 --- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java +++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java @@ -26,6 +26,7 @@ import android.content.rollback.RollbackManager; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import org.junit.After; @@ -54,6 +55,8 @@ public class StagedRollbackTest { "com.android.tests.rollback.testapex.RollbackTestApexV1.apex"; private static final String TEST_APEX_V2 = "com.android.tests.rollback.testapex.RollbackTestApexV2.apex"; + private static final String TEST_APEX_V3 = + "com.android.tests.rollback.testapex.RollbackTestApexV3.apex"; /** * Adopts common shell permissions needed for rollback tests. @@ -145,26 +148,13 @@ public class StagedRollbackTest { /** * Test rollbacks of staged installs an apk and an apex. - * Prepare apex (and apk) phase. - */ - @Test - public void testApkAndApexPrepare() throws Exception { - RollbackTestUtils.uninstall(TEST_APP_A); - assertEquals(-1, RollbackTestUtils.getInstalledVersion(TEST_APP_A)); - - // Note: can't uninstall the apex. See note in #testApexOnlyPrepareApex(). - RollbackTestUtils.installStaged(false, TEST_APP_A_V1, TEST_APEX_V1); - - // At this point, the host test driver will reboot the device and run - // testApkAndApexEnableRollback(). - } - - /** - * Test rollbacks of staged installs an apk and an apex. * Enable rollback phase. */ @Test public void testApkAndApexEnableRollback() throws Exception { + RollbackTestUtils.uninstall(TEST_APP_A); + RollbackTestUtils.install(TEST_APP_A_V1, false); + assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APEX_PKG)); assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APP_A)); @@ -225,22 +215,6 @@ public class StagedRollbackTest { /** * Test rollbacks of staged installs involving only apex. - * Prepare apex phase. - */ - @Test - public void testApexOnlyPrepareApex() throws Exception { - // Note: We can't uninstall the apex if it is already on device, - // because that isn't supported yet (b/123667725). As long as nothing - // is failing, this should be fine because we don't expect the tests - // to leave the device with v2 of the apex installed. - RollbackTestUtils.installStaged(false, TEST_APEX_V1); - - // At this point, the host test driver will reboot the device and run - // testApexOnlyEnableRollback(). - } - - /** - * Test rollbacks of staged installs involving only apex. * Enable rollback phase. */ @Test @@ -291,4 +265,51 @@ public class StagedRollbackTest { public void testApexOnlyConfirmRollback() throws Exception { assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APEX_PKG)); } + + /** + * Tests that apex update expires existing rollbacks for that apex. + * Enable rollback phase. + */ + @Test + public void testApexRollbackExpirationEnableRollback() throws Exception { + assertEquals(1, RollbackTestUtils.getInstalledVersion(TEST_APEX_PKG)); + RollbackTestUtils.installStaged(true, TEST_APEX_V2); + + // At this point, the host test driver will reboot the device and run + // testApexRollbackExpirationUpdateApex(). + } + + /** + * Tests that apex update expires existing rollbacks for that apex. + * Update apex phase. + */ + @Test + public void testApexRollbackExpirationUpdateApex() throws Exception { + assertEquals(2, RollbackTestUtils.getInstalledVersion(TEST_APEX_PKG)); + RollbackTestUtils.installStaged(false, TEST_APEX_V3); + + // At this point, the host test driver will reboot the device and run + // testApexRollbackExpirationConfirmExpiration(). + } + + /** + * Tests that apex update expires existing rollbacks for that apex. + * Confirm expiration phase. + */ + @Test + public void testApexRollbackExpirationConfirmExpiration() throws Exception { + assertEquals(3, RollbackTestUtils.getInstalledVersion(TEST_APEX_PKG)); + + RollbackManager rm = RollbackTestUtils.getRollbackManager(); + assertNull(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(), TEST_APEX_PKG)); + } + + /** + * Helper function called by the host test to install v1 of the test apex, + * assuming the test apex is not installed. + */ + @Test + public void installTestApexV1() throws Exception { + RollbackTestUtils.installStaged(false, TEST_APEX_V1); + } } diff --git a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java index ac7f634d51f1..1f87ed863034 100644 --- a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java +++ b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java @@ -18,6 +18,7 @@ package com.android.tests.rollback.host; import static org.junit.Assert.assertTrue; +import com.android.tradefed.device.ITestDevice; import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; @@ -30,6 +31,8 @@ import org.junit.runner.RunWith; @RunWith(DeviceJUnit4ClassRunner.class) public class StagedRollbackTest extends BaseHostJUnit4Test { + private static final String TEST_APEX_PKG = "com.android.tests.rollback.testapex"; + /** * Runs the given phase of a test by calling into the device. * Throws an exception if the test phase fails. @@ -59,8 +62,7 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { */ @Test public void testApexOnly() throws Exception { - runPhase("testApexOnlyPrepareApex"); - getDevice().reboot(); + installTestApexV1(); runPhase("testApexOnlyEnableRollback"); getDevice().reboot(); runPhase("testApexOnlyCommitRollback"); @@ -73,12 +75,45 @@ public class StagedRollbackTest extends BaseHostJUnit4Test { */ @Test public void testApkAndApex() throws Exception { - runPhase("testApkAndApexPrepare"); - getDevice().reboot(); + installTestApexV1(); runPhase("testApkAndApexEnableRollback"); getDevice().reboot(); runPhase("testApkAndApexCommitRollback"); getDevice().reboot(); runPhase("testApkAndApexConfirmRollback"); } + + /** + * Tests that apex update expires existing rollbacks for that apex. + */ + @Test + public void testApexRollbackExpiration() throws Exception { + installTestApexV1(); + runPhase("testApexRollbackExpirationEnableRollback"); + getDevice().reboot(); + runPhase("testApexRollbackExpirationUpdateApex"); + getDevice().reboot(); + runPhase("testApexRollbackExpirationConfirmExpiration"); + } + + /** + * Do whatever is necessary to get version 1 of the test apex installed on + * the device. Try to do so without extra reboots where possible to keep + * the test execution time down. + */ + private void installTestApexV1() throws Exception { + for (ITestDevice.ApexInfo apexInfo : getDevice().getActiveApexes()) { + if (TEST_APEX_PKG.equals(apexInfo.name)) { + if (apexInfo.versionCode == 1) { + return; + } + getDevice().uninstallPackage(TEST_APEX_PKG); + getDevice().reboot(); + break; + } + } + + runPhase("installTestApexV1"); + getDevice().reboot(); + } } diff --git a/tests/RollbackTest/TestApex/RollbackTestApexV3.json b/tests/RollbackTest/TestApex/RollbackTestApexV3.json new file mode 100644 index 000000000000..87a2c9dbcb6a --- /dev/null +++ b/tests/RollbackTest/TestApex/RollbackTestApexV3.json @@ -0,0 +1,4 @@ +{ + "name": "com.android.tests.rollback.testapex", + "version": 3 +} diff --git a/tests/net/common/Android.bp b/tests/net/common/Android.bp index 9ee58583779d..3b2e34a159ee 100644 --- a/tests/net/common/Android.bp +++ b/tests/net/common/Android.bp @@ -18,7 +18,7 @@ // They must be fast and stable, and exercise public or test APIs. java_library { name: "FrameworksNetCommonTests", - srcs: ["java/**/*.java"], + srcs: ["java/**/*.java", "java/**/*.kt"], static_libs: [ "androidx.test.rules", "frameworks-net-testutils", diff --git a/tests/net/common/java/android/net/LinkPropertiesTest.java b/tests/net/common/java/android/net/LinkPropertiesTest.java index 417729150be8..709f5f69aa2b 100644 --- a/tests/net/common/java/android/net/LinkPropertiesTest.java +++ b/tests/net/common/java/android/net/LinkPropertiesTest.java @@ -868,12 +868,12 @@ public class LinkPropertiesTest { source.setNat64Prefix(new IpPrefix("2001:db8:1:2:64:64::/96")); - TestUtils.assertParcelingIsLossless(source, LinkProperties.CREATOR); + TestUtils.assertParcelingIsLossless(source); } @Test public void testParcelUninitialized() throws Exception { LinkProperties empty = new LinkProperties(); - TestUtils.assertParcelingIsLossless(empty, LinkProperties.CREATOR); + TestUtils.assertParcelingIsLossless(empty); } } diff --git a/tests/net/common/java/android/net/NetworkCapabilitiesTest.java b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java index ad76388b3c9b..6bc7c1bb30f8 100644 --- a/tests/net/common/java/android/net/NetworkCapabilitiesTest.java +++ b/tests/net/common/java/android/net/NetworkCapabilitiesTest.java @@ -33,6 +33,8 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED; import static android.net.NetworkCapabilities.NET_CAPABILITY_WIFI_P2P; import static android.net.NetworkCapabilities.RESTRICTED_CAPABILITIES; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; +import static android.net.NetworkCapabilities.TRANSPORT_TEST; +import static android.net.NetworkCapabilities.TRANSPORT_VPN; import static android.net.NetworkCapabilities.TRANSPORT_WIFI; import static android.net.NetworkCapabilities.UNRESTRICTED_CAPABILITIES; @@ -585,4 +587,20 @@ public class NetworkCapabilitiesTest { nc2.set(nc1); // Overwrites, as opposed to combineCapabilities assertEquals(nc1, nc2); } + + @Test + public void testGetTransportTypes() { + final NetworkCapabilities nc = new NetworkCapabilities(); + nc.addTransportType(TRANSPORT_CELLULAR); + nc.addTransportType(TRANSPORT_WIFI); + nc.addTransportType(TRANSPORT_VPN); + nc.addTransportType(TRANSPORT_TEST); + + final int[] transportTypes = nc.getTransportTypes(); + assertEquals(4, transportTypes.length); + assertEquals(TRANSPORT_CELLULAR, transportTypes[0]); + assertEquals(TRANSPORT_WIFI, transportTypes[1]); + assertEquals(TRANSPORT_VPN, transportTypes[2]); + assertEquals(TRANSPORT_TEST, transportTypes[3]); + } } diff --git a/tests/net/common/java/android/net/NetworkTest.java b/tests/net/common/java/android/net/NetworkTest.java index 0bee7cd29d29..bef66b27df62 100644 --- a/tests/net/common/java/android/net/NetworkTest.java +++ b/tests/net/common/java/android/net/NetworkTest.java @@ -155,4 +155,12 @@ public class NetworkTest { private static <T> void assertNotEqual(T t1, T t2) { assertFalse(Objects.equals(t1, t2)); } + + @Test + public void testGetPrivateDnsBypassingCopy() { + final Network copy = mNetwork.getPrivateDnsBypassingCopy(); + assertEquals(mNetwork.netId, copy.netId); + assertNotEqual(copy.netId, copy.getNetIdForResolv()); + assertNotEqual(mNetwork.getNetIdForResolv(), copy.getNetIdForResolv()); + } } diff --git a/tests/net/common/java/android/net/StaticIpConfigurationTest.java b/tests/net/common/java/android/net/StaticIpConfigurationTest.java index 8449ca76d580..5096be221cbf 100644 --- a/tests/net/common/java/android/net/StaticIpConfigurationTest.java +++ b/tests/net/common/java/android/net/StaticIpConfigurationTest.java @@ -31,7 +31,9 @@ import org.junit.Test; import org.junit.runner.RunWith; import java.net.InetAddress; +import java.util.ArrayList; import java.util.HashSet; +import java.util.List; import java.util.Objects; @RunWith(AndroidJUnit4.class) @@ -46,6 +48,7 @@ public class StaticIpConfigurationTest { private static final InetAddress DNS2 = IpAddress("8.8.4.4"); private static final InetAddress DNS3 = IpAddress("4.2.2.2"); private static final String IFACE = "eth0"; + private static final String FAKE_DOMAINS = "google.com"; private static InetAddress IpAddress(String addr) { return InetAddress.parseNumericAddress(addr); @@ -69,7 +72,7 @@ public class StaticIpConfigurationTest { s.dnsServers.add(DNS1); s.dnsServers.add(DNS2); s.dnsServers.add(DNS3); - s.domains = "google.com"; + s.domains = FAKE_DOMAINS; return s; } @@ -178,8 +181,8 @@ public class StaticIpConfigurationTest { expected.addDnsServer(DNS3); assertEquals(expected, s.toLinkProperties(IFACE)); - s.domains = "google.com"; - expected.setDomains("google.com"); + s.domains = FAKE_DOMAINS; + expected.setDomains(FAKE_DOMAINS); assertEquals(expected, s.toLinkProperties(IFACE)); s.gateway = null; @@ -218,4 +221,53 @@ public class StaticIpConfigurationTest { StaticIpConfiguration s2 = passThroughParcel(s); assertEquals(s, s2); } + + @Test + public void testBuilder() { + final ArrayList<InetAddress> dnsServers = new ArrayList<>(); + dnsServers.add(DNS1); + + final StaticIpConfiguration s = new StaticIpConfiguration.Builder() + .setIpAddress(ADDR) + .setGateway(GATEWAY) + .setDomains(FAKE_DOMAINS) + .setDnsServers(dnsServers) + .build(); + + assertEquals(s.ipAddress, s.getIpAddress()); + assertEquals(ADDR, s.getIpAddress()); + assertEquals(s.gateway, s.getGateway()); + assertEquals(GATEWAY, s.getGateway()); + assertEquals(s.domains, s.getDomains()); + assertEquals(FAKE_DOMAINS, s.getDomains()); + assertTrue(s.dnsServers.equals(s.getDnsServers())); + assertEquals(1, s.getDnsServers().size()); + assertEquals(DNS1, s.getDnsServers().get(0)); + } + + @Test + public void testAddDnsServers() { + final StaticIpConfiguration s = new StaticIpConfiguration((StaticIpConfiguration) null); + checkEmpty(s); + + s.addDnsServer(DNS1); + assertEquals(1, s.getDnsServers().size()); + assertEquals(DNS1, s.getDnsServers().get(0)); + + s.addDnsServer(DNS2); + s.addDnsServer(DNS3); + assertEquals(3, s.getDnsServers().size()); + assertEquals(DNS2, s.getDnsServers().get(1)); + assertEquals(DNS3, s.getDnsServers().get(2)); + } + + @Test + public void testGetRoutes() { + final StaticIpConfiguration s = makeTestObject(); + final List<RouteInfo> routeInfoList = s.getRoutes(IFACE); + + assertEquals(2, routeInfoList.size()); + assertEquals(new RouteInfo(ADDR, (InetAddress) null, IFACE), routeInfoList.get(0)); + assertEquals(new RouteInfo((IpPrefix) null, GATEWAY, IFACE), routeInfoList.get(1)); + } } diff --git a/tests/net/common/java/android/net/apf/ApfCapabilitiesTest.java b/tests/net/common/java/android/net/apf/ApfCapabilitiesTest.java index 7238895b3657..3ed8a86b2fb5 100644 --- a/tests/net/common/java/android/net/apf/ApfCapabilitiesTest.java +++ b/tests/net/common/java/android/net/apf/ApfCapabilitiesTest.java @@ -36,7 +36,7 @@ public class ApfCapabilitiesTest { final ApfCapabilities caps = new ApfCapabilities(123, 456, 789); ParcelableTestUtil.assertFieldCountEquals(3, ApfCapabilities.class); - TestUtils.assertParcelingIsLossless(caps, ApfCapabilities.CREATOR); + TestUtils.assertParcelingIsLossless(caps); } @Test diff --git a/tests/net/common/java/android/net/metrics/DhcpErrorEventTest.kt b/tests/net/common/java/android/net/metrics/DhcpErrorEventTest.kt new file mode 100644 index 000000000000..e19195322e1d --- /dev/null +++ b/tests/net/common/java/android/net/metrics/DhcpErrorEventTest.kt @@ -0,0 +1,67 @@ +package android.net.metrics + +import android.net.metrics.DhcpErrorEvent.errorCodeWithOption +import android.net.metrics.DhcpErrorEvent.DHCP_INVALID_OPTION_LENGTH +import androidx.test.filters.SmallTest +import androidx.test.runner.AndroidJUnit4 +import com.android.internal.util.TestUtils.parcelingRoundTrip +import java.lang.reflect.Modifier +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertTrue +import org.junit.Test +import org.junit.runner.RunWith + +private const val TEST_ERROR_CODE = 12345 +/** + * DHCP Optional Type: DHCP Subnet Mask (Copy from DhcpPacket.java) + */ +private const val DHCP_SUBNET_MASK = 1 + +@RunWith(AndroidJUnit4::class) +@SmallTest +class DhcpErrorEventTest { + + @Test + fun testConstructor() { + val event = DhcpErrorEvent(TEST_ERROR_CODE) + assertEquals(TEST_ERROR_CODE, event.errorCode) + } + + @Test + fun testParcelUnparcel() { + val event = DhcpErrorEvent(TEST_ERROR_CODE) + val parceled = parcelingRoundTrip(event) + assertEquals(TEST_ERROR_CODE, parceled.errorCode) + } + + @Test + fun testErrorCodeWithOption() { + val errorCode = errorCodeWithOption(DHCP_INVALID_OPTION_LENGTH, DHCP_SUBNET_MASK); + assertTrue((DHCP_INVALID_OPTION_LENGTH and errorCode) == DHCP_INVALID_OPTION_LENGTH); + assertTrue((DHCP_SUBNET_MASK and errorCode) == DHCP_SUBNET_MASK); + } + + @Test + fun testToString() { + val names = listOf("L2_ERROR", "L3_ERROR", "L4_ERROR", "DHCP_ERROR", "MISC_ERROR") + val errorFields = DhcpErrorEvent::class.java.declaredFields.filter { + it.type == Int::class.javaPrimitiveType + && Modifier.isPublic(it.modifiers) && Modifier.isStatic(it.modifiers) + && it.name !in names + } + + errorFields.forEach { + val intValue = it.getInt(null) + val stringValue = DhcpErrorEvent(intValue).toString() + assertTrue("Invalid string for error 0x%08X (field %s): %s".format(intValue, it.name, + stringValue), + stringValue.contains(it.name)) + } + } + + @Test + fun testToString_InvalidErrorCode() { + assertNotNull(DhcpErrorEvent(TEST_ERROR_CODE).toString()) + } +}
\ No newline at end of file diff --git a/tests/net/java/android/net/TcpKeepalivePacketDataTest.java b/tests/net/java/android/net/TcpKeepalivePacketDataTest.java index e0b722761c34..583d3fd536aa 100644 --- a/tests/net/java/android/net/TcpKeepalivePacketDataTest.java +++ b/tests/net/java/android/net/TcpKeepalivePacketDataTest.java @@ -79,7 +79,7 @@ public final class TcpKeepalivePacketDataTest { assertEquals(testInfo.tos, resultData.ipTos); assertEquals(testInfo.ttl, resultData.ipTtl); - TestUtils.assertParcelingIsLossless(resultData, TcpKeepalivePacketData.CREATOR); + TestUtils.assertParcelingIsLossless(resultData); final byte[] packet = resultData.getPacket(); // IP version and IHL diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index c15775fc4734..363ac9ce1d25 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -567,6 +567,16 @@ public class ConnectivityServiceTest { protected void preventAutomaticReconnect() { mPreventReconnectReceived.open(); } + + @Override + protected void addKeepalivePacketFilter(Message msg) { + Log.i(TAG, "Add keepalive packet filter."); + } + + @Override + protected void removeKeepalivePacketFilter(Message msg) { + Log.i(TAG, "Remove keepalive packet filter."); + } }; assertEquals(mNetworkAgent.netId, nmNetworkCaptor.getValue().netId); diff --git a/tests/net/util/java/com/android/internal/util/TestUtils.java b/tests/net/util/java/com/android/internal/util/TestUtils.java index 75329a805606..a99cd4716f9a 100644 --- a/tests/net/util/java/com/android/internal/util/TestUtils.java +++ b/tests/net/util/java/com/android/internal/util/TestUtils.java @@ -69,9 +69,17 @@ public final class TestUtils { } } - // TODO : fetch the creator through reflection or something instead of passing it - public static <T extends Parcelable, C extends Parcelable.Creator<T>> - void assertParcelingIsLossless(T source, C creator) { + /** + * Return a new instance of {@code T} after being parceled then unparceled. + */ + public static <T extends Parcelable> T parcelingRoundTrip(T source) { + final Parcelable.Creator<T> creator; + try { + creator = (Parcelable.Creator<T>) source.getClass().getField("CREATOR").get(null); + } catch (IllegalAccessException | NoSuchFieldException e) { + fail("Missing CREATOR field: " + e.getMessage()); + return null; + } Parcel p = Parcel.obtain(); source.writeToParcel(p, /* flags */ 0); p.setDataPosition(0); @@ -79,7 +87,14 @@ public final class TestUtils { p = Parcel.obtain(); p.unmarshall(marshalled, 0, marshalled.length); p.setDataPosition(0); - T dest = creator.createFromParcel(p); - assertEquals(source, dest); + return creator.createFromParcel(p); + } + + /** + * Assert that after being parceled then unparceled, {@code source} is equal to the original + * object. + */ + public static <T extends Parcelable> void assertParcelingIsLossless(T source) { + assertEquals(source, parcelingRoundTrip(source)); } } diff --git a/tools/apilint/apilint.py b/tools/apilint/apilint.py index 97ca6dc7bf34..f41426d84af1 100644 --- a/tools/apilint/apilint.py +++ b/tools/apilint/apilint.py @@ -2132,6 +2132,10 @@ def show_deprecations_at_birth(cur, prev): # Remove all existing things so we're left with new for prev_clazz in prev.values(): + if prev_clazz.fullname not in cur: + # The class was removed this release; we can safely ignore it. + continue + cur_clazz = cur[prev_clazz.fullname] if not is_interesting(cur_clazz): continue |