aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2022-03-14 07:06:49 +0000
committerAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2022-03-14 07:06:49 +0000
commit0d61e3196a9fe265162129fdbbc2995f60a41e83 (patch)
tree8c8bf63883063689b7e029ff3ea3d51017a9a8b1
parenta7ec1fa6fdbab2862315161ecb8bb2aac4cfa4d6 (diff)
parentf880c8ba63960641868bca1c803f6a0a45fe84df (diff)
downloadtests-main-cg-testing-release.tar.gz
Snap for 8294919 from f880c8ba63960641868bca1c803f6a0a45fe84df to main-cg-testing-releasemain-cg-testing-release
Change-Id: I55ad93c5034a36ed5ad4d1e8623bdc10b13f8ea1
-rw-r--r--RotaryPlayground/build.gradle1
-rw-r--r--RotaryPlayground/res/layout/list_item.xml20
-rw-r--r--RotaryPlayground/res/layout/popup_window_fragment.xml26
-rw-r--r--RotaryPlayground/res/layout/rotary_menu.xml14
-rw-r--r--RotaryPlayground/res/layout/surface_view_fragment.xml58
-rw-r--r--RotaryPlayground/res/values/themes.xml1
-rw-r--r--RotaryPlayground/src/com/android/car/rotaryplayground/CustomSurfaceView.java104
-rw-r--r--RotaryPlayground/src/com/android/car/rotaryplayground/PopupWindowFragment.java131
-rw-r--r--RotaryPlayground/src/com/android/car/rotaryplayground/RotaryMenu.java44
-rw-r--r--RotaryPlayground/src/com/android/car/rotaryplayground/RotarySysUiDirectManipulationWidgets.java6
-rw-r--r--RotaryPlayground/src/com/android/car/rotaryplayground/SurfaceViewFragment.java61
-rw-r--r--TestMediaApp/Android.bp4
-rw-r--r--TestMediaApp/AndroidManifest.xml3
-rw-r--r--TestMediaApp/assets/media_items/simple_leaves.json16
-rw-r--r--TestMediaApp/build.gradle14
-rw-r--r--TestMediaApp/src/com/android/car/media/testmediaapp/TmaPlayer.java35
-rw-r--r--TestMediaApp/src/com/android/car/media/testmediaapp/prefs/TmaEnumPrefs.java6
-rw-r--r--TestMediaApp/src/com/android/car/media/testmediaapp/prefs/TmaPrefs.java9
-rw-r--r--build.gradle41
-rw-r--r--gradle.properties1
-rw-r--r--gradle/wrapper/gradle-wrapper.jarbin54329 -> 0 bytes
-rw-r--r--gradle/wrapper/gradle-wrapper.properties6
-rwxr-xr-xgradlew172
-rw-r--r--settings.gradle23
-rwxr-xr-xtools/rro/generate-overlays.py80
-rw-r--r--tools/rro/resource_utils.py57
-rwxr-xr-xtools/rro/verify-overlayable.py4
27 files changed, 660 insertions, 277 deletions
diff --git a/RotaryPlayground/build.gradle b/RotaryPlayground/build.gradle
index fa8d79c..d2f9f10 100644
--- a/RotaryPlayground/build.gradle
+++ b/RotaryPlayground/build.gradle
@@ -42,7 +42,6 @@ android {
main {
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = ['src']
- resources.srcDirs = ['src']
aidl.srcDirs = ['src']
renderscript.srcDirs = ['src']
res.srcDirs = ['res']
diff --git a/RotaryPlayground/res/layout/list_item.xml b/RotaryPlayground/res/layout/list_item.xml
new file mode 100644
index 0000000..dd36ec7
--- /dev/null
+++ b/RotaryPlayground/res/layout/list_item.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:focusable="true"/>
diff --git a/RotaryPlayground/res/layout/popup_window_fragment.xml b/RotaryPlayground/res/layout/popup_window_fragment.xml
new file mode 100644
index 0000000..f3ba9d2
--- /dev/null
+++ b/RotaryPlayground/res/layout/popup_window_fragment.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<com.android.car.ui.FocusArea
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <Button
+ android:id="@+id/overflow"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Overflow Button"/>
+</com.android.car.ui.FocusArea>
diff --git a/RotaryPlayground/res/layout/rotary_menu.xml b/RotaryPlayground/res/layout/rotary_menu.xml
index f72d6ec..53f17b7 100644
--- a/RotaryPlayground/res/layout/rotary_menu.xml
+++ b/RotaryPlayground/res/layout/rotary_menu.xml
@@ -76,4 +76,18 @@
android:layout_weight="1"
android:text="Custom FocusAreas"
style="@style/tab" />
+ <Button
+ android:id="@+id/popup_window"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:text="Popup window"
+ style="@style/tab" />
+ <Button
+ android:id="@+id/surface_view"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:text="SurfaceView"
+ style="@style/tab" />
</com.android.car.ui.FocusArea>
diff --git a/RotaryPlayground/res/layout/surface_view_fragment.xml b/RotaryPlayground/res/layout/surface_view_fragment.xml
new file mode 100644
index 0000000..b2e09f0
--- /dev/null
+++ b/RotaryPlayground/res/layout/surface_view_fragment.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <!-- The two FocusAreas are overlapping and nudge targeting is ambiguous. So set its
+ "app:startBoundOffset" to fix that. -->
+ <com.android.car.ui.FocusArea
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ app:startBoundOffset="300dp">
+ <com.android.car.rotaryplayground.CustomSurfaceView
+ android:id="@+id/surface_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:focusable="true"
+ android:background="#A0A0A0"/>
+ </com.android.car.ui.FocusArea>
+ <com.android.car.ui.FocusArea
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_margin="16dp"
+ android:background="#808080"
+ android:orientation="vertical">
+ <Button
+ android:layout_width="200dp"
+ android:layout_height="100dp"
+ android:text="Button 1"/>
+ <Button
+ android:layout_width="200dp"
+ android:layout_height="100dp"
+ android:text="Button 2"/>
+ <Button
+ android:layout_width="200dp"
+ android:layout_height="100dp"
+ android:text="Button 3"/>
+ <Button
+ android:layout_width="200dp"
+ android:layout_height="100dp"
+ android:text="Button 4"/>
+ </com.android.car.ui.FocusArea>
+</FrameLayout>
diff --git a/RotaryPlayground/res/values/themes.xml b/RotaryPlayground/res/values/themes.xml
index 02a0be9..69bfc4b 100644
--- a/RotaryPlayground/res/values/themes.xml
+++ b/RotaryPlayground/res/values/themes.xml
@@ -17,6 +17,7 @@
<resources>
<style name="Theme.App" parent="android:Theme.DeviceDefault">
<item name="android:buttonStyle">@style/ButtonStyle</item>
+ <item name="carUiActivity">true</item>
</style>
<style name="ButtonStyle" parent="android:Widget.Button">
diff --git a/RotaryPlayground/src/com/android/car/rotaryplayground/CustomSurfaceView.java b/RotaryPlayground/src/com/android/car/rotaryplayground/CustomSurfaceView.java
new file mode 100644
index 0000000..ec6d895
--- /dev/null
+++ b/RotaryPlayground/src/com/android/car/rotaryplayground/CustomSurfaceView.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.rotaryplayground;
+
+import static com.android.car.ui.utils.RotaryConstants.BOTTOM_BOUND_OFFSET_FOR_NUDGE;
+import static com.android.car.ui.utils.RotaryConstants.LEFT_BOUND_OFFSET_FOR_NUDGE;
+import static com.android.car.ui.utils.RotaryConstants.RIGHT_BOUND_OFFSET_FOR_NUDGE;
+import static com.android.car.ui.utils.RotaryConstants.TOP_BOUND_OFFSET_FOR_NUDGE;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.os.Bundle;
+import android.util.AttributeSet;
+import android.view.SurfaceView;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+import androidx.annotation.Nullable;
+
+/**
+ * A SurfaceView that allows to set its perceived bounds for the purposes of finding the nudge
+ * target.
+ */
+public class CustomSurfaceView extends SurfaceView {
+
+ /**
+ * The offset (in pixels) of the SurfaceView's bounds for the purposes of finding the nudge
+ * target.
+ */
+ private int mLeftOffset;
+ private int mRightOffset;
+ private int mTopOffset;
+ private int mBottomOffset;
+
+ public CustomSurfaceView(Context context) {
+ super(context);
+ }
+
+ public CustomSurfaceView(Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public CustomSurfaceView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public CustomSurfaceView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ /**
+ * Sets the offset (in pixels) of the SurfaceView's bounds.
+ * <p>
+ * It only affects the perceived bounds for the purposes of finding the nudge target.
+ * This doesn't affect the view's bounds. This method should only be used when this view
+ * overlaps other focusable views so that nudge targeting is ambiguous.
+ */
+ public void setBoundsOffset(int left, int top, int right, int bottom) {
+ mLeftOffset = left;
+ mTopOffset = top;
+ mRightOffset = right;
+ mBottomOffset = bottom;
+ }
+
+ @Override
+ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(info);
+ if (mLeftOffset == 0 && mTopOffset == 0 && mRightOffset == 0 && mBottomOffset == 0) {
+ return;
+ }
+ Bundle bundle = info.getExtras();
+ bundle.putInt(LEFT_BOUND_OFFSET_FOR_NUDGE, mLeftOffset);
+ bundle.putInt(RIGHT_BOUND_OFFSET_FOR_NUDGE, mRightOffset);
+ bundle.putInt(TOP_BOUND_OFFSET_FOR_NUDGE, mTopOffset);
+ bundle.putInt(BOTTOM_BOUND_OFFSET_FOR_NUDGE, mBottomOffset);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+
+ // Draw a text for demonstration purpose.
+ Paint paint = new Paint();
+ paint.setColor(Color.BLACK);
+ paint.setTextSize(40);
+ canvas.drawText("SurfaceView", 400, 200, paint);
+ }
+}
diff --git a/RotaryPlayground/src/com/android/car/rotaryplayground/PopupWindowFragment.java b/RotaryPlayground/src/com/android/car/rotaryplayground/PopupWindowFragment.java
new file mode 100644
index 0000000..f2ae91a
--- /dev/null
+++ b/RotaryPlayground/src/com/android/car/rotaryplayground/PopupWindowFragment.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.rotaryplayground;
+
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.ListPopupWindow;
+import android.widget.ListView;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+
+import com.android.car.ui.FocusParkingView;
+
+/** Fragment to demo a popup window. */
+public class PopupWindowFragment extends Fragment {
+
+ private static final String[] ACTIONS = {"Action 1", "Action 2", "Action 3"};
+ private static final int WINDOW_WIDTH = 300;
+ private static final int WINDOW_HEIGHT = 400;
+
+ @NonNull
+ private ListPopupWindow mPopupWindow;
+ @Nullable
+ private ListView mListView;
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
+ @Nullable Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.popup_window_fragment, container, false);
+ Button overflow = view.findViewById(R.id.overflow);
+
+ mPopupWindow = new ListPopupWindow(getActivity());
+ mPopupWindow.setAdapter(new ArrayAdapter<>(getActivity(), R.layout.list_item, ACTIONS));
+ mPopupWindow.setAnchorView(overflow);
+ mPopupWindow.setWidth(WINDOW_WIDTH);
+ mPopupWindow.setHeight(WINDOW_HEIGHT);
+ mPopupWindow.setModal(true);
+ mPopupWindow.setOnItemClickListener((parent, v, position, id) -> {
+ Toast.makeText(getActivity(), ACTIONS[position], Toast.LENGTH_SHORT).show();
+ mPopupWindow.dismiss();
+ });
+
+ overflow.setOnClickListener((v) -> {
+ mPopupWindow.show();
+ mListView = mPopupWindow.getListView();
+ if (mListView != null && mListView.isFocused()) {
+ initRotaryMode();
+ }
+ });
+
+ return view;
+ }
+
+ /**
+ * Initializes the popup window to support rotary controller rotations and nudges.
+ * <p>
+ * In rotary mode the first focusable element in the ListView should be focused automatically
+ * when the popup window shows up. However, in {@link ListPopupWindow#show}, Android framework
+ * creates the ListView, sets it focusable, then focuses on it automatically. What's worse,
+ * ListView doesn't handle focusSearch() properly, causing rotation fails once it's focused.
+ * To work around it, move focus to its first focusable element, then set the ListView
+ * to not be focusable.
+ */
+ private void initRotaryMode() {
+ if (mListView == null) {
+ return;
+ }
+
+ // Add a FocusParkingView to prevent rotary controller rotation wrap-around.
+ View root = mListView.getRootView();
+ if (root instanceof ViewGroup) {
+ ViewGroup rootView = (ViewGroup) root;
+ FocusParkingView fpv = new FocusParkingView(getActivity());
+
+ // This popup window can be dismissed by controller nudges.
+ fpv.setOnPopupWindowDismiss(() -> mPopupWindow.dismiss());
+
+ rootView.addView(fpv);
+ }
+
+ // At this time the elements in the list are not loaded yet. Because there is no callbacks
+ // to notify whether the elements are loaded, use an OnPreDrawListener as workaround.
+ ViewTreeObserver.OnPreDrawListener listeners[] =
+ new ViewTreeObserver.OnPreDrawListener[1];
+ listeners[0] = (() -> {
+ boolean focusInitialized = false;
+ for (int i = 0; i < mListView.getChildCount(); i++) {
+ View child = mListView.getChildAt(i);
+ if (child != null) {
+ // Because the elements are set focusable, they'll steal click events from the
+ // list, so they need to handle the events on their own.
+ int position = i;
+ child.setOnClickListener(v -> mPopupWindow.performItemClick(position));
+
+ // Move focus to the first focusable element and set the list non-focussable.
+ if (!focusInitialized) {
+ focusInitialized = child.requestFocus();
+ if (focusInitialized) {
+ mListView.setFocusable(false);
+ mListView.getViewTreeObserver().removeOnPreDrawListener(listeners[0]);
+ }
+ }
+ }
+ }
+ return true;
+ });
+ mListView.getViewTreeObserver().addOnPreDrawListener(listeners[0]);
+ }
+}
diff --git a/RotaryPlayground/src/com/android/car/rotaryplayground/RotaryMenu.java b/RotaryPlayground/src/com/android/car/rotaryplayground/RotaryMenu.java
index 0e417b4..f1ffe86 100644
--- a/RotaryPlayground/src/com/android/car/rotaryplayground/RotaryMenu.java
+++ b/RotaryPlayground/src/com/android/car/rotaryplayground/RotaryMenu.java
@@ -31,14 +31,16 @@ import androidx.fragment.app.Fragment;
*/
public class RotaryMenu extends Fragment {
- private Fragment mRotaryCards = null;
- private Fragment mRotaryGrid = null;
- private Fragment mDirectManipulation = null;
- private Fragment mSysUiDirectManipulation = null;
- private Fragment mNotificationFragment = null;
- private Fragment mScrollFragment = null;
- private Fragment mWebViewFragment = null;
- private Fragment mCustomFocusAreasFragment = null;
+ private Fragment mRotaryCards;
+ private Fragment mRotaryGrid;
+ private Fragment mDirectManipulation;
+ private Fragment mSysUiDirectManipulation;
+ private Fragment mNotificationFragment;
+ private Fragment mScrollFragment;
+ private Fragment mWebViewFragment;
+ private Fragment mCustomFocusAreasFragment;
+ private Fragment mPopupWindowFragment;
+ private Fragment mSurfaceViewFragment;
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
@@ -93,6 +95,18 @@ public class RotaryMenu extends Fragment {
showCustomFocusAreasFragment();
});
+ Button popupWindowButton = view.findViewById(R.id.popup_window);
+ popupWindowButton.setOnClickListener(v -> {
+ selectTab(v);
+ showPopupWindowFragment();
+ });
+
+ Button surfaceViewButton = view.findViewById(R.id.surface_view);
+ surfaceViewButton.setOnClickListener(v -> {
+ selectTab(v);
+ showSurfaceViewFragment();
+ });
+
return view;
}
@@ -162,6 +176,20 @@ public class RotaryMenu extends Fragment {
showFragment(mCustomFocusAreasFragment);
}
+ private void showPopupWindowFragment() {
+ if (mPopupWindowFragment == null) {
+ mPopupWindowFragment = new PopupWindowFragment();
+ }
+ showFragment(mPopupWindowFragment);
+ }
+
+ private void showSurfaceViewFragment() {
+ if (mSurfaceViewFragment == null) {
+ mSurfaceViewFragment = new SurfaceViewFragment();
+ }
+ showFragment(mSurfaceViewFragment);
+ }
+
private void showFragment(Fragment fragment) {
getFragmentManager().beginTransaction()
.replace(R.id.rotary_content, fragment)
diff --git a/RotaryPlayground/src/com/android/car/rotaryplayground/RotarySysUiDirectManipulationWidgets.java b/RotaryPlayground/src/com/android/car/rotaryplayground/RotarySysUiDirectManipulationWidgets.java
index 85fce89..f137343 100644
--- a/RotaryPlayground/src/com/android/car/rotaryplayground/RotarySysUiDirectManipulationWidgets.java
+++ b/RotaryPlayground/src/com/android/car/rotaryplayground/RotarySysUiDirectManipulationWidgets.java
@@ -38,7 +38,7 @@ public class RotarySysUiDirectManipulationWidgets extends Fragment {
View directManipulationSupportedSeekBar = view.findViewById(
R.id.direct_manipulation_supported_seek_bar);
- DirectManipulationHelper.setSupportsDirectManipulation(
+ DirectManipulationHelper.setSupportsRotateDirectly(
directManipulationSupportedSeekBar, true);
View directManipulationUnsupportedSeekBar = view.findViewById(
@@ -48,7 +48,7 @@ public class RotarySysUiDirectManipulationWidgets extends Fragment {
View directManipulationSupportedRadialTimePickerView = view.findViewById(
R.id.direct_manipulation_supported_radial_time_picker);
- DirectManipulationHelper.setSupportsDirectManipulation(
+ DirectManipulationHelper.setSupportsRotateDirectly(
directManipulationSupportedRadialTimePickerView, true);
View directManipulationUnsupportedRadialTimePickerView = view.findViewById(
@@ -92,4 +92,4 @@ public class RotarySysUiDirectManipulationWidgets extends Fragment {
mFlipFlop = !mFlipFlop;
}
}
-} \ No newline at end of file
+}
diff --git a/RotaryPlayground/src/com/android/car/rotaryplayground/SurfaceViewFragment.java b/RotaryPlayground/src/com/android/car/rotaryplayground/SurfaceViewFragment.java
new file mode 100644
index 0000000..8130638
--- /dev/null
+++ b/RotaryPlayground/src/com/android/car/rotaryplayground/SurfaceViewFragment.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.rotaryplayground;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.util.DisplayMetrics;
+import android.view.LayoutInflater;
+import android.view.SurfaceView;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+
+/** Fragment to demo a {@link SurfaceView} behind some buttons. */
+public class SurfaceViewFragment extends Fragment {
+
+ private CustomSurfaceView mSurfaceView;
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
+ @Nullable Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.surface_view_fragment, container, false);
+ mSurfaceView = view.findViewById(R.id.surface_view);
+
+ // Some buttons sit on top of the left part of the SurfaceView, so tailor its left bound
+ // so that RotaryService can find the correct nudge target.
+ mSurfaceView.setBoundsOffset(300, 0, 0, 0);
+
+ makeItFullScreen();
+ return view;
+ }
+
+ private void makeItFullScreen() {
+ Activity activity = getActivity();
+ activity.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
+ WindowManager.LayoutParams.FLAG_FULLSCREEN);
+
+ DisplayMetrics displayMetrics = new DisplayMetrics();
+ activity.getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
+ ViewGroup.LayoutParams layoutParams = mSurfaceView.getLayoutParams();
+ layoutParams.width = displayMetrics.widthPixels;
+ layoutParams.height = displayMetrics.heightPixels;
+ }
+}
diff --git a/TestMediaApp/Android.bp b/TestMediaApp/Android.bp
index 91b3aa7..1d35819 100644
--- a/TestMediaApp/Android.bp
+++ b/TestMediaApp/Android.bp
@@ -27,9 +27,11 @@ android_app {
resource_dirs: ["res"],
+ min_sdk_version: "28",
+ target_sdk_version: "31",
sdk_version: "system_current",
- certificate: "platform",
+ certificate: ":com-android-car-apps-test",
// Do NOT add dependencies preventing the app from being unbundled (compiled with gradle in Studio).
static_libs: [
diff --git a/TestMediaApp/AndroidManifest.xml b/TestMediaApp/AndroidManifest.xml
index 4f806c6..361b92f 100644
--- a/TestMediaApp/AndroidManifest.xml
+++ b/TestMediaApp/AndroidManifest.xml
@@ -16,7 +16,8 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="com.android.car.media.testmediaapp">
+ package="com.android.car.media.testmediaapp"
+ android:versionCode="10000" android:versionName="1.0.0">
<uses-feature android:name="android.hardware.type.automotive"
android:required="true"/>
diff --git a/TestMediaApp/assets/media_items/simple_leaves.json b/TestMediaApp/assets/media_items/simple_leaves.json
index dc6b0a3..dbe65b7 100644
--- a/TestMediaApp/assets/media_items/simple_leaves.json
+++ b/TestMediaApp/assets/media_items/simple_leaves.json
@@ -10,14 +10,6 @@
{
"FLAGS": "playable",
"METADATA": {
- "MEDIA_ID": "simple_leaves normal 10s song",
- "DISPLAY_TITLE": "A normal 10s song with a long title. A normal 10s song with a long title. A normal 10s song with a long title. ",
- "DURATION": 10000
- }
- },
- {
- "FLAGS": "playable",
- "METADATA": {
"MEDIA_ID": "simple_leaves normal 1H song",
"DISPLAY_TITLE": "A normal 1H song",
"ARTIST": "Artist",
@@ -28,6 +20,14 @@
{
"FLAGS": "playable",
"METADATA": {
+ "MEDIA_ID": "simple_leaves normal 10s song",
+ "DISPLAY_TITLE": "A normal 10s song with a long title. A normal 10s song with a long title. A normal 10s song with a long title. ",
+ "DURATION": 10000
+ }
+ },
+ {
+ "FLAGS": "playable",
+ "METADATA": {
"MEDIA_ID": "simple_leaves slow connection",
"DISPLAY_TITLE": "Connects and buffers for 4s each",
"DISPLAY_SUBTITLE": "A very long subtitle. A very long subtitle. A very long subtitle. A very long subtitle. A very long subtitle. A very long subtitle. ",
diff --git a/TestMediaApp/build.gradle b/TestMediaApp/build.gradle
index 6711d6d..23ffd94 100644
--- a/TestMediaApp/build.gradle
+++ b/TestMediaApp/build.gradle
@@ -18,11 +18,11 @@
apply plugin: 'com.android.application'
android {
- compileSdkVersion 30
+ compileSdkVersion gradle.ext.aaosLatestSDK
defaultConfig {
applicationId "com.android.car.media.testmediaapp"
minSdkVersion 28
- targetSdkVersion 30
+ targetSdkVersion 31
versionCode 1
versionName "1.0"
}
@@ -43,13 +43,21 @@ android {
main {
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = ['src']
- resources.srcDirs = ['src']
aidl.srcDirs = ['src']
renderscript.srcDirs = ['src']
res.srcDirs = ['res']
assets.srcDirs = ['assets']
}
}
+
+ signingConfigs {
+ debug {
+ storeFile file('../../libs/certs/com_android_car_apps_test.jks')
+ storePassword 'carapps'
+ keyAlias 'carapps'
+ keyPassword 'carapps'
+ }
+ }
}
dependencies {
diff --git a/TestMediaApp/src/com/android/car/media/testmediaapp/TmaPlayer.java b/TestMediaApp/src/com/android/car/media/testmediaapp/TmaPlayer.java
index e75d028..24e12d9 100644
--- a/TestMediaApp/src/com/android/car/media/testmediaapp/TmaPlayer.java
+++ b/TestMediaApp/src/com/android/car/media/testmediaapp/TmaPlayer.java
@@ -17,6 +17,9 @@
package com.android.car.media.testmediaapp;
import static android.media.AudioManager.AUDIOFOCUS_GAIN;
+import static android.media.AudioManager.AUDIOFOCUS_LOSS;
+import static android.media.AudioManager.AUDIOFOCUS_LOSS_TRANSIENT;
+import static android.media.AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK;
import static android.media.AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
import static android.support.v4.media.session.PlaybackStateCompat.ACTION_PAUSE;
import static android.support.v4.media.session.PlaybackStateCompat.ACTION_PLAY;
@@ -29,6 +32,7 @@ import static android.support.v4.media.session.PlaybackStateCompat.ACTION_SKIP_T
import static android.support.v4.media.session.PlaybackStateCompat.ERROR_CODE_APP_ERROR;
import static android.support.v4.media.session.PlaybackStateCompat.STATE_ERROR;
+import android.media.AudioManager.OnAudioFocusChangeListener;
import androidx.annotation.Nullable;
import android.app.PendingIntent;
@@ -77,6 +81,7 @@ public class TmaPlayer extends MediaSessionCompat.Callback {
@Nullable
private TmaMediaItem mActiveItem;
private int mNextEventIndex = -1;
+ private boolean mResumeOnFocusGain;
TmaPlayer(TmaBrowser browser, TmaLibrary library, AudioManager audioManager, Handler handler,
MediaSessionCompat session) {
@@ -87,8 +92,9 @@ public class TmaPlayer extends MediaSessionCompat.Callback {
mHandler = handler;
mSession = session;
- // TODO add focus listener ?
- mAudioFocusRequest = new AudioFocusRequest.Builder(AUDIOFOCUS_GAIN).build();
+ mAudioFocusRequest = new AudioFocusRequest.Builder(AUDIOFOCUS_GAIN)
+ .setOnAudioFocusChangeListener(this::onAudioFocusChange, mHandler)
+ .build();
}
/** Updates the state in the media session based on the given {@link TmaMediaEvent}. */
@@ -366,4 +372,29 @@ public class TmaPlayer extends MediaSessionCompat.Callback {
return actions;
}
+
+ private void onAudioFocusChange(int focusChange) {
+ // Adapted from samples at https://developer.android.com/guide/topics/media-apps/audio-focus
+ // Android Auto emulator tests rely on the app pausing and resuming in response to focus
+ // transient loss and focus gain, respectively.
+ switch (focusChange) {
+ case AUDIOFOCUS_GAIN:
+ if (mResumeOnFocusGain) {
+ mResumeOnFocusGain = false;
+ startPlayBack(/* requestAudioFocus= */ false);
+ }
+ break;
+ case AUDIOFOCUS_LOSS:
+ mResumeOnFocusGain = false;
+ pausePlayback();
+ break;
+ case AUDIOFOCUS_LOSS_TRANSIENT:
+ case AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK:
+ mResumeOnFocusGain = mIsPlaying;
+ pausePlayback();
+ break;
+ default:
+ Log.w(TAG, "Unknown audio focus change " + focusChange);
+ }
+ }
}
diff --git a/TestMediaApp/src/com/android/car/media/testmediaapp/prefs/TmaEnumPrefs.java b/TestMediaApp/src/com/android/car/media/testmediaapp/prefs/TmaEnumPrefs.java
index 3702929..1ee7d73 100644
--- a/TestMediaApp/src/com/android/car/media/testmediaapp/prefs/TmaEnumPrefs.java
+++ b/TestMediaApp/src/com/android/car/media/testmediaapp/prefs/TmaEnumPrefs.java
@@ -29,9 +29,9 @@ public class TmaEnumPrefs {
}
public enum TmaAccountType implements EnumPrefValue {
- NONE("None", "none"),
FREE("Free", "free"),
- PAID("Paid", "paid");
+ PAID("Paid", "paid"),
+ NONE("None", "none");
private final PrefValueImpl mPrefValue;
@@ -81,11 +81,11 @@ public class TmaEnumPrefs {
public enum TmaBrowseNodeType implements EnumPrefValue {
+ NODE_CHILDREN("Only browse-able content", "nodes"),
NULL("Null (error)", "null"),
EMPTY("Empty", "empty"),
QUEUE_ONLY("Queue only", "queue-only"),
SINGLE_TAB("Single browse-able tab", "single-tab"),
- NODE_CHILDREN("Only browse-able content", "nodes"),
LEAF_CHILDREN("Only playable content (basic working and error cases)", "leaves"),
MIXED_CHILDREN("Mixed content (apps are not supposed to do that)", "mixed"),
UNTAGGED("Untagged media items (not playable or browsable)", "untagged");
diff --git a/TestMediaApp/src/com/android/car/media/testmediaapp/prefs/TmaPrefs.java b/TestMediaApp/src/com/android/car/media/testmediaapp/prefs/TmaPrefs.java
index dd08cd2..fd52697 100644
--- a/TestMediaApp/src/com/android/car/media/testmediaapp/prefs/TmaPrefs.java
+++ b/TestMediaApp/src/com/android/car/media/testmediaapp/prefs/TmaPrefs.java
@@ -130,11 +130,16 @@ public class TmaPrefs {
private TmaPrefs(Context context) {
mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(context);
+ // Note: Android Auto emulator tests depend on a default account type which permits access
+ // to the browse tree (e.g. FREE), and the default root node type NODE_CHILDREN.
+ // Also, they assert on the titles of each root child of NODE_CHILDREN as well as the first
+ // track under the first root child ("Basic Songs"), i.e. "A normal 1H song".
+
mAccountType = new EnumPrefEntry<>(TmaPrefKey.ACCOUNT_TYPE_KEY,
- TmaAccountType.values(), TmaAccountType.NONE);
+ TmaAccountType.values(), TmaAccountType.FREE);
mRootNodeType = new EnumPrefEntry<>(TmaPrefKey.ROOT_NODE_TYPE_KEY,
- TmaBrowseNodeType.values(), TmaBrowseNodeType.NULL);
+ TmaBrowseNodeType.values(), TmaBrowseNodeType.NODE_CHILDREN);
mRootReplyDelay = new EnumPrefEntry<>(TmaPrefKey.ROOT_REPLY_DELAY_KEY,
TmaReplyDelay.values(), TmaReplyDelay.NONE);
diff --git a/build.gradle b/build.gradle
deleted file mode 100644
index 5493299..0000000
--- a/build.gradle
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * 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.
- */
-
-// Top-level build file where you can add configuration options common to all sub-projects/modules.
-
-buildscript {
- repositories {
- google()
- jcenter()
- }
- dependencies {
- classpath 'com.android.tools.build:gradle:4.0.1'
-
- // NOTE: Do not place your application dependencies here; they belong
- // in the individual module build.gradle files
- }
-}
-
-allprojects {
- repositories {
- google()
- jcenter()
- }
-}
-
-task clean(type: Delete) {
- delete rootProject.buildDir
-}
diff --git a/gradle.properties b/gradle.properties
deleted file mode 100644
index 5bac8ac..0000000
--- a/gradle.properties
+++ /dev/null
@@ -1 +0,0 @@
-android.useAndroidX=true
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
deleted file mode 100644
index f6b961f..0000000
--- a/gradle/wrapper/gradle-wrapper.jar
+++ /dev/null
Binary files differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
deleted file mode 100644
index e5a0e03..0000000
--- a/gradle/wrapper/gradle-wrapper.properties
+++ /dev/null
@@ -1,6 +0,0 @@
-#Mon Aug 24 13:36:33 PDT 2020
-distributionBase=GRADLE_USER_HOME
-distributionPath=wrapper/dists
-zipStoreBase=GRADLE_USER_HOME
-zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip
diff --git a/gradlew b/gradlew
deleted file mode 100755
index cccdd3d..0000000
--- a/gradlew
+++ /dev/null
@@ -1,172 +0,0 @@
-#!/usr/bin/env sh
-
-##############################################################################
-##
-## Gradle start up script for UN*X
-##
-##############################################################################
-
-# Attempt to set APP_HOME
-# Resolve links: $0 may be a link
-PRG="$0"
-# Need this for relative symlinks.
-while [ -h "$PRG" ] ; do
- ls=`ls -ld "$PRG"`
- link=`expr "$ls" : '.*-> \(.*\)$'`
- if expr "$link" : '/.*' > /dev/null; then
- PRG="$link"
- else
- PRG=`dirname "$PRG"`"/$link"
- fi
-done
-SAVED="`pwd`"
-cd "`dirname \"$PRG\"`/" >/dev/null
-APP_HOME="`pwd -P`"
-cd "$SAVED" >/dev/null
-
-APP_NAME="Gradle"
-APP_BASE_NAME=`basename "$0"`
-
-# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-DEFAULT_JVM_OPTS=""
-
-# Use the maximum available, or set MAX_FD != -1 to use that value.
-MAX_FD="maximum"
-
-warn () {
- echo "$*"
-}
-
-die () {
- echo
- echo "$*"
- echo
- exit 1
-}
-
-# OS specific support (must be 'true' or 'false').
-cygwin=false
-msys=false
-darwin=false
-nonstop=false
-case "`uname`" in
- CYGWIN* )
- cygwin=true
- ;;
- Darwin* )
- darwin=true
- ;;
- MINGW* )
- msys=true
- ;;
- NONSTOP* )
- nonstop=true
- ;;
-esac
-
-CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
-
-# Determine the Java command to use to start the JVM.
-if [ -n "$JAVA_HOME" ] ; then
- if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
- # IBM's JDK on AIX uses strange locations for the executables
- JAVACMD="$JAVA_HOME/jre/sh/java"
- else
- JAVACMD="$JAVA_HOME/bin/java"
- fi
- if [ ! -x "$JAVACMD" ] ; then
- die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
-
-Please set the JAVA_HOME variable in your environment to match the
-location of your Java installation."
- fi
-else
- JAVACMD="java"
- which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
-
-Please set the JAVA_HOME variable in your environment to match the
-location of your Java installation."
-fi
-
-# Increase the maximum file descriptors if we can.
-if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
- MAX_FD_LIMIT=`ulimit -H -n`
- if [ $? -eq 0 ] ; then
- if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
- MAX_FD="$MAX_FD_LIMIT"
- fi
- ulimit -n $MAX_FD
- if [ $? -ne 0 ] ; then
- warn "Could not set maximum file descriptor limit: $MAX_FD"
- fi
- else
- warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
- fi
-fi
-
-# For Darwin, add options to specify how the application appears in the dock
-if $darwin; then
- GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
-fi
-
-# For Cygwin, switch paths to Windows format before running java
-if $cygwin ; then
- APP_HOME=`cygpath --path --mixed "$APP_HOME"`
- CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
- JAVACMD=`cygpath --unix "$JAVACMD"`
-
- # We build the pattern for arguments to be converted via cygpath
- ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
- SEP=""
- for dir in $ROOTDIRSRAW ; do
- ROOTDIRS="$ROOTDIRS$SEP$dir"
- SEP="|"
- done
- OURCYGPATTERN="(^($ROOTDIRS))"
- # Add a user-defined pattern to the cygpath arguments
- if [ "$GRADLE_CYGPATTERN" != "" ] ; then
- OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
- fi
- # Now convert the arguments - kludge to limit ourselves to /bin/sh
- i=0
- for arg in "$@" ; do
- CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
- CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
-
- if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
- eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
- else
- eval `echo args$i`="\"$arg\""
- fi
- i=$((i+1))
- done
- case $i in
- (0) set -- ;;
- (1) set -- "$args0" ;;
- (2) set -- "$args0" "$args1" ;;
- (3) set -- "$args0" "$args1" "$args2" ;;
- (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
- (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
- (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
- (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
- (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
- (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
- esac
-fi
-
-# Escape application args
-save () {
- for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
- echo " "
-}
-APP_ARGS=$(save "$@")
-
-# Collect all arguments for the java command, following the shell quoting and substitution rules
-eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
-
-# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
-if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
- cd "$(dirname "$0")"
-fi
-
-exec "$JAVACMD" "$@"
diff --git a/settings.gradle b/settings.gradle
deleted file mode 100644
index d3837c7..0000000
--- a/settings.gradle
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * 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.
- */
-
-// You need to have car-ui-lib project in place. See tools/git_clone_projects.sh for example.
-include ':car-ui-lib'
-project(':car-ui-lib').projectDir= new File('../libs/car-ui-lib/car-ui-lib')
-
-include ':TestMediaApp'
-include ':RotaryPlayground'
-include ':RotaryIME'
diff --git a/tools/rro/generate-overlays.py b/tools/rro/generate-overlays.py
new file mode 100755
index 0000000..5ba4cda
--- /dev/null
+++ b/tools/rro/generate-overlays.py
@@ -0,0 +1,80 @@
+#!/usr/bin/env python3
+# Copyright (C) 2021 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import argparse
+import sys
+from resource_utils import get_all_resources, get_androidx_resources, Resource
+from datetime import datetime
+import lxml.etree as etree
+if sys.version_info[0] != 3:
+ print("Must use python 3")
+ sys.exit(1)
+
+COPYRIGHT_STR = """ Copyright (C) %s 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.""" % (datetime.today().strftime("%Y"))
+
+AUTOGENERATION_NOTICE_STR = """
+THIS FILE WAS AUTO GENERATED, DO NOT EDIT MANUALLY.
+REGENERATE USING packages/apps/Car/tests/tools/rro/generate-overlays.py
+"""
+
+"""
+Script used to update the 'overlayable.xml' file.
+"""
+def main():
+ parser = argparse.ArgumentParser(description="Generate overlayable.xml. This script assumes that all the RRO resources have the exact same name as the app resource they're overlaying.")
+ parser.add_argument('-o', '--outputFile', default='', help='Output file path. If empty, output to stdout')
+ parser.add_argument('-a', '--appResources', help="Path to the app's resource folder. If given, will be used to exclude any rro-specific resources from overlays.xml.")
+ required_args = parser.add_argument_group('Required arguments')
+ required_args.add_argument('-r', '--resourcePath', help="Path to the RRO's resource directory", required=True)
+ args = parser.parse_args()
+
+ resources = get_all_resources(args.resourcePath)
+ try:
+ resources.remove(Resource('overlays', 'xml'))
+ except KeyError:
+ pass
+
+ if args.appResources:
+ resources = resources.intersection(
+ get_all_resources(args.appResources).union(get_androidx_resources()))
+ generate_overlays_file(resources, args.outputFile)
+
+def generate_overlays_file(resources, output_file):
+ resources = sorted(resources, key=lambda x: x.type + x.name)
+ root = etree.Element('overlay')
+ root.addprevious(etree.Comment(COPYRIGHT_STR))
+ root.addprevious(etree.Comment(AUTOGENERATION_NOTICE_STR))
+ for resource in resources:
+ item = etree.SubElement(root, 'item')
+ item.set('target', f'{resource.type}/{resource.name}')
+ item.set('value', f'@{resource.type}/{resource.name}')
+ rootTree = etree.ElementTree(root)
+ if not output_file:
+ print(etree.tostring(rootTree, pretty_print=True, xml_declaration=True).decode())
+ else:
+ with open(output_file, 'wb') as f:
+ rootTree.write(f, pretty_print=True, xml_declaration=True, encoding='utf-8')
+
+if __name__ == '__main__':
+ main()
diff --git a/tools/rro/resource_utils.py b/tools/rro/resource_utils.py
index 8628abf..5f35e1a 100644
--- a/tools/rro/resource_utils.py
+++ b/tools/rro/resource_utils.py
@@ -111,7 +111,7 @@ def get_resources_from_single_file(filename, ignore_strings=False):
resType = "array"
if resource.tag == 'item' or resource.tag == 'public':
resType = resource.get('type')
- if resType == 'string' and ignore_strings:
+ if (resType == 'string' or resType == 'plurals') and ignore_strings:
continue
if resType == 'overlayable':
for policy in resource:
@@ -149,3 +149,58 @@ def add_resource_to_set(resourceset, resource):
def merge_resources(set1, set2):
for resource in set2:
add_resource_to_set(set1, resource)
+
+def get_androidx_resources():
+ # source: https://android.googlesource.com/platform/frameworks/opt/sherpa/+/studio-3.0/constraintlayout/src/main/res/values/attrs.xml
+ resources = set()
+ add_resource_to_set(resources, Resource('layout_optimizationLevel', 'attr'))
+ add_resource_to_set(resources, Resource('constraintSet', 'attr'))
+ add_resource_to_set(resources, Resource('barrierDirection', 'attr'))
+ add_resource_to_set(resources, Resource('constraint_referenced_ids', 'attr'))
+ add_resource_to_set(resources, Resource('chainUseRtl', 'attr'))
+ add_resource_to_set(resources, Resource('title', 'attr'))
+ add_resource_to_set(resources, Resource('layout_constraintGuide_begin', 'attr'))
+ add_resource_to_set(resources, Resource('layout_constraintGuide_end', 'attr'))
+ add_resource_to_set(resources, Resource('layout_constraintGuide_percent', 'attr'))
+ add_resource_to_set(resources, Resource('layout_constraintLeft_toLeftOf', 'attr'))
+ add_resource_to_set(resources, Resource('layout_constraintLeft_toRightOf', 'attr'))
+ add_resource_to_set(resources, Resource('layout_constraintRight_toLeftOf', 'attr'))
+ add_resource_to_set(resources, Resource('layout_constraintRight_toRightOf', 'attr'))
+ add_resource_to_set(resources, Resource('layout_constraintTop_toTopOf', 'attr'))
+ add_resource_to_set(resources, Resource('layout_constraintTop_toBottomOf', 'attr'))
+ add_resource_to_set(resources, Resource('layout_constraintBottom_toTopOf', 'attr'))
+ add_resource_to_set(resources, Resource('layout_constraintBottom_toBottomOf', 'attr'))
+ add_resource_to_set(resources, Resource('layout_constraintBaseline_toBaselineOf', 'attr'))
+ add_resource_to_set(resources, Resource('layout_constraintStart_toEndOf', 'attr'))
+ add_resource_to_set(resources, Resource('layout_constraintStart_toStartOf', 'attr'))
+ add_resource_to_set(resources, Resource('layout_constraintEnd_toStartOf', 'attr'))
+ add_resource_to_set(resources, Resource('layout_constraintEnd_toEndOf', 'attr'))
+ add_resource_to_set(resources, Resource('layout_goneMarginLeft', 'attr'))
+ add_resource_to_set(resources, Resource('layout_goneMarginTop', 'attr'))
+ add_resource_to_set(resources, Resource('layout_goneMarginRight', 'attr'))
+ add_resource_to_set(resources, Resource('layout_goneMarginBottom', 'attr'))
+ add_resource_to_set(resources, Resource('layout_goneMarginStart', 'attr'))
+ add_resource_to_set(resources, Resource('layout_goneMarginEnd', 'attr'))
+ add_resource_to_set(resources, Resource('layout_constraintHorizontal_bias', 'attr'))
+ add_resource_to_set(resources, Resource('layout_constraintVertical_bias', 'attr'))
+ add_resource_to_set(resources, Resource('layout_constraintWidth_default', 'attr'))
+ add_resource_to_set(resources, Resource('layout_constraintHeight_default', 'attr'))
+ add_resource_to_set(resources, Resource('layout_constraintWidth_min', 'attr'))
+ add_resource_to_set(resources, Resource('layout_constraintWidth_max', 'attr'))
+ add_resource_to_set(resources, Resource('layout_constraintWidth_percent', 'attr'))
+ add_resource_to_set(resources, Resource('layout_constraintHeight_min', 'attr'))
+ add_resource_to_set(resources, Resource('layout_constraintHeight_max', 'attr'))
+ add_resource_to_set(resources, Resource('layout_constraintHeight_percent', 'attr'))
+ add_resource_to_set(resources, Resource('layout_constraintLeft_creator', 'attr'))
+ add_resource_to_set(resources, Resource('layout_constraintTop_creator', 'attr'))
+ add_resource_to_set(resources, Resource('layout_constraintRight_creator', 'attr'))
+ add_resource_to_set(resources, Resource('layout_constraintBottom_creator', 'attr'))
+ add_resource_to_set(resources, Resource('layout_constraintBaseline_creator', 'attr'))
+ add_resource_to_set(resources, Resource('layout_constraintDimensionRatio', 'attr'))
+ add_resource_to_set(resources, Resource('layout_constraintHorizontal_weight', 'attr'))
+ add_resource_to_set(resources, Resource('layout_constraintVertical_weight', 'attr'))
+ add_resource_to_set(resources, Resource('layout_constraintHorizontal_chainStyle', 'attr'))
+ add_resource_to_set(resources, Resource('layout_constraintVertical_chainStyle', 'attr'))
+ add_resource_to_set(resources, Resource('layout_editor_absoluteX', 'attr'))
+ add_resource_to_set(resources, Resource('layout_editor_absoluteY', 'attr'))
+ return resources
diff --git a/tools/rro/verify-overlayable.py b/tools/rro/verify-overlayable.py
index b469e3c..05feb51 100755
--- a/tools/rro/verify-overlayable.py
+++ b/tools/rro/verify-overlayable.py
@@ -46,7 +46,9 @@ def compare_resources(old_mapping, new_mapping, res_public_file):
print('Resources added:\n' + '\n'.join(map(lambda x: str(x), added)))
if len(added) + len(removed) > 0:
print("Some resource have been modified. If this is intentional please " +
- "run 'python generate-overlayable.py' again and submit the new %s" % res_public_file)
+ "run 'python3 generate-overlayable.py' again and submit the new %s" % res_public_file)
+ print("Some projects may include $PROJECT_TOP/tools/generate-overlayable.sh which calls " +
+ "the above command with the appropriate command line arguments")
sys.exit(1)
if __name__ == '__main__':