Add SVG display overlay service
Fix: 152042126
Test: Manual
Change-Id: I35ccb34d5ac6ef4c1184ef3eb8e049787f7641e9
diff --git a/car-ui-lib/tests/paintbooth/AndroidManifest.xml b/car-ui-lib/tests/paintbooth/AndroidManifest.xml
index cd58378..8120095 100644
--- a/car-ui-lib/tests/paintbooth/AndroidManifest.xml
+++ b/car-ui-lib/tests/paintbooth/AndroidManifest.xml
@@ -96,5 +96,12 @@
<action android:name="com.android.car.ui.paintbooth.StopService" />
</intent-filter>
</service>
+ <service
+ android:name=".VisibleBoundsSimulator"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="com.android.car.ui.paintbooth.VisibleBoundsSimulator.StopService" />
+ </intent-filter>
+ </service>
</application>
</manifest>
diff --git a/car-ui-lib/tests/paintbooth/res/drawable/simulated_screen_shape.xml b/car-ui-lib/tests/paintbooth/res/drawable/simulated_screen_shape.xml
new file mode 100644
index 0000000..569f098
--- /dev/null
+++ b/car-ui-lib/tests/paintbooth/res/drawable/simulated_screen_shape.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 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.
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:height="100dp"
+ android:width="100dp"
+ android:viewportHeight="100"
+ android:viewportWidth="100">
+
+ <path
+ android:strokeWidth="1"
+ android:strokeColor="#0f0"
+ android:pathData="M 0,0 L 100,0 100,100 0,100 z" />
+
+</vector>
diff --git a/car-ui-lib/tests/paintbooth/res/layout/simulated_screen_shape_container.xml b/car-ui-lib/tests/paintbooth/res/layout/simulated_screen_shape_container.xml
new file mode 100644
index 0000000..96387b1
--- /dev/null
+++ b/car-ui-lib/tests/paintbooth/res/layout/simulated_screen_shape_container.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 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.
+ -->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:focusableInTouchMode="false"
+ android:focusable="false"
+ android:background="@drawable/simulated_screen_shape">
+
+</RelativeLayout>
diff --git a/car-ui-lib/tests/paintbooth/res/values/dimens.xml b/car-ui-lib/tests/paintbooth/res/values/dimens.xml
new file mode 100644
index 0000000..35dfeec
--- /dev/null
+++ b/car-ui-lib/tests/paintbooth/res/values/dimens.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 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.
+ -->
+<resources>
+ <!-- Width of the overlay container. Setting to 0 will use full width of the screen -->
+ <dimen name="screen_shape_container_width">0dp</dimen>
+ <!-- Height of the overlay container. Setting to 0 will use full height of the screen -->
+ <dimen name="screen_shape_container_height">0dp</dimen>
+</resources>
diff --git a/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/MainActivity.java b/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/MainActivity.java
index 395cadd..cafbd9c 100644
--- a/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/MainActivity.java
+++ b/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/MainActivity.java
@@ -22,7 +22,6 @@
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
-import android.util.Pair;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -61,26 +60,18 @@
/**
* List of all sample activities.
*/
- private final List<Pair<String, Class<? extends Activity>>> mActivities = Arrays.asList(
- Pair.create("Dialogs sample", DialogsActivity.class),
- Pair.create("List sample", CarUiRecyclerViewActivity.class),
- Pair.create("Grid sample", GridCarUiRecyclerViewActivity.class),
- Pair.create("Preferences sample", PreferenceActivity.class),
- Pair.create("Overlays", OverlayActivity.class),
- Pair.create("Toolbar sample", ToolbarActivity.class),
- Pair.create("No CarUiToolbar sample", NoCarUiToolbarActivity.class),
- Pair.create("Widget sample", WidgetActivity.class),
- Pair.create("ListItem sample", CarUiListItemActivity.class)
- );
-
- private final List<Pair<String, View.OnClickListener>> mSwitches = Arrays.asList(
- Pair.create("Show foreground activities", v -> {
- Intent intent = new Intent(this, CurrentActivityService.class);
- if (isCurrentActivityServiceRunning()) {
- intent.setAction(CurrentActivityService.STOP_SERVICE);
- }
- startForegroundService(intent);
- })
+ private final List<Element> mActivities = Arrays.asList(
+ new Element("Show foreground activities", CurrentActivityService.class, true),
+ new Element("Simulate Screen Bounds", VisibleBoundsSimulator.class, true),
+ new Element("Dialogs sample", DialogsActivity.class, false),
+ new Element("List sample", CarUiRecyclerViewActivity.class, false),
+ new Element("Grid sample", GridCarUiRecyclerViewActivity.class, false),
+ new Element("Preferences sample", PreferenceActivity.class, false),
+ new Element("Overlays", OverlayActivity.class, false),
+ new Element("Toolbar sample", ToolbarActivity.class, false),
+ new Element("No CarUiToolbar sample", NoCarUiToolbarActivity.class, false),
+ new Element("Widget sample", WidgetActivity.class, false),
+ new Element("ListItem sample", CarUiListItemActivity.class, false)
);
private class ViewHolder extends RecyclerView.ViewHolder {
@@ -91,12 +82,12 @@
mButton = itemView.findViewById(R.id.button);
}
- void bind(String text, View.OnClickListener listener) {
- mButton.setText(text);
- mButton.setOnClickListener(listener);
+ void bind(Element element) {
+ mButton.setText(element.mDisplayText);
+ mButton.setOnClickListener(element.mOnClickListener);
- if (mButton instanceof Switch) {
- ((Switch) mButton).setChecked(isCurrentActivityServiceRunning());
+ if (element.mIsService && mButton instanceof Switch) {
+ ((Switch) mButton).setChecked(isServiceRunning(element.mClass));
}
}
}
@@ -120,29 +111,41 @@
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
if (getItemViewType(position) == TYPE_SWITCH) {
- Pair<String, View.OnClickListener> item = mSwitches.get(position);
- holder.bind(item.first, item.second);
+ Element item = mActivities.get(position);
+ item.mOnClickListener = v -> {
+ Intent intent = new Intent(holder.itemView.getContext(), item.mClass);
+ if (isServiceRunning(item.mClass)) {
+ // If you are about to add a new service you should extract an
+ // interface and generalize this instead.
+ if (item.mClass.getName().equals(
+ CurrentActivityService.class.getName())) {
+ intent.setAction(CurrentActivityService.STOP_SERVICE);
+ } else if (item.mClass.getName().equals(
+ VisibleBoundsSimulator.class.getName())) {
+ intent.setAction(VisibleBoundsSimulator.STOP_SERVICE);
+ }
+ }
+ startForegroundService(intent);
+ };
+ holder.bind(item);
} else {
- Pair<String, Class<? extends Activity>> item =
- mActivities.get(position - mSwitches.size());
- holder.bind(item.first, v -> {
- Intent intent = new Intent(holder.itemView.getContext(), item.second);
+ Element item = mActivities.get(position);
+ item.mOnClickListener = v -> {
+ Intent intent = new Intent(holder.itemView.getContext(), item.mClass);
startActivity(intent);
- });
+ };
+ holder.bind(item);
}
}
@Override
public int getItemCount() {
- return mSwitches.size() + mActivities.size();
+ return mActivities.size();
}
@Override
public int getItemViewType(int position) {
- if (position < mSwitches.size()) {
- return TYPE_SWITCH;
- }
- return TYPE_ACTIVITY;
+ return mActivities.get(position).mIsService ? TYPE_SWITCH : TYPE_ACTIVITY;
}
};
@@ -238,11 +241,11 @@
}
}
- private boolean isCurrentActivityServiceRunning() {
+ private boolean isServiceRunning(Class serviceClazz) {
ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(
Integer.MAX_VALUE)) {
- if (CurrentActivityService.class.getName().equals(service.service.getClassName())) {
+ if (serviceClazz.getName().equals(service.service.getClassName())) {
return true;
}
}
@@ -256,4 +259,17 @@
requireViewById(android.R.id.content)
.setPadding(insets.getLeft(), 0, insets.getRight(), 0);
}
+
+ private class Element {
+ String mDisplayText;
+ Class mClass;
+ boolean mIsService;
+ View.OnClickListener mOnClickListener;
+
+ Element(String displayText, Class clazz, boolean isService) {
+ this.mDisplayText = displayText;
+ this.mClass = clazz;
+ this.mIsService = isService;
+ }
+ }
}
diff --git a/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/VisibleBoundsSimulator.java b/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/VisibleBoundsSimulator.java
new file mode 100644
index 0000000..f3f871e
--- /dev/null
+++ b/car-ui-lib/tests/paintbooth/src/com/android/car/ui/paintbooth/VisibleBoundsSimulator.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2020 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.ui.paintbooth;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.os.IBinder;
+import android.util.DisplayMetrics;
+import android.view.Display;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.WindowManager;
+
+import androidx.core.app.NotificationCompat;
+
+/**
+ * To start the service:
+ * adb shell am start-foreground-service com.android.car.ui.paintbooth/.DisplayService
+ *
+ * To stop the service:
+ * adb shell am stopservice com.android.car.ui.paintbooth/.DisplayService
+ *
+ * When the service is started it will draw a overlay view on top of the screen displayed. This
+ * overlay comes from a SVG file that can be modified to take different shapes. This service will be
+ * used to display different screen styles from OEMs.
+ */
+public class VisibleBoundsSimulator extends Service {
+
+ public static final String STOP_SERVICE =
+ "com.android.car.ui.paintbooth.DisplayService.StopService";
+ private static final int FOREGROUND_SERVICE_ID = 222;
+ private View mContainer;
+
+ private WindowManager mWindowManager;
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ throw new UnsupportedOperationException("Not yet implemented.");
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ if (STOP_SERVICE.equals(intent.getAction())) {
+ stopSelf();
+ }
+
+ return START_STICKY;
+ }
+
+ @Override
+ public void onCreate() {
+
+ Intent notificationIntent = new Intent(this, VisibleBoundsSimulator.class);
+
+ PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
+ notificationIntent, 0);
+
+ NotificationChannel channel = new NotificationChannel("DisplayService",
+ "Show overlay screen",
+ NotificationManager.IMPORTANCE_DEFAULT);
+ NotificationManager notificationManager =
+ (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+ notificationManager.createNotificationChannel(channel);
+
+ Notification notification =
+ new NotificationCompat.Builder(this, "DisplayService")
+ .setSmallIcon(R.drawable.ic_launcher)
+ .setContentTitle("DisplayService")
+ .setContentText("Show overlay screen")
+ .setContentIntent(pendingIntent).build();
+
+ startForeground(FOREGROUND_SERVICE_ID, notification);
+ applyDisplayOverlay();
+ }
+
+ /**
+ * Creates a view overlay on top of a new window. The overlay gravity is set to left and
+ * bottom. If the width and height is not provided then the default is to take up the entire
+ * screen. Overlay will show bounds around the view and we can still click through the window.
+ */
+ private void applyDisplayOverlay() {
+ mWindowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
+
+ DisplayMetrics displayMetrics = new DisplayMetrics();
+
+ mWindowManager.getDefaultDisplay().getRealMetrics(displayMetrics);
+ int screenHeight = displayMetrics.heightPixels;
+ int screenWidth = displayMetrics.widthPixels;
+ LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
+ WindowManager.LayoutParams params = new WindowManager.LayoutParams(
+ WindowManager.LayoutParams.WRAP_CONTENT,
+ WindowManager.LayoutParams.WRAP_CONTENT,
+ WindowManager.LayoutParams.TYPE_DISPLAY_OVERLAY,
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
+ | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
+ & ~WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH,
+ PixelFormat.TRANSLUCENT);
+
+ params.packageName = this.getPackageName();
+ params.gravity = Gravity.BOTTOM | Gravity.LEFT;
+
+ Display display = mWindowManager.getDefaultDisplay();
+ Point size = new Point();
+ display.getSize(size);
+ int height = size.y;
+
+ params.x = 0;
+ // If the sysUI is showing and nav bar is taking up some space at the bottom we want to
+ // offset the height of the navBar so that the overlay starts from the bottom left.
+ params.y = -(screenHeight - height);
+
+ float overlayWidth = getApplicationContext().getResources().getDimension(
+ R.dimen.screen_shape_container_width);
+ float overlayHeight = getApplicationContext().getResources().getDimension(
+ R.dimen.screen_shape_container_height);
+
+
+ params.width = (int) (overlayWidth == 0 ? screenWidth : overlayHeight);
+ params.height = (int) (overlayHeight == 0 ? screenHeight : overlayHeight);
+ params.setTitle("Simulated display bound");
+
+ mContainer = inflater.inflate(R.layout.simulated_screen_shape_container, null);
+ mContainer.setLayoutParams(params);
+
+ mWindowManager.addView(mContainer, params);
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ mWindowManager.removeView(mContainer);
+ stopSelf();
+ }
+}