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();
+    }
+}